diff options
Diffstat (limited to 'services')
302 files changed, 47079 insertions, 11983 deletions
diff --git a/services/Android.mk b/services/Android.mk index e4b0cbb..1918db5 100644 --- a/services/Android.mk +++ b/services/Android.mk @@ -24,6 +24,7 @@ services := \ appwidget \ backup \ devicepolicy \ + midi \ net \ print \ restrictions \ @@ -49,10 +50,6 @@ include $(wildcard $(LOCAL_PATH)/*/jni/Android.mk) LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES -ifeq ($(WITH_MALLOC_LEAK_CHECK),true) - LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK -endif - LOCAL_MODULE:= libandroid_servers include $(BUILD_SHARED_LIBRARY) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 93c65f3..82a77d2 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -352,6 +352,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { intentFilter.addAction(Intent.ACTION_USER_SWITCHED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_USER_PRESENT); + intentFilter.addAction(Intent.ACTION_SETTING_RESTORED); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override @@ -369,6 +370,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { onUserStateChangedLocked(userState); } } + } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { + final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); + if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) { + synchronized (mLock) { + restoreEnabledAccessibilityServicesLocked( + intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE), + intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)); + } + } } } }, UserHandle.ALL, intentFilter, null, null); @@ -857,6 +867,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } + // Called only during settings restore; currently supports only the owner user + void restoreEnabledAccessibilityServicesLocked(String oldSetting, String newSetting) { + readComponentNamesFromStringLocked(oldSetting, mTempComponentNameSet, false); + readComponentNamesFromStringLocked(newSetting, mTempComponentNameSet, true); + + UserState userState = getUserStateLocked(UserHandle.USER_OWNER); + userState.mEnabledServices.clear(); + userState.mEnabledServices.addAll(mTempComponentNameSet); + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userState.mEnabledServices, + UserHandle.USER_OWNER); + onUserStateChangedLocked(userState); + } + private InteractionBridge getInteractionBridgeLocked() { if (mInteractionBridge == null) { mInteractionBridge = new InteractionBridge(); @@ -944,7 +969,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( new Intent(AccessibilityService.SERVICE_INTERFACE), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, + PackageManager.GET_SERVICES + | PackageManager.GET_META_DATA + | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, mCurrentUserId); for (int i = 0, count = installedServices.size(); i < count; i++) { @@ -1127,10 +1154,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { Set<ComponentName> outComponentNames) { String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), settingName, userId); - outComponentNames.clear(); - if (settingValue != null) { + readComponentNamesFromStringLocked(settingValue, outComponentNames, false); + } + + /** + * Populates a set with the {@link ComponentName}s contained in a colon-delimited string. + * + * @param names The colon-delimited string to parse. + * @param outComponentNames The set of component names to be populated based on + * the contents of the <code>names</code> string. + * @param doMerge If true, the parsed component names will be merged into the output + * set, rather than replacing the set's existing contents entirely. + */ + private void readComponentNamesFromStringLocked(String names, + Set<ComponentName> outComponentNames, + boolean doMerge) { + if (!doMerge) { + outComponentNames.clear(); + } + if (names != null) { TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(settingValue); + splitter.setString(names); while (splitter.hasNext()) { String str = splitter.next(); if (str == null || str.length() <= 0) { @@ -3110,6 +3154,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: case WindowManager.LayoutParams.TYPE_APPLICATION_STARTING: case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL: case WindowManager.LayoutParams.TYPE_BASE_APPLICATION: case WindowManager.LayoutParams.TYPE_PHONE: case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: diff --git a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java index b68b09f..bc76191 100644 --- a/services/accessibility/java/com/android/server/accessibility/GestureUtils.java +++ b/services/accessibility/java/com/android/server/accessibility/GestureUtils.java @@ -69,8 +69,7 @@ final class GestureUtils { return true; } - final float firstMagnitude = - (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY); + final float firstMagnitude = (float) Math.hypot(firstDeltaX, firstDeltaY); final float firstXNormalized = (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX; final float firstYNormalized = @@ -83,8 +82,7 @@ final class GestureUtils { return true; } - final float secondMagnitude = - (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY); + final float secondMagnitude = (float) Math.hypot(secondDeltaX, secondDeltaY); final float secondXNormalized = (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX; final float secondYNormalized = diff --git a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java index c8b080e..b4613d6 100644 --- a/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java +++ b/services/accessibility/java/com/android/server/accessibility/ScreenMagnifier.java @@ -34,6 +34,7 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Property; import android.util.Slog; +import android.util.TypedValue; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MagnificationSpec; @@ -110,7 +111,6 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio private static final int STATE_MAGNIFIED_INTERACTION = 4; private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; - private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50; private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1; private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2; @@ -135,9 +135,8 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio private final AccessibilityManagerService mAms; - private final int mTapTimeSlop = ViewConfiguration.getTapTimeout(); - private final int mMultiTapTimeSlop = - ViewConfiguration.getDoubleTapTimeout() - MULTI_TAP_TIME_SLOP_ADJUSTMENT; + private final int mTapTimeSlop = ViewConfiguration.getJumpTapTimeout(); + private final int mMultiTapTimeSlop; private final int mTapDistanceSlop; private final int mMultiTapDistanceSlop; @@ -192,6 +191,9 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio mWindowManager = LocalServices.getService(WindowManagerInternal.class); mAms = service; + mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout() + + mContext.getResources().getInteger( + com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment); mLongAnimationDuration = context.getResources().getInteger( com.android.internal.R.integer.config_longAnimTime); mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); @@ -481,15 +483,20 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio private static final float MIN_SCALE = 1.3f; private static final float MAX_SCALE = 5.0f; - private static final float SCALING_THRESHOLD = 0.3f; - private final ScaleGestureDetector mScaleGestureDetector; private final GestureDetector mGestureDetector; + private final float mScalingThreshold; + private float mInitialScaleFactor = -1; private boolean mScaling; public MagnifiedContentInteractonStateHandler(Context context) { + final TypedValue scaleValue = new TypedValue(); + context.getResources().getValue( + com.android.internal.R.dimen.config_screen_magnification_scaling_threshold, + scaleValue, false); + mScalingThreshold = scaleValue.getFloat(); mScaleGestureDetector = new ScaleGestureDetector(context, this); mScaleGestureDetector.setQuickScaleEnabled(false); mGestureDetector = new GestureDetector(context, this); @@ -537,7 +544,7 @@ public final class ScreenMagnifier implements WindowManagerInternal.Magnificatio mInitialScaleFactor = detector.getScaleFactor(); } else { final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor; - if (Math.abs(deltaScale) > SCALING_THRESHOLD) { + if (Math.abs(deltaScale) > mScalingThreshold) { mScaling = true; return true; } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 7623514..f42aef1 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -674,7 +674,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku @Override public IntentSender createAppWidgetConfigIntentSender(String callingPackage, int appWidgetId, - int intentFlags) { + final int intentFlags) { final int userId = UserHandle.getCallingUserId(); if (DEBUG) { @@ -701,18 +701,21 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku throw new IllegalArgumentException("Widget not bound " + appWidgetId); } + // Make sure only safe flags can be passed it. + final int secureFlags = intentFlags & ~Intent.IMMUTABLE_FLAGS; + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intent.setComponent(provider.info.configure); - intent.setFlags(intentFlags); + intent.setFlags(secureFlags); // All right, create the sender. final long identity = Binder.clearCallingIdentity(); try { return PendingIntent.getActivityAsUser( mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_CANCEL_CURRENT, null, - new UserHandle(provider.getUserId())) + | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT, + null, new UserHandle(provider.getUserId())) .getIntentSender(); } finally { Binder.restoreCallingIdentity(identity); @@ -2837,10 +2840,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return providersUpdated; } - private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) { + private boolean removeProvidersForPackageLocked(String pkgName, int userId) { boolean removed = false; - int N = mProviders.size(); + final int N = mProviders.size(); for (int i = N - 1; i >= 0; i--) { Provider provider = mProviders.get(i); if (pkgName.equals(provider.info.provider.getPackageName()) @@ -2849,11 +2852,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku removed = true; } } + return removed; + } + + private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) { + boolean removed = removeProvidersForPackageLocked(pkgName, userId); // Delete the hosts for this package too // By now, we have removed any AppWidgets that were in any hosts here, // so we don't need to worry about sending DISABLE broadcasts to them. - N = mHosts.size(); + final int N = mHosts.size(); for (int i = N - 1; i >= 0; i--) { Host host = mHosts.get(i); if (pkgName.equals(host.id.packageName) @@ -2925,13 +2933,30 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku synchronized (mLock) { boolean providersChanged = false; + ArraySet<String> previousPackages = new ArraySet<String>(); + final int providerCount = mProviders.size(); + for (int i = 0; i < providerCount; ++i) { + Provider provider = mProviders.get(i); + if (provider.getUserId() == userId) { + previousPackages.add(provider.id.componentName.getPackageName()); + } + } + final int packageCount = packages.size(); for (int i = 0; i < packageCount; i++) { String packageName = packages.get(i); + previousPackages.remove(packageName); providersChanged |= updateProvidersForPackageLocked(packageName, userId, null); } + // Some packages are no longer whitelisted. + final int removedCount = previousPackages.size(); + for (int i = 0; i < removedCount; ++i) { + providersChanged |= removeProvidersForPackageLocked( + previousPackages.valueAt(i), userId); + } + if (providersChanged) { saveGroupStateAsync(userId); scheduleNotifyGroupHostsForProvidersChangedLocked(userId); @@ -3142,10 +3167,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (parentId != callerId) { return false; } - return isProviderWhitelListed(packageName, profileId); + return isProviderWhiteListed(packageName, profileId); } - public boolean isProviderWhitelListed(String packageName, int profileId) { + public boolean isProviderWhiteListed(String packageName, int profileId) { DevicePolicyManagerInternal devicePolicyManager = LocalServices.getService( DevicePolicyManagerInternal.class); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index c1e4994..1bed4f3 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -22,12 +22,14 @@ import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.IBackupAgent; +import android.app.PackageInstallObserver; import android.app.PendingIntent; import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupTransport; import android.app.backup.FullBackup; +import android.app.backup.FullBackupDataOutput; import android.app.backup.RestoreDescription; import android.app.backup.RestoreSet; import android.app.backup.IBackupManager; @@ -45,7 +47,6 @@ import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; -import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -93,6 +94,7 @@ import com.android.server.AppWidgetBackupBridge; import com.android.server.EventLogTags; import com.android.server.SystemService; import com.android.server.backup.PackageManagerBackupAgent.Metadata; +import com.android.server.pm.PackageManagerService; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -134,6 +136,7 @@ import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.Deflater; @@ -157,9 +160,9 @@ import libcore.io.IoUtils; public class BackupManagerService { private static final String TAG = "BackupManagerService"; - private static final boolean DEBUG = true; - private static final boolean MORE_DEBUG = false; - private static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true; + static final boolean DEBUG = true; + static final boolean MORE_DEBUG = false; + static final boolean DEBUG_SCHEDULING = MORE_DEBUG || true; // System-private key used for backing up an app's widget state. Must // begin with U+FFxx by convention (we reserve all keys starting @@ -195,23 +198,11 @@ public class BackupManagerService { static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup"; static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; - // How often we perform a backup pass. Privileged external callers can - // trigger an immediate pass. - private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR; - - // Random variation in backup scheduling time to avoid server load spikes - private static final int FUZZ_MILLIS = 5 * 60 * 1000; - - // The amount of time between the initial provisioning of the device and - // the first backup pass. - private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR; - // Retry interval for clear/init when the transport is unavailable private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR; private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN"; private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT"; - private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR"; private static final int MSG_RUN_BACKUP = 1; private static final int MSG_RUN_ADB_BACKUP = 2; private static final int MSG_RUN_RESTORE = 3; @@ -299,7 +290,6 @@ public class BackupManagerService { volatile boolean mBackupRunning; volatile boolean mConnecting; volatile long mLastBackupPass; - volatile long mNextBackupPass; // For debugging, we maintain a progress trace of operations during backup static final boolean DEBUG_BACKUP_TRACE = true; @@ -381,8 +371,8 @@ public class BackupManagerService { if (mProvisioned && !wasProvisioned && mEnabled) { // we're now good to go, so start the backup alarms if (MORE_DEBUG) Slog.d(TAG, "Now provisioned, so starting backups"); - startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL); - scheduleNextFullBackupJob(); + KeyValueBackupJob.schedule(mContext); + scheduleNextFullBackupJob(0); } } } @@ -613,7 +603,7 @@ public class BackupManagerService { return token; } - // High level policy: apps are ineligible for backup if certain conditions apply + // High level policy: apps are generally ineligible for backup if certain conditions apply public static boolean appIsEligibleForBackup(ApplicationInfo app) { // 1. their manifest states android:allowBackup="false" if ((app.flags&ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) { @@ -640,7 +630,7 @@ public class BackupManagerService { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FULL_BACKUP_ONLY) != 0; } - // No agent means we do full backups for it + // No agent or fullBackupOnly="true" means we do indeed perform full-data backups for it return true; } @@ -657,7 +647,6 @@ public class BackupManagerService { case MSG_RUN_BACKUP: { mLastBackupPass = System.currentTimeMillis(); - mNextBackupPass = mLastBackupPass + BACKUP_INTERVAL; IBackupTransport transport = getTransport(mCurrentTransport); if (transport == null) { @@ -740,7 +729,7 @@ public class BackupManagerService { { try { BackupRestoreTask task = (BackupRestoreTask) msg.obj; - task.operationComplete(); + task.operationComplete(msg.arg1); } catch (ClassCastException e) { Slog.e(TAG, "Invalid completion in flight, obj=" + msg.obj); } @@ -1211,11 +1200,13 @@ public class BackupManagerService { temp = new RandomAccessFile(tempProcessedFile, "rws"); in = new RandomAccessFile(mEverStored, "r"); + // Loop until we hit EOF while (true) { - PackageInfo info; String pkg = in.readUTF(); try { - info = mPackageManager.getPackageInfo(pkg, 0); + // is this package still present? + mPackageManager.getPackageInfo(pkg, 0); + // if we get here then yes it is; remember it mEverStoredApps.add(pkg); temp.writeUTF(pkg); if (MORE_DEBUG) Slog.v(TAG, " + " + pkg); @@ -1279,7 +1270,23 @@ public class BackupManagerService { for (int i = 0; i < N; i++) { String pkgName = in.readUTF(); long lastBackup = in.readLong(); - schedule.add(new FullBackupEntry(pkgName, lastBackup)); + try { + PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0); + if (appGetsFullBackup(pkg) + && appIsEligibleForBackup(pkg.applicationInfo)) { + schedule.add(new FullBackupEntry(pkgName, lastBackup)); + } else { + if (DEBUG) { + Slog.i(TAG, "Package " + pkgName + + " no longer eligible for full backup"); + } + } + } catch (NameNotFoundException e) { + if (DEBUG) { + Slog.i(TAG, "Package " + pkgName + + " not installed; dropping from full backup"); + } + } } Collections.sort(schedule); } catch (Exception e) { @@ -1302,7 +1309,7 @@ public class BackupManagerService { schedule = new ArrayList<FullBackupEntry>(N); for (int i = 0; i < N; i++) { PackageInfo info = apps.get(i); - if (appGetsFullBackup(info)) { + if (appGetsFullBackup(info) && appIsEligibleForBackup(info.applicationInfo)) { schedule.add(new FullBackupEntry(info.packageName, 0)); } } @@ -1774,13 +1781,13 @@ public class BackupManagerService { addPackageParticipantsLocked(pkgList); } // If they're full-backup candidates, add them there instead + final long now = System.currentTimeMillis(); for (String packageName : pkgList) { try { PackageInfo app = mPackageManager.getPackageInfo(packageName, 0); - long now = System.currentTimeMillis(); - if (appGetsFullBackup(app)) { + if (appGetsFullBackup(app) && appIsEligibleForBackup(app.applicationInfo)) { enqueueFullBackup(packageName, now); - scheduleNextFullBackupJob(); + scheduleNextFullBackupJob(0); } // Transport maintenance: rebind to known existing transports that have @@ -1865,7 +1872,8 @@ public class BackupManagerService { boolean tryBindTransport(ServiceInfo info) { try { PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0); - if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) { + if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) + != 0) { return bindTransport(info); } else { Slog.w(TAG, "Transport package " + info.packageName + " not privileged"); @@ -2201,7 +2209,10 @@ public class BackupManagerService { // Get the restore-set token for the best-available restore set for this package: // the active set if possible, else the ancestral one. Returns zero if none available. - long getAvailableRestoreToken(String packageName) { + public long getAvailableRestoreToken(String packageName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, + "getAvailableRestoreToken"); + long token = mAncestralToken; synchronized (mQueueLock) { if (mEverStoredApps.contains(packageName)) { @@ -2219,7 +2230,7 @@ public class BackupManagerService { void execute(); // An operation that wanted a callback has completed - void operationComplete(); + void operationComplete(int result); // An operation that wanted a callback has timed out void handleTimeout(); @@ -2474,7 +2485,7 @@ public class BackupManagerService { BackupRequest request = mQueue.get(0); mQueue.remove(0); - Slog.d(TAG, "starting agent for backup of " + request); + Slog.d(TAG, "starting key/value backup of " + request); addBackupTrace("launch agent for " + request.packageName); // Verify that the requested app exists; it might be something that @@ -2485,13 +2496,24 @@ public class BackupManagerService { try { mCurrentPackage = mPackageManager.getPackageInfo(request.packageName, PackageManager.GET_SIGNATURES); - if (mCurrentPackage.applicationInfo.backupAgentName == null) { + if (!appIsEligibleForBackup(mCurrentPackage.applicationInfo)) { // The manifest has changed but we had a stale backup request pending. // This won't happen again because the app won't be requesting further // backups. Slog.i(TAG, "Package " + request.packageName + " no longer supports backup; skipping"); - addBackupTrace("skipping - no agent, completion is noop"); + addBackupTrace("skipping - not eligible, completion is noop"); + executeNextState(BackupState.RUNNING_QUEUE); + return; + } + + if (appGetsFullBackup(mCurrentPackage)) { + // It's possible that this app *formerly* was enqueued for key/value backup, + // but has since been updated and now only supports the full-data path. + // Don't proceed with a key/value backup for it in this case. + Slog.i(TAG, "Package " + request.packageName + + " requests full-data rather than key/value; skipping"); + addBackupTrace("skipping - fullBackupOnly, completion is noop"); executeNextState(BackupState.RUNNING_QUEUE); return; } @@ -2776,8 +2798,23 @@ public class BackupManagerService { } @Override - public void operationComplete() { - // Okay, the agent successfully reported back to us! + public void operationComplete(int unusedResult) { + // The agent reported back to us! + + if (mBackupData == null) { + // This callback was racing with our timeout, so we've cleaned up the + // agent state already and are on to the next thing. We have nothing + // further to do here: agent state having been cleared means that we've + // initiated the appropriate next operation. + final String pkg = (mCurrentPackage != null) + ? mCurrentPackage.packageName : "[none]"; + if (DEBUG) { + Slog.i(TAG, "Callback after agent teardown: " + pkg); + } + addBackupTrace("late opComplete; curPkg = " + pkg); + return; + } + final String pkgName = mCurrentPackage.packageName; final long filepos = mBackupDataName.length(); FileDescriptor fd = mBackupData.getFileDescriptor(); @@ -2923,12 +2960,22 @@ public class BackupManagerService { void revertAndEndBackup() { if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything"); addBackupTrace("transport error; reverting"); + + // We want to reset the backup schedule based on whatever the transport suggests + // by way of retry/backoff time. + long delay; + try { + delay = mTransport.requestBackupTime(); + } catch (Exception e) { + Slog.w(TAG, "Unable to contact transport for recommended backoff"); + delay = 0; // use the scheduler's default + } + KeyValueBackupJob.schedule(mContext, delay); + for (BackupRequest request : mOriginalQueue) { dataChangedImpl(request.packageName); } - // We also want to reset the backup schedule based on whatever - // the transport suggests by way of retry/backoff time. - restartBackupAlarm(); + } void agentErrorCleanup() { @@ -2961,15 +3008,6 @@ public class BackupManagerService { } } - void restartBackupAlarm() { - addBackupTrace("setting backup trigger"); - synchronized (mQueueLock) { - try { - startBackupAlarmsLocked(mTransport.requestBackupTime()); - } catch (RemoteException e) { /* cannot happen */ } - } - } - void executeNextState(BackupState nextState) { if (MORE_DEBUG) Slog.i(TAG, " => executing next step on " + this + " nextState=" + nextState); @@ -3096,8 +3134,23 @@ public class BackupManagerService { // Core logic for performing one package's full backup, gathering the tarball from the // application and emitting it to the designated OutputStream. + + // Callout from the engine to an interested participant that might need to communicate + // with the agent prior to asking it to move data + interface FullBackupPreflight { + /** + * Perform the preflight operation necessary for the given package. + * @param pkg The name of the package being proposed for full-data backup + * @param agent Live BackupAgent binding to the target app's agent + * @return BackupTransport.TRANSPORT_OK to proceed with the backup operation, + * or one of the other BackupTransport.* error codes as appropriate + */ + int preflightFullBackup(PackageInfo pkg, IBackupAgent agent); + }; + class FullBackupEngine { OutputStream mOutput; + FullBackupPreflight mPreflightHook; IFullBackupRestoreObserver mObserver; File mFilesDir; File mManifestFile; @@ -3128,8 +3181,7 @@ public class BackupManagerService { @Override public void run() { try { - BackupDataOutput output = new BackupDataOutput( - mPipe.getFileDescriptor()); + FullBackupDataOutput output = new FullBackupDataOutput(mPipe); if (mWriteManifest) { final boolean writeWidgetData = mWidgetData != null; @@ -3172,15 +3224,16 @@ public class BackupManagerService { } } - FullBackupEngine(OutputStream output, String packageName, boolean alsoApks) { + FullBackupEngine(OutputStream output, String packageName, FullBackupPreflight preflightHook, + boolean alsoApks) { mOutput = output; + mPreflightHook = preflightHook; mIncludeApks = alsoApks; mFilesDir = new File("/data/system"); mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME); mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME); } - public int backupOnePackage(PackageInfo pkg) throws RemoteException { int result = BackupTransport.TRANSPORT_OK; Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName); @@ -3190,42 +3243,52 @@ public class BackupManagerService { if (agent != null) { ParcelFileDescriptor[] pipes = null; try { - pipes = ParcelFileDescriptor.createPipe(); - - ApplicationInfo app = pkg.applicationInfo; - final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); - final boolean sendApk = mIncludeApks - && !isSharedStorage - && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0) - && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || + // Call the preflight hook, if any + if (mPreflightHook != null) { + result = mPreflightHook.preflightFullBackup(pkg, agent); + if (MORE_DEBUG) { + Slog.v(TAG, "preflight returned " + result); + } + } + + // If we're still good to go after preflighting, start moving data + if (result == BackupTransport.TRANSPORT_OK) { + pipes = ParcelFileDescriptor.createPipe(); + + ApplicationInfo app = pkg.applicationInfo; + final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); + final boolean sendApk = mIncludeApks + && !isSharedStorage + && ((app.privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) == 0) + && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); - byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName, - UserHandle.USER_OWNER); + byte[] widgetBlob = AppWidgetBackupBridge.getWidgetState(pkg.packageName, + UserHandle.USER_OWNER); - final int token = generateToken(); - FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1], - token, sendApk, !isSharedStorage, widgetBlob); - pipes[1].close(); // the runner has dup'd it - pipes[1] = null; - Thread t = new Thread(runner, "app-data-runner"); - t.start(); + final int token = generateToken(); + FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1], + token, sendApk, !isSharedStorage, widgetBlob); + pipes[1].close(); // the runner has dup'd it + pipes[1] = null; + Thread t = new Thread(runner, "app-data-runner"); + t.start(); - // Now pull data from the app and stuff it into the output - try { - routeSocketDataToOutput(pipes[0], mOutput); - } catch (IOException e) { - Slog.i(TAG, "Caught exception reading from agent", e); - result = BackupTransport.AGENT_ERROR; - } + // Now pull data from the app and stuff it into the output + try { + routeSocketDataToOutput(pipes[0], mOutput); + } catch (IOException e) { + Slog.i(TAG, "Caught exception reading from agent", e); + result = BackupTransport.AGENT_ERROR; + } - if (!waitUntilOperationComplete(token)) { - Slog.e(TAG, "Full backup failed on package " + pkg.packageName); - result = BackupTransport.AGENT_ERROR; - } else { - if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName); + if (!waitUntilOperationComplete(token)) { + Slog.e(TAG, "Full backup failed on package " + pkg.packageName); + result = BackupTransport.AGENT_ERROR; + } else { + if (DEBUG) Slog.d(TAG, "Full package backup success: " + pkg.packageName); + } } - } catch (IOException e) { Slog.e(TAG, "Error backing up " + pkg.packageName, e); result = BackupTransport.AGENT_ERROR; @@ -3250,7 +3313,7 @@ public class BackupManagerService { return result; } - private void writeApkToBackup(PackageInfo pkg, BackupDataOutput output) { + private void writeApkToBackup(PackageInfo pkg, FullBackupDataOutput output) { // Forward-locked apps, system-bundled .apks, etc are filtered out before we get here // TODO: handle backing up split APKs final String appSourceDir = pkg.applicationInfo.getBaseCodePath(); @@ -3749,7 +3812,7 @@ public class BackupManagerService { final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); - mBackupEngine = new FullBackupEngine(out, pkg.packageName, mIncludeApks); + mBackupEngine = new FullBackupEngine(out, pkg.packageName, null, mIncludeApks); sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName); mBackupEngine.backupOnePackage(pkg); @@ -3796,13 +3859,13 @@ public class BackupManagerService { static final String TAG = "PFTBT"; ArrayList<PackageInfo> mPackages; boolean mUpdateSchedule; - AtomicBoolean mLatch; + CountDownLatch mLatch; AtomicBoolean mKeepRunning; // signal from job scheduler FullBackupJob mJob; // if a scheduled job needs to be finished afterwards PerformFullTransportBackupTask(IFullBackupRestoreObserver observer, String[] whichPackages, boolean updateSchedule, - FullBackupJob runningJob, AtomicBoolean latch) { + FullBackupJob runningJob, CountDownLatch latch) { super(observer); mUpdateSchedule = updateSchedule; mLatch = latch; @@ -3831,6 +3894,14 @@ public class BackupManagerService { Slog.d(TAG, "Ignoring non-agent system package " + pkg); } continue; + } else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { + // Cull any packages in the 'stopped' state: they've either just been + // installed or have explicitly been force-stopped by the user. In both + // cases we do not want to launch them for backup. + if (MORE_DEBUG) { + Slog.d(TAG, "Ignoring stopped package " + pkg); + } + continue; } mPackages.add(info); } catch (NameNotFoundException e) { @@ -3852,6 +3923,7 @@ public class BackupManagerService { ParcelFileDescriptor[] transportPipes = null; PackageInfo currentPackage; + long backoff = 0; try { if (!mEnabled || !mProvisioned) { @@ -3894,10 +3966,10 @@ public class BackupManagerService { // Now set up the backup engine / data source end of things enginePipes = ParcelFileDescriptor.createPipe(); - AtomicBoolean runnerLatch = new AtomicBoolean(false); + CountDownLatch runnerLatch = new CountDownLatch(1); SinglePackageBackupRunner backupRunner = new SinglePackageBackupRunner(enginePipes[1], currentPackage, - runnerLatch); + transport, runnerLatch); // The runner dup'd the pipe half, so we close it here enginePipes[1].close(); enginePipes[1] = null; @@ -3922,6 +3994,9 @@ public class BackupManagerService { break; } nRead = in.read(buffer); + if (MORE_DEBUG) { + Slog.v(TAG, "in.read(buffer) from app: " + nRead); + } if (nRead > 0) { out.write(buffer, 0, nRead); result = transport.sendBackupData(nRead); @@ -3953,6 +4028,14 @@ public class BackupManagerService { Slog.e(TAG, "Error " + result + " backing up " + currentPackage.packageName); } + + // Also ask the transport how long it wants us to wait before + // moving on to the next package, if any. + backoff = transport.requestFullBackupTime(); + if (DEBUG_SCHEDULING) { + Slog.i(TAG, "Transport suggested backoff=" + backoff); + } + } // Roll this package to the end of the backup queue if we're @@ -4005,15 +4088,12 @@ public class BackupManagerService { mRunningFullBackupTask = null; } - synchronized (mLatch) { - mLatch.set(true); - mLatch.notifyAll(); - } + mLatch.countDown(); // Now that we're actually done with schedule-driven work, reschedule // the next pass based on the new queue state. if (mUpdateSchedule) { - scheduleNextFullBackupJob(); + scheduleNextFullBackupJob(backoff); } } } @@ -4044,16 +4124,79 @@ public class BackupManagerService { // Run the backup and pipe it back to the given socket -- expects to run on // a standalone thread. The runner owns this half of the pipe, and closes // it to indicate EOD to the other end. + class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight { + final AtomicInteger mResult = new AtomicInteger(); + final CountDownLatch mLatch = new CountDownLatch(1); + final IBackupTransport mTransport; + + public SinglePackageBackupPreflight(IBackupTransport transport) { + mTransport = transport; + } + + @Override + public int preflightFullBackup(PackageInfo pkg, IBackupAgent agent) { + int result; + try { + final int token = generateToken(); + prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL, this); + addBackupTrace("preflighting"); + if (MORE_DEBUG) { + Slog.d(TAG, "Preflighting full payload of " + pkg.packageName); + } + agent.doMeasureFullBackup(token, mBackupManagerBinder); + + // now wait to get our result back + mLatch.await(); + int totalSize = mResult.get(); + if (MORE_DEBUG) { + Slog.v(TAG, "Got preflight response; size=" + totalSize); + } + + result = mTransport.checkFullBackupSize(totalSize); + } catch (Exception e) { + Slog.w(TAG, "Exception preflighting " + pkg.packageName + ": " + e.getMessage()); + result = BackupTransport.AGENT_ERROR; + } + return result; + } + + @Override + public void execute() { + // Unused in this case + } + + @Override + public void operationComplete(int result) { + // got the callback, and our preflightFullBackup() method is waiting for the result + if (MORE_DEBUG) { + Slog.i(TAG, "Preflight op complete, result=" + result); + } + mResult.set(result); + mLatch.countDown(); + } + + @Override + public void handleTimeout() { + if (MORE_DEBUG) { + Slog.i(TAG, "Preflight timeout; failing"); + } + mResult.set(BackupTransport.AGENT_ERROR); + mLatch.countDown(); + } + + } + class SinglePackageBackupRunner implements Runnable { final ParcelFileDescriptor mOutput; final PackageInfo mTarget; - final AtomicBoolean mLatch; + final FullBackupPreflight mPreflight; + final CountDownLatch mLatch; SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target, - AtomicBoolean latch) throws IOException { - int oldfd = output.getFd(); + IBackupTransport transport, CountDownLatch latch) throws IOException { mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor()); mTarget = target; + mPreflight = new SinglePackageBackupPreflight(transport); mLatch = latch; } @@ -4061,15 +4204,13 @@ public class BackupManagerService { public void run() { try { FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor()); - FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName, false); + FullBackupEngine engine = new FullBackupEngine(out, mTarget.packageName, + mPreflight, false); engine.backupOnePackage(mTarget); } catch (Exception e) { Slog.e(TAG, "Exception during full package backup of " + mTarget); } finally { - synchronized (mLatch) { - mLatch.set(true); - mLatch.notifyAll(); - } + mLatch.countDown(); try { mOutput.close(); } catch (IOException e) { @@ -4077,7 +4218,6 @@ public class BackupManagerService { } } } - } } @@ -4086,16 +4226,17 @@ public class BackupManagerService { /** * Schedule a job to tell us when it's a good time to run a full backup */ - void scheduleNextFullBackupJob() { + void scheduleNextFullBackupJob(long transportMinLatency) { synchronized (mQueueLock) { if (mFullBackupQueue.size() > 0) { // schedule the next job at the point in the future when the least-recently // backed up app comes due for backup again; or immediately if it's already // due. - long upcomingLastBackup = mFullBackupQueue.get(0).lastBackup; - long timeSinceLast = System.currentTimeMillis() - upcomingLastBackup; - final long latency = (timeSinceLast < MIN_FULL_BACKUP_INTERVAL) + final long upcomingLastBackup = mFullBackupQueue.get(0).lastBackup; + final long timeSinceLast = System.currentTimeMillis() - upcomingLastBackup; + final long appLatency = (timeSinceLast < MIN_FULL_BACKUP_INTERVAL) ? (MIN_FULL_BACKUP_INTERVAL - timeSinceLast) : 0; + final long latency = Math.min(transportMinLatency, appLatency); Runnable r = new Runnable() { @Override public void run() { FullBackupJob.schedule(mContext, latency); @@ -4149,6 +4290,31 @@ public class BackupManagerService { writeFullBackupScheduleAsync(); } + private boolean fullBackupAllowable(IBackupTransport transport) { + if (transport == null) { + Slog.w(TAG, "Transport not present; full data backup not performed"); + return false; + } + + // Don't proceed unless we have already established package metadata + // for the current dataset via a key/value backup pass. + try { + File stateDir = new File(mBaseStateDir, transport.transportDirName()); + File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL); + if (pmState.length() <= 0) { + if (DEBUG) { + Slog.i(TAG, "Full backup requested but dataset not yet initialized"); + } + return false; + } + } catch (Exception e) { + Slog.w(TAG, "Unable to contact transport"); + return false; + } + + return true; + } + /** * Conditions are right for a full backup operation, so run one. The model we use is * to perform one app backup per scheduled job execution, and to reschedule the job @@ -4160,6 +4326,7 @@ public class BackupManagerService { boolean beginFullBackup(FullBackupJob scheduledJob) { long now = System.currentTimeMillis(); FullBackupEntry entry = null; + long latency = MIN_FULL_BACKUP_INTERVAL; if (!mEnabled || !mProvisioned) { // Backups are globally disabled, so don't proceed. We also don't reschedule @@ -4191,17 +4358,41 @@ public class BackupManagerService { return false; } - entry = mFullBackupQueue.get(0); - long timeSinceRun = now - entry.lastBackup; - if (timeSinceRun < MIN_FULL_BACKUP_INTERVAL) { - // It's too early to back up the next thing in the queue, so bow out + // At this point we know that we have work to do, just not right now. Any + // exit without actually running backups will also require that we + // reschedule the job. + boolean runBackup = true; + + if (!fullBackupAllowable(getTransport(mCurrentTransport))) { if (MORE_DEBUG) { - Slog.i(TAG, "Device ready but too early to back up next app"); + Slog.i(TAG, "Preconditions not met; not running full backup"); } - final long latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun; + runBackup = false; + // Typically this means we haven't run a key/value backup yet. Back off + // full-backup operations by the key/value job's run interval so that + // next time we run, we are likely to be able to make progress. + latency = KeyValueBackupJob.BATCH_INTERVAL; + } + + if (runBackup) { + entry = mFullBackupQueue.get(0); + long timeSinceRun = now - entry.lastBackup; + runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL); + if (!runBackup) { + // It's too early to back up the next thing in the queue, so bow out + if (MORE_DEBUG) { + Slog.i(TAG, "Device ready but too early to back up next app"); + } + // Wait until the next app in the queue falls due for a full data backup + latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun; + } + } + + if (!runBackup) { + final long deferTime = latency; // pin for the closure mBackupHandler.post(new Runnable() { @Override public void run() { - FullBackupJob.schedule(mContext, latency); + FullBackupJob.schedule(mContext, deferTime); } }); return false; @@ -4209,7 +4400,7 @@ public class BackupManagerService { // Okay, the top thing is runnable now. Pop it off and get going. mFullBackupQueue.remove(0); - AtomicBoolean latch = new AtomicBoolean(false); + CountDownLatch latch = new CountDownLatch(1); String[] pkg = new String[] {entry.packageName}; mRunningFullBackupTask = new PerformFullTransportBackupTask(null, pkg, true, scheduledJob, latch); @@ -4763,7 +4954,7 @@ public class BackupManagerService { } } - class RestoreInstallObserver extends IPackageInstallObserver.Stub { + class RestoreInstallObserver extends PackageInstallObserver { final AtomicBoolean mDone = new AtomicBoolean(); String mPackageName; int mResult; @@ -4789,8 +4980,8 @@ public class BackupManagerService { } @Override - public void packageInstalled(String packageName, int returnCode) - throws RemoteException { + public void onPackageInstalled(String packageName, int returnCode, + String msg, Bundle extras) { synchronized (mDone) { mResult = returnCode; mPackageName = packageName; @@ -5046,7 +5237,9 @@ public class BackupManagerService { offset = extractLine(buffer, offset, str); version = Integer.parseInt(str[0]); // app version offset = extractLine(buffer, offset, str); - int platformVersion = Integer.parseInt(str[0]); + // This is the platform version, which we don't use, but we parse it + // as a safety against corruption in the manifest. + Integer.parseInt(str[0]); offset = extractLine(buffer, offset, str); info.installerPackageName = (str[0].length() > 0) ? str[0] : null; offset = extractLine(buffer, offset, str); @@ -6107,7 +6300,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } - class RestoreInstallObserver extends IPackageInstallObserver.Stub { + class RestoreInstallObserver extends PackageInstallObserver { final AtomicBoolean mDone = new AtomicBoolean(); String mPackageName; int mResult; @@ -6133,8 +6326,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } @Override - public void packageInstalled(String packageName, int returnCode) - throws RemoteException { + public void onPackageInstalled(String packageName, int returnCode, + String msg, Bundle extras) { synchronized (mDone) { mResult = returnCode; mPackageName = packageName; @@ -6383,7 +6576,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF offset = extractLine(buffer, offset, str); version = Integer.parseInt(str[0]); // app version offset = extractLine(buffer, offset, str); - int platformVersion = Integer.parseInt(str[0]); + // This is the platform version, which we don't use, but we parse it + // as a safety against corruption in the manifest. + Integer.parseInt(str[0]); offset = extractLine(buffer, offset, str); info.installerPackageName = (str[0].length() > 0) ? str[0] : null; offset = extractLine(buffer, offset, str); @@ -7842,7 +8037,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } @Override - public void operationComplete() { + public void operationComplete(int unusedResult) { if (MORE_DEBUG) { Slog.i(TAG, "operationComplete() during restore: target=" + mCurrentPackage.packageName @@ -8095,6 +8290,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } } + + // ...and schedule a backup pass if necessary + KeyValueBackupJob.schedule(mContext); } // Note: packageName is currently unused, but may be in the future @@ -8229,16 +8427,16 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { - // Because the alarms we are using can jitter, and we want an *immediate* - // backup pass to happen, we restart the timer beginning with "next time," - // then manually fire the backup trigger intent ourselves. - startBackupAlarmsLocked(BACKUP_INTERVAL); + // Fire the intent that kicks off the whole shebang... try { mRunBackupIntent.send(); } catch (PendingIntent.CanceledException e) { // should never happen Slog.e(TAG, "run-backup intent cancelled!"); } + + // ...and cancel any pending scheduled job, because we've just superseded it + KeyValueBackupJob.cancel(mContext); } } @@ -8305,7 +8503,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } // make sure the screen is lit for the user interaction - mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + mPowerManager.userActivity(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_OTHER, + 0); // start the confirmation countdown startConfirmationTimeout(token, params); @@ -8333,21 +8533,33 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF throw new IllegalStateException("Restore supported only for the device owner"); } - if (DEBUG) { - Slog.d(TAG, "fullTransportBackup()"); - } + if (!fullBackupAllowable(getTransport(mCurrentTransport))) { + Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?"); + } else { + if (DEBUG) { + Slog.d(TAG, "fullTransportBackup()"); + } - AtomicBoolean latch = new AtomicBoolean(false); - PerformFullTransportBackupTask task = - new PerformFullTransportBackupTask(null, pkgNames, false, null, latch); - (new Thread(task, "full-transport-master")).start(); - synchronized (latch) { - try { - while (latch.get() == false) { - latch.wait(); + CountDownLatch latch = new CountDownLatch(1); + PerformFullTransportBackupTask task = + new PerformFullTransportBackupTask(null, pkgNames, false, null, latch); + (new Thread(task, "full-transport-master")).start(); + do { + try { + latch.await(); + break; + } catch (InterruptedException e) { + // Just go back to waiting for the latch to indicate completion } - } catch (InterruptedException e) {} + } while (true); + + // We just ran a backup on these packages, so kick them to the end of the queue + final long now = System.currentTimeMillis(); + for (String pkg : pkgNames) { + enqueueFullBackup(pkg, now); + } } + if (DEBUG) { Slog.d(TAG, "Done with full transport backup."); } @@ -8388,7 +8600,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } // make sure the screen is lit for the user interaction - mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + mPowerManager.userActivity(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_OTHER, + 0); // start the confirmation countdown startConfirmationTimeout(token, params); @@ -8514,13 +8728,13 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF synchronized (mQueueLock) { if (enable && !wasEnabled && mProvisioned) { // if we've just been enabled, start scheduling backup passes - startBackupAlarmsLocked(BACKUP_INTERVAL); - scheduleNextFullBackupJob(); + KeyValueBackupJob.schedule(mContext); + scheduleNextFullBackupJob(0); } else if (!enable) { // No longer enabled, so stop running backups if (DEBUG) Slog.i(TAG, "Opting out of backup"); - mAlarmManager.cancel(mRunBackupIntent); + KeyValueBackupJob.cancel(mContext); // This also constitutes an opt-out, so we wipe any data for // this device from the backend. We start that process with @@ -8574,19 +8788,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF */ } - private void startBackupAlarmsLocked(long delayBeforeFirstBackup) { - // We used to use setInexactRepeating(), but that may be linked to - // backups running at :00 more often than not, creating load spikes. - // Schedule at an exact time for now, and also add a bit of "fuzz". - - Random random = new Random(); - long when = System.currentTimeMillis() + delayBeforeFirstBackup + - random.nextInt(FUZZ_MILLIS); - mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, when, - BACKUP_INTERVAL + random.nextInt(FUZZ_MILLIS), mRunBackupIntent); - mNextBackupPass = when; - } - // Report whether the backup mechanism is currently enabled public boolean isBackupEnabled() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "isBackupEnabled"); @@ -8901,8 +9102,10 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // Note that a currently-active backup agent has notified us that it has // completed the given outstanding asynchronous backup/restore operation. - public void opComplete(int token) { - if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token)); + public void opComplete(int token, long result) { + if (MORE_DEBUG) { + Slog.v(TAG, "opComplete: " + Integer.toHexString(token) + " result=" + result); + } Operation op = null; synchronized (mCurrentOpLock) { op = mCurrentOperations.get(token); @@ -8915,6 +9118,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // The completion callback, if any, is invoked on the handler if (op != null && op.callback != null) { Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, op.callback); + // NB: this cannot distinguish between results > 2 gig + msg.arg1 = (result > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) result; mBackupHandler.sendMessage(msg); } } @@ -9167,6 +9372,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // check whether there is data for it in the current dataset, falling back // to the ancestral dataset if not. long token = getAvailableRestoreToken(packageName); + if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName + + " token=" + Long.toHexString(token)); // If we didn't come up with a place to look -- no ancestral dataset and // the app has never been backed up from this device -- there's nothing @@ -9292,7 +9499,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF if (mBackupRunning) pw.println("Backup currently running"); pw.println("Last backup pass started: " + mLastBackupPass + " (now = " + System.currentTimeMillis() + ')'); - pw.println(" next scheduled: " + mNextBackupPass); + pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled()); pw.println("Available transports:"); final String[] transports = listAllTransports(); diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java new file mode 100644 index 0000000..a4489c1 --- /dev/null +++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2015 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.backup; + +import android.app.AlarmManager; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.Random; + +/** + * Job for scheduling key/value backup work. This module encapsulates all + * of the policy around when those backup passes are executed. + */ +public class KeyValueBackupJob extends JobService { + private static final String TAG = "KeyValueBackupJob"; + private static ComponentName sKeyValueJobService = + new ComponentName("android", KeyValueBackupJob.class.getName()); + private static final int JOB_ID = 0x5039; + + // Once someone asks for a backup, this is how long we hold off, batching + // up additional requests, before running the actual backup pass. Privileged + // callers can always trigger an immediate pass via BackupManager.backupNow(). + static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR; + + // Random variation in next-backup scheduling time to avoid server load spikes + private static final int FUZZ_MILLIS = 10 * 60 * 1000; + + // Don't let the job scheduler defer forever; give it a (lenient) deadline + private static final long MAX_DEFERRAL = 1 * AlarmManager.INTERVAL_HOUR; + + private static boolean sScheduled = false; + private static long sNextScheduled = 0; + + public static void schedule(Context ctx) { + schedule(ctx, 0); + } + + public static void schedule(Context ctx, long delay) { + synchronized (KeyValueBackupJob.class) { + if (!sScheduled) { + if (delay <= 0) { + delay = BATCH_INTERVAL + new Random().nextInt(FUZZ_MILLIS); + } + if (BackupManagerService.DEBUG_SCHEDULING) { + Slog.v(TAG, "Scheduling k/v pass in " + + (delay / 1000 / 60) + " minutes"); + } + JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); + JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService) + .setMinimumLatency(delay) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setOverrideDeadline(delay + MAX_DEFERRAL); + js.schedule(builder.build()); + + sNextScheduled = System.currentTimeMillis() + delay; + sScheduled = true; + } + } + } + + public static void cancel(Context ctx) { + synchronized (KeyValueBackupJob.class) { + JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); + js.cancel(JOB_ID); + sNextScheduled = 0; + sScheduled = false; + } + } + + public static long nextScheduled() { + synchronized (KeyValueBackupJob.class) { + return sNextScheduled; + } + } + + @Override + public boolean onStartJob(JobParameters params) { + synchronized (KeyValueBackupJob.class) { + sNextScheduled = 0; + sScheduled = false; + } + + // Time to run a key/value backup! + Trampoline service = BackupManagerService.getInstance(); + try { + service.backupNow(); + } catch (RemoteException e) {} + + // This was just a trigger; ongoing wakelock management is done by the + // rest of the backup system. + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + // Intentionally empty; the job starting was just a trigger + return false; + } + +} diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 8bd7132..5859c6a 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -309,15 +309,23 @@ public class Trampoline extends IBackupManager.Stub { } @Override - public void opComplete(int token) throws RemoteException { + public void opComplete(int token, long result) throws RemoteException { BackupManagerService svc = mService; if (svc != null) { - svc.opComplete(token); + svc.opComplete(token, result); } } @Override + public long getAvailableRestoreToken(String packageName) { + BackupManagerService svc = mService; + return (svc != null) ? svc.getAvailableRestoreToken(packageName) : 0; + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + BackupManagerService svc = mService; if (svc != null) { svc.dump(fd, pw, args); diff --git a/services/core/Android.mk b/services/core/Android.mk index 5c45201..64b6134 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -9,6 +9,7 @@ LOCAL_SRC_FILES += \ java/com/android/server/EventLogTags.logtags \ java/com/android/server/am/EventLogTags.logtags -LOCAL_JAVA_LIBRARIES := android.policy telephony-common +LOCAL_JAVA_LIBRARIES := telephony-common +LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 65a5c23..0e3867d 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -34,6 +34,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; +import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; @@ -61,6 +62,7 @@ import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.Locale; +import java.util.Random; import java.util.TimeZone; import static android.app.AlarmManager.RTC_WAKEUP; @@ -97,6 +99,7 @@ class AlarmManagerService extends SystemService { static final boolean DEBUG_BATCH = localLOGV || false; static final boolean DEBUG_VALIDATE = localLOGV || false; static final boolean DEBUG_ALARM_CLOCK = localLOGV || false; + static final boolean RECORD_ALARMS_IN_HISTORY = true; static final int ALARM_EVENT = 1; static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; @@ -128,6 +131,7 @@ class AlarmManagerService extends SystemService { final ResultReceiver mResultReceiver = new ResultReceiver(); PendingIntent mTimeTickSender; PendingIntent mDateChangeSender; + Random mRandom; boolean mInteractive = true; long mNonInteractiveStartTime; long mNonInteractiveTime; @@ -185,18 +189,20 @@ class AlarmManagerService extends SystemService { final class Batch { long start; // These endpoints are always in ELAPSED long end; - boolean standalone; // certain "batches" don't participate in coalescing + int flags; // Flags for alarms, such as FLAG_STANDALONE. final ArrayList<Alarm> alarms = new ArrayList<Alarm>(); Batch() { start = 0; end = Long.MAX_VALUE; + flags = 0; } Batch(Alarm seed) { start = seed.whenElapsed; - end = seed.maxWhen; + end = seed.maxWhenElapsed; + flags = seed.flags; alarms.add(seed); } @@ -227,9 +233,10 @@ class AlarmManagerService extends SystemService { start = alarm.whenElapsed; newStart = true; } - if (alarm.maxWhen < end) { - end = alarm.maxWhen; + if (alarm.maxWhenElapsed < end) { + end = alarm.maxWhenElapsed; } + flags |= alarm.flags; if (DEBUG_BATCH) { Slog.v(TAG, " => now " + this); @@ -241,6 +248,7 @@ class AlarmManagerService extends SystemService { boolean didRemove = false; long newStart = 0; // recalculate endpoints as we go long newEnd = Long.MAX_VALUE; + int newFlags = 0; for (int i = 0; i < alarms.size(); ) { Alarm alarm = alarms.get(i); if (alarm.operation.equals(operation)) { @@ -253,9 +261,10 @@ class AlarmManagerService extends SystemService { if (alarm.whenElapsed > newStart) { newStart = alarm.whenElapsed; } - if (alarm.maxWhen < newEnd) { - newEnd = alarm.maxWhen; + if (alarm.maxWhenElapsed < newEnd) { + newEnd = alarm.maxWhenElapsed; } + newFlags |= alarm.flags; i++; } } @@ -263,6 +272,7 @@ class AlarmManagerService extends SystemService { // commit the new batch bounds start = newStart; end = newEnd; + flags = newFlags; } return didRemove; } @@ -271,6 +281,7 @@ class AlarmManagerService extends SystemService { boolean didRemove = false; long newStart = 0; // recalculate endpoints as we go long newEnd = Long.MAX_VALUE; + int newFlags = 0; for (int i = 0; i < alarms.size(); ) { Alarm alarm = alarms.get(i); if (alarm.operation.getTargetPackage().equals(packageName)) { @@ -283,9 +294,10 @@ class AlarmManagerService extends SystemService { if (alarm.whenElapsed > newStart) { newStart = alarm.whenElapsed; } - if (alarm.maxWhen < newEnd) { - newEnd = alarm.maxWhen; + if (alarm.maxWhenElapsed < newEnd) { + newEnd = alarm.maxWhenElapsed; } + newFlags |= alarm.flags; i++; } } @@ -293,6 +305,7 @@ class AlarmManagerService extends SystemService { // commit the new batch bounds start = newStart; end = newEnd; + flags = newFlags; } return didRemove; } @@ -313,8 +326,8 @@ class AlarmManagerService extends SystemService { if (alarm.whenElapsed > newStart) { newStart = alarm.whenElapsed; } - if (alarm.maxWhen < newEnd) { - newEnd = alarm.maxWhen; + if (alarm.maxWhenElapsed < newEnd) { + newEnd = alarm.maxWhenElapsed; } i++; } @@ -357,8 +370,9 @@ class AlarmManagerService extends SystemService { b.append(" num="); b.append(size()); b.append(" start="); b.append(start); b.append(" end="); b.append(end); - if (standalone) { - b.append(" STANDALONE"); + if (flags != 0) { + b.append(" flgs=0x"); + b.append(Integer.toHexString(flags)); } b.append('}'); return b.toString(); @@ -441,7 +455,13 @@ class AlarmManagerService extends SystemService { // minimum recurrence period or alarm futurity for us to be able to fuzz it static final long MIN_FUZZABLE_INTERVAL = 10000; static final BatchTimeOrder sBatchOrder = new BatchTimeOrder(); - final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>(); + final ArrayList<Batch> mAlarmBatches = new ArrayList<>(); + + // set to null if in idle mode; while in this mode, any alarms we don't want + // to run during this time are placed in mPendingWhileIdleAlarms + Alarm mPendingIdleUntil = null; + Alarm mNextWakeFromIdle = null; + final ArrayList<Alarm> mPendingWhileIdleAlarms = new ArrayList<>(); public AlarmManagerService(Context context) { super(context); @@ -486,7 +506,7 @@ class AlarmManagerService extends SystemService { final int N = mAlarmBatches.size(); for (int i = 0; i < N; i++) { Batch b = mAlarmBatches.get(i); - if (!b.standalone && b.canHold(whenElapsed, maxWhen)) { + if ((b.flags&AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) { return i; } } @@ -503,31 +523,65 @@ class AlarmManagerService extends SystemService { void rebatchAllAlarmsLocked(boolean doValidate) { ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone(); mAlarmBatches.clear(); + Alarm oldPendingIdleUntil = mPendingIdleUntil; 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); - final long maxElapsed; - if (a.whenElapsed == a.maxWhen) { - // Exact - maxElapsed = whenElapsed; - } else { - // Not exact. Preserve any explicit window, otherwise recalculate - // the window based on the alarm's new futurity. Note that this - // reflects a policy of preferring timely to deferred delivery. - maxElapsed = (a.windowLength > 0) - ? (whenElapsed + a.windowLength) - : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval); - } - setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed, - a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource, - a.alarmClock, a.userId); + reAddAlarmLocked(batch.get(i), nowElapsed, doValidate); } } + if (oldPendingIdleUntil != null && oldPendingIdleUntil != mPendingIdleUntil) { + Slog.wtf(TAG, "Rebatching: idle until changed from " + oldPendingIdleUntil + + " to " + mPendingIdleUntil); + if (mPendingIdleUntil == null) { + // Somehow we lost this... we need to restore all of the pending alarms. + restorePendingWhileIdleAlarmsLocked(); + } + } + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } + + void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) { + a.when = a.origWhen; + long whenElapsed = convertToElapsed(a.when, a.type); + final long maxElapsed; + if (a.whenElapsed == a.maxWhenElapsed) { + // Exact + maxElapsed = whenElapsed; + } else { + // Not exact. Preserve any explicit window, otherwise recalculate + // the window based on the alarm's new futurity. Note that this + // reflects a policy of preferring timely to deferred delivery. + maxElapsed = (a.windowLength > 0) + ? (whenElapsed + a.windowLength) + : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval); + } + a.whenElapsed = whenElapsed; + a.maxWhenElapsed = maxElapsed; + setImplLocked(a, true, doValidate); + } + + void restorePendingWhileIdleAlarmsLocked() { + // Bring pending alarms back into the main list. + final long nowElapsed = SystemClock.elapsedRealtime(); + for (int i=mPendingWhileIdleAlarms.size() - 1; i >= 0 && mPendingIdleUntil == null; i--) { + Alarm a = mPendingWhileIdleAlarms.remove(i); + reAddAlarmLocked(a, nowElapsed, false); + } + + // Reschedule everything. + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + + // And send a TIME_TICK right now, since it is important to get the UI updated. + try { + mTimeTickSender.send(); + } catch (PendingIntent.CanceledException e) { + } } static final class InFlight extends Intent { @@ -539,7 +593,7 @@ class AlarmManagerService extends SystemService { final int mAlarmType; InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource, - int alarmType, String tag) { + int alarmType, String tag, long nowELAPSED) { mPendingIntent = pendingIntent; mWorkSource = workSource; mTag = tag; @@ -549,6 +603,7 @@ class AlarmManagerService extends SystemService { fs = new FilterStats(mBroadcastStats, mTag); mBroadcastStats.filterStats.put(mTag, fs); } + fs.lastTime = nowELAPSED; mFilterStats = fs; mAlarmType = alarmType; } @@ -558,6 +613,7 @@ class AlarmManagerService extends SystemService { final BroadcastStats mBroadcastStats; final String mTag; + long lastTime; long aggregateTime; int count; int numWakeup; @@ -687,7 +743,7 @@ class AlarmManagerService extends SystemService { } void setImpl(int type, long triggerAtTime, long windowLength, long interval, - PendingIntent operation, boolean isStandalone, WorkSource workSource, + PendingIntent operation, int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) { if (operation == null) { Slog.w(TAG, "set/setRepeating ignored because there is no intent"); @@ -745,25 +801,71 @@ class AlarmManagerService extends SystemService { Slog.v(TAG, "set(" + operation + ") : type=" + type + " triggerAtTime=" + triggerAtTime + " win=" + windowLength + " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed - + " interval=" + interval + " standalone=" + isStandalone); + + " interval=" + interval + " flags=0x" + Integer.toHexString(flags)); } setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed, - interval, operation, isStandalone, true, workSource, alarmClock, userId); + interval, operation, flags, true, workSource, alarmClock, userId); } } private void setImplLocked(int type, long when, long whenElapsed, long windowLength, - long maxWhen, long interval, PendingIntent operation, boolean isStandalone, + long maxWhen, long interval, PendingIntent operation, int flags, boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock, int userId) { Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval, - operation, workSource, alarmClock, userId); + operation, workSource, flags, alarmClock, userId); removeLocked(operation); + setImplLocked(a, false, doValidate); + } + + private void updateNextWakeFromIdleFuzzLocked() { + if (mNextWakeFromIdle != null) { + + } + } - int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen); + private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) { + if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) { + // This is a special alarm that will put the system into idle until it goes off. + // The caller has given the time they want this to happen at, however we need + // to pull that earlier if there are existing alarms that have requested to + // bring us out of idle. + if (mNextWakeFromIdle != null) { + a.when = a.whenElapsed = a.maxWhenElapsed = mNextWakeFromIdle.whenElapsed; + } + // Add fuzz to make the alarm go off some time before the actual desired time. + final long nowElapsed = SystemClock.elapsedRealtime(); + final int fuzz = fuzzForDuration(a.whenElapsed-nowElapsed); + if (fuzz > 0) { + if (mRandom == null) { + mRandom = new Random(); + } + final int delta = mRandom.nextInt(fuzz); + a.whenElapsed -= delta; + if (false) { + Slog.d(TAG, "Alarm when: " + a.whenElapsed); + Slog.d(TAG, "Delta until alarm: " + (a.whenElapsed-nowElapsed)); + Slog.d(TAG, "Applied fuzz: " + fuzz); + Slog.d(TAG, "Final delta: " + delta); + Slog.d(TAG, "Final when: " + a.whenElapsed); + } + a.when = a.maxWhenElapsed = a.whenElapsed; + } + + } else if (mPendingIdleUntil != null) { + // We currently have an idle until alarm scheduled; if the new alarm has + // not explicitly stated it wants to run while idle, then put it on hold. + if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE|AlarmManager.FLAG_WAKE_FROM_IDLE)) + == 0) { + mPendingWhileIdleAlarms.add(a); + return; + } + } + + int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0) + ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed); if (whichBatch < 0) { Batch batch = new Batch(a); - batch.standalone = isStandalone; addBatchLocked(mAlarmBatches, batch); } else { Batch batch = mAlarmBatches.get(whichBatch); @@ -775,28 +877,53 @@ class AlarmManagerService extends SystemService { } } - if (alarmClock != null) { + if (a.alarmClock != null) { mNextAlarmClockMayChange = true; - updateNextAlarmClockLocked(); } - 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); + boolean needRebatch = false; + + if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) { + mPendingIdleUntil = a; + needRebatch = true; + } else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) { + if (mNextWakeFromIdle == null || mNextWakeFromIdle.whenElapsed > a.whenElapsed) { + mNextWakeFromIdle = a; + // If this wake from idle is earlier than whatever was previously scheduled, + // and we are currently idling, then we need to rebatch alarms in case the idle + // until time needs to be updated. + if (mPendingIdleUntil != null) { + needRebatch = true; + } } } - rescheduleKernelAlarmsLocked(); + if (!rebatching) { + if (DEBUG_VALIDATE) { + if (doValidate && !validateConsistencyLocked()) { + Slog.v(TAG, "Tipping-point operation: type=" + a.type + " when=" + a.when + + " when(hex)=" + Long.toHexString(a.when) + + " whenElapsed=" + a.whenElapsed + + " maxWhenElapsed=" + a.maxWhenElapsed + + " interval=" + a.repeatInterval + " op=" + a.operation + + " flags=0x" + Integer.toHexString(a.flags)); + rebatchAllAlarmsLocked(false); + needRebatch = false; + } + } + + if (needRebatch) { + rebatchAllAlarmsLocked(false); + } + + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } } private final IBinder mService = new IAlarmManager.Stub() { @Override - public void set(int type, long triggerAtTime, long windowLength, long interval, + public void set(int type, long triggerAtTime, long windowLength, long interval, int flags, PendingIntent operation, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) { if (workSource != null) { @@ -805,8 +932,17 @@ class AlarmManagerService extends SystemService { "AlarmManager.set"); } + if (windowLength == AlarmManager.WINDOW_EXACT) { + flags |= AlarmManager.FLAG_STANDALONE; + } + if (alarmClock != null) { + flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE; + } + if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) { + flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE; + } setImpl(type, triggerAtTime, windowLength, interval, operation, - windowLength == AlarmManager.WINDOW_EXACT, workSource, alarmClock); + flags, workSource, alarmClock); } @Override @@ -846,6 +982,11 @@ class AlarmManagerService extends SystemService { } @Override + public long getNextWakeFromIdleTime() { + return getNextWakeFromIdleTimeImpl(); + } + + @Override public AlarmManager.AlarmClockInfo getNextAlarmClock(int userId) { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false /* allowAll */, false /* requireFull */, @@ -912,6 +1053,19 @@ class AlarmManagerService extends SystemService { dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC, sdf); } } + if (mPendingIdleUntil != null) { + pw.println(); + pw.println("Idle mode state:"); + pw.print(" Idling until: "); pw.println(mPendingIdleUntil); + mPendingIdleUntil.dump(pw, " ", nowRTC, nowELAPSED, sdf); + pw.println(" Pending alarms:"); + dumpAlarmList(pw, mPendingWhileIdleAlarms, " ", nowELAPSED, nowRTC, sdf); + } + if (mNextWakeFromIdle != null) { + pw.println(); + pw.print(" Next wake from idle: "); pw.println(mNextWakeFromIdle); + mNextWakeFromIdle.dump(pw, " ", nowRTC, nowELAPSED, sdf); + } pw.println(); pw.print("Past-due non-wakeup alarms: "); @@ -1018,7 +1172,10 @@ class AlarmManagerService extends SystemService { TimeUtils.formatDuration(fs.aggregateTime, pw); pw.print(" "); pw.print(fs.numWakeup); pw.print(" wakes " ); pw.print(fs.count); - pw.print(" alarms: "); + pw.print(" alarms, last "); + TimeUtils.formatDuration(fs.lastTime, nowELAPSED, pw); + pw.println(":"); + pw.print(" "); pw.print(fs.mTag); pw.println(); } @@ -1094,7 +1251,13 @@ class AlarmManagerService extends SystemService { return null; } - private AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) { + long getNextWakeFromIdleTimeImpl() { + synchronized (mLock) { + return mNextWakeFromIdle != null ? mNextWakeFromIdle.whenElapsed : Long.MAX_VALUE; + } + } + + AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) { synchronized (mLock) { return mNextAlarmClockForUser.get(userId); } @@ -1260,13 +1423,29 @@ class AlarmManagerService extends SystemService { mAlarmBatches.remove(i); } } + for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) { + if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) { + // Don't set didRemove, since this doesn't impact the scheduled alarms. + mPendingWhileIdleAlarms.remove(i); + } + } if (didRemove) { if (DEBUG_BATCH) { Slog.v(TAG, "remove(operation) changed bounds; rebatching"); } + boolean restorePending = false; + if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) { + mPendingIdleUntil = null; + restorePending = true; + } + if (mNextWakeFromIdle != null && mNextWakeFromIdle.operation.equals(operation)) { + mNextWakeFromIdle = null; + } rebatchAllAlarmsLocked(true); - rescheduleKernelAlarmsLocked(); + if (restorePending) { + restorePendingWhileIdleAlarmsLocked(); + } updateNextAlarmClockLocked(); } } @@ -1280,6 +1459,12 @@ class AlarmManagerService extends SystemService { mAlarmBatches.remove(i); } } + for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) { + if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) { + // Don't set didRemove, since this doesn't impact the scheduled alarms. + mPendingWhileIdleAlarms.remove(i); + } + } if (didRemove) { if (DEBUG_BATCH) { @@ -1300,6 +1485,13 @@ class AlarmManagerService extends SystemService { mAlarmBatches.remove(i); } } + for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) { + if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid()) + == userHandle) { + // Don't set didRemove, since this doesn't impact the scheduled alarms. + mPendingWhileIdleAlarms.remove(i); + } + } if (didRemove) { if (DEBUG_BATCH) { @@ -1344,6 +1536,11 @@ class AlarmManagerService extends SystemService { return true; } } + for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) { + if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) { + return true; + } + } return false; } @@ -1432,6 +1629,19 @@ class AlarmManagerService extends SystemService { Alarm alarm = batch.get(i); alarm.count = 1; triggerList.add(alarm); + if ((alarm.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) { + EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0, + alarm.tag); + } + if (mPendingIdleUntil == alarm) { + mPendingIdleUntil = null; + rebatchAllAlarmsLocked(false); + restorePendingWhileIdleAlarmsLocked(); + } + if (mNextWakeFromIdle == alarm) { + mNextWakeFromIdle = null; + rebatchAllAlarmsLocked(false); + } // Recurring alarms may have passed several alarm intervals while the // phone was asleep or off, so pass a trigger count when sending them. @@ -1445,7 +1655,7 @@ class AlarmManagerService extends SystemService { final long nextElapsed = alarm.whenElapsed + delta; setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength, maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval), - alarm.repeatInterval, alarm.operation, batch.standalone, true, + alarm.repeatInterval, alarm.operation, alarm.flags, true, alarm.workSource, alarm.alarmClock, alarm.userId); } @@ -1494,34 +1704,38 @@ class AlarmManagerService extends SystemService { private static class Alarm { public final int type; + public final long origWhen; public final boolean wakeup; public final PendingIntent operation; - public final String tag; + public final String tag; public final WorkSource workSource; + public final int flags; public int count; public long when; public long windowLength; public long whenElapsed; // 'when' in the elapsed time base - public long maxWhen; // also in the elapsed time base + public long maxWhenElapsed; // also in the elapsed time base public long repeatInterval; public final AlarmManager.AlarmClockInfo alarmClock; public final int userId; public PriorityClass priorityClass; public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen, - long _interval, PendingIntent _op, WorkSource _ws, + long _interval, PendingIntent _op, WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info, int _userId) { type = _type; + origWhen = _when; wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP || _type == AlarmManager.RTC_WAKEUP; when = _when; whenElapsed = _whenElapsed; windowLength = _windowLength; - maxWhen = _maxWhen; + maxWhenElapsed = _maxWhen; repeatInterval = _interval; operation = _op; tag = makeTag(_op, _type); workSource = _ws; + flags = _flags; alarmClock = _info; userId = _userId; } @@ -1561,7 +1775,14 @@ class AlarmManagerService extends SystemService { pw.println(); pw.print(prefix); pw.print("window="); pw.print(windowLength); pw.print(" repeatInterval="); pw.print(repeatInterval); - pw.print(" count="); pw.println(count); + pw.print(" count="); pw.print(count); + pw.print(" flags=0x"); pw.println(Integer.toHexString(flags)); + if (alarmClock != null) { + pw.print(prefix); pw.println("Alarm clock:"); + pw.print(prefix); pw.print(" triggerTime="); + pw.println(sdf.format(new Date(alarmClock.getTriggerTime()))); + pw.print(prefix); pw.print(" showIntent="); pw.println(alarmClock.getShowIntent()); + } pw.print(prefix); pw.print("operation="); pw.println(operation); } } @@ -1599,6 +1820,20 @@ class AlarmManagerService extends SystemService { } } + static int fuzzForDuration(long duration) { + if (duration < 15*60*1000) { + // If the duration until the time is less than 15 minutes, the maximum fuzz + // is the duration. + return (int)duration; + } else if (duration < 90*60*1000) { + // If duration is less than 1 1/2 hours, the maximum fuzz is 15 minutes, + return 15*60*1000; + } else { + // Otherwise, we will fuzz by at most half an hour. + return 30*60*1000; + } + } + boolean checkAllowNonWakeupDelayLocked(long nowELAPSED) { if (mInteractive) { return false; @@ -1606,7 +1841,7 @@ class AlarmManagerService extends SystemService { if (mLastAlarmDeliveryTime <= 0) { return false; } - if (mPendingNonWakeupAlarms.size() > 0 && mNextNonWakeupDeliveryTime > nowELAPSED) { + if (mPendingNonWakeupAlarms.size() > 0 && mNextNonWakeupDeliveryTime < nowELAPSED) { // This is just a little paranoia, if somehow we have pending non-wakeup alarms // and the next delivery time is in the past, then just deliver them all. This // avoids bugs where we get stuck in a loop trying to poll for alarms. @@ -1624,6 +1859,17 @@ class AlarmManagerService extends SystemService { if (localLOGV) { Slog.v(TAG, "sending alarm " + alarm); } + if (RECORD_ALARMS_IN_HISTORY) { + if (alarm.workSource != null && alarm.workSource.size() > 0) { + for (int wi=0; wi<alarm.workSource.size(); wi++) { + ActivityManagerNative.noteAlarmStart( + alarm.operation, alarm.workSource.get(wi), alarm.tag); + } + } else { + ActivityManagerNative.noteAlarmStart( + alarm.operation, -1, alarm.tag); + } + } alarm.operation.send(getContext(), 0, mBackgroundIntent.putExtra( Intent.EXTRA_ALARM_COUNT, alarm.count), @@ -1636,7 +1882,7 @@ class AlarmManagerService extends SystemService { mWakeLock.acquire(); } final InFlight inflight = new InFlight(AlarmManagerService.this, - alarm.operation, alarm.workSource, alarm.type, alarm.tag); + alarm.operation, alarm.workSource, alarm.type, alarm.tag, nowELAPSED); mInFlight.add(inflight); mBroadcastRefCount++; @@ -1664,11 +1910,11 @@ class AlarmManagerService extends SystemService { for (int wi=0; wi<alarm.workSource.size(); wi++) { ActivityManagerNative.noteWakeupAlarm( alarm.operation, alarm.workSource.get(wi), - alarm.workSource.getName(wi)); + alarm.workSource.getName(wi), alarm.tag); } } else { ActivityManagerNative.noteWakeupAlarm( - alarm.operation, -1, null); + alarm.operation, -1, null, alarm.tag); } } } catch (PendingIntent.CanceledException e) { @@ -1886,7 +2132,7 @@ class AlarmManagerService extends SystemService { final WorkSource workSource = null; // Let system take blame for time tick events. setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0, - 0, mTimeTickSender, true, workSource, null); + 0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null); } public void scheduleDateChangedEvent() { @@ -1899,8 +2145,8 @@ class AlarmManagerService extends SystemService { calendar.add(Calendar.DAY_OF_MONTH, 1); final WorkSource workSource = null; // Let system take blame for date change events. - setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource, - null); + setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, + AlarmManager.FLAG_STANDALONE, workSource, null); } } @@ -2030,6 +2276,17 @@ class AlarmManagerService extends SystemService { fs.nesting = 0; fs.aggregateTime += nowELAPSED - fs.startTime; } + if (RECORD_ALARMS_IN_HISTORY) { + if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) { + for (int wi=0; wi<inflight.mWorkSource.size(); wi++) { + ActivityManagerNative.noteAlarmFinish( + pi, inflight.mWorkSource.get(wi), inflight.mTag); + } + } else { + ActivityManagerNative.noteAlarmFinish( + pi, -1, inflight.mTag); + } + } } else { mLog.w("No in-flight alarm for " + pi + " " + intent); } diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 42a5195..17b4939 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -813,7 +813,8 @@ public class AppOpsService extends IAppOpsService.Stub { .getApplicationInfo(packageName, 0, UserHandle.getUserId(uid)); if (appInfo != null) { pkgUid = appInfo.uid; - isPrivileged = (appInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0; + isPrivileged = (appInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } else { if ("media".equals(packageName)) { pkgUid = Process.MEDIA_UID; @@ -996,7 +997,8 @@ public class AppOpsService extends IAppOpsService.Stub { ApplicationInfo appInfo = ActivityThread.getPackageManager() .getApplicationInfo(pkgName, 0, UserHandle.getUserId(uid)); if (appInfo != null) { - isPrivileged = (appInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0; + isPrivileged = (appInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } } else { // Could not load data, don't add to cache so it will be loaded later. diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java index bc31450..9e28b64 100644 --- a/services/core/java/com/android/server/AssetAtlasService.java +++ b/services/core/java/com/android/server/AssetAtlasService.java @@ -46,7 +46,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -200,9 +199,6 @@ public class AssetAtlasService extends IAssetAtlas.Stub { private final ArrayList<Bitmap> mBitmaps; private final int mPixelCount; - private long mNativeBitmap; - - // Used for debugging only private Bitmap mAtlasBitmap; Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) { @@ -292,7 +288,7 @@ public class AssetAtlasService extends IAssetAtlas.Stub { } canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); canvas.restore(); - atlasMap[mapIndex++] = bitmap.mNativeBitmap; + atlasMap[mapIndex++] = bitmap.getSkBitmap(); atlasMap[mapIndex++] = entry.x; atlasMap[mapIndex++] = entry.y; atlasMap[mapIndex++] = entry.rotated ? 1 : 0; @@ -300,9 +296,7 @@ public class AssetAtlasService extends IAssetAtlas.Stub { } final long endRender = System.nanoTime(); - if (mNativeBitmap != 0) { - result = nUploadAtlas(buffer, mNativeBitmap); - } + result = nUploadAtlas(buffer, mAtlasBitmap); final long endUpload = System.nanoTime(); if (DEBUG_ATLAS) { @@ -327,14 +321,8 @@ public class AssetAtlasService extends IAssetAtlas.Stub { * @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; - } + mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + return new Canvas(mAtlasBitmap); } /** @@ -344,8 +332,8 @@ public class AssetAtlasService extends IAssetAtlas.Stub { * to disk in /data/system/atlas.png for debugging. */ private void releaseCanvas(Canvas canvas) { + canvas.setBitmap(null); if (DEBUG_ATLAS_TEXTURE) { - canvas.setBitmap(null); File systemDirectory = new File(Environment.getDataDirectory(), "system"); File dataFile = new File(systemDirectory, "atlas.png"); @@ -359,18 +347,13 @@ public class AssetAtlasService extends IAssetAtlas.Stub { } catch (IOException e) { // Ignore } - - mAtlasBitmap.recycle(); - mAtlasBitmap = null; - } else { - nReleaseAtlasCanvas(canvas, mNativeBitmap); } + mAtlasBitmap.recycle(); + mAtlasBitmap = null; } } - private static native long nAcquireAtlasCanvas(Canvas canvas, int width, int height); - private static native void nReleaseAtlasCanvas(Canvas canvas, long bitmap); - private static native boolean nUploadAtlas(GraphicBuffer buffer, long bitmap); + private static native boolean nUploadAtlas(GraphicBuffer buffer, Bitmap bitmap); @Override public boolean isCompatible(int ppid) { @@ -404,24 +387,32 @@ public class AssetAtlasService extends IAssetAtlas.Stub { 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 start = MIN_SIZE + (cpuCount - 1) * STEP; + int end = MAX_SIZE; int step = STEP * cpuCount; final CountDownLatch signal = new CountDownLatch(cpuCount); - for (int i = 0; i < cpuCount; i++, start += STEP, end += STEP) { + 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(); } + boolean isAllWorkerFinished; try { - signal.await(10, TimeUnit.SECONDS); + isAllWorkerFinished = signal.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { Log.w(LOG_TAG, "Could not complete configuration computation"); return null; } + + if (!isAllWorkerFinished) { + // We have to abort here, otherwise the async updates on "results" would crash the + // sort later. + Log.w(LOG_TAG, "Could not complete configuration computation before timeout."); + return null; + } } // Maximize the number of packed bitmaps, minimize the texture size @@ -436,7 +427,8 @@ public class AssetAtlasService extends IAssetAtlas.Stub { 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)); + Log.d(LOG_TAG, String.format("Found best atlas configuration (out of %d) in %.2fs", + results.size(), delay)); } WorkerResult result = results.get(0); @@ -697,8 +689,8 @@ public class AssetAtlasService extends IAssetAtlas.Stub { 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) { + for (int width = mEnd; width > mStart; width -= mStep) { + for (int height = MAX_SIZE; height > MIN_SIZE; height -= STEP) { // If the atlas is not big enough, skip it if (width * height <= mThreshold) continue; diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 912a181..b3b4651 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -626,6 +626,22 @@ public final class BatteryService extends SystemService { pw.println(" voltage: " + mBatteryProps.batteryVoltage); pw.println(" temperature: " + mBatteryProps.batteryTemperature); pw.println(" technology: " + mBatteryProps.batteryTechnology); + + } else if ("unplug".equals(args[0])) { + if (!mUpdatesStopped) { + mLastBatteryProps.set(mBatteryProps); + } + mBatteryProps.chargerAcOnline = false; + mBatteryProps.chargerUsbOnline = false; + mBatteryProps.chargerWirelessOnline = false; + long ident = Binder.clearCallingIdentity(); + try { + mUpdatesStopped = true; + processValuesLocked(false); + } finally { + Binder.restoreCallingIdentity(ident); + } + } else if (args.length == 3 && "set".equals(args[0])) { String key = args[1]; String value = args[2]; @@ -662,6 +678,7 @@ public final class BatteryService extends SystemService { } catch (NumberFormatException ex) { pw.println("Bad value: " + value); } + } else if (args.length == 1 && "reset".equals(args[0])) { long ident = Binder.clearCallingIdentity(); try { @@ -676,6 +693,7 @@ public final class BatteryService extends SystemService { } else { pw.println("Dump current battery state, or:"); pw.println(" set [ac|usb|wireless|status|level|invalid] <value>"); + pw.println(" unplug"); pw.println(" reset"); } } diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 46a4599..ef82bb7 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -55,8 +55,6 @@ import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; -import java.util.List; -import java.util.Vector; import java.util.*; class BluetoothManagerService extends IBluetoothManager.Stub { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index ff6dc38..7d8e9de 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -16,10 +16,8 @@ package com.android.server; -import static android.Manifest.permission.MANAGE_NETWORK_POLICY; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; -import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_VPN; @@ -28,6 +26,7 @@ import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; +import android.annotation.Nullable; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; @@ -100,6 +99,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnInfo; import com.android.internal.net.VpnProfile; import com.android.internal.telephony.DctConstants; import com.android.internal.util.AsyncChannel; @@ -162,6 +162,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final String NETWORK_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore"; + // How long to wait before putting up a "This network doesn't have an Internet connection, + // connect anyway?" dialog after the user selects a network that doesn't validate. + private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000; + // How long to delay to removal of a pending intent based request. // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS private final int mReleasePendingIntentDelayMs; @@ -325,6 +329,19 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27; + /** + * used to specify whether a network should be used even if unvalidated. + * arg1 = whether to accept the network if it's unvalidated (1 or 0) + * arg2 = whether to remember this choice in the future (1 or 0) + * obj = network + */ + private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28; + + /** + * used to ask the user to confirm a connection to an unvalidated network. + * obj = network + */ + private static final int EVENT_PROMPT_UNVALIDATED = 29; /** Handler used for internal events. */ final private InternalHandler mHandler; @@ -1368,7 +1385,6 @@ public class ConnectivityService extends IConnectivityManager.Stub public void sendConnectedBroadcast(NetworkInfo info) { enforceConnectivityInternalPermission(); - sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE); sendGeneralBroadcast(info, CONNECTIVITY_ACTION); } @@ -1513,7 +1529,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkCapabilities.TRANSPORT_WIFI)) { timeout = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI, - 0); + 5); type = ConnectivityManager.TYPE_WIFI; } else { // do not track any other networks @@ -1869,6 +1885,7 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("ERROR: created network explicitly selected."); } nai.networkMisc.explicitlySelected = true; + nai.networkMisc.acceptUnvalidated = (boolean) msg.obj; break; } case NetworkMonitor.EVENT_NETWORK_TESTED: { @@ -1892,6 +1909,9 @@ public class ConnectivityService extends IConnectivityManager.Stub android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS, (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), 0, null); + + // TODO: trigger a NetworkCapabilities update so that the dialog can know + // that the network is now validated and close itself. } break; } @@ -2051,6 +2071,8 @@ public class ConnectivityService extends IConnectivityManager.Stub ReapUnvalidatedNetworks.DONT_REAP); } } + NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo); + if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name); } // If this method proves to be too slow then we can maintain a separate @@ -2253,6 +2275,91 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + public void setAcceptUnvalidated(Network network, boolean accept, boolean always) { + enforceConnectivityInternalPermission(); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED, + accept ? 1 : 0, always ? 1: 0, network)); + } + + private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) { + if (DBG) log("handleSetAcceptUnvalidated network=" + network + + " accept=" + accept + " always=" + always); + + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai == null) { + // Nothing to do. + return; + } + + if (nai.everValidated) { + // The network validated while the dialog box was up. Don't make any changes. There's a + // TODO in the dialog code to make it go away if the network validates; once that's + // implemented, taking action here will be confusing. + return; + } + + if (!nai.networkMisc.explicitlySelected) { + Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network"); + } + + if (accept != nai.networkMisc.acceptUnvalidated) { + int oldScore = nai.getCurrentScore(); + nai.networkMisc.acceptUnvalidated = accept; + rematchAllNetworksAndRequests(nai, oldScore); + sendUpdatedScoreToFactories(nai); + } + + if (always) { + nai.asyncChannel.sendMessage( + NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0); + } + + // TODO: should we also disconnect from the network if accept is false? + } + + private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network), + PROMPT_UNVALIDATED_DELAY_MS); + } + + private void handlePromptUnvalidated(Network network) { + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + + // Only prompt if the network is unvalidated and was explicitly selected by the user, and if + // we haven't already been told to switch to it regardless of whether it validated or not. + if (nai == null || nai.everValidated || + !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) { + return; + } + + // TODO: What should we do if we've already switched to this network because we had no + // better option? There are two obvious alternatives. + // + // 1. Decide that there's no point prompting because this is our only usable network. + // However, because we didn't prompt, if later on a validated network comes along, we'll + // either a) silently switch to it - bad if the user wanted to connect to stay on this + // unvalidated network - or b) prompt the user at that later time - bad because the user + // might not understand why they are now being prompted. + // + // 2. Always prompt the user, even if we have no other network to use. The user could then + // try to find an alternative network to join (remember, if we got here, then the user + // selected this network manually). This is bad because the prompt isn't really very + // useful. + // + // For now we do #1, but we can revisit that later. + if (isDefaultNetwork(nai)) { + return; + } + + Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setClassName("com.android.settings", + "com.android.settings.wifi.WifiNoInternetDialog"); + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } + private class InternalHandler extends Handler { public InternalHandler(Looper looper) { super(looper); @@ -2323,6 +2430,14 @@ public class ConnectivityService extends IConnectivityManager.Stub handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1); break; } + case EVENT_SET_ACCEPT_UNVALIDATED: { + handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0); + break; + } + case EVENT_PROMPT_UNVALIDATED: { + handlePromptUnvalidated((Network) msg.obj); + break; + } case EVENT_SYSTEM_READY: { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { nai.networkMonitor.systemReady = true; @@ -2804,7 +2919,6 @@ public class ConnectivityService extends IConnectivityManager.Stub * Return the information of the ongoing legacy VPN. This method is used * by VpnSettings and not available in ConnectivityManager. Permissions * are checked in Vpn class. - * @hide */ @Override public LegacyVpnInfo getLegacyVpnInfo() { @@ -2816,6 +2930,56 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Return the information of all ongoing VPNs. This method is used by NetworkStatsService + * and not available in ConnectivityManager. + */ + @Override + public VpnInfo[] getAllVpnInfo() { + enforceConnectivityInternalPermission(); + if (mLockdownEnabled) { + return new VpnInfo[0]; + } + + synchronized(mVpns) { + List<VpnInfo> infoList = new ArrayList<>(); + for (int i = 0; i < mVpns.size(); i++) { + VpnInfo info = createVpnInfo(mVpns.valueAt(i)); + if (info != null) { + infoList.add(info); + } + } + return infoList.toArray(new VpnInfo[infoList.size()]); + } + } + + /** + * @return VPN information for accounting, or null if we can't retrieve all required + * information, e.g primary underlying iface. + */ + @Nullable + private VpnInfo createVpnInfo(Vpn vpn) { + VpnInfo info = vpn.getVpnInfo(); + if (info == null) { + return null; + } + Network[] underlyingNetworks = vpn.getUnderlyingNetworks(); + // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret + // the underlyingNetworks list. + if (underlyingNetworks == null) { + NetworkAgentInfo defaultNetwork = getDefaultNetwork(); + if (defaultNetwork != null && defaultNetwork.linkProperties != null) { + info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName(); + } + } else if (underlyingNetworks.length > 0) { + LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]); + if (linkProperties != null) { + info.primaryUnderlyingIface = linkProperties.getInterfaceName(); + } + } + return info.primaryUnderlyingIface == null ? null : info; + } + + /** * Returns the information of the ongoing VPN. This method is used by VpnDialogs and * not available in ConnectivityManager. * Permissions are checked in Vpn class. @@ -2962,7 +3126,7 @@ public class ConnectivityService extends IConnectivityManager.Stub notification.icon = icon; notification.flags = Notification.FLAG_AUTO_CANCEL; notification.tickerText = title; - notification.color = mContext.getResources().getColor( + notification.color = mContext.getColor( com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); notification.contentIntent = intent; @@ -3289,6 +3453,24 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @Override + public boolean requestBandwidthUpdate(Network network) { + enforceAccessPermission(); + NetworkAgentInfo nai = null; + if (network == null) { + return false; + } + synchronized (mNetworkForNetId) { + nai = mNetworkForNetId.get(network.netId); + } + if (nai != null) { + nai.asyncChannel.sendMessage(android.net.NetworkAgent.CMD_REQUEST_BANDWIDTH_UPDATE); + return true; + } + return false; + } + + private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) { // if UID is restricted, don't allow them to bring up metered APNs if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) @@ -3336,10 +3518,34 @@ public class ConnectivityService extends IConnectivityManager.Stub getCallingUid(), 0, operation)); } + // In order to implement the compatibility measure for pre-M apps that call + // WifiManager.enableNetwork(..., true) without also binding to that network explicitly, + // WifiManager registers a network listen for the purpose of calling setProcessDefaultNetwork. + // This ensures it has permission to do so. + private boolean hasWifiNetworkListenPermission(NetworkCapabilities nc) { + if (nc == null) { + return false; + } + int[] transportTypes = nc.getTransportTypes(); + if (transportTypes.length != 1 || transportTypes[0] != NetworkCapabilities.TRANSPORT_WIFI) { + return false; + } + try { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_WIFI_STATE, + "ConnectivityService"); + } catch (SecurityException e) { + return false; + } + return true; + } + @Override public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities, Messenger messenger, IBinder binder) { - enforceAccessPermission(); + if (!hasWifiNetworkListenPermission(networkCapabilities)) { + enforceAccessPermission(); + } NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities( networkCapabilities), TYPE_NONE, nextNetworkRequestId()); @@ -4071,8 +4277,10 @@ public class ConnectivityService extends IConnectivityManager.Stub networkAgent.created = true; updateLinkProperties(networkAgent, null); notifyIfacesChanged(); - notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); + networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); + scheduleUnvalidatedPrompt(networkAgent); + if (networkAgent.isVPN()) { // Temporarily disable the default proxy (not global). synchronized (mProxyLock) { @@ -4085,9 +4293,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } // TODO: support proxy per network. } + // Consider network even though it is not yet validated. rematchNetworkAndRequests(networkAgent, NascentState.NOT_JUST_VALIDATED, ReapUnvalidatedNetworks.REAP); + + // This has to happen after matching the requests, because callbacks are just requests. + notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); } else if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.SUSPENDED) { networkAgent.asyncChannel.disconnect(); @@ -4175,9 +4387,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished); - final Intent immediateIntent = new Intent(intent); - immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE); - sendStickyBroadcast(immediateIntent); sendStickyBroadcast(intent); if (newDefaultAgent != null) { sendConnectedBroadcast(newDefaultAgent.networkInfo); @@ -4245,8 +4454,39 @@ public class ConnectivityService extends IConnectivityManager.Stub public boolean setUnderlyingNetworksForVpn(Network[] networks) { throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); + boolean success; synchronized (mVpns) { - return mVpns.get(user).setUnderlyingNetworks(networks); + success = mVpns.get(user).setUnderlyingNetworks(networks); + } + if (success) { + notifyIfacesChanged(); + } + return success; + } + + @Override + public void factoryReset() { + enforceConnectivityInternalPermission(); + // Turn airplane mode off + setAirplaneMode(false); + + // Untether + for (String tether : getTetheredIfaces()) { + untether(tether); + } + + // Turn VPN off + VpnConfig vpnConfig = getVpnConfig(); + if (vpnConfig != null) { + if (vpnConfig.legacy) { + prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); + } else { + // Prevent this app from initiating VPN connections in the future without + // user intervention. + setVpnPackageAuthorization(false); + + prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN); + } } } } diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index f04487e..abd2ca0 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -72,7 +72,7 @@ option java_package com.android.server # when a notification action button has been clicked 27521 notification_action_clicked (key|3),(action_index|1) # when a notification has been canceled -27530 notification_canceled (key|3),(reason|1) +27530 notification_canceled (key|3),(reason|1),(lifespan|1) # --------------------------- # Watchdog.java @@ -173,6 +173,13 @@ option java_package com.android.server # --------------------------- 33000 wp_wallpaper_crashed (component|3) +# --------------------------- +# Device idle +# --------------------------- +34000 device_idle (state|1|5), (reason|3) +34001 device_idle_step +34002 device_idle_wake_from_idle (is_idle|1|5), (reason|3) + # --------------------------- # ConnectivityService.java @@ -216,3 +223,9 @@ option java_package com.android.server # --------------------------- 2755 fstrim_start (time|2|3) 2756 fstrim_finish (time|2|3) + +# --------------------------- +# AudioService.java +# --------------------------- +40000 volume_changed (stream|1), (prev_level|1), (level|1), (max_level|1), (caller|3) +40001 stream_devices_changed (stream|1), (prev_devices|1), (devices|1) diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java new file mode 100644 index 0000000..c79fdfc --- /dev/null +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2015 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.NameNotFoundException; +import android.os.Binder; +import android.os.IBinder; +import android.os.MemoryFile; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; +import android.view.IGraphicsStats; +import android.view.ThreadedRenderer; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * This service's job is to collect aggregate rendering profile data. It + * does this by allowing rendering processes to request an ashmem buffer + * to place their stats into. This buffer will be pre-initialized with historical + * data for that process if it exists (if the userId & packageName match a buffer + * in the historical log) + * + * This service does not itself attempt to understand the data in the buffer, + * its primary job is merely to manage distributing these buffers. However, + * it is assumed that this buffer is for ThreadedRenderer and delegates + * directly to ThreadedRenderer for dumping buffers. + * + * MEMORY USAGE: + * + * This class consumes UP TO: + * 1) [active rendering processes] * (ASHMEM_SIZE * 2) + * 2) ASHMEM_SIZE (for scratch space used during dumping) + * 3) ASHMEM_SIZE * HISTORY_SIZE + * + * Currently ASHMEM_SIZE is 256 bytes and HISTORY_SIZE is 10. Assuming + * the system then also has 10 active rendering processes in the worst case + * this would end up using under 10KiB (8KiB for the buffers, plus some overhead + * for userId, pid, package name, and a couple other objects) + * + * @hide */ +public class GraphicsStatsService extends IGraphicsStats.Stub { + public static final String GRAPHICS_STATS_SERVICE = "graphicsstats"; + + private static final String TAG = "GraphicsStatsService"; + private static final int ASHMEM_SIZE = 256; + private static final int HISTORY_SIZE = 10; + + private final Context mContext; + private final Object mLock = new Object(); + private ArrayList<ActiveBuffer> mActive = new ArrayList<>(); + private HistoricalData[] mHistoricalLog = new HistoricalData[HISTORY_SIZE]; + private int mNextHistoricalSlot = 0; + private byte[] mTempBuffer = new byte[ASHMEM_SIZE]; + + public GraphicsStatsService(Context context) { + mContext = context; + } + + private boolean isValid(int uid, String packageName) { + try { + PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, 0); + return info.applicationInfo.uid == uid; + } catch (NameNotFoundException e) { + } + return false; + } + + @Override + public ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token) + throws RemoteException { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + ParcelFileDescriptor pfd = null; + long callingIdentity = Binder.clearCallingIdentity(); + try { + if (!isValid(uid, packageName)) { + throw new RemoteException("Invalid package name"); + } + synchronized (mLock) { + pfd = requestBufferForProcessLocked(token, uid, pid, packageName); + } + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + return pfd; + } + + private ParcelFileDescriptor getPfd(MemoryFile file) { + try { + return new ParcelFileDescriptor(file.getFileDescriptor()); + } catch (IOException ex) { + throw new IllegalStateException("Failed to get PFD from memory file", ex); + } + } + + private ParcelFileDescriptor requestBufferForProcessLocked(IBinder token, + int uid, int pid, String packageName) throws RemoteException { + ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName); + return getPfd(buffer.mProcessBuffer); + } + + private void processDied(ActiveBuffer buffer) { + synchronized (mLock) { + mActive.remove(buffer); + Log.d("GraphicsStats", "Buffer count: " + mActive.size()); + } + HistoricalData data = buffer.mPreviousData; + buffer.mPreviousData = null; + if (data == null) { + data = mHistoricalLog[mNextHistoricalSlot]; + if (data == null) { + data = new HistoricalData(); + } + } + data.update(buffer.mPackageName, buffer.mUid, buffer.mProcessBuffer); + buffer.closeAllBuffers(); + + mHistoricalLog[mNextHistoricalSlot] = data; + mNextHistoricalSlot = (mNextHistoricalSlot + 1) % mHistoricalLog.length; + } + + private ActiveBuffer fetchActiveBuffersLocked(IBinder token, int uid, int pid, + String packageName) throws RemoteException { + int size = mActive.size(); + for (int i = 0; i < size; i++) { + ActiveBuffer buffers = mActive.get(i); + if (buffers.mPid == pid + && buffers.mUid == uid) { + return buffers; + } + } + // Didn't find one, need to create it + try { + ActiveBuffer buffers = new ActiveBuffer(token, uid, pid, packageName); + mActive.add(buffers); + return buffers; + } catch (IOException ex) { + throw new RemoteException("Failed to allocate space"); + } + } + + private HistoricalData removeHistoricalDataLocked(int uid, String packageName) { + for (int i = 0; i < mHistoricalLog.length; i++) { + final HistoricalData data = mHistoricalLog[i]; + if (data != null && data.mUid == uid + && data.mPackageName.equals(packageName)) { + if (i == mNextHistoricalSlot) { + mHistoricalLog[i] = null; + } else { + mHistoricalLog[i] = mHistoricalLog[mNextHistoricalSlot]; + mHistoricalLog[mNextHistoricalSlot] = null; + } + return data; + } + } + return null; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + synchronized (mLock) { + for (int i = 0; i < mActive.size(); i++) { + final ActiveBuffer buffer = mActive.get(i); + fout.print("Package: "); + fout.print(buffer.mPackageName); + fout.flush(); + try { + buffer.mProcessBuffer.readBytes(mTempBuffer, 0, 0, ASHMEM_SIZE); + ThreadedRenderer.dumpProfileData(mTempBuffer, fd); + } catch (IOException e) { + fout.println("Failed to dump"); + } + fout.println(); + } + for (HistoricalData buffer : mHistoricalLog) { + if (buffer == null) continue; + fout.print("Package: "); + fout.print(buffer.mPackageName); + fout.flush(); + ThreadedRenderer.dumpProfileData(buffer.mBuffer, fd); + fout.println(); + } + } + } + + private final class ActiveBuffer implements DeathRecipient { + final int mUid; + final int mPid; + final String mPackageName; + final IBinder mToken; + MemoryFile mProcessBuffer; + HistoricalData mPreviousData; + + ActiveBuffer(IBinder token, int uid, int pid, String packageName) + throws RemoteException, IOException { + mUid = uid; + mPid = pid; + mPackageName = packageName; + mToken = token; + mToken.linkToDeath(this, 0); + mProcessBuffer = new MemoryFile("GFXStats-" + uid, ASHMEM_SIZE); + mPreviousData = removeHistoricalDataLocked(mUid, mPackageName); + if (mPreviousData != null) { + mProcessBuffer.writeBytes(mPreviousData.mBuffer, 0, 0, ASHMEM_SIZE); + } + } + + @Override + public void binderDied() { + mToken.unlinkToDeath(this, 0); + processDied(this); + } + + void closeAllBuffers() { + if (mProcessBuffer != null) { + mProcessBuffer.close(); + mProcessBuffer = null; + } + } + } + + private final static class HistoricalData { + final byte[] mBuffer = new byte[ASHMEM_SIZE]; + int mUid; + String mPackageName; + + void update(String packageName, int uid, MemoryFile file) { + mUid = uid; + mPackageName = packageName; + try { + file.readBytes(mBuffer, 0, 0, ASHMEM_SIZE); + } catch (IOException e) {} + } + } +} diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 4d0868a..0f9090d 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -15,6 +15,7 @@ package com.android.server; +import android.annotation.NonNull; import com.android.internal.content.PackageMonitor; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; @@ -86,7 +87,10 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; +import android.text.TextUtils.SimpleStringSplitter; import android.text.style.SuggestionSpan; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.EventLog; import android.util.LruCache; @@ -125,6 +129,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -134,8 +139,12 @@ import java.util.Locale; public class InputMethodManagerService extends IInputMethodManager.Stub implements ServiceConnection, Handler.Callback { static final boolean DEBUG = false; + static final boolean DEBUG_RESTORE = DEBUG || false; static final String TAG = "InputMethodManagerService"; + private static final char INPUT_METHOD_SEPARATOR = ':'; + private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';'; + static final int MSG_SHOW_IM_PICKER = 1; static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3; @@ -466,12 +475,101 @@ public class InputMethodManagerService extends IInputMethodManager.Stub || Intent.ACTION_USER_REMOVED.equals(action)) { updateCurrentProfileIds(); return; + } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { + final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); + if (Settings.Secure.ENABLED_INPUT_METHODS.equals(name)) { + final String prevValue = intent.getStringExtra( + Intent.EXTRA_SETTING_PREVIOUS_VALUE); + final String newValue = intent.getStringExtra( + Intent.EXTRA_SETTING_NEW_VALUE); + restoreEnabledInputMethods(mContext, prevValue, newValue); + } } else { Slog.w(TAG, "Unexpected intent " + intent); } } } + // Apply the results of a restore operation to the set of enabled IMEs. Note that this + // does not attempt to validate on the fly with any installed device policy, so must only + // be run in the context of initial device setup. + // + // TODO: Move this method to InputMethodUtils with adding unit tests. + static void restoreEnabledInputMethods(Context context, String prevValue, String newValue) { + if (DEBUG_RESTORE) { + Slog.i(TAG, "Restoring enabled input methods:"); + Slog.i(TAG, "prev=" + prevValue); + Slog.i(TAG, " new=" + newValue); + } + // 'new' is the just-restored state, 'prev' is what was in settings prior to the restore + ArrayMap<String, ArraySet<String>> prevMap = parseInputMethodsAndSubtypesString(prevValue); + ArrayMap<String, ArraySet<String>> newMap = parseInputMethodsAndSubtypesString(newValue); + + // Merge the restored ime+subtype enabled states into the live state + for (ArrayMap.Entry<String, ArraySet<String>> entry : newMap.entrySet()) { + final String imeId = entry.getKey(); + ArraySet<String> prevSubtypes = prevMap.get(imeId); + if (prevSubtypes == null) { + prevSubtypes = new ArraySet<String>(2); + prevMap.put(imeId, prevSubtypes); + } + prevSubtypes.addAll(entry.getValue()); + } + + final String mergedImesAndSubtypesString = buildInputMethodsAndSubtypesString(prevMap); + if (DEBUG_RESTORE) { + Slog.i(TAG, "Merged IME string:"); + Slog.i(TAG, " " + mergedImesAndSubtypesString); + } + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString); + } + + // TODO: Move this method to InputMethodUtils with adding unit tests. + static String buildInputMethodsAndSubtypesString(ArrayMap<String, ArraySet<String>> map) { + // we want to use the canonical InputMethodSettings implementation, + // so we convert data structures first. + List<Pair<String, ArrayList<String>>> imeMap = + new ArrayList<Pair<String, ArrayList<String>>>(4); + for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) { + final String imeName = entry.getKey(); + final ArraySet<String> subtypeSet = entry.getValue(); + final ArrayList<String> subtypes = new ArrayList<String>(2); + if (subtypeSet != null) { + subtypes.addAll(subtypeSet); + } + imeMap.add(new Pair<String, ArrayList<String>>(imeName, subtypes)); + } + return InputMethodSettings.buildInputMethodsSettingString(imeMap); + } + + // TODO: Move this method to InputMethodUtils with adding unit tests. + static ArrayMap<String, ArraySet<String>> parseInputMethodsAndSubtypesString( + final String inputMethodsAndSubtypesString) { + final ArrayMap<String, ArraySet<String>> imeMap = + new ArrayMap<String, ArraySet<String>>(); + if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) { + return imeMap; + } + + final SimpleStringSplitter typeSplitter = + new SimpleStringSplitter(INPUT_METHOD_SEPARATOR); + final SimpleStringSplitter subtypeSplitter = + new SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR); + List<Pair<String, ArrayList<String>>> allImeSettings = + InputMethodSettings.buildInputMethodsAndSubtypeList(inputMethodsAndSubtypesString, + typeSplitter, + subtypeSplitter); + for (Pair<String, ArrayList<String>> ime : allImeSettings) { + ArraySet<String> subtypes = new ArraySet<String>(); + if (ime.second != null) { + subtypes.addAll(ime.second); + } + imeMap.put(ime.first, subtypes); + } + return imeMap; + } + class MyPackageMonitor extends PackageMonitor { private boolean isChangingPackagesOfCurrentUser() { final int userId = getChangingUserId(); @@ -675,6 +773,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); broadcastFilter.addAction(Intent.ACTION_USER_ADDED); broadcastFilter.addAction(Intent.ACTION_USER_REMOVED); + broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED); mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter); mNotificationShown = false; @@ -698,6 +797,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onUserSwitchComplete(int newUserId) throws RemoteException { } + + @Override + public void onForegroundProfileSwitch(int newProfileId) { + // Ignore. + } }); userId = ActivityManagerNative.getDefault().getCurrentUser().id; } catch (RemoteException e) { @@ -764,9 +868,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InputMethodInfo defIm = null; for (InputMethodInfo imi : mMethodList) { - if (defIm == null) { - if (InputMethodUtils.isValidSystemDefaultIme( - mSystemReady, imi, context)) { + if (defIm == null && mSystemReady) { + final Locale systemLocale = context.getResources().getConfiguration().locale; + if (InputMethodUtils.isSystemImeThatHasSubtypeOf(imi, context, + true /* checkDefaultAttribute */, systemLocale, false /* checkCountry */, + InputMethodUtils.SUBTYPE_MODE_ANY)) { defIm = imi; Slog.i(TAG, "Selected default: " + imi.getId()); } @@ -1185,13 +1291,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags); } - InputBindResult startInputUncheckedLocked(ClientState cs, + InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext, EditorInfo attribute, int controlFlags) { // If no method is currently selected, do nothing. if (mCurMethodId == null) { return mNoBinding; } + if (attribute != null) { + // We accept an empty package name as a valid data. + if (!TextUtils.isEmpty(attribute.packageName) && + !InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid, + attribute.packageName)) { + Slog.e(TAG, "Rejecting this client as it reported an invalid package name." + + " uid=" + cs.uid + " package=" + attribute.packageName); + return mNoBinding; + } + } + if (mCurClient != cs) { // Was the keyguard locked when switching over to the new client? mCurClientInKeyguard = isKeyguardLocked(); @@ -1597,7 +1714,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName( mContext, imi, mCurrentSubtype); - mImeSwitcherNotification.color = mContext.getResources().getColor( + mImeSwitcherNotification.color = mContext.getColor( com.android.internal.R.color.system_notification_accent_color); mImeSwitcherNotification.setLatestEventInfo( mContext, title, summary, mImeSwitchPendingIntent); @@ -1755,16 +1872,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (mCurClient != null && mCurAttribute != null) { - final int uid = mCurClient.uid; - final String packageName = mCurAttribute.packageName; - if (SystemConfig.getInstance().getFixedImeApps().contains(packageName)) { - if (InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, uid, packageName)) { - return; - } - // TODO: Do we need to lock the input method when the application reported an - // incorrect package name? - Slog.e(TAG, "Ignoring FixedImeApps due to the validation failure. uid=" + uid - + " package=" + packageName); + // We have already made sure that the package name belongs to the application's UID. + // No further UID check is required. + if (SystemConfig.getInstance().getFixedImeApps().contains(mCurAttribute.packageName)) { + return; } } @@ -2048,7 +2159,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // more quickly (not get stuck behind it initializing itself for the // new focused input, even if its window wants to hide the IME). boolean didStart = false; - + switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: if (!isTextEditor || !doAutoShow) { diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index cea1ebe..744156b 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -47,6 +47,7 @@ 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 localVerificationLOGV = DEBUG || false; public void addFilter(F f) { if (localLOGV) { @@ -478,7 +479,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { /** * Returns whether the object associated with the given filter is - * "stopped," that is whether it should not be included in the result + * "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) { @@ -486,6 +487,22 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } /** + * Returns whether the given filter is "verified" that is whether it has been verified against + * its data URIs. + * + * The verification would happen only and only if the Intent action is + * {@link android.content.Intent#ACTION_VIEW} and the Intent category is + * {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent data scheme + * is "http" or "https". + * + * @see android.content.IntentFilter#setAutoVerify(boolean) + * @see android.content.IntentFilter#getAutoVerify() + */ + protected boolean isFilterVerified(F filter) { + return filter.isVerified(); + } + + /** * 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. @@ -710,6 +727,13 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { continue; } + // Are we verified ? + if (filter.getAutoVerify()) { + if (localVerificationLOGV || debug) { + Slog.v(TAG, " Filter verified: " + isFilterVerified(filter)); + } + } + // Do we already have this one? if (!allowFilterResult(filter, dest)) { if (debug) { diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index be83b9b..1683485 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -60,8 +60,6 @@ import android.location.Address; import android.location.Criteria; import android.location.GeocoderParams; import android.location.Geofence; -import android.location.GpsMeasurementsEvent; -import android.location.GpsNavigationMessageEvent; import android.location.IGpsMeasurementsListener; import android.location.IGpsNavigationMessageListener; import android.location.IGpsStatusListener; @@ -1452,14 +1450,13 @@ public class LocationManagerService extends ILocationManager.Stub { if (receiver == null) { receiver = new Receiver(listener, null, pid, uid, packageName, workSource, hideFromAppOps); - mReceivers.put(binder, receiver); - try { receiver.getListener().asBinder().linkToDeath(receiver, 0); } catch (RemoteException e) { Slog.e(TAG, "linkToDeath failed:", e); return null; } + mReceivers.put(binder, receiver); } return receiver; } diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 77662cc..5df74c5 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -16,6 +16,8 @@ package com.android.server; +import android.app.admin.DevicePolicyManager; +import android.app.backup.BackupManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -42,13 +44,15 @@ import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; import android.security.KeyStore; +import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; import android.util.Slog; +import com.android.internal.util.ArrayUtils; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; +import com.android.server.LockSettingsStorage.CredentialHash; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -70,6 +74,7 @@ public class LockSettingsService extends ILockSettings.Stub { private LockPatternUtils mLockPatternUtils; private boolean mFirstCallToVold; + private IGateKeeperService mGateKeeperService; public LockSettingsService(Context context) { mContext = context; @@ -81,6 +86,7 @@ public class LockSettingsService extends ILockSettings.Stub { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_STARTING); + filter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() { @@ -117,12 +123,22 @@ public class LockSettingsService extends ILockSettings.Stub { } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) { final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); mStorage.prefetchUser(userHandle); + } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { + final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + if (userHandle > 0) { + removeUser(userHandle); + } } } }; public void systemReady() { migrateOldData(); + try { + getGateKeeperService(); + } catch (RemoteException e) { + Slog.e(TAG, "Failure retrieving IGateKeeperService", e); + } mStorage.prefetchUser(UserHandle.USER_OWNER); } @@ -179,6 +195,60 @@ public class LockSettingsService extends ILockSettings.Stub { setString("migrated_user_specific", "true", 0); Slog.i(TAG, "Migrated per-user lock settings to new location"); } + + // Migrates biometric weak such that the fallback mechanism becomes the primary. + if (getString("migrated_biometric_weak", null, 0) == null) { + final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); + List<UserInfo> users = um.getUsers(); + for (int i = 0; i < users.size(); i++) { + int userId = users.get(i).id; + long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId); + long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId); + if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) { + setLong(LockPatternUtils.PASSWORD_TYPE_KEY, + alternateType, + userId); + } + setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + userId); + } + setString("migrated_biometric_weak", "true", 0); + Slog.i(TAG, "Migrated biometric weak to use the fallback instead"); + } + + // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one + // user was present on the system, so if we're upgrading to M and there is more than one + // user we disable the flag to remain consistent. + if (getString("migrated_lockscreen_disabled", null, 0) == null) { + final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); + + final List<UserInfo> users = um.getUsers(); + final int userCount = users.size(); + int switchableUsers = 0; + for (int i = 0; i < userCount; i++) { + if (users.get(i).supportsSwitchTo()) { + switchableUsers++; + } + } + + if (switchableUsers > 1) { + for (int i = 0; i < userCount; i++) { + int id = users.get(i).id; + + if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) { + setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id); + } + } + } + + setString("migrated_lockscreen_disabled", "true", 0); + Slog.i(TAG, "Migrated lockscreen disabled flag"); + } } catch (RemoteException re) { Slog.e(TAG, "Unable to migrate old data", re); } @@ -194,6 +264,7 @@ public class LockSettingsService extends ILockSettings.Stub { private final void checkReadPermission(String requestedKey, int userId) { final int callingUid = Binder.getCallingUid(); + for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) { String key = READ_PROFILE_PROTECTED_SETTINGS[i]; if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE) @@ -203,6 +274,16 @@ public class LockSettingsService extends ILockSettings.Stub { + requestedKey + " for user " + userId); } } + + for (int i = 0; i < READ_PASSWORD_PROTECTED_SETTINGS.length; i++) { + String key = READ_PASSWORD_PROTECTED_SETTINGS[i]; + if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(PERMISSION) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("uid=" + callingUid + + " needs permission " + PERMISSION + " to read " + + requestedKey + " for user " + userId); + } + } } @Override @@ -225,13 +306,15 @@ public class LockSettingsService extends ILockSettings.Stub { private void setStringUnchecked(String key, int userId, String value) { mStorage.writeKeyValue(key, value, userId); + if (ArrayUtils.contains(SETTINGS_TO_BACKUP, key)) { + BackupManager.dataChanged("com.android.providers.settings"); + } } @Override public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { checkReadPermission(key, userId); - - String value = mStorage.readKeyValue(key, null, userId); + String value = getStringUnchecked(key, null, userId); return TextUtils.isEmpty(value) ? defaultValue : (value.equals("1") || value.equals("true")); } @@ -240,7 +323,7 @@ public class LockSettingsService extends ILockSettings.Stub { public long getLong(String key, long defaultValue, int userId) throws RemoteException { checkReadPermission(key, userId); - String value = mStorage.readKeyValue(key, null, userId); + String value = getStringUnchecked(key, null, userId); return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value); } @@ -248,6 +331,14 @@ public class LockSettingsService extends ILockSettings.Stub { public String getString(String key, String defaultValue, int userId) throws RemoteException { checkReadPermission(key, userId); + return getStringUnchecked(key, defaultValue, userId); + } + + public String getStringUnchecked(String key, String defaultValue, int userId) { + if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) { + return mLockPatternUtils.isLockPatternEnabled(userId) ? "1" : "0"; + } + return mStorage.readKeyValue(key, defaultValue, userId); } @@ -290,61 +381,255 @@ public class LockSettingsService extends ILockSettings.Stub { } } + + private byte[] getCurrentHandle(int userId) { + CredentialHash credential; + byte[] currentHandle; + + int currentHandleType = mStorage.getStoredCredentialType(userId); + switch (currentHandleType) { + case CredentialHash.TYPE_PATTERN: + credential = mStorage.readPatternHash(userId); + currentHandle = credential != null + ? credential.hash + : null; + break; + case CredentialHash.TYPE_PASSWORD: + credential = mStorage.readPasswordHash(userId); + currentHandle = credential != null + ? credential.hash + : null; + break; + case CredentialHash.TYPE_NONE: + default: + currentHandle = null; + break; + } + + // sanity check + if (currentHandleType != CredentialHash.TYPE_NONE && currentHandle == null) { + Slog.e(TAG, "Stored handle type [" + currentHandleType + "] but no handle available"); + } + + return currentHandle; + } + + @Override - public void setLockPattern(String pattern, int userId) throws RemoteException { - checkWritePermission(userId); + public void setLockPattern(String pattern, String savedCredential, int userId) + throws RemoteException { + byte[] currentHandle = getCurrentHandle(userId); + + if (pattern == null) { + getGateKeeperService().clearSecureUserId(userId); + mStorage.writePatternHash(null, userId); + maybeUpdateKeystore(null, userId); + return; + } - maybeUpdateKeystore(pattern, userId); + if (currentHandle == null) { + if (savedCredential != null) { + Slog.w(TAG, "Saved credential provided, but none stored"); + } + savedCredential = null; + } - final byte[] hash = LockPatternUtils.patternToHash( - LockPatternUtils.stringToPattern(pattern)); - mStorage.writePatternHash(hash, userId); + byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId); + if (enrolledHandle != null) { + mStorage.writePatternHash(enrolledHandle, userId); + } else { + Slog.e(TAG, "Failed to enroll pattern"); + } } + @Override - public void setLockPassword(String password, int userId) throws RemoteException { - checkWritePermission(userId); + public void setLockPassword(String password, String savedCredential, int userId) + throws RemoteException { + byte[] currentHandle = getCurrentHandle(userId); - maybeUpdateKeystore(password, userId); + if (password == null) { + getGateKeeperService().clearSecureUserId(userId); + mStorage.writePasswordHash(null, userId); + maybeUpdateKeystore(null, userId); + return; + } + + if (currentHandle == null) { + if (savedCredential != null) { + Slog.w(TAG, "Saved credential provided, but none stored"); + } + savedCredential = null; + } + + byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId); + if (enrolledHandle != null) { + mStorage.writePasswordHash(enrolledHandle, userId); + } else { + Slog.e(TAG, "Failed to enroll password"); + } + } + + private byte[] enrollCredential(byte[] enrolledHandle, + String enrolledCredential, String toEnroll, int userId) + throws RemoteException { + checkWritePermission(userId); + byte[] enrolledCredentialBytes = enrolledCredential == null + ? null + : enrolledCredential.getBytes(); + byte[] toEnrollBytes = toEnroll == null + ? null + : toEnroll.getBytes(); + byte[] hash = getGateKeeperService().enroll(userId, enrolledHandle, enrolledCredentialBytes, + toEnrollBytes); + + if (hash != null) { + maybeUpdateKeystore(toEnroll, userId); + } - mStorage.writePasswordHash(mLockPatternUtils.passwordToHash(password, userId), userId); + return hash; } @Override public boolean checkPattern(String pattern, int userId) throws RemoteException { - checkPasswordReadPermission(userId); - byte[] hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(pattern)); - byte[] storedHash = mStorage.readPatternHash(userId); + try { + doVerifyPattern(pattern, false, 0, userId); + } catch (VerificationFailedException ex) { + return false; + } + + return true; + } - if (storedHash == null) { - return true; + @Override + public byte[] verifyPattern(String pattern, long challenge, int userId) + throws RemoteException { + try { + return doVerifyPattern(pattern, true, challenge, userId); + } catch (VerificationFailedException ex) { + return null; } + } + + private byte[] doVerifyPattern(String pattern, boolean hasChallenge, long challenge, + int userId) throws VerificationFailedException, RemoteException { + checkPasswordReadPermission(userId); - boolean matched = Arrays.equals(hash, storedHash); - if (matched && !TextUtils.isEmpty(pattern)) { - maybeUpdateKeystore(pattern, userId); + CredentialHash storedHash = mStorage.readPatternHash(userId); + + if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(pattern)) { + // don't need to pass empty passwords to GateKeeper + return null; } - return matched; + + if (TextUtils.isEmpty(pattern)) { + throw new VerificationFailedException(); + } + + if (storedHash.version == CredentialHash.VERSION_LEGACY) { + byte[] hash = mLockPatternUtils.patternToHash( + mLockPatternUtils.stringToPattern(pattern)); + if (Arrays.equals(hash, storedHash.hash)) { + maybeUpdateKeystore(pattern, userId); + // migrate password to GateKeeper + setLockPattern(pattern, null, userId); + if (!hasChallenge) { + return null; + } + // Fall through to get the auth token. Technically this should never happen, + // as a user that had a legacy pattern would have to unlock their device + // before getting to a flow with a challenge, but supporting for consistency. + } else { + throw new VerificationFailedException(); + } + } + + byte[] token = null; + if (hasChallenge) { + token = getGateKeeperService() + .verifyChallenge(userId, challenge, storedHash.hash, pattern.getBytes()); + if (token == null) { + throw new VerificationFailedException(); + } + } else if (!getGateKeeperService().verify(userId, storedHash.hash, pattern.getBytes())) { + throw new VerificationFailedException(); + } + + // pattern has matched + maybeUpdateKeystore(pattern, userId); + return token; + } @Override public boolean checkPassword(String password, int userId) throws RemoteException { - checkPasswordReadPermission(userId); + try { + doVerifyPassword(password, false, 0, userId); + } catch (VerificationFailedException ex) { + return false; + } + return true; + } + + @Override + public byte[] verifyPassword(String password, long challenge, int userId) + throws RemoteException { + try { + return doVerifyPassword(password, true, challenge, userId); + } catch (VerificationFailedException ex) { + return null; + } + } + + private byte[] doVerifyPassword(String password, boolean hasChallenge, long challenge, + int userId) throws VerificationFailedException, RemoteException { + checkPasswordReadPermission(userId); + + CredentialHash storedHash = mStorage.readPasswordHash(userId); - byte[] hash = mLockPatternUtils.passwordToHash(password, userId); - byte[] storedHash = mStorage.readPasswordHash(userId); + if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(password)) { + // don't need to pass empty passwords to GateKeeper + return null; + } - if (storedHash == null) { - return true; + if (TextUtils.isEmpty(password)) { + throw new VerificationFailedException(); } - boolean matched = Arrays.equals(hash, storedHash); - if (matched && !TextUtils.isEmpty(password)) { - maybeUpdateKeystore(password, userId); + if (storedHash.version == CredentialHash.VERSION_LEGACY) { + byte[] hash = mLockPatternUtils.passwordToHash(password, userId); + if (Arrays.equals(hash, storedHash.hash)) { + maybeUpdateKeystore(password, userId); + // migrate password to GateKeeper + setLockPassword(password, null, userId); + if (!hasChallenge) { + return null; + } + // Fall through to get the auth token. Technically this should never happen, + // as a user that had a legacy password would have to unlock their device + // before getting to a flow with a challenge, but supporting for consistency. + } else { + throw new VerificationFailedException(); + } } - return matched; + + byte[] token = null; + if (hasChallenge) { + token = getGateKeeperService() + .verifyChallenge(userId, challenge, storedHash.hash, password.getBytes()); + if (token == null) { + throw new VerificationFailedException(); + } + } else if (!getGateKeeperService().verify(userId, storedHash.hash, password.getBytes())) { + throw new VerificationFailedException(); + } + + // password has matched + maybeUpdateKeystore(password, userId); + return token; } + @Override public boolean checkVoldPassword(int userId) throws RemoteException { if (!mFirstCallToVold) { @@ -370,7 +655,7 @@ public class LockSettingsService extends ILockSettings.Stub { } try { - if (mLockPatternUtils.isLockPatternEnabled()) { + if (mLockPatternUtils.isLockPatternEnabled(userId)) { if (checkPattern(password, userId)) { return true; } @@ -379,7 +664,7 @@ public class LockSettingsService extends ILockSettings.Stub { } try { - if (mLockPatternUtils.isLockPasswordEnabled()) { + if (mLockPatternUtils.isLockPasswordEnabled(userId)) { if (checkPassword(password, userId)) { return true; } @@ -390,10 +675,7 @@ public class LockSettingsService extends ILockSettings.Stub { return false; } - @Override - public void removeUser(int userId) { - checkWritePermission(userId); - + private void removeUser(int userId) { mStorage.removeUser(userId); final KeyStore ks = KeyStore.getInstance(); @@ -420,12 +702,24 @@ public class LockSettingsService extends ILockSettings.Stub { Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED }; - // These are protected with a read permission + // Reading these settings needs the profile permission private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] { Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, Secure.LOCK_SCREEN_OWNER_INFO }; + // Reading these settings needs the same permission as checking the password + private static final String[] READ_PASSWORD_PROTECTED_SETTINGS = new String[] { + LockPatternUtils.LOCK_PASSWORD_SALT_KEY, + LockPatternUtils.PASSWORD_HISTORY_KEY, + LockPatternUtils.PASSWORD_TYPE_KEY, + }; + + private static final String[] SETTINGS_TO_BACKUP = new String[] { + Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, + Secure.LOCK_SCREEN_OWNER_INFO + }; + private IMountService getMountService() { final IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -433,4 +727,33 @@ public class LockSettingsService extends ILockSettings.Stub { } return null; } + + private class GateKeeperDiedRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + mGateKeeperService.asBinder().unlinkToDeath(this, 0); + mGateKeeperService = null; + } + } + + private synchronized IGateKeeperService getGateKeeperService() + throws RemoteException { + if (mGateKeeperService != null) { + return mGateKeeperService; + } + + final IBinder service = + ServiceManager.getService("android.service.gatekeeper.IGateKeeperService"); + if (service != null) { + service.linkToDeath(new GateKeeperDiedRecipient(), 0); + mGateKeeperService = IGateKeeperService.Stub.asInterface(service); + return mGateKeeperService; + } + + Slog.e(TAG, "Unable to acquire GateKeeperService"); + return null; + } + + private class VerificationFailedException extends Exception {} + } diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java index c03bb58..f202c36 100644 --- a/services/core/java/com/android/server/LockSettingsStorage.java +++ b/services/core/java/com/android/server/LockSettingsStorage.java @@ -56,8 +56,10 @@ class LockSettingsStorage { }; private static final String SYSTEM_DIRECTORY = "/system/"; - private static final String LOCK_PATTERN_FILE = "gesture.key"; - private static final String LOCK_PASSWORD_FILE = "password.key"; + private static final String LOCK_PATTERN_FILE = "gatekeeper.gesture.key"; + private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key"; + private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key"; + private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key"; private static final Object DEFAULT = new Object(); @@ -66,6 +68,25 @@ class LockSettingsStorage { private final Cache mCache = new Cache(); private final Object mFileWriteLock = new Object(); + private int mStoredCredentialType; + + class CredentialHash { + static final int TYPE_NONE = -1; + static final int TYPE_PATTERN = 1; + static final int TYPE_PASSWORD = 2; + + static final int VERSION_LEGACY = 0; + static final int VERSION_GATEKEEPER = 1; + + CredentialHash(byte[] hash, int version) { + this.hash = hash; + this.version = version; + } + + byte[] hash; + int version; + } + public LockSettingsStorage(Context context, Callback callback) { mContext = context; mOpenHelper = new DatabaseHelper(context, callback); @@ -148,28 +169,72 @@ class LockSettingsStorage { readPatternHash(userId); } - public byte[] readPasswordHash(int userId) { - final byte[] stored = readFile(getLockPasswordFilename(userId)); + public int getStoredCredentialType(int userId) { + if (mStoredCredentialType != 0) { + return mStoredCredentialType; + } + + CredentialHash pattern = readPatternHash(userId); + if (pattern == null) { + if (readPasswordHash(userId) != null) { + mStoredCredentialType = CredentialHash.TYPE_PASSWORD; + } else { + mStoredCredentialType = CredentialHash.TYPE_NONE; + } + } else { + CredentialHash password = readPasswordHash(userId); + if (password != null) { + // Both will never be GateKeeper + if (password.version == CredentialHash.VERSION_GATEKEEPER) { + mStoredCredentialType = CredentialHash.TYPE_PASSWORD; + } else { + mStoredCredentialType = CredentialHash.TYPE_PATTERN; + } + } else { + mStoredCredentialType = CredentialHash.TYPE_PATTERN; + } + } + + return mStoredCredentialType; + } + + + public CredentialHash readPasswordHash(int userId) { + byte[] stored = readFile(getLockPasswordFilename(userId)); if (stored != null && stored.length > 0) { - return stored; + return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER); } + + stored = readFile(getLegacyLockPasswordFilename(userId)); + if (stored != null && stored.length > 0) { + return new CredentialHash(stored, CredentialHash.VERSION_LEGACY); + } + return null; } - public byte[] readPatternHash(int userId) { - final byte[] stored = readFile(getLockPatternFilename(userId)); + public CredentialHash readPatternHash(int userId) { + byte[] stored = readFile(getLockPatternFilename(userId)); + if (stored != null && stored.length > 0) { + return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER); + } + + stored = readFile(getLegacyLockPatternFilename(userId)); if (stored != null && stored.length > 0) { - return stored; + return new CredentialHash(stored, CredentialHash.VERSION_LEGACY); } + return null; } public boolean hasPassword(int userId) { - return hasFile(getLockPasswordFilename(userId)); + return hasFile(getLockPasswordFilename(userId)) || + hasFile(getLegacyLockPasswordFilename(userId)); } public boolean hasPattern(int userId) { - return hasFile(getLockPatternFilename(userId)); + return hasFile(getLockPatternFilename(userId)) || + hasFile(getLegacyLockPatternFilename(userId)); } private boolean hasFile(String name) { @@ -237,13 +302,28 @@ class LockSettingsStorage { } public void writePatternHash(byte[] hash, int userId) { + mStoredCredentialType = hash == null + ? CredentialHash.TYPE_NONE + : CredentialHash.TYPE_PATTERN; writeFile(getLockPatternFilename(userId), hash); + clearPasswordHash(userId); + } + + private void clearPatternHash(int userId) { + writeFile(getLockPatternFilename(userId), null); } public void writePasswordHash(byte[] hash, int userId) { + mStoredCredentialType = hash == null + ? CredentialHash.TYPE_NONE + : CredentialHash.TYPE_PASSWORD; writeFile(getLockPasswordFilename(userId), hash); + clearPatternHash(userId); } + private void clearPasswordHash(int userId) { + writeFile(getLockPasswordFilename(userId), null); + } @VisibleForTesting String getLockPatternFilename(int userId) { @@ -255,6 +335,16 @@ class LockSettingsStorage { return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE); } + @VisibleForTesting + String getLegacyLockPatternFilename(int userId) { + return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE); + } + + @VisibleForTesting + String getLegacyLockPasswordFilename(int userId) { + return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE); + } + private String getLockCredentialFilePathForUser(int userId, String basename) { userId = getUserParentOrSelfId(userId); String dataSystemDirectory = @@ -279,16 +369,15 @@ class LockSettingsStorage { return userId; } - public void removeUser(int userId) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); final UserInfo parentInfo = um.getProfileParent(userId); - synchronized (mFileWriteLock) { - if (parentInfo == null) { - // This user owns its lock settings files - safe to delete them + if (parentInfo == null) { + // This user owns its lock settings files - safe to delete them + synchronized (mFileWriteLock) { String name = getLockPasswordFilename(userId); File file = new File(name); if (file.exists()) { diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java index 0de6a03..e0352e0 100644 --- a/services/core/java/com/android/server/MmsServiceBroker.java +++ b/services/core/java/com/android/server/MmsServiceBroker.java @@ -36,6 +36,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.service.carrier.CarrierMessagingService; +import android.telephony.SmsManager; import android.telephony.TelephonyManager; import android.util.Slog; @@ -111,6 +112,106 @@ public class MmsServiceBroker extends SystemService { } }; + // Instance of IMms for returning failure to service API caller, + // used when MmsService cannot be connected. + private final IMms mServiceStubForFailure = new IMms() { + + @Override + public IBinder asBinder() { + return null; + } + + @Override + public void sendMessage(int subId, String callingPkg, Uri contentUri, String locationUrl, + Bundle configOverrides, PendingIntent sentIntent) throws RemoteException { + returnPendingIntentWithError(sentIntent); + } + + @Override + public void downloadMessage(int subId, String callingPkg, String locationUrl, + Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent) + throws RemoteException { + returnPendingIntentWithError(downloadedIntent); + } + + @Override + public Bundle getCarrierConfigValues(int subId) throws RemoteException { + return null; + } + + @Override + public Uri importTextMessage(String callingPkg, String address, int type, String text, + long timestampMillis, boolean seen, boolean read) throws RemoteException { + return null; + } + + @Override + public Uri importMultimediaMessage(String callingPkg, Uri contentUri, String messageId, + long timestampSecs, boolean seen, boolean read) throws RemoteException { + return null; + } + + @Override + public boolean deleteStoredMessage(String callingPkg, Uri messageUri) + throws RemoteException { + return false; + } + + @Override + public boolean deleteStoredConversation(String callingPkg, long conversationId) + throws RemoteException { + return false; + } + + @Override + public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri, + ContentValues statusValues) throws RemoteException { + return false; + } + + @Override + public boolean archiveStoredConversation(String callingPkg, long conversationId, + boolean archived) throws RemoteException { + return false; + } + + @Override + public Uri addTextMessageDraft(String callingPkg, String address, String text) + throws RemoteException { + return null; + } + + @Override + public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri) + throws RemoteException { + return null; + } + + @Override + public void sendStoredMessage(int subId, String callingPkg, Uri messageUri, + Bundle configOverrides, PendingIntent sentIntent) throws RemoteException { + returnPendingIntentWithError(sentIntent); + } + + @Override + public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException { + // Do nothing + } + + @Override + public boolean getAutoPersisting() throws RemoteException { + return false; + } + + private void returnPendingIntentWithError(PendingIntent pendingIntent) { + try { + pendingIntent.send(mContext, SmsManager.MMS_ERROR_UNSPECIFIED, null); + } catch (PendingIntent.CanceledException e) { + Slog.e(TAG, "Failed to return pending intent result", e); + } + } + }; + public MmsServiceBroker(Context context) { super(context); mContext = context; @@ -145,44 +246,51 @@ public class MmsServiceBroker extends SystemService { } } - private void ensureService() { + private IMms getOrConnectService() { synchronized (this) { - if (mService == null) { - // Service is not connected. Try blocking connecting. - Slog.w(TAG, "MmsService not connected. Try connecting..."); - mConnectionHandler.sendMessage( - mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING)); - final long shouldEnd = - SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS; - long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS; - while (waitTime > 0) { - try { - // TODO: consider using Java concurrent construct instead of raw object wait - this.wait(waitTime); - } catch (InterruptedException e) { - Slog.w(TAG, "Connection wait interrupted", e); - } - if (mService != null) { - // Success - return; - } - // Calculate remaining waiting time to make sure we wait the full timeout period - waitTime = shouldEnd - SystemClock.elapsedRealtime(); + if (mService != null) { + return mService; + } + // Service is not connected. Try blocking connecting. + Slog.w(TAG, "MmsService not connected. Try connecting..."); + mConnectionHandler.sendMessage( + mConnectionHandler.obtainMessage(MSG_TRY_CONNECTING)); + final long shouldEnd = + SystemClock.elapsedRealtime() + SERVICE_CONNECTION_WAIT_TIME_MS; + long waitTime = SERVICE_CONNECTION_WAIT_TIME_MS; + while (waitTime > 0) { + try { + // TODO: consider using Java concurrent construct instead of raw object wait + this.wait(waitTime); + } catch (InterruptedException e) { + Slog.w(TAG, "Connection wait interrupted", e); } - // Timed out. Something's really wrong. - Slog.e(TAG, "Can not connect to MmsService (timed out)"); - throw new RuntimeException("Timed out in connecting to MmsService"); + if (mService != null) { + // Success + return mService; + } + // Calculate remaining waiting time to make sure we wait the full timeout period + waitTime = shouldEnd - SystemClock.elapsedRealtime(); } + // Timed out. Something's really wrong. + Slog.e(TAG, "Can not connect to MmsService (timed out)"); + return null; } } /** - * Making sure when we obtain the mService instance it is always valid. - * Throws {@link RuntimeException} when it is empty. + * Make sure to return a non-empty service instance. Return the connected MmsService + * instance, if not connected, try connecting. If fail to connect, return a fake service + * instance which returns failure to service caller. + * + * @return a non-empty service instance, real or fake */ private IMms getServiceGuarded() { - ensureService(); - return mService; + final IMms service = getOrConnectService(); + if (service != null) { + return service; + } + return mServiceStubForFailure; } private AppOpsManager getAppOpsManager() { @@ -264,7 +372,6 @@ public class MmsServiceBroker extends SystemService { @Override public Uri importTextMessage(String callingPkg, String address, int type, String text, long timestampMillis, boolean seen, boolean read) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import SMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { // Silently fail AppOps failure due to not being the default SMS app @@ -279,7 +386,6 @@ public class MmsServiceBroker extends SystemService { public Uri importMultimediaMessage(String callingPkg, Uri contentUri, String messageId, long timestampSecs, boolean seen, boolean read) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Import MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { // Silently fail AppOps failure due to not being the default SMS app @@ -293,8 +399,6 @@ public class MmsServiceBroker extends SystemService { @Override public boolean deleteStoredMessage(String callingPkg, Uri messageUri) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, - "Delete SMS/MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { return false; @@ -305,7 +409,6 @@ public class MmsServiceBroker extends SystemService { @Override public boolean deleteStoredConversation(String callingPkg, long conversationId) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Delete conversation"); if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { return false; @@ -316,8 +419,10 @@ public class MmsServiceBroker extends SystemService { @Override public boolean updateStoredMessageStatus(String callingPkg, Uri messageUri, ContentValues statusValues) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, - "Update SMS/MMS message"); + if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), + callingPkg) != AppOpsManager.MODE_ALLOWED) { + return false; + } return getServiceGuarded() .updateStoredMessageStatus(callingPkg, messageUri, statusValues); } @@ -325,8 +430,10 @@ public class MmsServiceBroker extends SystemService { @Override public boolean archiveStoredConversation(String callingPkg, long conversationId, boolean archived) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, - "Update SMS/MMS message"); + if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), + callingPkg) != AppOpsManager.MODE_ALLOWED) { + return false; + } return getServiceGuarded() .archiveStoredConversation(callingPkg, conversationId, archived); } @@ -334,7 +441,6 @@ public class MmsServiceBroker extends SystemService { @Override public Uri addTextMessageDraft(String callingPkg, String address, String text) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add SMS draft"); if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { // Silently fail AppOps failure due to not being the default SMS app @@ -347,7 +453,6 @@ public class MmsServiceBroker extends SystemService { @Override public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Add MMS draft"); if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { // Silently fail AppOps failure due to not being the default SMS app @@ -360,8 +465,6 @@ public class MmsServiceBroker extends SystemService { @Override public void sendStoredMessage(int subId, String callingPkg, Uri messageUri, Bundle configOverrides, PendingIntent sentIntent) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, - "Send stored MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { return; @@ -372,7 +475,6 @@ public class MmsServiceBroker extends SystemService { @Override public void setAutoPersisting(String callingPkg, boolean enabled) throws RemoteException { - mContext.enforceCallingPermission(Manifest.permission.WRITE_SMS, "Set auto persist"); if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { return; diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 6c981c0..89a7173 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -16,40 +16,41 @@ package com.android.server; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static com.android.internal.util.XmlUtils.readIntAttribute; +import static com.android.internal.util.XmlUtils.readStringAttribute; +import static com.android.internal.util.XmlUtils.writeIntAttribute; +import static com.android.internal.util.XmlUtils.writeStringAttribute; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.Manifest; import android.app.ActivityManagerNative; import android.app.AppOpsManager; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.ObbInfo; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.hardware.usb.UsbManager; +import android.mtp.MtpStorage; import android.net.Uri; import android.os.Binder; import android.os.Environment; import android.os.Environment.UserEnvironment; +import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.DiskInfo; import android.os.storage.IMountService; import android.os.storage.IMountServiceListener; import android.os.storage.IMountShutdownObserver; @@ -58,31 +59,40 @@ import android.os.storage.OnObbStateChangeListener; import android.os.storage.StorageManager; import android.os.storage.StorageResultCode; import android.os.storage.StorageVolume; +import android.os.storage.VolumeInfo; import android.text.TextUtils; -import android.util.AttributeSet; +import android.text.format.DateUtils; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.DebugUtils; +import android.util.Log; import android.util.Slog; import android.util.Xml; +import libcore.io.IoUtils; +import libcore.util.EmptyArray; +import libcore.util.HexEncoding; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IMediaContainerService; +import com.android.internal.os.SomeArgs; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; -import com.android.internal.util.XmlUtils; import com.android.server.NativeDaemonConnector.Command; import com.android.server.NativeDaemonConnector.SensitiveArg; -import com.android.server.am.ActivityManagerService; import com.android.server.pm.PackageManagerService; -import com.android.server.pm.UserManagerService; -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.DecoderException; +import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -102,7 +112,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -111,21 +121,50 @@ import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; /** - * MountService implements back-end services for platform storage - * management. - * @hide - Applications should use android.os.storage.StorageManager - * to access the MountService. + * Service responsible for various storage media. Connects to {@code vold} to + * watch for and manage dynamically added storage, such as SD cards and USB mass + * storage. Also decides how storage should be presented to users on the device. */ class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks, Watchdog.Monitor { + // TODO: finish enforcing UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA + // Static direct instance pointer for the tightly-coupled idle service to use static MountService sSelf = null; - // TODO: listen for user creation/deletion + public static class Lifecycle extends SystemService { + private MountService mMountService; + + public Lifecycle(Context context) { + super(context); + } + + @Override + public void onStart() { + mMountService = new MountService(getContext()); + publishBinderService("mount", mMountService); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { + mMountService.systemReady(); + } + } + + @Override + public void onStartUser(int userHandle) { + mMountService.onStartUser(userHandle); + } + + @Override + public void onCleanupUser(int userHandle) { + mMountService.onCleanupUser(userHandle); + } + } private static final boolean LOCAL_LOGD = false; - private static final boolean DEBUG_UNMOUNT = false; private static final boolean DEBUG_EVENTS = false; private static final boolean DEBUG_OBB = false; @@ -140,22 +179,6 @@ class MountService extends IMountService.Stub private static final int MAX_CONTAINERS = 250; /* - * Internal vold volume state constants - */ - class VolumeState { - public static final int Init = -1; - public static final int NoMedia = 0; - public static final int Idle = 1; - public static final int Pending = 2; - public static final int Checking = 3; - public static final int Mounted = 4; - public static final int Unmounting = 5; - public static final int Formatting = 6; - public static final int Shared = 7; - public static final int SharedMnt = 8; - } - - /* * Internal vold response code constants */ class VoldResponseCode { @@ -189,12 +212,19 @@ class MountService extends IMountService.Stub /* * 600 series - Unsolicited broadcasts. */ - public static final int VolumeStateChange = 605; - public static final int VolumeUuidChange = 613; - public static final int VolumeUserLabelChange = 614; - public static final int VolumeDiskInserted = 630; - public static final int VolumeDiskRemoved = 631; - public static final int VolumeBadRemoval = 632; + public static final int DISK_CREATED = 640; + public static final int DISK_SIZE_CHANGED = 641; + public static final int DISK_LABEL_CHANGED = 642; + public static final int DISK_SCANNED = 643; + public static final int DISK_DESTROYED = 649; + + public static final int VOLUME_CREATED = 650; + public static final int VOLUME_STATE_CHANGED = 651; + public static final int VOLUME_FS_TYPE_CHANGED = 652; + public static final int VOLUME_FS_UUID_CHANGED = 653; + public static final int VOLUME_FS_LABEL_CHANGED = 654; + public static final int VOLUME_PATH_CHANGED = 655; + public static final int VOLUME_DESTROYED = 659; /* * 700 series - fstrim @@ -202,6 +232,151 @@ class MountService extends IMountService.Stub public static final int FstrimCompleted = 700; } + private static final int VERSION_INIT = 1; + private static final int VERSION_ADD_PRIMARY = 2; + + private static final String TAG_VOLUMES = "volumes"; + private static final String ATTR_VERSION = "version"; + private static final String ATTR_PRIMARY_STORAGE_UUID = "primaryStorageUuid"; + private static final String TAG_VOLUME = "volume"; + private static final String ATTR_TYPE = "type"; + private static final String ATTR_FS_UUID = "fsUuid"; + private static final String ATTR_NICKNAME = "nickname"; + private static final String ATTR_USER_FLAGS = "userFlags"; + + private final AtomicFile mMetadataFile; + + private static class VolumeMetadata { + public final int type; + public final String fsUuid; + public String nickname; + public int userFlags; + + public VolumeMetadata(int type, String fsUuid) { + this.type = type; + this.fsUuid = Preconditions.checkNotNull(fsUuid); + } + + public static VolumeMetadata read(XmlPullParser in) throws IOException { + final int type = readIntAttribute(in, ATTR_TYPE); + final String fsUuid = readStringAttribute(in, ATTR_FS_UUID); + final VolumeMetadata meta = new VolumeMetadata(type, fsUuid); + meta.nickname = readStringAttribute(in, ATTR_NICKNAME); + meta.userFlags = readIntAttribute(in, ATTR_USER_FLAGS); + return meta; + } + + public static void write(XmlSerializer out, VolumeMetadata meta) throws IOException { + out.startTag(null, TAG_VOLUME); + writeIntAttribute(out, ATTR_TYPE, meta.type); + writeStringAttribute(out, ATTR_FS_UUID, meta.fsUuid); + writeStringAttribute(out, ATTR_NICKNAME, meta.nickname); + writeIntAttribute(out, ATTR_USER_FLAGS, meta.userFlags); + out.endTag(null, TAG_VOLUME); + } + + public void dump(IndentingPrintWriter pw) { + pw.println("VolumeMetadata:"); + pw.increaseIndent(); + pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type)); + pw.printPair("fsUuid", fsUuid); + pw.printPair("nickname", nickname); + pw.printPair("userFlags", + DebugUtils.flagsToString(VolumeInfo.class, "USER_FLAG_", userFlags)); + pw.decreaseIndent(); + pw.println(); + } + } + + /** + * <em>Never</em> hold the lock while performing downcalls into vold, since + * unsolicited events can suddenly appear to update data structures. + */ + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private int[] mStartedUsers = EmptyArray.INT; + + /** Map from disk ID to disk */ + @GuardedBy("mLock") + private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>(); + /** Map from volume ID to disk */ + @GuardedBy("mLock") + private ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>(); + + /** Map from UUID to metadata */ + @GuardedBy("mLock") + private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>(); + @GuardedBy("mLock") + private String mPrimaryStorageUuid; + + /** Map from disk ID to latches */ + @GuardedBy("mLock") + private ArrayMap<String, CountDownLatch> mDiskScanLatches = new ArrayMap<>(); + + private DiskInfo findDiskById(String id) { + synchronized (mLock) { + final DiskInfo disk = mDisks.get(id); + if (disk != null) { + return disk; + } + } + throw new IllegalArgumentException("No disk found for ID " + id); + } + + private VolumeInfo findVolumeById(String id) { + synchronized (mLock) { + final VolumeInfo vol = mVolumes.get(id); + if (vol != null) { + return vol; + } + } + throw new IllegalArgumentException("No volume found for ID " + id); + } + + @Deprecated + private String findVolumeIdForPath(String path) { + synchronized (mLock) { + for (int i = 0; i < mVolumes.size(); i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + if (vol.path != null && path.startsWith(vol.path)) { + return vol.id; + } + } + } + throw new IllegalArgumentException("No volume found for path " + path); + } + + private VolumeMetadata findOrCreateMetadataLocked(VolumeInfo vol) { + VolumeMetadata meta = mMetadata.get(vol.fsUuid); + if (meta == null) { + meta = new VolumeMetadata(vol.type, vol.fsUuid); + mMetadata.put(meta.fsUuid, meta); + } + return meta; + } + + private CountDownLatch findOrCreateDiskScanLatch(String diskId) { + synchronized (mLock) { + CountDownLatch latch = mDiskScanLatches.get(diskId); + if (latch == null) { + latch = new CountDownLatch(1); + mDiskScanLatches.put(diskId, latch); + } + return latch; + } + } + + private static int sNextMtpIndex = 1; + + private static int allocateMtpIndex(String volId) { + if (VolumeInfo.ID_EMULATED_INTERNAL.equals(volId)) { + return 0; + } else { + return sNextMtpIndex++; + } + } + /** List of crypto types. * These must match CRYPT_TYPE_XXX in cryptfs.h AND their * corresponding commands in CommandListener.cpp */ @@ -211,33 +386,19 @@ class MountService extends IMountService.Stub private final Context mContext; private final NativeDaemonConnector mConnector; - private final Object mVolumesLock = new Object(); - - /** When defined, base template for user-specific {@link StorageVolume}. */ - private StorageVolume mEmulatedTemplate; + private volatile boolean mSystemReady = false; + private volatile boolean mDaemonConnected = false; - // TODO: separate storage volumes on per-user basis + private PackageManagerService mPms; - @GuardedBy("mVolumesLock") - private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList(); - /** Map from path to {@link StorageVolume} */ - @GuardedBy("mVolumesLock") - private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap(); - /** Map from path to state */ - @GuardedBy("mVolumesLock") - private final HashMap<String, String> mVolumeStates = Maps.newHashMap(); + private final Callbacks mCallbacks; - private volatile boolean mSystemReady = false; - - private PackageManagerService mPms; - private boolean mUmsEnabling; - private boolean mUmsAvailable = false; - // Used as a lock for methods that register/unregister listeners. - final private ArrayList<MountServiceBinderListener> mListeners = - new ArrayList<MountServiceBinderListener>(); private final CountDownLatch mConnectedSignal = new CountDownLatch(1); private final CountDownLatch mAsecsScanned = new CountDownLatch(1); - private boolean mSendUmsConnectedOnBoot = false; + + private final Object mUnmountLock = new Object(); + @GuardedBy("mUnmountLock") + private CountDownLatch mUnmountSignal; /** * Private hash of currently mounted secure containers. @@ -346,6 +507,7 @@ class MountService extends IMountService.Stub final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); class DefaultContainerConnection implements ServiceConnection { + @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG_OBB) Slog.i(TAG, "onServiceConnected"); @@ -353,6 +515,7 @@ class MountService extends IMountService.Stub mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs)); } + @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG_OBB) Slog.i(TAG, "onServiceDisconnected"); @@ -368,181 +531,35 @@ class MountService extends IMountService.Stub private long mLastMaintenance; // Handler messages - private static final int H_UNMOUNT_PM_UPDATE = 1; - private static final int H_UNMOUNT_PM_DONE = 2; - private static final int H_UNMOUNT_MS = 3; - private static final int H_SYSTEM_READY = 4; - private static final int H_FSTRIM = 5; - - private static final int RETRY_UNMOUNT_DELAY = 30; // in ms - private static final int MAX_UNMOUNT_RETRIES = 4; - - class UnmountCallBack { - final String path; - final boolean force; - final boolean removeEncryption; - int retries; - - UnmountCallBack(String path, boolean force, boolean removeEncryption) { - retries = 0; - this.path = path; - this.force = force; - this.removeEncryption = removeEncryption; - } - - void handleFinished() { - if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path); - doUnmountVolume(path, true, removeEncryption); - } - } - - class UmsEnableCallBack extends UnmountCallBack { - final String method; - - UmsEnableCallBack(String path, String method, boolean force) { - super(path, force, false); - this.method = method; - } - - @Override - void handleFinished() { - super.handleFinished(); - doShareUnshareVolume(path, method, true); - } - } - - class ShutdownCallBack extends UnmountCallBack { - MountShutdownLatch mMountShutdownLatch; - ShutdownCallBack(String path, final MountShutdownLatch mountShutdownLatch) { - super(path, true, false); - mMountShutdownLatch = mountShutdownLatch; - } - - @Override - void handleFinished() { - int ret = doUnmountVolume(path, true, removeEncryption); - Slog.i(TAG, "Unmount completed: " + path + ", result code: " + ret); - mMountShutdownLatch.countDown(); - } - } - - static class MountShutdownLatch { - private IMountShutdownObserver mObserver; - private AtomicInteger mCount; - - MountShutdownLatch(final IMountShutdownObserver observer, int count) { - mObserver = observer; - mCount = new AtomicInteger(count); - } - - void countDown() { - boolean sendShutdown = false; - if (mCount.decrementAndGet() == 0) { - sendShutdown = true; - } - if (sendShutdown && mObserver != null) { - try { - mObserver.onShutDownComplete(StorageResultCode.OperationSucceeded); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException when shutting down"); - } - } - } - } + private static final int H_SYSTEM_READY = 1; + private static final int H_DAEMON_CONNECTED = 2; + private static final int H_SHUTDOWN = 3; + private static final int H_FSTRIM = 4; + private static final int H_VOLUME_MOUNT = 5; + private static final int H_VOLUME_BROADCAST = 6; class MountServiceHandler extends Handler { - ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>(); - boolean mUpdatingStatus = false; - - MountServiceHandler(Looper l) { - super(l); + public MountServiceHandler(Looper looper) { + super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { - case H_UNMOUNT_PM_UPDATE: { - if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE"); - UnmountCallBack ucb = (UnmountCallBack) msg.obj; - mForceUnmounts.add(ucb); - if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus); - // Register only if needed. - if (!mUpdatingStatus) { - if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager"); - mUpdatingStatus = true; - mPms.updateExternalMediaStatus(false, true); - } - break; - } - case H_UNMOUNT_PM_DONE: { - if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE"); - if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests"); - mUpdatingStatus = false; - int size = mForceUnmounts.size(); - int sizeArr[] = new int[size]; - int sizeArrN = 0; - // Kill processes holding references first - ActivityManagerService ams = (ActivityManagerService) - ServiceManager.getService("activity"); - for (int i = 0; i < size; i++) { - UnmountCallBack ucb = mForceUnmounts.get(i); - String path = ucb.path; - boolean done = false; - if (!ucb.force) { - done = true; - } else { - int pids[] = getStorageUsers(path); - if (pids == null || pids.length == 0) { - done = true; - } else { - // Eliminate system process here? - ams.killPids(pids, "unmount media", true); - // Confirm if file references have been freed. - pids = getStorageUsers(path); - if (pids == null || pids.length == 0) { - done = true; - } - } - } - if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) { - // Retry again - Slog.i(TAG, "Retrying to kill storage users again"); - mHandler.sendMessageDelayed( - mHandler.obtainMessage(H_UNMOUNT_PM_DONE, - ucb.retries++), - RETRY_UNMOUNT_DELAY); - } else { - if (ucb.retries >= MAX_UNMOUNT_RETRIES) { - Slog.i(TAG, "Failed to unmount media inspite of " + - MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now"); - } - sizeArr[sizeArrN++] = i; - mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS, - ucb)); - } - } - // Remove already processed elements from list. - for (int i = (sizeArrN-1); i >= 0; i--) { - mForceUnmounts.remove(sizeArr[i]); - } - break; - } - case H_UNMOUNT_MS: { - if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); - UnmountCallBack ucb = (UnmountCallBack) msg.obj; - ucb.handleFinished(); + case H_SYSTEM_READY: { + handleSystemReady(); break; } - case H_SYSTEM_READY: { - try { - handleSystemReady(); - } catch (Exception ex) { - Slog.e(TAG, "Boot-time mount exception", ex); - } + case H_DAEMON_CONNECTED: { + handleDaemonConnected(); break; } case H_FSTRIM: { - waitForReady(); + if (!isReady()) { + Slog.i(TAG, "fstrim requested, but no daemon connection yet; trying again"); + sendMessageDelayed(obtainMessage(H_FSTRIM), DateUtils.SECOND_IN_MILLIS); + } + Slog.i(TAG, "Running fstrim idle maintenance"); // Remember when we kicked it off @@ -569,31 +586,72 @@ class MountService extends IMountService.Stub } break; } + case H_SHUTDOWN: { + final IMountShutdownObserver obs = (IMountShutdownObserver) msg.obj; + boolean success = false; + try { + success = mConnector.execute("volume", "shutdown").isClassOk(); + } catch (NativeDaemonConnectorException ignored) { + } + if (obs != null) { + try { + obs.onShutDownComplete(success ? 0 : -1); + } catch (RemoteException ignored) { + } + } + break; + } + case H_VOLUME_MOUNT: { + final VolumeInfo vol = (VolumeInfo) msg.obj; + try { + mConnector.execute("volume", "mount", vol.id, vol.mountFlags, + vol.mountUserId); + } catch (NativeDaemonConnectorException ignored) { + } + break; + } + case H_VOLUME_BROADCAST: { + final StorageVolume userVol = (StorageVolume) msg.obj; + final String envState = userVol.getState(); + Slog.d(TAG, "Volume " + userVol.getId() + " broadcasting " + envState + " to " + + userVol.getOwner()); + + final String action = VolumeInfo.getBroadcastForEnvironment(envState); + if (action != null) { + final Intent intent = new Intent(action, + Uri.fromFile(userVol.getPathFile())); + intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, userVol.getOwner()); + } + break; + } } } - }; + } private final Handler mHandler; - void waitForAsecScan() { - waitForLatch(mAsecsScanned); + @Override + public void waitForAsecScan() { + waitForLatch(mAsecsScanned, "mAsecsScanned"); } private void waitForReady() { - waitForLatch(mConnectedSignal); + waitForLatch(mConnectedSignal, "mConnectedSignal"); } - private void waitForLatch(CountDownLatch latch) { - for (;;) { + private void waitForLatch(CountDownLatch latch, String condition) { + while (true) { try { if (latch.await(5000, TimeUnit.MILLISECONDS)) { return; } else { Slog.w(TAG, "Thread " + Thread.currentThread().getName() - + " still waiting for MountService ready..."); + + " still waiting for " + condition + "..."); } } catch (InterruptedException e) { - Slog.w(TAG, "Interrupt while waiting for MountService to be ready."); + Slog.w(TAG, "Interrupt while waiting for " + condition); } } } @@ -607,109 +665,72 @@ class MountService extends IMountService.Stub } private void handleSystemReady() { - // Snapshot current volume states since it's not safe to call into vold - // while holding locks. - final HashMap<String, String> snapshot; - synchronized (mVolumesLock) { - snapshot = new HashMap<String, String>(mVolumeStates); - } - - for (Map.Entry<String, String> entry : snapshot.entrySet()) { - final String path = entry.getKey(); - final String state = entry.getValue(); - - if (state.equals(Environment.MEDIA_UNMOUNTED)) { - int rc = doMountVolume(path); - if (rc != StorageResultCode.OperationSucceeded) { - Slog.e(TAG, String.format("Boot-time mount failed (%d)", - rc)); - } - } else if (state.equals(Environment.MEDIA_SHARED)) { - /* - * Bootstrap UMS enabled state since vold indicates - * the volume is shared (runtime restart while ums enabled) - */ - notifyVolumeStateChange(null, path, VolumeState.NoMedia, - VolumeState.Shared); - } - } - - // Push mounted state for all emulated storage - synchronized (mVolumesLock) { - for (StorageVolume volume : mVolumes) { - if (volume.isEmulated()) { - updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); - } - } - } - - /* - * If UMS was connected on boot, send the connected event - * now that we're up. - */ - if (mSendUmsConnectedOnBoot) { - sendUmsIntent(true); - mSendUmsConnectedOnBoot = false; - } + resetIfReadyAndConnected(); - /* - * Start scheduling nominally-daily fstrim operations - */ + // Start scheduling nominally-daily fstrim operations MountServiceIdler.scheduleIdlePass(mContext); } - private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (userId == -1) return; - final UserHandle user = new UserHandle(userId); - - final String action = intent.getAction(); - if (Intent.ACTION_USER_ADDED.equals(action)) { - synchronized (mVolumesLock) { - createEmulatedVolumeForUserLocked(user); - } + private void resetIfReadyAndConnected() { + Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady + + ", mDaemonConnected=" + mDaemonConnected); + if (mSystemReady && mDaemonConnected) { + mDisks.clear(); + mVolumes.clear(); - } else if (Intent.ACTION_USER_REMOVED.equals(action)) { - synchronized (mVolumesLock) { - final List<StorageVolume> toRemove = Lists.newArrayList(); - for (StorageVolume volume : mVolumes) { - if (user.equals(volume.getOwner())) { - toRemove.add(volume); - } - } - for (StorageVolume volume : toRemove) { - removeVolumeLocked(volume); - } - } + // Create a stub volume that represents internal storage + final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL, + VolumeInfo.TYPE_PRIVATE, null, 0); + internal.state = VolumeInfo.STATE_MOUNTED; + internal.path = Environment.getDataDirectory().getAbsolutePath(); + mVolumes.put(internal.id, internal); + + try { + mConnector.execute("volume", "reset"); + } catch (NativeDaemonConnectorException e) { + Slog.w(TAG, "Failed to reset vold", e); } } - }; + } - private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) && - intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false)); - notifyShareAvailabilityChange(available); + private void onStartUser(int userId) { + Slog.d(TAG, "onStartUser " + userId); + + // We purposefully block here to make sure that user-specific + // staging area is ready so it's ready for zygote-forked apps to + // bind mount against. + try { + mConnector.execute("volume", "start_user", userId); + } catch (NativeDaemonConnectorException ignored) { } - }; - private final class MountServiceBinderListener implements IBinder.DeathRecipient { - final IMountServiceListener mListener; + // Record user as started so newly mounted volumes kick off events + // correctly, then synthesize events for any already-mounted volumes. + synchronized (mVolumes) { + for (int i = 0; i < mVolumes.size(); i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + if (vol.isVisibleToUser(userId) && vol.isMountedReadable()) { + final StorageVolume userVol = vol.buildStorageVolume(mContext, userId); + mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget(); + + final String envState = VolumeInfo.getEnvironmentForState(vol.getState()); + mCallbacks.notifyStorageStateChanged(userVol.getPath(), envState, envState); + } + } + mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userId); + } + } - MountServiceBinderListener(IMountServiceListener listener) { - mListener = listener; + private void onCleanupUser(int userId) { + Slog.d(TAG, "onCleanupUser " + userId); + try { + mConnector.execute("volume", "cleanup_user", userId); + } catch (NativeDaemonConnectorException ignored) { } - public void binderDied() { - if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!"); - synchronized (mListeners) { - mListeners.remove(this); - mListener.asBinder().unlinkToDeath(this, 0); - } + synchronized (mVolumes) { + mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userId); } } @@ -720,7 +741,7 @@ class MountService extends IMountService.Stub // Binder entry point for kicking off an immediate fstrim @Override public void runMaintenance() { - validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); runIdleMaintenance(null); } @@ -729,144 +750,35 @@ class MountService extends IMountService.Stub return mLastMaintenance; } - private void doShareUnshareVolume(String path, String method, boolean enable) { - // TODO: Add support for multiple share methods - if (!method.equals("ums")) { - throw new IllegalArgumentException(String.format("Method %s not supported", method)); - } - - try { - mConnector.execute("volume", enable ? "share" : "unshare", path, method); - } catch (NativeDaemonConnectorException e) { - Slog.e(TAG, "Failed to share/unshare", e); - } - } - - private void updatePublicVolumeState(StorageVolume volume, String state) { - final String path = volume.getPath(); - final String oldState; - synchronized (mVolumesLock) { - oldState = mVolumeStates.put(path, state); - volume.setState(state); - } - - if (state.equals(oldState)) { - Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s", - state, state, path)); - return; - } - - Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")"); - - // Tell PackageManager about changes to primary volume state, but only - // when not emulated. - if (volume.isPrimary() && !volume.isEmulated()) { - if (Environment.MEDIA_UNMOUNTED.equals(state)) { - mPms.updateExternalMediaStatus(false, false); - - /* - * Some OBBs might have been unmounted when this volume was - * unmounted, so send a message to the handler to let it know to - * remove those from the list of mounted OBBS. - */ - mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( - OBB_FLUSH_MOUNT_STATE, path)); - } else if (Environment.MEDIA_MOUNTED.equals(state)) { - mPms.updateExternalMediaStatus(true, false); - } - } - - synchronized (mListeners) { - for (int i = mListeners.size() -1; i >= 0; i--) { - MountServiceBinderListener bl = mListeners.get(i); - try { - bl.mListener.onStorageStateChanged(path, oldState, state); - } catch (RemoteException rex) { - Slog.e(TAG, "Listener dead"); - mListeners.remove(i); - } catch (Exception ex) { - Slog.e(TAG, "Listener failed", ex); - } - } - } - } - /** * Callback from NativeDaemonConnector */ + @Override public void onDaemonConnected() { - /* - * Since we'll be calling back into the NativeDaemonConnector, - * we need to do our work in a new thread. - */ - new Thread("MountService#onDaemonConnected") { - @Override - public void run() { - /** - * Determine media state and UMS detection status - */ - try { - final String[] vols = NativeDaemonEvent.filterMessageList( - mConnector.executeForList("volume", "list", "broadcast"), - VoldResponseCode.VolumeListResult); - for (String volstr : vols) { - String[] tok = volstr.split(" "); - // FMT: <label> <mountpoint> <state> - String path = tok[1]; - String state = Environment.MEDIA_REMOVED; - - final StorageVolume volume; - synchronized (mVolumesLock) { - volume = mVolumesByPath.get(path); - } - - int st = Integer.parseInt(tok[2]); - if (st == VolumeState.NoMedia) { - state = Environment.MEDIA_REMOVED; - } else if (st == VolumeState.Idle) { - state = Environment.MEDIA_UNMOUNTED; - } else if (st == VolumeState.Mounted) { - state = Environment.MEDIA_MOUNTED; - Slog.i(TAG, "Media already mounted on daemon connection"); - } else if (st == VolumeState.Shared) { - state = Environment.MEDIA_SHARED; - Slog.i(TAG, "Media shared on daemon connection"); - } else { - throw new Exception(String.format("Unexpected state %d", st)); - } + mDaemonConnected = true; + mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget(); + } - if (state != null) { - if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); - updatePublicVolumeState(volume, state); - } - } - } catch (Exception e) { - Slog.e(TAG, "Error processing initial volume state", e); - final StorageVolume primary = getPrimaryPhysicalVolume(); - if (primary != null) { - updatePublicVolumeState(primary, Environment.MEDIA_REMOVED); - } - } + private void handleDaemonConnected() { + resetIfReadyAndConnected(); - /* - * Now that we've done our initialization, release - * the hounds! - */ - mConnectedSignal.countDown(); + /* + * Now that we've done our initialization, release + * the hounds! + */ + mConnectedSignal.countDown(); - // On an encrypted device we can't see system properties yet, so pull - // the system locale out of the mount service. - if ("".equals(SystemProperties.get("vold.encrypt_progress"))) { - copyLocaleFromMountService(); - } + // On an encrypted device we can't see system properties yet, so pull + // the system locale out of the mount service. + if ("".equals(SystemProperties.get("vold.encrypt_progress"))) { + copyLocaleFromMountService(); + } - // Let package manager load internal ASECs. - mPms.scanAvailableAsecs(); + // Let package manager load internal ASECs. + mPms.scanAvailableAsecs(); - // Notify people waiting for ASECs to be scanned that it's done. - mAsecsScanned.countDown(); - } - }.start(); + // Notify people waiting for ASECs to be scanned that it's done. + mAsecsScanned.countDown(); } private void copyLocaleFromMountService() { @@ -892,13 +804,13 @@ class MountService extends IMountService.Stub // Temporary workaround for http://b/17945169. Slog.d(TAG, "Setting system properties to " + systemLocale + " from mount service"); - SystemProperties.set("persist.sys.language", locale.getLanguage()); - SystemProperties.set("persist.sys.country", locale.getCountry()); + SystemProperties.set("persist.sys.locale", locale.toLanguageTag()); } /** * Callback from NativeDaemonConnector */ + @Override public boolean onCheckHoldWakeLock(int code) { return false; } @@ -906,568 +818,282 @@ class MountService extends IMountService.Stub /** * Callback from NativeDaemonConnector */ + @Override public boolean onEvent(int code, String raw, String[] cooked) { - if (DEBUG_EVENTS) { - StringBuilder builder = new StringBuilder(); - builder.append("onEvent::"); - builder.append(" raw= " + raw); - if (cooked != null) { - builder.append(" cooked = " ); - for (String str : cooked) { - builder.append(" " + str); - } - } - Slog.i(TAG, builder.toString()); + synchronized (mLock) { + return onEventLocked(code, raw, cooked); } - if (code == VoldResponseCode.VolumeStateChange) { - /* - * One of the volumes we're managing has changed state. - * Format: "NNN Volume <label> <path> state changed - * from <old_#> (<old_str>) to <new_#> (<new_str>)" - */ - notifyVolumeStateChange( - cooked[2], cooked[3], Integer.parseInt(cooked[7]), - Integer.parseInt(cooked[10])); - } else if (code == VoldResponseCode.VolumeUuidChange) { - // Format: nnn <label> <path> <uuid> - final String path = cooked[2]; - final String uuid = (cooked.length > 3) ? cooked[3] : null; - - final StorageVolume vol = mVolumesByPath.get(path); - if (vol != null) { - vol.setUuid(uuid); - } - - } else if (code == VoldResponseCode.VolumeUserLabelChange) { - // Format: nnn <label> <path> <label> - final String path = cooked[2]; - final String userLabel = (cooked.length > 3) ? cooked[3] : null; - - final StorageVolume vol = mVolumesByPath.get(path); - if (vol != null) { - vol.setUserLabel(userLabel); - } - - } else if ((code == VoldResponseCode.VolumeDiskInserted) || - (code == VoldResponseCode.VolumeDiskRemoved) || - (code == VoldResponseCode.VolumeBadRemoval)) { - // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>) - // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>) - // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>) - String action = null; - final String label = cooked[2]; - final String path = cooked[3]; - int major = -1; - int minor = -1; + } - try { - String devComp = cooked[6].substring(1, cooked[6].length() -1); - String[] devTok = devComp.split(":"); - major = Integer.parseInt(devTok[0]); - minor = Integer.parseInt(devTok[1]); - } catch (Exception ex) { - Slog.e(TAG, "Failed to parse major/minor", ex); + private boolean onEventLocked(int code, String raw, String[] cooked) { + switch (code) { + case VoldResponseCode.DISK_CREATED: { + if (cooked.length != 3) break; + final String id = cooked[1]; + int flags = Integer.parseInt(cooked[2]); + if (SystemProperties.getBoolean(StorageManager.PROP_FORCE_ADOPTABLE, false)) { + flags |= DiskInfo.FLAG_ADOPTABLE; + } + mDisks.put(id, new DiskInfo(id, flags)); + break; + } + case VoldResponseCode.DISK_SIZE_CHANGED: { + if (cooked.length != 3) break; + final DiskInfo disk = mDisks.get(cooked[1]); + if (disk != null) { + disk.size = Long.parseLong(cooked[2]); + } + break; + } + case VoldResponseCode.DISK_LABEL_CHANGED: { + final DiskInfo disk = mDisks.get(cooked[1]); + if (disk != null) { + final StringBuilder builder = new StringBuilder(); + for (int i = 2; i < cooked.length; i++) { + builder.append(cooked[i]).append(' '); + } + disk.label = builder.toString().trim(); + } + break; } - - final StorageVolume volume; - final String state; - synchronized (mVolumesLock) { - volume = mVolumesByPath.get(path); - state = mVolumeStates.get(path); + case VoldResponseCode.DISK_SCANNED: { + if (cooked.length != 2) break; + final DiskInfo disk = mDisks.get(cooked[1]); + if (disk != null) { + onDiskScannedLocked(disk); + } + break; + } + case VoldResponseCode.DISK_DESTROYED: { + if (cooked.length != 2) break; + mDisks.remove(cooked[1]); + break; + } + + case VoldResponseCode.VOLUME_CREATED: { + final String id = cooked[1]; + final int type = Integer.parseInt(cooked[2]); + final String diskId = (cooked.length == 4) ? cooked[3] : null; + final DiskInfo disk = mDisks.get(diskId); + final int mtpIndex = allocateMtpIndex(id); + final VolumeInfo vol = new VolumeInfo(id, type, disk, mtpIndex); + mVolumes.put(id, vol); + onVolumeCreatedLocked(vol); + break; + } + case VoldResponseCode.VOLUME_STATE_CHANGED: { + if (cooked.length != 3) break; + final VolumeInfo vol = mVolumes.get(cooked[1]); + if (vol != null) { + final int oldState = vol.state; + final int newState = Integer.parseInt(cooked[2]); + vol.state = newState; + onVolumeStateChangedLocked(vol.clone(), oldState, newState); + } + break; } - - if (code == VoldResponseCode.VolumeDiskInserted) { - new Thread("MountService#VolumeDiskInserted") { - @Override - public void run() { - try { - int rc; - if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { - Slog.w(TAG, String.format("Insertion mount failed (%d)", rc)); - } - } catch (Exception ex) { - Slog.w(TAG, "Failed to mount media on insertion", ex); - } + case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: { + if (cooked.length != 3) break; + final VolumeInfo vol = mVolumes.get(cooked[1]); + if (vol != null) { + vol.fsType = cooked[2]; + } + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + break; + } + case VoldResponseCode.VOLUME_FS_UUID_CHANGED: { + if (cooked.length != 3) break; + final VolumeInfo vol = mVolumes.get(cooked[1]); + if (vol != null) { + vol.fsUuid = cooked[2]; + } + refreshMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + break; + } + case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: { + final VolumeInfo vol = mVolumes.get(cooked[1]); + if (vol != null) { + final StringBuilder builder = new StringBuilder(); + for (int i = 2; i < cooked.length; i++) { + builder.append(cooked[i]).append(' '); } - }.start(); - } else if (code == VoldResponseCode.VolumeDiskRemoved) { - /* - * This event gets trumped if we're already in BAD_REMOVAL state - */ - if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) { - return true; + vol.fsLabel = builder.toString().trim(); } - /* Send the media unmounted event first */ - if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); - updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); - sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); - - if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed"); - updatePublicVolumeState(volume, Environment.MEDIA_REMOVED); - action = Intent.ACTION_MEDIA_REMOVED; - } else if (code == VoldResponseCode.VolumeBadRemoval) { - if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); - /* Send the media unmounted event first */ - updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); - sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); - - if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal"); - updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL); - action = Intent.ACTION_MEDIA_BAD_REMOVAL; - } else if (code == VoldResponseCode.FstrimCompleted) { - EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime()); - } else { - Slog.e(TAG, String.format("Unknown code {%d}", code)); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + break; + } + case VoldResponseCode.VOLUME_PATH_CHANGED: { + if (cooked.length != 3) break; + final VolumeInfo vol = mVolumes.get(cooked[1]); + if (vol != null) { + vol.path = cooked[2]; + } + break; + } + case VoldResponseCode.VOLUME_DESTROYED: { + if (cooked.length != 2) break; + mVolumes.remove(cooked[1]); + break; } - if (action != null) { - sendStorageIntent(action, volume, UserHandle.ALL); + case VoldResponseCode.FstrimCompleted: { + EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime()); + break; + } + default: { + Slog.d(TAG, "Unhandled vold event " + code); } - } else { - return false; } return true; } - private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { - final StorageVolume volume; - final String state; - synchronized (mVolumesLock) { - volume = mVolumesByPath.get(path); - state = getVolumeState(path); - } - - if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state); - - String action = null; - - if (oldState == VolumeState.Shared && newState != oldState) { - if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent"); - sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL); - } - - if (newState == VolumeState.Init) { - } else if (newState == VolumeState.NoMedia) { - // NoMedia is handled via Disk Remove events - } else if (newState == VolumeState.Idle) { - /* - * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or - * if we're in the process of enabling UMS - */ - if (!state.equals( - Environment.MEDIA_BAD_REMOVAL) && !state.equals( - Environment.MEDIA_NOFS) && !state.equals( - Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) { - if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable"); - updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); - action = Intent.ACTION_MEDIA_UNMOUNTED; - } - } else if (newState == VolumeState.Pending) { - } else if (newState == VolumeState.Checking) { - if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking"); - updatePublicVolumeState(volume, Environment.MEDIA_CHECKING); - action = Intent.ACTION_MEDIA_CHECKING; - } else if (newState == VolumeState.Mounted) { - if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted"); - updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); - action = Intent.ACTION_MEDIA_MOUNTED; - } else if (newState == VolumeState.Unmounting) { - action = Intent.ACTION_MEDIA_EJECT; - } else if (newState == VolumeState.Formatting) { - } else if (newState == VolumeState.Shared) { - if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted"); - /* Send the media unmounted event first */ - updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); - sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); - - if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared"); - updatePublicVolumeState(volume, Environment.MEDIA_SHARED); - action = Intent.ACTION_MEDIA_SHARED; - if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent"); - } else if (newState == VolumeState.SharedMnt) { - Slog.e(TAG, "Live shared mounts not supported yet!"); - return; - } else { - Slog.e(TAG, "Unhandled VolumeState {" + newState + "}"); - } - - if (action != null) { - sendStorageIntent(action, volume, UserHandle.ALL); - } - } - - private int doMountVolume(String path) { - int rc = StorageResultCode.OperationSucceeded; - - final StorageVolume volume; - synchronized (mVolumesLock) { - volume = mVolumesByPath.get(path); - } + private void onDiskScannedLocked(DiskInfo disk) { + final Intent intent = new Intent(DiskInfo.ACTION_DISK_SCANNED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.WRITE_MEDIA_STORAGE); - if (!volume.isEmulated() && hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) { - Slog.w(TAG, "User has restriction DISALLOW_MOUNT_PHYSICAL_MEDIA; cannot mount volume."); - return StorageResultCode.OperationFailedInternalError; + final CountDownLatch latch = mDiskScanLatches.remove(disk.id); + if (latch != null) { + latch.countDown(); } - if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); - try { - mConnector.execute("volume", "mount", path); - } catch (NativeDaemonConnectorException e) { - /* - * Mount failed for some reason - */ - String action = null; - int code = e.getCode(); - if (code == VoldResponseCode.OpFailedNoMedia) { - /* - * Attempt to mount but no media inserted - */ - rc = StorageResultCode.OperationFailedNoMedia; - } else if (code == VoldResponseCode.OpFailedMediaBlank) { - if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs"); - /* - * Media is blank or does not contain a supported filesystem - */ - updatePublicVolumeState(volume, Environment.MEDIA_NOFS); - action = Intent.ACTION_MEDIA_NOFS; - rc = StorageResultCode.OperationFailedMediaBlank; - } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { - if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt"); - /* - * Volume consistency check failed - */ - updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE); - action = Intent.ACTION_MEDIA_UNMOUNTABLE; - rc = StorageResultCode.OperationFailedMediaCorrupt; - } else { - rc = StorageResultCode.OperationFailedInternalError; - } - - /* - * Send broadcast intent (if required for the failure) - */ - if (action != null) { - sendStorageIntent(action, volume, UserHandle.ALL); + int volumeCount = 0; + for (int i = 0; i < mVolumes.size(); i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + if (Objects.equals(disk.id, vol.getDiskId())) { + volumeCount++; } } - return rc; + mCallbacks.notifyDiskScanned(disk, volumeCount); } - /* - * If force is not set, we do not unmount if there are - * processes holding references to the volume about to be unmounted. - * If force is set, all the processes holding references need to be - * killed via the ActivityManager before actually unmounting the volume. - * This might even take a while and might be retried after timed delays - * to make sure we dont end up in an instable state and kill some core - * processes. - * If removeEncryption is set, force is implied, and the system will remove any encryption - * mapping set on the volume when unmounting. - */ - private int doUnmountVolume(String path, boolean force, boolean removeEncryption) { - if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) { - return VoldResponseCode.OpFailedVolNotMounted; - } - - /* - * Force a GC to make sure AssetManagers in other threads of the - * system_server are cleaned up. We have to do this since AssetManager - * instances are kept as a WeakReference and it's possible we have files - * open on the external storage. - */ - Runtime.getRuntime().gc(); + private void onVolumeCreatedLocked(VolumeInfo vol) { + final boolean primaryPhysical = SystemProperties.getBoolean( + StorageManager.PROP_PRIMARY_PHYSICAL, false); + // TODO: enable switching to another emulated primary + if (VolumeInfo.ID_EMULATED_INTERNAL.equals(vol.id) && !primaryPhysical) { + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY; + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE; + mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget(); - // Redundant probably. But no harm in updating state again. - mPms.updateExternalMediaStatus(false, false); - try { - final Command cmd = new Command("volume", "unmount", path); - if (removeEncryption) { - cmd.appendArg("force_and_revert"); - } else if (force) { - cmd.appendArg("force"); - } - mConnector.execute(cmd); - // We unmounted the volume. None of the asec containers are available now. - synchronized (mAsecMountSet) { - mAsecMountSet.clear(); - } - return StorageResultCode.OperationSucceeded; - } catch (NativeDaemonConnectorException e) { - // Don't worry about mismatch in PackageManager since the - // call back will handle the status changes any way. - int code = e.getCode(); - if (code == VoldResponseCode.OpFailedVolNotMounted) { - return StorageResultCode.OperationFailedStorageNotMounted; - } else if (code == VoldResponseCode.OpFailedStorageBusy) { - return StorageResultCode.OperationFailedStorageBusy; - } else { - return StorageResultCode.OperationFailedInternalError; + } else if (vol.type == VolumeInfo.TYPE_PUBLIC) { + if (primaryPhysical) { + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY; + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE; } - } - } - private int doFormatVolume(String path) { - try { - mConnector.execute("volume", "format", path); - return StorageResultCode.OperationSucceeded; - } catch (NativeDaemonConnectorException e) { - int code = e.getCode(); - if (code == VoldResponseCode.OpFailedNoMedia) { - return StorageResultCode.OperationFailedNoMedia; - } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { - return StorageResultCode.OperationFailedMediaCorrupt; - } else { - return StorageResultCode.OperationFailedInternalError; + // Adoptable public disks are visible to apps, since they meet + // public API requirement of being in a stable location. + final DiskInfo disk = mDisks.get(vol.getDiskId()); + if (disk != null && disk.isAdoptable()) { + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE; } - } - } - private boolean doGetVolumeShared(String path, String method) { - final NativeDaemonEvent event; - try { - event = mConnector.execute("volume", "shared", path, method); - } catch (NativeDaemonConnectorException ex) { - Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); - return false; - } + vol.mountUserId = UserHandle.USER_OWNER; + mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget(); + + } else if (vol.type == VolumeInfo.TYPE_PRIVATE) { + mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget(); - if (event.getCode() == VoldResponseCode.ShareEnabledResult) { - return event.getMessage().endsWith("enabled"); } else { - return false; + Slog.d(TAG, "Skipping automatic mounting of " + vol); } } - private void notifyShareAvailabilityChange(final boolean avail) { - synchronized (mListeners) { - mUmsAvailable = avail; - for (int i = mListeners.size() -1; i >= 0; i--) { - MountServiceBinderListener bl = mListeners.get(i); - try { - bl.mListener.onUsbMassStorageConnectionChanged(avail); - } catch (RemoteException rex) { - Slog.e(TAG, "Listener dead"); - mListeners.remove(i); - } catch (Exception ex) { - Slog.e(TAG, "Listener failed", ex); - } - } + private boolean isBroadcastWorthy(VolumeInfo vol) { + switch (vol.getType()) { + case VolumeInfo.TYPE_PUBLIC: + case VolumeInfo.TYPE_EMULATED: + break; + default: + return false; } - if (mSystemReady == true) { - sendUmsIntent(avail); - } else { - mSendUmsConnectedOnBoot = avail; + switch (vol.getState()) { + case VolumeInfo.STATE_MOUNTED: + case VolumeInfo.STATE_MOUNTED_READ_ONLY: + case VolumeInfo.STATE_EJECTING: + case VolumeInfo.STATE_UNMOUNTED: + break; + default: + return false; } - final StorageVolume primary = getPrimaryPhysicalVolume(); - if (avail == false && primary != null - && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) { - final String path = primary.getPath(); - /* - * USB mass storage disconnected while enabled - */ - new Thread("MountService#AvailabilityChange") { - @Override - public void run() { - try { - int rc; - Slog.w(TAG, "Disabling UMS after cable disconnect"); - doShareUnshareVolume(path, "ums", false); - if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) { - Slog.e(TAG, String.format( - "Failed to remount {%s} on UMS enabled-disconnect (%d)", - path, rc)); - } - } catch (Exception ex) { - Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex); - } - } - }.start(); - } - } - - private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) { - final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath())); - intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - Slog.d(TAG, "sendStorageIntent " + intent + " to " + user); - mContext.sendBroadcastAsUser(intent, user); - } - - private void sendUmsIntent(boolean c) { - mContext.sendBroadcastAsUser( - new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)), - UserHandle.ALL); - } - - private void validatePermission(String perm) { - if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException(String.format("Requires %s permission", perm)); - } + return true; } - private boolean hasUserRestriction(String restriction) { - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - return um.hasUserRestriction(restriction, Binder.getCallingUserHandle()); - } + private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) { + mCallbacks.notifyVolumeStateChanged(vol, oldState, newState); - private void validateUserRestriction(String restriction) { - if (hasUserRestriction(restriction)) { - throw new SecurityException("User has restriction " + restriction); + if (isBroadcastWorthy(vol)) { + final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.WRITE_MEDIA_STORAGE); } - } - // Storage list XML tags - private static final String TAG_STORAGE_LIST = "StorageList"; - private static final String TAG_STORAGE = "storage"; + final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState); + final String newStateEnv = VolumeInfo.getEnvironmentForState(newState); - private void readStorageListLocked() { - mVolumes.clear(); - mVolumeStates.clear(); - - Resources resources = mContext.getResources(); - - int id = com.android.internal.R.xml.storage_list; - XmlResourceParser parser = resources.getXml(id); - AttributeSet attrs = Xml.asAttributeSet(parser); - - try { - XmlUtils.beginDocument(parser, TAG_STORAGE_LIST); - while (true) { - XmlUtils.nextElement(parser); - - String element = parser.getName(); - if (element == null) break; - - if (TAG_STORAGE.equals(element)) { - TypedArray a = resources.obtainAttributes(attrs, - com.android.internal.R.styleable.Storage); - - String path = a.getString( - com.android.internal.R.styleable.Storage_mountPoint); - int descriptionId = a.getResourceId( - com.android.internal.R.styleable.Storage_storageDescription, -1); - CharSequence description = a.getText( - com.android.internal.R.styleable.Storage_storageDescription); - boolean primary = a.getBoolean( - com.android.internal.R.styleable.Storage_primary, false); - boolean removable = a.getBoolean( - com.android.internal.R.styleable.Storage_removable, false); - boolean emulated = a.getBoolean( - com.android.internal.R.styleable.Storage_emulated, false); - int mtpReserve = a.getInt( - com.android.internal.R.styleable.Storage_mtpReserve, 0); - boolean allowMassStorage = a.getBoolean( - com.android.internal.R.styleable.Storage_allowMassStorage, false); - // resource parser does not support longs, so XML value is in megabytes - long maxFileSize = a.getInt( - com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L; - - Slog.d(TAG, "got storage path: " + path + " description: " + description + - " primary: " + primary + " removable: " + removable + - " emulated: " + emulated + " mtpReserve: " + mtpReserve + - " allowMassStorage: " + allowMassStorage + - " maxFileSize: " + maxFileSize); - - if (emulated) { - // For devices with emulated storage, we create separate - // volumes for each known user. - mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false, - true, mtpReserve, false, maxFileSize, null); - - final UserManagerService userManager = UserManagerService.getInstance(); - for (UserInfo user : userManager.getUsers(false)) { - createEmulatedVolumeForUserLocked(user.getUserHandle()); - } - - } else { - if (path == null || description == null) { - Slog.e(TAG, "Missing storage path or description in readStorageList"); - } else { - final StorageVolume volume = new StorageVolume(new File(path), - descriptionId, primary, removable, emulated, mtpReserve, - allowMassStorage, maxFileSize, null); - addVolumeLocked(volume); - - // Until we hear otherwise, treat as unmounted - mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED); - volume.setState(Environment.MEDIA_UNMOUNTED); - } - } + if (!Objects.equals(oldStateEnv, newStateEnv)) { + // Kick state changed event towards all started users. Any users + // started after this point will trigger additional + // user-specific broadcasts. + for (int userId : mStartedUsers) { + if (vol.isVisibleToUser(userId)) { + final StorageVolume userVol = vol.buildStorageVolume(mContext, userId); + mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget(); - a.recycle(); + mCallbacks.notifyStorageStateChanged(userVol.getPath(), oldStateEnv, + newStateEnv); } } - } catch (XmlPullParserException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - // Compute storage ID for each physical volume; emulated storage is - // always 0 when defined. - int index = isExternalStorageEmulated() ? 1 : 0; - for (StorageVolume volume : mVolumes) { - if (!volume.isEmulated()) { - volume.setStorageId(index++); - } - } - parser.close(); - } - } - - /** - * Create and add new {@link StorageVolume} for given {@link UserHandle} - * using {@link #mEmulatedTemplate} as template. - */ - private void createEmulatedVolumeForUserLocked(UserHandle user) { - if (mEmulatedTemplate == null) { - throw new IllegalStateException("Missing emulated volume multi-user template"); } - final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier()); - final File path = userEnv.getExternalStorageDirectory(); - final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user); - volume.setStorageId(0); - addVolumeLocked(volume); - - if (mSystemReady) { - updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); - } else { - // Place stub status for early callers to find - mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED); - volume.setState(Environment.MEDIA_MOUNTED); + if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.state == VolumeInfo.STATE_EJECTING) { + // TODO: this should eventually be handled by new ObbVolume state changes + /* + * Some OBBs might have been unmounted when this volume was + * unmounted, so send a message to the handler to let it know to + * remove those from the list of mounted OBBS. + */ + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( + OBB_FLUSH_MOUNT_STATE, vol.path)); } } - private void addVolumeLocked(StorageVolume volume) { - Slog.d(TAG, "addVolumeLocked() " + volume); - mVolumes.add(volume); - final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume); - if (existing != null) { - throw new IllegalStateException( - "Volume at " + volume.getPath() + " already exists: " + existing); + /** + * Refresh latest metadata into any currently active {@link VolumeInfo}. + */ + private void refreshMetadataLocked() { + final int size = mVolumes.size(); + for (int i = 0; i < size; i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + final VolumeMetadata meta = mMetadata.get(vol.fsUuid); + + if (meta != null) { + vol.nickname = meta.nickname; + vol.userFlags = meta.userFlags; + } else { + vol.nickname = null; + vol.userFlags = 0; + } } } - private void removeVolumeLocked(StorageVolume volume) { - Slog.d(TAG, "removeVolumeLocked() " + volume); - mVolumes.remove(volume); - mVolumesByPath.remove(volume.getPath()); - mVolumeStates.remove(volume.getPath()); + private void enforcePermission(String perm) { + mContext.enforceCallingOrSelfPermission(perm, perm); } - private StorageVolume getPrimaryPhysicalVolume() { - synchronized (mVolumesLock) { - for (StorageVolume volume : mVolumes) { - if (volume.isPrimary() && !volume.isEmulated()) { - return volume; - } - } + private void enforceUserRestriction(String restriction) { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (um.hasUserRestriction(restriction, Binder.getCallingUserHandle())) { + throw new SecurityException("User has restriction " + restriction); } - return null; } /** @@ -1479,10 +1105,7 @@ class MountService extends IMountService.Stub sSelf = this; mContext = context; - - synchronized (mVolumesLock) { - readStorageListLocked(); - } + mCallbacks = new Callbacks(FgThread.get().getLooper()); // XXX: This will go away soon in favor of IMountServiceObserver mPms = (PackageManagerService) ServiceManager.getService("package"); @@ -1491,19 +1114,6 @@ class MountService extends IMountService.Stub hthread.start(); mHandler = new MountServiceHandler(hthread.getLooper()); - // Watch for user changes - final IntentFilter userFilter = new IntentFilter(); - userFilter.addAction(Intent.ACTION_USER_ADDED); - userFilter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); - - // Watch for USB changes on primary volume - final StorageVolume primary = getPrimaryPhysicalVolume(); - if (primary != null && primary.allowMassStorage()) { - mContext.registerReceiver( - mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler); - } - // Add OBB Action Handler to MountService thread. mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper()); @@ -1523,6 +1133,13 @@ class MountService extends IMountService.Stub mLastMaintenance = mLastMaintenanceFile.lastModified(); } + mMetadataFile = new AtomicFile( + new File(Environment.getSystemSecureDirectory(), "storage.xml")); + + synchronized (mLock) { + readMetadataLocked(); + } + /* * Create the connection to vold with a maximum queue of twice the * amount of containers we'd ever expect to have. This keeps an @@ -1530,6 +1147,7 @@ class MountService extends IMountService.Stub */ mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25, null); + mConnector.setDebug(true); Thread thread = new Thread(mConnector, VOLD_TAG); thread.start(); @@ -1540,234 +1158,292 @@ class MountService extends IMountService.Stub } } - public void systemReady() { + private void systemReady() { mSystemReady = true; mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); } + private void readMetadataLocked() { + mMetadata.clear(); + + FileInputStream fis = null; + try { + fis = mMetadataFile.openRead(); + final XmlPullParser in = Xml.newPullParser(); + in.setInput(fis, null); + + int type; + while ((type = in.next()) != END_DOCUMENT) { + if (type == START_TAG) { + final String tag = in.getName(); + if (TAG_VOLUMES.equals(tag)) { + final int version = readIntAttribute(in, ATTR_VERSION, VERSION_INIT); + if (version >= VERSION_ADD_PRIMARY) { + mPrimaryStorageUuid = readStringAttribute(in, + ATTR_PRIMARY_STORAGE_UUID); + } else { + if (SystemProperties.getBoolean(StorageManager.PROP_PRIMARY_PHYSICAL, + false)) { + mPrimaryStorageUuid = StorageManager.UUID_PRIMARY_PHYSICAL; + } else { + mPrimaryStorageUuid = StorageManager.UUID_PRIVATE_INTERNAL; + } + } + + } else if (TAG_VOLUME.equals(tag)) { + final VolumeMetadata meta = VolumeMetadata.read(in); + mMetadata.put(meta.fsUuid, meta); + } + } + } + } catch (FileNotFoundException e) { + // Missing metadata is okay, probably first boot + } catch (IOException e) { + Slog.wtf(TAG, "Failed reading metadata", e); + } catch (XmlPullParserException e) { + Slog.wtf(TAG, "Failed reading metadata", e); + } finally { + IoUtils.closeQuietly(fis); + } + } + + private void writeMetadataLocked() { + FileOutputStream fos = null; + try { + fos = mMetadataFile.startWrite(); + + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + out.startDocument(null, true); + out.startTag(null, TAG_VOLUMES); + writeIntAttribute(out, ATTR_VERSION, VERSION_ADD_PRIMARY); + writeStringAttribute(out, ATTR_PRIMARY_STORAGE_UUID, mPrimaryStorageUuid); + final int size = mMetadata.size(); + for (int i = 0; i < size; i++) { + final VolumeMetadata meta = mMetadata.valueAt(i); + VolumeMetadata.write(out, meta); + } + out.endTag(null, TAG_VOLUMES); + out.endDocument(); + + mMetadataFile.finishWrite(fos); + } catch (IOException e) { + if (fos != null) { + mMetadataFile.failWrite(fos); + } + } + } + /** * Exposed API calls below here */ + @Override public void registerListener(IMountServiceListener listener) { - synchronized (mListeners) { - MountServiceBinderListener bl = new MountServiceBinderListener(listener); - try { - listener.asBinder().linkToDeath(bl, 0); - mListeners.add(bl); - } catch (RemoteException rex) { - Slog.e(TAG, "Failed to link to listener death"); - } - } + mCallbacks.register(listener); } + @Override public void unregisterListener(IMountServiceListener listener) { - synchronized (mListeners) { - for(MountServiceBinderListener bl : mListeners) { - if (bl.mListener.asBinder() == listener.asBinder()) { - mListeners.remove(mListeners.indexOf(bl)); - listener.asBinder().unlinkToDeath(bl, 0); - return; - } - } - } + mCallbacks.unregister(listener); } + @Override public void shutdown(final IMountShutdownObserver observer) { - validatePermission(android.Manifest.permission.SHUTDOWN); + enforcePermission(android.Manifest.permission.SHUTDOWN); Slog.i(TAG, "Shutting down"); - synchronized (mVolumesLock) { - // Get all volumes to be unmounted. - MountShutdownLatch mountShutdownLatch = new MountShutdownLatch(observer, - mVolumeStates.size()); - - for (String path : mVolumeStates.keySet()) { - String state = mVolumeStates.get(path); - - if (state.equals(Environment.MEDIA_SHARED)) { - /* - * If the media is currently shared, unshare it. - * XXX: This is still dangerous!. We should not - * be rebooting at *all* if UMS is enabled, since - * the UMS host could have dirty FAT cache entries - * yet to flush. - */ - setUsbMassStorageEnabled(false); - } else if (state.equals(Environment.MEDIA_CHECKING)) { - /* - * If the media is being checked, then we need to wait for - * it to complete before being able to proceed. - */ - // XXX: @hackbod - Should we disable the ANR timer here? - int retries = 30; - while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) { - try { - Thread.sleep(1000); - } catch (InterruptedException iex) { - Slog.e(TAG, "Interrupted while waiting for media", iex); - break; - } - state = Environment.getExternalStorageState(); - } - if (retries == 0) { - Slog.e(TAG, "Timed out waiting for media to check"); - } - } + mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget(); + } - if (state.equals(Environment.MEDIA_MOUNTED)) { - // Post a unmount message. - ShutdownCallBack ucb = new ShutdownCallBack(path, mountShutdownLatch); - mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); - } else if (observer != null) { - /* - * Count down, since nothing will be done. The observer will be - * notified when we are done so shutdown sequence can continue. - */ - mountShutdownLatch.countDown(); - Slog.i(TAG, "Unmount completed: " + path + - ", result code: " + StorageResultCode.OperationSucceeded); - } - } - } + @Override + public boolean isUsbMassStorageConnected() { + throw new UnsupportedOperationException(); } - private boolean getUmsEnabling() { - synchronized (mListeners) { - return mUmsEnabling; - } + @Override + public void setUsbMassStorageEnabled(boolean enable) { + throw new UnsupportedOperationException(); } - private void setUmsEnabling(boolean enable) { - synchronized (mListeners) { - mUmsEnabling = enable; - } + @Override + public boolean isUsbMassStorageEnabled() { + throw new UnsupportedOperationException(); } - public boolean isUsbMassStorageConnected() { + @Override + public String getVolumeState(String mountPoint) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isExternalStorageEmulated() { + throw new UnsupportedOperationException(); + } + + @Override + public int mountVolume(String path) { + mount(findVolumeIdForPath(path)); + return 0; + } + + @Override + public void unmountVolume(String path, boolean force, boolean removeEncryption) { + unmount(findVolumeIdForPath(path)); + } + + @Override + public int formatVolume(String path) { + format(findVolumeIdForPath(path)); + return 0; + } + + @Override + public void mount(String volId) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); - if (getUmsEnabling()) { - return true; + final VolumeInfo vol = findVolumeById(volId); + if (vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_PRIVATE) { + enforceUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA); } - synchronized (mListeners) { - return mUmsAvailable; + try { + mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); } } - public void setUsbMassStorageEnabled(boolean enable) { + @Override + public void unmount(String volId) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); - validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); - validateUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER); - - final StorageVolume primary = getPrimaryPhysicalVolume(); - if (primary == null) return; - // TODO: Add support for multiple share methods + final VolumeInfo vol = findVolumeById(volId); - /* - * If the volume is mounted and we're enabling then unmount it - */ - String path = primary.getPath(); - String vs = getVolumeState(path); - String method = "ums"; - if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { - // Override for isUsbMassStorageEnabled() - setUmsEnabling(enable); - UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true); - mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb)); - // Clear override - setUmsEnabling(false); - } - /* - * If we disabled UMS then mount the volume - */ - if (!enable) { - doShareUnshareVolume(path, method, enable); - if (doMountVolume(path) != StorageResultCode.OperationSucceeded) { - Slog.e(TAG, "Failed to remount " + path + - " after disabling share method " + method); - /* - * Even though the mount failed, the unshare didn't so don't indicate an error. - * The mountVolume() call will have set the storage state and sent the necessary - * broadcasts. - */ + // TODO: expand PMS to know about multiple volumes + if (vol.isPrimary()) { + synchronized (mUnmountLock) { + mUnmountSignal = new CountDownLatch(1); + mPms.updateExternalMediaStatus(false, true); + waitForLatch(mUnmountSignal, "mUnmountSignal"); + mUnmountSignal = null; } } + + try { + mConnector.execute("volume", "unmount", vol.id); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } } - public boolean isUsbMassStorageEnabled() { + @Override + public void format(String volId) { + enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); waitForReady(); - final StorageVolume primary = getPrimaryPhysicalVolume(); - if (primary != null) { - return doGetVolumeShared(primary.getPath(), "ums"); - } else { - return false; + final VolumeInfo vol = findVolumeById(volId); + try { + mConnector.execute("volume", "format", vol.id); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); } } - /** - * @return state of the volume at the specified mount point - */ - public String getVolumeState(String mountPoint) { - synchronized (mVolumesLock) { - String state = mVolumeStates.get(mountPoint); - if (state == null) { - Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); - if (SystemProperties.get("vold.encrypt_progress").length() != 0) { - state = Environment.MEDIA_REMOVED; - } else { - throw new IllegalArgumentException(); - } - } + @Override + public void partitionPublic(String diskId) { + enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); + waitForReady(); - return state; + final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + try { + mConnector.execute("volume", "partition", diskId, "public"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); } + waitForLatch(latch, "partitionPublic"); } @Override - public boolean isExternalStorageEmulated() { - return mEmulatedTemplate != null; + public void partitionPrivate(String diskId) { + enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); + waitForReady(); + + final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + try { + mConnector.execute("volume", "partition", diskId, "private"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + waitForLatch(latch, "partitionPrivate"); } - public int mountVolume(String path) { - validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + @Override + public void partitionMixed(String diskId, int ratio) { + enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); waitForReady(); - return doMountVolume(path); + + final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + try { + mConnector.execute("volume", "partition", diskId, "mixed", ratio); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + waitForLatch(latch, "partitionMixed"); } - public void unmountVolume(String path, boolean force, boolean removeEncryption) { - validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + @Override + public void setVolumeNickname(String volId, String nickname) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); - String volState = getVolumeState(path); - if (DEBUG_UNMOUNT) { - Slog.i(TAG, "Unmounting " + path - + " force = " + force - + " removeEncryption = " + removeEncryption); - } - if (Environment.MEDIA_UNMOUNTED.equals(volState) || - Environment.MEDIA_REMOVED.equals(volState) || - Environment.MEDIA_SHARED.equals(volState) || - Environment.MEDIA_UNMOUNTABLE.equals(volState)) { - // Media already unmounted or cannot be unmounted. - // TODO return valid return code when adding observer call back. - return; + synchronized (mLock) { + final VolumeInfo vol = findVolumeById(volId); + final VolumeMetadata meta = findOrCreateMetadataLocked(vol); + meta.nickname = nickname; + refreshMetadataLocked(); + writeMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); } - UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption); - mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb)); } - public int formatVolume(String path) { - validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); + @Override + public void setVolumeUserFlags(String volId, int flags, int mask) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); - return doFormatVolume(path); + synchronized (mLock) { + final VolumeInfo vol = findVolumeById(volId); + final VolumeMetadata meta = findOrCreateMetadataLocked(vol); + meta.userFlags = (meta.userFlags & ~mask) | (flags & mask); + refreshMetadataLocked(); + writeMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + } + } + + @Override + public String getPrimaryStorageUuid() throws RemoteException { + synchronized (mLock) { + return mPrimaryStorageUuid; + } + } + + @Override + public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException { + synchronized (mLock) { + Slog.d(TAG, "Changing primary storage UUID to " + volumeUuid); + mPrimaryStorageUuid = volumeUuid; + writeMetadataLocked(); + + // TODO: reevaluate all volumes we know about! + } } + @Override public int[] getStorageUsers(String path) { - validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); try { final String[] r = NativeDaemonEvent.filterMessageList( @@ -1793,22 +1469,21 @@ class MountService extends IMountService.Stub } private void warnOnNotMounted() { - final StorageVolume primary = getPrimaryPhysicalVolume(); - if (primary != null) { - boolean mounted = false; - try { - mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath())); - } catch (IllegalArgumentException e) { - } - - if (!mounted) { - Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); + synchronized (mLock) { + for (int i = 0; i < mVolumes.size(); i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + if (vol.isPrimary() && vol.isMountedWritable()) { + // Cool beans, we have a mounted primary volume + return; + } } } + + Slog.w(TAG, "No primary storage mounted!"); } public String[] getSecureContainerList() { - validatePermission(android.Manifest.permission.ASEC_ACCESS); + enforcePermission(android.Manifest.permission.ASEC_ACCESS); waitForReady(); warnOnNotMounted(); @@ -1822,7 +1497,7 @@ class MountService extends IMountService.Stub public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid, boolean external) { - validatePermission(android.Manifest.permission.ASEC_CREATE); + enforcePermission(android.Manifest.permission.ASEC_CREATE); waitForReady(); warnOnNotMounted(); @@ -1844,7 +1519,7 @@ class MountService extends IMountService.Stub @Override public int resizeSecureContainer(String id, int sizeMb, String key) { - validatePermission(android.Manifest.permission.ASEC_CREATE); + enforcePermission(android.Manifest.permission.ASEC_CREATE); waitForReady(); warnOnNotMounted(); @@ -1858,7 +1533,7 @@ class MountService extends IMountService.Stub } public int finalizeSecureContainer(String id) { - validatePermission(android.Manifest.permission.ASEC_CREATE); + enforcePermission(android.Manifest.permission.ASEC_CREATE); warnOnNotMounted(); int rc = StorageResultCode.OperationSucceeded; @@ -1875,7 +1550,7 @@ class MountService extends IMountService.Stub } public int fixPermissionsSecureContainer(String id, int gid, String filename) { - validatePermission(android.Manifest.permission.ASEC_CREATE); + enforcePermission(android.Manifest.permission.ASEC_CREATE); warnOnNotMounted(); int rc = StorageResultCode.OperationSucceeded; @@ -1892,7 +1567,7 @@ class MountService extends IMountService.Stub } public int destroySecureContainer(String id, boolean force) { - validatePermission(android.Manifest.permission.ASEC_DESTROY); + enforcePermission(android.Manifest.permission.ASEC_DESTROY); waitForReady(); warnOnNotMounted(); @@ -1932,7 +1607,7 @@ class MountService extends IMountService.Stub } public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) { - validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); + enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); waitForReady(); warnOnNotMounted(); @@ -1962,7 +1637,7 @@ class MountService extends IMountService.Stub } public int unmountSecureContainer(String id, boolean force) { - validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); + enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT); waitForReady(); warnOnNotMounted(); @@ -2005,7 +1680,7 @@ class MountService extends IMountService.Stub } public boolean isSecureContainerMounted(String id) { - validatePermission(android.Manifest.permission.ASEC_ACCESS); + enforcePermission(android.Manifest.permission.ASEC_ACCESS); waitForReady(); warnOnNotMounted(); @@ -2015,7 +1690,7 @@ class MountService extends IMountService.Stub } public int renameSecureContainer(String oldId, String newId) { - validatePermission(android.Manifest.permission.ASEC_RENAME); + enforcePermission(android.Manifest.permission.ASEC_RENAME); waitForReady(); warnOnNotMounted(); @@ -2040,7 +1715,7 @@ class MountService extends IMountService.Stub } public String getSecureContainerPath(String id) { - validatePermission(android.Manifest.permission.ASEC_ACCESS); + enforcePermission(android.Manifest.permission.ASEC_ACCESS); waitForReady(); warnOnNotMounted(); @@ -2061,7 +1736,7 @@ class MountService extends IMountService.Stub } public String getSecureContainerFilesystemPath(String id) { - validatePermission(android.Manifest.permission.ASEC_ACCESS); + enforcePermission(android.Manifest.permission.ASEC_ACCESS); waitForReady(); warnOnNotMounted(); @@ -2081,8 +1756,13 @@ class MountService extends IMountService.Stub } } + @Override public void finishMediaUpdate() { - mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE); + if (mUnmountSignal != null) { + mUnmountSignal.countDown(); + } else { + Slog.w(TAG, "Odd, nobody asked to unmount?"); + } } private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) { @@ -2204,25 +1884,21 @@ class MountService extends IMountService.Stub } } - private String toHex(String password) { + private static String toHex(String password) { if (password == null) { - return new String(); + return ""; } byte[] bytes = password.getBytes(StandardCharsets.UTF_8); - return new String(Hex.encodeHex(bytes)); + return new String(HexEncoding.encode(bytes)); } - private String fromHex(String hexPassword) { + private static String fromHex(String hexPassword) throws IllegalArgumentException { if (hexPassword == null) { return null; } - try { - byte[] bytes = Hex.decodeHex(hexPassword.toCharArray()); - return new String(bytes, StandardCharsets.UTF_8); - } catch (DecoderException e) { - return null; - } + final byte[] bytes = HexEncoding.decode(hexPassword.toCharArray(), false); + return new String(bytes, StandardCharsets.UTF_8); } @Override @@ -2424,9 +2100,16 @@ class MountService extends IMountService.Stub final NativeDaemonEvent event; try { event = mConnector.execute("cryptfs", "getpw"); + if ("-1".equals(event.getMessage())) { + // -1 equals no password + return null; + } return fromHex(event.getMessage()); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Invalid response to getPassword"); + return null; } } @@ -2454,108 +2137,106 @@ class MountService extends IMountService.Stub Context.APP_OPS_SERVICE); appOps.checkPackage(Binder.getCallingUid(), callingPkg); + File appFile = null; try { - appPath = new File(appPath).getCanonicalPath(); + appFile = new File(appPath).getCanonicalFile(); } 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(); + if (FileUtils.contains(userEnv.buildExternalStorageAppDataDirs(callingPkg), appFile) || + FileUtils.contains(userEnv.buildExternalStorageAppObbDirs(callingPkg), appFile) || + FileUtils.contains(userEnv.buildExternalStorageAppMediaDirs(callingPkg), appFile)) { + appPath = appFile.getAbsolutePath(); + if (!appPath.endsWith("/")) { + appPath = appPath + "/"; } - } - voldPath = maybeTranslatePathForVold(appPath, - userEnv.buildExternalStorageAppObbDirs(callingPkg), - userEnv.buildExternalStorageAppObbDirsForVold(callingPkg)); - if (voldPath != null) { try { - mConnector.execute("volume", "mkdirs", voldPath); + mConnector.execute("volume", "mkdirs", appPath); return 0; } catch (NativeDaemonConnectorException e) { return e.getCode(); } } - voldPath = maybeTranslatePathForVold(appPath, - userEnv.buildExternalStorageAppMediaDirs(callingPkg), - userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg)); - if (voldPath != null) { - try { - mConnector.execute("volume", "mkdirs", voldPath); - return 0; - } catch (NativeDaemonConnectorException e) { - return e.getCode(); + throw new SecurityException("Invalid mkdirs path: " + appFile); + } + + @Override + public StorageVolume[] getVolumeList(int userId) { + final ArrayList<StorageVolume> res = new ArrayList<>(); + boolean foundPrimary = false; + + synchronized (mLock) { + for (int i = 0; i < mVolumes.size(); i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + if (vol.isVisibleToUser(userId)) { + final StorageVolume userVol = vol.buildStorageVolume(mContext, userId); + if (vol.isPrimary()) { + res.add(0, userVol); + foundPrimary = true; + } else { + res.add(userVol); + } + } } } - throw new SecurityException("Invalid mkdirs path: " + appPath); + if (!foundPrimary) { + Log.w(TAG, "No primary storage defined yet; hacking together a stub"); + + final boolean primaryPhysical = SystemProperties.getBoolean( + StorageManager.PROP_PRIMARY_PHYSICAL, false); + + final String id = "stub_primary"; + final File path = Environment.getLegacyExternalStorageDirectory(); + final String description = mContext.getString(android.R.string.unknownName); + final boolean primary = true; + final boolean removable = primaryPhysical; + final boolean emulated = !primaryPhysical; + final long mtpReserveSize = 0L; + final boolean allowMassStorage = false; + final long maxFileSize = 0L; + final UserHandle owner = new UserHandle(userId); + final String uuid = null; + final String state = Environment.MEDIA_REMOVED; + + res.add(0, new StorageVolume(id, MtpStorage.getStorageIdForIndex(0), path, + description, primary, removable, emulated, mtpReserveSize, + allowMassStorage, maxFileSize, owner, uuid, state)); + } + + return res.toArray(new StorageVolume[res.size()]); } - /** - * 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; + @Override + public DiskInfo[] getDisks() { + synchronized (mLock) { + final DiskInfo[] res = new DiskInfo[mDisks.size()]; + for (int i = 0; i < mDisks.size(); i++) { + res[i] = mDisks.valueAt(i); } + return res; } - return null; } @Override - public StorageVolume[] getVolumeList() { - final int callingUserId = UserHandle.getCallingUserId(); - final boolean accessAll = (mContext.checkPermission( - android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, - Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED); - - synchronized (mVolumesLock) { - final ArrayList<StorageVolume> filtered = Lists.newArrayList(); - for (StorageVolume volume : mVolumes) { - final UserHandle owner = volume.getOwner(); - final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId; - if (accessAll || ownerMatch) { - filtered.add(volume); - } + public VolumeInfo[] getVolumes(int flags) { + if ((flags & StorageManager.FLAG_ALL_METADATA) != 0) { + // TODO: implement support for returning all metadata + throw new UnsupportedOperationException(); + } + + synchronized (mLock) { + final VolumeInfo[] res = new VolumeInfo[mVolumes.size()]; + for (int i = 0; i < mVolumes.size(); i++) { + res[i] = mVolumes.valueAt(i); } - return filtered.toArray(new StorageVolume[filtered.size()]); + return res; } } @@ -3050,20 +2731,101 @@ class MountService extends IMountService.Stub if (path.startsWith(obbPath)) { path = path.substring(obbPath.length() + 1); - if (forVold) { - return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath(); - } else { - final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER); - return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path) - .getAbsolutePath(); - } + final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER); + return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path) + .getAbsolutePath(); } // Handle normal external storage paths - if (forVold) { - return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath(); - } else { - return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath(); + return new File(userEnv.getExternalStorageDirectory(), path).getAbsolutePath(); + } + + private static class Callbacks extends Handler { + private static final int MSG_STORAGE_STATE_CHANGED = 1; + private static final int MSG_VOLUME_STATE_CHANGED = 2; + private static final int MSG_VOLUME_METADATA_CHANGED = 3; + private static final int MSG_DISK_SCANNED = 4; + + private final RemoteCallbackList<IMountServiceListener> + mCallbacks = new RemoteCallbackList<>(); + + public Callbacks(Looper looper) { + super(looper); + } + + public void register(IMountServiceListener callback) { + mCallbacks.register(callback); + } + + public void unregister(IMountServiceListener callback) { + mCallbacks.unregister(callback); + } + + @Override + public void handleMessage(Message msg) { + final SomeArgs args = (SomeArgs) msg.obj; + final int n = mCallbacks.beginBroadcast(); + for (int i = 0; i < n; i++) { + final IMountServiceListener callback = mCallbacks.getBroadcastItem(i); + try { + invokeCallback(callback, msg.what, args); + } catch (RemoteException ignored) { + } + } + mCallbacks.finishBroadcast(); + args.recycle(); + } + + private void invokeCallback(IMountServiceListener callback, int what, SomeArgs args) + throws RemoteException { + switch (what) { + case MSG_STORAGE_STATE_CHANGED: { + callback.onStorageStateChanged((String) args.arg1, (String) args.arg2, + (String) args.arg3); + break; + } + case MSG_VOLUME_STATE_CHANGED: { + callback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); + break; + } + case MSG_VOLUME_METADATA_CHANGED: { + callback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + break; + } + case MSG_DISK_SCANNED: { + callback.onDiskScanned((DiskInfo) args.arg1, args.argi2); + break; + } + } + } + + private void notifyStorageStateChanged(String path, String oldState, String newState) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = path; + args.arg2 = oldState; + args.arg3 = newState; + obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget(); + } + + private void notifyVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = vol; + args.argi2 = oldState; + args.argi3 = newState; + obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); + } + + private void notifyVolumeMetadataChanged(VolumeInfo vol) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = vol; + obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); + } + + private void notifyDiskScanned(DiskInfo disk, int volumeCount) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = disk; + args.argi2 = volumeCount; + obtainMessage(MSG_DISK_SCANNED, args).sendToTarget(); } } @@ -3071,9 +2833,47 @@ class MountService extends IMountService.Stub protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + for (String arg : args) { + if ("--clear-metadata".equals(arg)) { + synchronized (mLock) { + mMetadata.clear(); + writeMetadataLocked(); + } + } + } + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); + synchronized (mLock) { + pw.println("Disks:"); + pw.increaseIndent(); + for (int i = 0; i < mDisks.size(); i++) { + final DiskInfo disk = mDisks.valueAt(i); + disk.dump(pw); + } + pw.decreaseIndent(); + + pw.println(); + pw.println("Volumes:"); + pw.increaseIndent(); + for (int i = 0; i < mVolumes.size(); i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) continue; + vol.dump(pw); + } + pw.decreaseIndent(); + + pw.println(); + pw.println("Metadata:"); + pw.increaseIndent(); + for (int i = 0; i < mMetadata.size(); i++) { + final VolumeMetadata meta = mMetadata.valueAt(i); + meta.dump(pw); + } + pw.decreaseIndent(); + } synchronized (mObbMounts) { + pw.println(); pw.println("mObbMounts:"); pw.increaseIndent(); final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet() @@ -3103,19 +2903,6 @@ class MountService extends IMountService.Stub pw.decreaseIndent(); } - synchronized (mVolumesLock) { - pw.println(); - pw.println("mVolumes:"); - pw.increaseIndent(); - for (StorageVolume volume : mVolumes) { - pw.println(volume); - pw.increaseIndent(); - pw.println("Current state: " + mVolumeStates.get(volume.getPath())); - pw.decreaseIndent(); - } - pw.decreaseIndent(); - } - pw.println(); pw.println("mConnection:"); pw.increaseIndent(); @@ -3130,6 +2917,7 @@ class MountService extends IMountService.Stub } /** {@inheritDoc} */ + @Override public void monitor() { if (mConnector != null) { mConnector.monitor(); diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java index d2dfc7b..78c7f38 100644 --- a/services/core/java/com/android/server/NativeDaemonConnector.java +++ b/services/core/java/com/android/server/NativeDaemonConnector.java @@ -48,8 +48,6 @@ import java.util.LinkedList; * {@code libsysutils} FrameworkListener protocol. */ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor { - private static final boolean LOGD = false; - private final static boolean VDBG = false; private final String TAG; @@ -58,6 +56,8 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo private OutputStream mOutputStream; private LocalLog mLocalLog; + private volatile boolean mDebug = false; + private final ResponseQueue mResponseQueue; private final PowerManager.WakeLock mWakeLock; @@ -99,6 +99,14 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo mLocalLog = new LocalLog(maxLogSize); } + /** + * Enable Set debugging mode, which causes messages to also be written to both + * {@link Slog} in addition to internal log. + */ + public void setDebug(boolean debug) { + mDebug = debug; + } + @Override public void run() { mCallbackHandler = new Handler(mLooper, this); @@ -513,7 +521,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo } private void log(String logstring) { - if (LOGD) Slog.d(TAG, logstring); + if (mDebug) Slog.d(TAG, logstring); mLocalLog.log(logstring); } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 0437a2a..b5b62b4 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -24,9 +24,6 @@ import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_TETHERING; -import static android.net.RouteInfo.RTN_THROW; -import static android.net.RouteInfo.RTN_UNICAST; -import static android.net.RouteInfo.RTN_UNREACHABLE; import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult; import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult; import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult; @@ -38,6 +35,7 @@ import static com.android.server.NetworkManagementService.NetdResponseCode.Tethe import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult; import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; +import android.app.ActivityManagerNative; import android.content.Context; import android.net.ConnectivityManager; import android.net.INetworkManagementEventObserver; @@ -61,6 +59,7 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.telephony.DataConnectionRealTimeInfo; @@ -70,9 +69,12 @@ import android.telephony.TelephonyManager; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.net.NetworkStatsFactory; +import com.android.internal.util.HexDump; import com.android.internal.util.Preconditions; import com.android.server.NativeDaemonConnector.Command; import com.android.server.NativeDaemonConnector.SensitiveArg; @@ -87,8 +89,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; -import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.NetworkInterface; @@ -145,6 +145,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub public static final int InterfaceAddressChange = 614; public static final int InterfaceDnsServerInfo = 615; public static final int RouteChange = 616; + public static final int StrictCleartext = 617; } static final int DAEMON_MSG_MOBILE_CONN_REAL_TIME_INFO = 1; @@ -174,12 +175,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); private Object mQuotaLock = new Object(); + /** Set of interfaces with active quotas. */ + @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveQuotas = Maps.newHashMap(); /** Set of interfaces with active alerts. */ + @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveAlerts = Maps.newHashMap(); /** Set of UIDs with active reject rules. */ + @GuardedBy("mQuotaLock") private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray(); + /** Set of UIDs with cleartext penalties. */ + @GuardedBy("mQuotaLock") + private SparseIntArray mUidCleartextPolicy = new SparseIntArray(); private Object mIdleTimerLock = new Object(); /** Set of interfaces with active idle timers. */ @@ -198,9 +206,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub private volatile boolean mBandwidthControlEnabled; private volatile boolean mFirewallEnabled; + private volatile boolean mStrictEnabled; private boolean mMobileActivityFromRadio = false; private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + private int mLastPowerStateFromWifi = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners = new RemoteCallbackList<INetworkActivityListener>(); @@ -425,6 +435,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } + if (ConnectivityManager.isNetworkTypeWifi(type)) { + if (mLastPowerStateFromWifi != powerState) { + mLastPowerStateFromWifi = powerState; + try { + getBatteryStats().noteWifiRadioPowerState(powerState, tsNanos); + } catch (RemoteException e) { + } + } + } + boolean isActive = powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH; @@ -495,11 +515,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } + try { + mConnector.execute("strict", "enable"); + mStrictEnabled = true; + } catch (NativeDaemonConnectorException e) { + Log.wtf(TAG, "Failed strict enable", e); + } + // push any existing quota or UID rules synchronized (mQuotaLock) { int size = mActiveQuotas.size(); if (size > 0) { - Slog.d(TAG, "pushing " + size + " active quota rules"); + Slog.d(TAG, "Pushing " + size + " active quota rules"); final HashMap<String, Long> activeQuotas = mActiveQuotas; mActiveQuotas = Maps.newHashMap(); for (Map.Entry<String, Long> entry : activeQuotas.entrySet()) { @@ -509,7 +536,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub size = mActiveAlerts.size(); if (size > 0) { - Slog.d(TAG, "pushing " + size + " active alert rules"); + Slog.d(TAG, "Pushing " + size + " active alert rules"); final HashMap<String, Long> activeAlerts = mActiveAlerts; mActiveAlerts = Maps.newHashMap(); for (Map.Entry<String, Long> entry : activeAlerts.entrySet()) { @@ -519,13 +546,23 @@ public class NetworkManagementService extends INetworkManagementService.Stub size = mUidRejectOnQuota.size(); if (size > 0) { - Slog.d(TAG, "pushing " + size + " active uid rules"); + Slog.d(TAG, "Pushing " + size + " active UID rules"); final SparseBooleanArray uidRejectOnQuota = mUidRejectOnQuota; mUidRejectOnQuota = new SparseBooleanArray(); for (int i = 0; i < uidRejectOnQuota.size(); i++) { setUidNetworkRules(uidRejectOnQuota.keyAt(i), uidRejectOnQuota.valueAt(i)); } } + + size = mUidCleartextPolicy.size(); + if (size > 0) { + Slog.d(TAG, "Pushing " + size + " active UID cleartext policies"); + final SparseIntArray local = mUidCleartextPolicy; + mUidCleartextPolicy = new SparseIntArray(); + for (int i = 0; i < local.size(); i++) { + setUidCleartextNetworkPolicy(local.keyAt(i), local.valueAt(i)); + } + } } // TODO: Push any existing firewall state @@ -792,6 +829,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub } throw new IllegalStateException(errorMessage); // break; + case NetdResponseCode.StrictCleartext: + final int uid = Integer.parseInt(cooked[1]); + final byte[] firstPacket = HexDump.hexStringToByteArray(cooked[2]); + try { + ActivityManagerNative.getDefault().notifyCleartextNetwork(uid, firstPacket); + } catch (RemoteException ignored) { + } + break; default: break; } return false; @@ -1674,6 +1719,49 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override + public void setUidCleartextNetworkPolicy(int uid, int policy) { + if (Binder.getCallingUid() != uid) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + } + + synchronized (mQuotaLock) { + final int oldPolicy = mUidCleartextPolicy.get(uid, StrictMode.NETWORK_POLICY_ACCEPT); + if (oldPolicy == policy) { + return; + } + + if (!mStrictEnabled) { + // Module isn't enabled yet; stash the requested policy away to + // apply later once the daemon is connected. + mUidCleartextPolicy.put(uid, policy); + return; + } + + final String policyString; + switch (policy) { + case StrictMode.NETWORK_POLICY_ACCEPT: + policyString = "accept"; + break; + case StrictMode.NETWORK_POLICY_LOG: + policyString = "log"; + break; + case StrictMode.NETWORK_POLICY_REJECT: + policyString = "reject"; + break; + default: + throw new IllegalArgumentException("Unknown policy " + policy); + } + + try { + mConnector.execute("strict", "set_uid_cleartext_policy", uid, policyString); + mUidCleartextPolicy.put(uid, policy); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + } + + @Override public boolean isBandwidthControlEnabled() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mBandwidthControlEnabled; diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java index d6abce9..a0d305c 100644 --- a/services/core/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java index 39aa972..f4c6225 100644 --- a/services/core/java/com/android/server/NsdService.java +++ b/services/core/java/com/android/server/NsdService.java @@ -38,7 +38,6 @@ import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 97d16c0..56f9942 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -18,14 +18,18 @@ package com.android.server; import android.Manifest; import android.app.ActivityManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.service.persistentdata.IPersistentDataBlockService; +import android.service.persistentdata.PersistentDataBlockManager; import android.util.Slog; import com.android.internal.R; @@ -70,6 +74,7 @@ public class PersistentDataBlockService extends SystemService { // Limit to 100k as blocks larger than this might cause strain on Binder. private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100; public static final int DIGEST_SIZE_BYTES = 32; + private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed"; private final Context mContext; private final String mDataBlockFile; @@ -108,11 +113,14 @@ public class PersistentDataBlockService extends SystemService { } private void formatIfOemUnlockEnabled() { - if (doGetOemUnlockEnabled()) { + boolean enabled = doGetOemUnlockEnabled(); + if (enabled) { synchronized (mLock) { formatPartitionLocked(true); } } + + SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0"); } private void enforceOemUnlockPermission() { @@ -132,7 +140,6 @@ public class PersistentDataBlockService extends SystemService { throw new SecurityException("Only the Owner is allowed to change OEM unlock state"); } } - private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException { // skip over checksum inputStream.skipBytes(DIGEST_SIZE_BYTES); @@ -290,6 +297,7 @@ public class PersistentDataBlockService extends SystemService { Slog.e(TAG, "unable to access persistent partition", e); return; } finally { + SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0"); IoUtils.closeQuietly(outputStream); } } @@ -424,6 +432,29 @@ public class PersistentDataBlockService extends SystemService { } @Override + public void wipeIfAllowed(Bundle bundle, PendingIntent pi) { + // Should only be called by owner + if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) { + throw new SecurityException("Only the Owner is allowed to wipe"); + } + // Caller must be able to query the the state of the PersistentDataBlock + enforcePersistentDataBlockAccess(); + String allowedPackage = mContext.getResources() + .getString(R.string.config_persistentDataPackageName); + Intent intent = new Intent(); + intent.setPackage(allowedPackage); + intent.setAction(PersistentDataBlockManager.ACTION_WIPE_IF_ALLOWED); + intent.putExtras(bundle); + intent.putExtra(PersistentDataBlockManager.EXTRA_WIPE_IF_ALLOWED_CALLBACK, pi); + long id = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.OWNER); + } finally { + restoreCallingIdentity(id); + } + } + + @Override public void setOemUnlockEnabled(boolean enabled) { // do not allow monkey to flip the flag if (ActivityManager.isUserAMonkey()) { @@ -446,10 +477,7 @@ public class PersistentDataBlockService extends SystemService { @Override public int getDataBlockSize() { - if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE) - != PackageManager.PERMISSION_GRANTED) { - enforceUid(Binder.getCallingUid()); - } + enforcePersistentDataBlockAccess(); DataInputStream inputStream; try { @@ -471,6 +499,13 @@ public class PersistentDataBlockService extends SystemService { } } + private void enforcePersistentDataBlockAccess() { + if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE) + != PackageManager.PERMISSION_GRANTED) { + enforceUid(Binder.getCallingUid()); + } + } + @Override public long getMaximumDataBlockSize() { long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1; diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 6ad128c..4c9d7d3 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -71,9 +71,11 @@ public class SystemConfig { public static final class PermissionEntry { public final String name; public int[] gids; + public boolean perUser; - PermissionEntry(String _name) { - name = _name; + PermissionEntry(String name, boolean perUser) { + this.name = name; + this.perUser = perUser; } } @@ -363,14 +365,14 @@ public class SystemConfig { void readPermission(XmlPullParser parser, String name) throws IOException, XmlPullParserException { + if (mPermissions.containsKey(name)) { + throw new IllegalStateException("Duplicate permission definition for " + name); + } - name = name.intern(); + final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false); + final PermissionEntry perm = new PermissionEntry(name, perUser); + mPermissions.put(name, perm); - PermissionEntry perm = mPermissions.get(name); - if (perm == null) { - perm = new PermissionEntry(name); - mPermissions.put(name, perm); - } int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 4fbf23b..4ee6657 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -17,6 +17,7 @@ package com.android.server; import android.app.ActivityManager; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -84,7 +85,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private static final boolean VDBG = false; // STOPSHIP if true private static class Record { - String pkgForDebug; + String callingPackage; IBinder binder; @@ -109,7 +110,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { @Override public String toString() { - return "{pkgForDebug=" + pkgForDebug + " binder=" + binder + " callback=" + callback + return "{callingPackage=" + callingPackage + " binder=" + binder + + " callback=" + callback + " onSubscriptionsChangedListenererCallback=" + onSubscriptionsChangedListenerCallback + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId @@ -125,6 +127,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private final IBatteryStats mBatteryStats; + private final AppOpsManager mAppOps; + private boolean hasNotifySubscriptionInfoChangedOccurred = false; private int mNumPhones; @@ -181,6 +185,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private PreciseCallState mPreciseCallState = new PreciseCallState(); + private boolean mCarrierNetworkChangeState = false; + private PreciseDataConnectionState mPreciseDataConnectionState = new PreciseDataConnectionState(); @@ -325,6 +331,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } mConnectedApns = new ArrayList<String>(); + + mAppOps = mContext.getSystemService(AppOpsManager.class); } public void systemRunning() { @@ -338,18 +346,24 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } @Override - public void addOnSubscriptionsChangedListener(String pkgForDebug, + public void addOnSubscriptionsChangedListener(String callingPackage, IOnSubscriptionsChangedListener callback) { int callerUid = UserHandle.getCallingUserId(); int myUid = UserHandle.myUserId(); if (VDBG) { - log("listen oscl: E pkg=" + pkgForDebug + " myUid=" + myUid + log("listen oscl: E pkg=" + callingPackage + " myUid=" + myUid + " callerUid=" + callerUid + " callback=" + callback + " callback.asBinder=" + callback.asBinder()); } - /* Checks permission and throws Security exception */ - checkOnSubscriptionsChangedListenerPermission(); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PHONE_STATE, null); + + if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(), + callingPackage) != AppOpsManager.MODE_ALLOWED) { + return; + } + Record r = null; synchronized (mRecords) { @@ -370,7 +384,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } r.onSubscriptionsChangedListenerCallback = callback; - r.pkgForDebug = pkgForDebug; + r.callingPackage = callingPackage; r.callerUid = callerUid; r.events = 0; if (DBG) { @@ -399,12 +413,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(callback.asBinder()); } - private void checkOnSubscriptionsChangedListenerPermission() { - mContext.enforceCallingOrSelfPermission( - SubscriptionManager.OnSubscriptionsChangedListener - .PERMISSION_ON_SUBSCRIPTIONS_CHANGED, null); - } - @Override public void notifySubscriptionInfoChanged() { if (VDBG) log("notifySubscriptionInfoChanged:"); @@ -444,12 +452,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { listen(pkgForDebug, callback, events, notifyNow, subId); } - private void listen(String pkgForDebug, IPhoneStateListener callback, int events, + private void listen(String callingPackage, IPhoneStateListener callback, int events, boolean notifyNow, int subId) { int callerUid = UserHandle.getCallingUserId(); int myUid = UserHandle.myUserId(); if (VDBG) { - log("listen: E pkg=" + pkgForDebug + " events=0x" + Integer.toHexString(events) + log("listen: E pkg=" + callingPackage + " events=0x" + Integer.toHexString(events) + " notifyNow=" + notifyNow + " subId=" + subId + " myUid=" + myUid + " callerUid=" + callerUid); } @@ -457,6 +465,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (events != PhoneStateListener.LISTEN_NONE) { /* Checks permission and throws Security exception */ checkListenerPermission(events); + + if ((events & PHONE_STATE_PERMISSION_MASK) != 0) { + if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(), + callingPackage) != AppOpsManager.MODE_ALLOWED) { + return; + } + } + synchronized (mRecords) { // register Record r = null; @@ -476,7 +492,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } r.callback = callback; - r.pkgForDebug = pkgForDebug; + r.callingPackage = callingPackage; r.callerUid = callerUid; // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID @@ -607,6 +623,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE) != 0) { + try { + r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } else { @@ -622,7 +645,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (mRecords.get(i).binder == binder) { if (DBG) { Record r = mRecords.get(i); - log("remove: binder=" + binder + "r.pkgForDebug" + r.pkgForDebug + log("remove: binder=" + binder + "r.callingPackage" + r.callingPackage + "r.callback" + r.callback); } mRecords.remove(i); @@ -736,50 +759,47 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifySignalStrengthForSubscriber(int subId, SignalStrength signalStrength) { + log("notifySignalStrengthForSubscriber: subId=" + subId + + " signalStrength=" + signalStrength); if (!checkNotifyPermission("notifySignalStrength()")) { + log("notifySignalStrengthForSubscriber: permission check failure"); return; } - if (VDBG) { - log("notifySignalStrengthForSubscriber: subId=" + subId - + " signalStrength=" + signalStrength); - toStringLogSSC("notifySignalStrengthForSubscriber"); - } + toStringLogSSC("notifySignalStrengthForSubscriber"); synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); if (validatePhoneId(phoneId)) { - if (VDBG) log("notifySignalStrengthForSubscriber: valid phoneId=" + phoneId); + log("notifySignalStrengthForSubscriber: valid phoneId=" + phoneId); mSignalStrength[phoneId] = signalStrength; for (Record r : mRecords) { - if (VDBG) { - log("notifySignalStrengthForSubscriber: r=" + r + " subId=" + subId - + " phoneId=" + phoneId + " ss=" + signalStrength); - } + log("notifySignalStrengthForSubscriber: r=" + r + " subId=" + subId + + " phoneId=" + phoneId + " ss=" + signalStrength); if (r.matchPhoneStateListenerEvent( PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) && idMatch(r.subId, subId, phoneId)) { try { - if (DBG) { - log("notifySignalStrengthForSubscriber: callback.onSsS r=" + r - + " subId=" + subId + " phoneId=" + phoneId - + " ss=" + signalStrength); - } + log("notifySignalStrengthForSubscriber: callback.onSsS r=" + r + + " subId=" + subId + " phoneId=" + phoneId + + " ss=" + signalStrength); r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength)); } catch (RemoteException ex) { + log("notifySignalStrengthForSubscriber: Exception while calling callback!!"); mRemoveList.add(r.binder); } + } else { + log("notifySignalStrengthForSubscriber: no match for LISTEN_SIGNAL_STRENGTHS"); } if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SIGNAL_STRENGTH) && idMatch(r.subId, subId, phoneId)){ try { int gsmSignalStrength = signalStrength.getGsmSignalStrength(); int ss = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength); - if (DBG) { - log("notifySignalStrengthForSubscriber: callback.onSS r=" + r - + " subId=" + subId + " phoneId=" + phoneId - + " gsmSS=" + gsmSignalStrength + " ss=" + ss); - } + log("notifySignalStrengthForSubscriber: callback.onSS r=" + r + + " subId=" + subId + " phoneId=" + phoneId + + " gsmSS=" + gsmSignalStrength + " ss=" + ss); r.callback.onSignalStrengthChanged(ss); } catch (RemoteException ex) { + log("notifySignalStrengthForSubscriber: Exception in deprecated LISTEN_SIGNAL_STRENGTH"); mRemoveList.add(r.binder); } } @@ -787,11 +807,37 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } else { log("notifySignalStrengthForSubscriber: invalid phoneId=" + phoneId); } + log("notifySignalStrengthForSubscriber: done with all records"); handleRemoveListLocked(); } broadcastSignalStrengthChanged(signalStrength, subId); } + @Override + public void notifyCarrierNetworkChange(boolean active) { + if (!checkNotifyPermissionOrCarrierPrivilege("notifyCarrierNetworkChange()")) { + return; + } + if (VDBG) { + log("notifyCarrierNetworkChange: active=" + active); + } + + synchronized (mRecords) { + mCarrierNetworkChangeState = active; + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE)) { + try { + r.callback.onCarrierNetworkChange(active); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); + } + } + public void notifyCellInfo(List<CellInfo> cellInfo) { notifyCellInfoForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellInfo); } @@ -1348,7 +1394,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, - android.Manifest.permission.READ_PHONE_STATE); + android.Manifest.permission.READ_PHONE_STATE, + AppOpsManager.OP_READ_PHONE_STATE); } private void broadcastDataConnectionStateChanged(int state, @@ -1424,9 +1471,19 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { android.Manifest.permission.READ_PRECISE_PHONE_STATE); } + private boolean checkNotifyPermissionOrCarrierPrivilege(String method) { + if (checkNotifyPermission() || checkCarrierPrivilege()) { + return true; + } + + String msg = "Modify Phone State or Carrier Privilege Permission Denial: " + method + + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid(); + if (DBG) log(msg); + return false; + } + private boolean checkNotifyPermission(String method) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - == PackageManager.PERMISSION_GRANTED) { + if (checkNotifyPermission()) { return true; } String msg = "Modify Phone State Permission Denial: " + method + " from pid=" @@ -1435,6 +1492,24 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { return false; } + private boolean checkNotifyPermission() { + return mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean checkCarrierPrivilege() { + TelephonyManager tm = TelephonyManager.getDefault(); + String[] pkgs = mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid()); + for (String pkg : pkgs) { + if (tm.checkCarrierPrivilegesForPackage(pkg) == + TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + } + + return false; + } + private void checkListenerPermission(int events) { if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { mContext.enforceCallingOrSelfPermission( diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java index 5add88e..9a6f696 100644 --- a/services/core/java/com/android/server/TextServicesManagerService.java +++ b/services/core/java/com/android/server/TextServicesManagerService.java @@ -116,6 +116,11 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { @Override public void onUserSwitchComplete(int newUserId) throws RemoteException { } + + @Override + public void onForegroundProfileSwitch(int newProfileId) { + // Ignore. + } }); userId = ActivityManagerNative.getDefault().getCurrentUser().id; } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/TwilightCalculator.java b/services/core/java/com/android/server/TwilightCalculator.java index a5c93b5..5839b16 100644 --- a/services/core/java/com/android/server/TwilightCalculator.java +++ b/services/core/java/com/android/server/TwilightCalculator.java @@ -17,7 +17,6 @@ package com.android.server; import android.text.format.DateUtils; -import android.util.FloatMath; /** @hide */ public class TwilightCalculator { @@ -75,24 +74,24 @@ public class TwilightCalculator { final float meanAnomaly = 6.240059968f + daysSince2000 * 0.01720197f; // true anomaly - final float trueAnomaly = meanAnomaly + C1 * FloatMath.sin(meanAnomaly) + C2 - * FloatMath.sin(2 * meanAnomaly) + C3 * FloatMath.sin(3 * meanAnomaly); + final double trueAnomaly = meanAnomaly + C1 * Math.sin(meanAnomaly) + C2 + * Math.sin(2 * meanAnomaly) + C3 * Math.sin(3 * meanAnomaly); // ecliptic longitude - final float solarLng = trueAnomaly + 1.796593063f + (float) Math.PI; + final double solarLng = trueAnomaly + 1.796593063d + Math.PI; // solar transit in days since 2000 final double arcLongitude = -longitude / 360; float n = Math.round(daysSince2000 - J0 - arcLongitude); - double solarTransitJ2000 = n + J0 + arcLongitude + 0.0053f * FloatMath.sin(meanAnomaly) - + -0.0069f * FloatMath.sin(2 * solarLng); + double solarTransitJ2000 = n + J0 + arcLongitude + 0.0053d * Math.sin(meanAnomaly) + + -0.0069d * Math.sin(2 * solarLng); // declination of sun - double solarDec = Math.asin(FloatMath.sin(solarLng) * FloatMath.sin(OBLIQUITY)); + double solarDec = Math.asin(Math.sin(solarLng) * Math.sin(OBLIQUITY)); final double latRad = latiude * DEGREES_TO_RADIANS; - double cosHourAngle = (FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad) + double cosHourAngle = (Math.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad) * Math.sin(solarDec)) / (Math.cos(latRad) * Math.cos(solarDec)); // The day or night never ends for the given date and location, if this value is out of // range. diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index d1b4569..64f3070 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.content.res.Resources; import android.os.BatteryManager; import android.os.Binder; import android.os.Handler; @@ -63,7 +64,7 @@ final class UiModeManagerService extends SystemService { private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; - int mNightMode = UiModeManager.MODE_NIGHT_NO; + private int mNightMode = UiModeManager.MODE_NIGHT_NO; private boolean mCarModeEnabled = false; private boolean mCharging = false; @@ -156,7 +157,7 @@ final class UiModeManagerService extends SystemService { @Override public void onStart() { final Context context = getContext(); - mTwilightManager = getLocalService(TwilightManager.class); + final PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); @@ -168,22 +169,29 @@ final class UiModeManagerService extends SystemService { mConfiguration.setToDefaults(); - mDefaultUiModeType = context.getResources().getInteger( + final Resources res = context.getResources(); + mDefaultUiModeType = res.getInteger( com.android.internal.R.integer.config_defaultUiModeType); - mCarModeKeepsScreenOn = (context.getResources().getInteger( + mCarModeKeepsScreenOn = (res.getInteger( com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1); - mDeskModeKeepsScreenOn = (context.getResources().getInteger( + mDeskModeKeepsScreenOn = (res.getInteger( com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1); - mTelevision = context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_TELEVISION) || - context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_LEANBACK); - mWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); + final PackageManager pm = context.getPackageManager(); + mTelevision = pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION) + || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); + mWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH); + + final int defaultNightMode = res.getInteger( + com.android.internal.R.integer.config_defaultNightMode); mNightMode = Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO); + Settings.Secure.UI_NIGHT_MODE, defaultNightMode); - mTwilightManager.registerListener(mTwilightListener, mHandler); + // Update the initial, static configurations. + synchronized (this) { + updateConfigurationLocked(); + sendConfigurationLocked(); + } publishBinderService(Context.UI_MODE_SERVICE, mService); } @@ -245,7 +253,7 @@ final class UiModeManagerService extends SystemService { final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { - if (isDoingNightModeLocked() && mNightMode != mode) { + if (mNightMode != mode) { Settings.Secure.putInt(getContext().getContentResolver(), Settings.Secure.UI_NIGHT_MODE, mode); mNightMode = mode; @@ -292,8 +300,11 @@ final class UiModeManagerService extends SystemService { pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode)); pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration); pw.print(" mSystemReady="); pw.println(mSystemReady); - pw.print(" mTwilightService.getCurrentState()="); - pw.println(mTwilightManager.getCurrentState()); + if (mTwilightManager != null) { + // We may not have a TwilightManager. + pw.print(" mTwilightService.getCurrentState()="); + pw.println(mTwilightManager.getCurrentState()); + } } } @@ -301,6 +312,10 @@ final class UiModeManagerService extends SystemService { public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { synchronized (mLock) { + mTwilightManager = getLocalService(TwilightManager.class); + if (mTwilightManager != null) { + mTwilightManager.registerListener(mTwilightListener, mHandler); + } mSystemReady = true; mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; updateComputedNightModeLocked(); @@ -309,10 +324,6 @@ final class UiModeManagerService extends SystemService { } } - boolean isDoingNightModeLocked() { - return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED; - } - void setCarModeLocked(boolean enabled, int flags) { if (mCarModeEnabled != enabled) { mCarModeEnabled = enabled; @@ -354,17 +365,13 @@ final class UiModeManagerService extends SystemService { } else if (isDeskDockState(mDockState)) { uiMode = Configuration.UI_MODE_TYPE_DESK; } - if (mCarModeEnabled) { - if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { - updateComputedNightModeLocked(); - uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES - : Configuration.UI_MODE_NIGHT_NO; - } else { - uiMode |= mNightMode << 4; - } + + if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + updateComputedNightModeLocked(); + uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES + : Configuration.UI_MODE_NIGHT_NO; } else { - // Disabling the car mode clears the night mode. - uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO; + uiMode |= mNightMode << 4; } if (LOG) { @@ -599,7 +606,7 @@ final class UiModeManagerService extends SystemService { n.defaults = Notification.DEFAULT_LIGHTS; n.flags = Notification.FLAG_ONGOING_EVENT; n.when = 0; - n.color = context.getResources().getColor( + n.color = context.getColor( com.android.internal.R.color.system_notification_accent_color); n.setLatestEventInfo( context, @@ -618,7 +625,7 @@ final class UiModeManagerService extends SystemService { void updateTwilight() { synchronized (mLock) { - if (isDoingNightModeLocked() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { updateComputedNightModeLocked(); updateLocked(0, 0); } @@ -626,9 +633,11 @@ final class UiModeManagerService extends SystemService { } private void updateComputedNightModeLocked() { - TwilightState state = mTwilightManager.getCurrentState(); - if (state != null) { - mComputedNightMode = state.isNight(); + if (mTwilightManager != null) { + TwilightState state = mTwilightManager.getCurrentState(); + if (state != null) { + mComputedNightMode = state.isNight(); + } } } diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 8e46c4d..772a15c 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -46,7 +46,6 @@ import java.util.ArrayList; /** This class calls its monitor every minute. Killing this process if they don't return **/ public class Watchdog extends Thread { static final String TAG = "Watchdog"; - static final boolean localLOGV = false || false; // Set this to true to use debug default values. static final boolean DB = false; @@ -73,7 +72,7 @@ public class Watchdog extends Thread { static Watchdog sWatchdog; /* This handler will be used to post message back onto the main thread */ - final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<HandlerChecker>(); + final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>(); final HandlerChecker mMonitorChecker; ContentResolver mResolver; ActivityManagerService mActivity; @@ -106,8 +105,8 @@ public class Watchdog extends Thread { } public void scheduleCheckLocked() { - if (mMonitors.size() == 0 && mHandler.getLooper().isIdling()) { - // If the target looper is or just recently was idling, then + if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) { + // If the target looper has recently been polling, 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 @@ -191,6 +190,17 @@ public class Watchdog extends Thread { } } + /** Monitor for checking the availability of binder threads. The monitor will block until + * there is a binder thread available to process in coming IPCs to make sure other processes + * can still communicate with the service. + */ + private static final class BinderThreadMonitor implements Watchdog.Monitor { + @Override + public void monitor() { + Binder.blockUntilThreadAvailable(); + } + } + public interface Monitor { void monitor(); } @@ -228,6 +238,9 @@ public class Watchdog extends Thread { // And the display thread. mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(), "display thread", DEFAULT_TIMEOUT)); + + // Initialize monitor for Binder threads. + addMonitor(new BinderThreadMonitor()); } public void init(Context context, ActivityManagerService activity) { diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java index bffbb4c2..0de8c8d 100644 --- a/services/core/java/com/android/server/WiredAccessoryManager.java +++ b/services/core/java/com/android/server/WiredAccessoryManager.java @@ -16,10 +16,7 @@ package com.android.server; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -219,6 +216,7 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { mWakeLock.acquire(); + Log.i(TAG, "MSG_NEW_DEVICE_STATE "); Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState, mHeadsetState, newName); mHandler.sendMessage(msg); @@ -286,14 +284,16 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { return; } - if (LOG) - Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected")); + if (LOG) { + Slog.v(TAG, "headsetName: " + headsetName + + (state == 1 ? " connected" : " disconnected")); + } if (outDevice != 0) { - mAudioManager.setWiredDeviceConnectionState(outDevice, state, headsetName); + mAudioManager.setWiredDeviceConnectionState(outDevice, state, "", headsetName); } if (inDevice != 0) { - mAudioManager.setWiredDeviceConnectionState(inDevice, state, headsetName); + mAudioManager.setWiredDeviceConnectionState(inDevice, state, "", headsetName); } } } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index e52b2bf..1b32f57 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -109,7 +109,7 @@ public class AccountManagerService private static final int TIMEOUT_DELAY_MS = 1000 * 60; private static final String DATABASE_NAME = "accounts.db"; - private static final int DATABASE_VERSION = 6; + private static final int DATABASE_VERSION = 7; private final Context mContext; @@ -131,6 +131,8 @@ public class AccountManagerService private static final String ACCOUNTS_TYPE_COUNT = "count(type)"; private static final String ACCOUNTS_PASSWORD = "password"; private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name"; + private static final String ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS = + "last_password_entry_time_millis_epoch"; private static final String TABLE_AUTHTOKENS = "authtokens"; private static final String AUTHTOKENS_ID = "_id"; @@ -697,7 +699,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(fromAccounts, response, account.type, false, - false /* stripAuthTokenFromResult */) { + false /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */) { @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAccountCredentialsForClone" @@ -725,12 +728,43 @@ public class AccountManagerService } } + @Override + public boolean accountAuthenticated(final Account account) { + if (account == null) { + throw new IllegalArgumentException("account is null"); + } + checkAuthenticateAccountsPermission(account); + + final UserAccounts accounts = getUserAccountsForCaller(); + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) { + return false; + } + synchronized (accounts.cacheLock) { + final ContentValues values = new ContentValues(); + values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis()); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + int i = db.update( + TABLE_ACCOUNTS, + values, + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", + new String[] { + account.name, account.type + }); + if (i > 0) { + return true; + } + } + return false; + } + private void completeCloningAccount(IAccountManagerResponse response, final Bundle accountCredentials, final Account account, final UserAccounts targetUser) { long id = clearCallingIdentity(); try { new Session(targetUser, response, account.type, false, - false /* stripAuthTokenFromResult */) { + false /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */) { @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAccountCredentialsForClone" @@ -795,6 +829,7 @@ public class AccountManagerService values.put(ACCOUNTS_NAME, account.name); values.put(ACCOUNTS_TYPE, account.type); values.put(ACCOUNTS_PASSWORD, password); + values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis()); long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); if (accountId < 0) { Log.w(TAG, "insertAccountIntoDatabase: " + account @@ -885,7 +920,8 @@ public class AccountManagerService public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response, Account account, String[] features) { super(accounts, response, account.type, false /* expectActivityLaunch */, - true /* stripAuthTokenFromResult */); + true /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */); mFeatures = features; mAccount = account; } @@ -1184,7 +1220,8 @@ public class AccountManagerService public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, Account account, boolean expectActivityLaunch) { super(accounts, response, account.type, expectActivityLaunch, - true /* stripAuthTokenFromResult */); + true /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */); mAccount = account; } @@ -1419,6 +1456,13 @@ public class AccountManagerService try { final ContentValues values = new ContentValues(); values.put(ACCOUNTS_PASSWORD, password); + long time = 0; + // Only set current time, if it is a valid password. For clear password case, it + // should not be set. + if (password != null) { + time = System.currentTimeMillis(); + } + values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, time); final long accountId = getAccountIdLocked(db, account); if (accountId >= 0) { final String[] argsAccountId = {String.valueOf(accountId)}; @@ -1547,8 +1591,9 @@ public class AccountManagerService UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); long identityToken = clearCallingIdentity(); try { - new Session(accounts, response, accountType, false, - false /* stripAuthTokenFromResult */) { + new Session(accounts, response, accountType, false /* expectActivityLaunch */, + false /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */) { @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAuthTokenLabel" @@ -1648,7 +1693,8 @@ public class AccountManagerService } new Session(accounts, response, account.type, expectActivityLaunch, - false /* stripAuthTokenFromResult */) { + false /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */) { @Override protected String toDebugString(long now) { if (loginOptions != null) loginOptions.keySet(); @@ -1736,7 +1782,7 @@ public class AccountManagerService } UserHandle user = new UserHandle(userId); Context contextForUser = getContextForUser(user); - n.color = contextForUser.getResources().getColor( + n.color = contextForUser.getColor( com.android.internal.R.color.system_notification_accent_color); n.setLatestEventInfo(contextForUser, title, subtitle, PendingIntent.getActivityAsUser(mContext, 0, intent, @@ -1842,7 +1888,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, accountType, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, @@ -1917,7 +1964,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, accountType, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, @@ -1973,7 +2021,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, account.type, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, account.name, + true /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.confirmCredentials(this, account, options); @@ -2009,7 +2058,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, account.type, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); @@ -2045,7 +2095,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, accountType, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.editProperties(this, mAccountType); @@ -2071,7 +2122,8 @@ public class AccountManagerService public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, IAccountManagerResponse response, String type, String[] features, int callingUid) { super(accounts, response, type, false /* expectActivityLaunch */, - true /* stripAuthTokenFromResult */); + true /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */); mCallingUid = callingUid; mFeatures = features; } @@ -2437,6 +2489,9 @@ public class AccountManagerService final String mAccountType; final boolean mExpectActivityLaunch; final long mCreationTime; + final String mAccountName; + // Indicates if we need to add auth details(like last credential time) + final boolean mAuthDetailsRequired; public int mNumResults = 0; private int mNumRequestContinued = 0; @@ -2448,7 +2503,8 @@ public class AccountManagerService protected final UserAccounts mAccounts; public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType, - boolean expectActivityLaunch, boolean stripAuthTokenFromResult) { + boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName, + boolean authDetailsRequired) { super(); //if (response == null) throw new IllegalArgumentException("response is null"); if (accountType == null) throw new IllegalArgumentException("accountType is null"); @@ -2458,6 +2514,9 @@ public class AccountManagerService mAccountType = accountType; mExpectActivityLaunch = expectActivityLaunch; mCreationTime = SystemClock.elapsedRealtime(); + mAccountName = accountName; + mAuthDetailsRequired = authDetailsRequired; + synchronized (mSessions) { mSessions.put(toString(), this); } @@ -2592,6 +2651,16 @@ public class AccountManagerService public void onResult(Bundle result) { mNumResults++; Intent intent = null; + if (result != null && mAuthDetailsRequired) { + long lastAuthenticatedTime = DatabaseUtils.longForQuery( + mAccounts.openHelper.getReadableDatabase(), + "select " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " from " + + TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND " + + ACCOUNTS_TYPE + "=?", + new String[]{mAccountName, mAccountType}); + result.putLong(AccountManager.KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH, + lastAuthenticatedTime); + } if (result != null && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { /* @@ -2798,6 +2867,7 @@ public class AccountManagerService + ACCOUNTS_TYPE + " TEXT NOT NULL, " + ACCOUNTS_PASSWORD + " TEXT, " + ACCOUNTS_PREVIOUS_NAME + " TEXT, " + + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, " + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " @@ -2833,6 +2903,11 @@ public class AccountManagerService + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); } + private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0"); + } + private void addOldAccountNameColumn(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME); } @@ -2892,6 +2967,11 @@ public class AccountManagerService oldVersion++; } + if (oldVersion == 6) { + addLastSuccessfullAuthenticatedTimeColumn(db); + oldVersion++; + } + if (oldVersion != newVersion) { Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion); } @@ -3009,7 +3089,7 @@ public class AccountManagerService Context contextForUser = getContextForUser(user); final String notificationTitleFormat = contextForUser.getText(R.string.notification_title).toString(); - n.color = contextForUser.getResources().getColor( + n.color = contextForUser.getColor( com.android.internal.R.color.system_notification_accent_color); n.setLatestEventInfo(contextForUser, String.format(notificationTitleFormat, account.name), @@ -3083,7 +3163,8 @@ public class AccountManagerService try { PackageInfo packageInfo = userPackageManager.getPackageInfo(name, 0 /* flags */); if (packageInfo != null - && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) { + && (packageInfo.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { return true; } } catch (PackageManager.NameNotFoundException e) { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index aefbf60..3dece49 100755 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static com.android.server.am.ActivityManagerDebugConfig.*; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -31,8 +33,10 @@ import android.os.DeadObjectException; import android.os.Handler; import android.os.Looper; import android.os.SystemProperties; +import android.os.TransactionTooLargeException; import android.util.ArrayMap; import android.util.ArraySet; + import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.TransferPipe; @@ -67,14 +71,15 @@ import android.util.SparseArray; import android.util.TimeUtils; 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_STARTS = DEBUG_DELAYED_SERVICE; - static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU; - static final boolean LOG_SERVICE_START_STOP = false; - static final String TAG = ActivityManagerService.TAG; - static final String TAG_MU = ActivityManagerService.TAG_MU; + private static final String TAG = TAG_WITH_CLASS_NAME ? "ActiveServices" : TAG_AM; + private static final String TAG_MU = TAG + POSTFIX_MU; + private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE; + private static final String TAG_SERVICE_EXECUTING = TAG + POSTFIX_SERVICE_EXECUTING; + + private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE; + private static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE; + + private static final boolean LOG_SERVICE_START_STOP = false; // How long we wait for a service to finish executing. static final int SERVICE_TIMEOUT = 20*1000; @@ -206,11 +211,12 @@ public final class ActiveServices { void ensureNotStartingBackground(ServiceRecord r) { if (mStartingBackground.remove(r)) { - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer background starting: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, + "No longer background starting: " + r); rescheduleDelayedStarts(); } if (mDelayedStartList.remove(r)) { - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer delaying start: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "No longer delaying start: " + r); } } @@ -229,26 +235,31 @@ public final class ActiveServices { while (mDelayedStartList.size() > 0 && mStartingBackground.size() < mMaxStartingBackground) { ServiceRecord r = mDelayedStartList.remove(0); - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, + "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:"); + Slog.v(TAG_SERVICE, "Remaining delayed list:"); for (int i=0; i<mDelayedStartList.size(); i++) { - Slog.v(TAG, " #" + i + ": " + mDelayedStartList.get(i)); + Slog.v(TAG_SERVICE, " #" + i + ": " + mDelayedStartList.get(i)); } } } r.delayed = false; - startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true); + try { + startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true); + } catch (TransactionTooLargeException e) { + // Ignore, nobody upstack cares. + } } 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 + if (DEBUG_DELAYED_SERVICE) Slog.v(TAG_SERVICE, "Top bg start is " + next + ", can delay others up to " + when); Message msg = obtainMessage(MSG_BG_START_TIMEOUT); sendMessageAtTime(msg, when); @@ -295,10 +306,10 @@ public final class ActiveServices { return getServiceMap(callingUser).mServicesByName; } - ComponentName startServiceLocked(IApplicationThread caller, - Intent service, String resolvedType, - int callingPid, int callingUid, int userId) { - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "startService: " + service + ComponentName startServiceLocked(IApplicationThread caller, Intent service, + String resolvedType, int callingPid, int callingUid, int userId) + throws TransactionTooLargeException { + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service + " type=" + resolvedType + " args=" + service.getExtras()); final boolean callerFg; @@ -337,7 +348,7 @@ public final class ActiveServices { NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked( callingUid, r.packageName, service, service.getFlags(), null, r.userId); if (unscheduleServiceRestartLocked(r, callingUid, false)) { - if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "START SERVICE WHILE RESTART PENDING: " + r); } r.lastActivity = SystemClock.uptimeMillis(); r.startRequested = true; @@ -360,29 +371,30 @@ public final class ActiveServices { // 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 (DEBUG_DELAYED_SERVICE) Slog.v(TAG_SERVICE, "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_STARTS) Slog.v(TAG, "Continuing to delay: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Continuing to delay: " + r); return r.name; } if (smap.mStartingBackground.size() >= mMaxStartingBackground) { // Something else is starting, delay! - Slog.i(TAG, "Delaying start of: " + r); + Slog.i(TAG_SERVICE, "Delaying start of: " + r); smap.mDelayedStartList.add(r); r.delayed = true; return r.name; } - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "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_STARTS) Slog.v(TAG, "Not delaying, but counting as bg: " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, + "Not delaying, but counting as bg: " + r); } else if (DEBUG_DELAYED_STARTS) { StringBuilder sb = new StringBuilder(128); sb.append("Not potential delay (state=").append(proc.curProcState) @@ -394,24 +406,25 @@ public final class ActiveServices { } sb.append("): "); sb.append(r.toString()); - Slog.v(TAG, sb.toString()); + Slog.v(TAG_SERVICE, sb.toString()); } } else if (DEBUG_DELAYED_STARTS) { if (callerFg) { - Slog.v(TAG, "Not potential delay (callerFg=" + callerFg + " uid=" + Slog.v(TAG_SERVICE, "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); + Slog.v(TAG_SERVICE, "Not potential delay (cur app=" + r.app + "): " + r); } else { - Slog.v(TAG, "Not potential delay (user " + r.userId + " not started): " + r); + Slog.v(TAG_SERVICE, + "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) { + ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r, + boolean callerFg, boolean addToStarting) throws TransactionTooLargeException { ProcessStats.ServiceState stracker = r.getTracker(); if (stracker != null) { stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity); @@ -432,9 +445,9 @@ public final class ActiveServices { if (DEBUG_DELAYED_SERVICE) { RuntimeException here = new RuntimeException("here"); here.fillInStackTrace(); - Slog.v(TAG, "Starting background (first=" + first + "): " + r, here); + Slog.v(TAG_SERVICE, "Starting background (first=" + first + "): " + r, here); } else if (DEBUG_DELAYED_STARTS) { - Slog.v(TAG, "Starting background (first=" + first + "): " + r); + Slog.v(TAG_SERVICE, "Starting background (first=" + first + "): " + r); } if (first) { smap.rescheduleDelayedStarts(); @@ -451,7 +464,7 @@ public final class ActiveServices { // 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_STARTS) Slog.v(TAG, "Delaying stop of pending: " + service); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Delaying stop of pending: " + service); service.delayedStop = true; return; } @@ -469,7 +482,7 @@ public final class ActiveServices { int stopServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int userId) { - if (DEBUG_SERVICE) Slog.v(TAG, "stopService: " + service + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "stopService: " + service + " type=" + resolvedType); final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); @@ -525,7 +538,7 @@ public final class ActiveServices { boolean stopServiceTokenLocked(ComponentName className, IBinder token, int startId) { - if (DEBUG_SERVICE) Slog.v(TAG, "stopServiceToken: " + className + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "stopServiceToken: " + className + " " + token + " startId=" + startId); ServiceRecord r = findServiceLocked(className, token, UserHandle.getCallingUserId()); if (r != null) { @@ -684,10 +697,10 @@ public final class ActiveServices { return false; } - int bindServiceLocked(IApplicationThread caller, IBinder token, - Intent service, String resolvedType, - IServiceConnection connection, int flags, int userId) { - if (DEBUG_SERVICE) Slog.v(TAG, "bindService: " + service + int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, + String resolvedType, IServiceConnection connection, int flags, int userId) + throws TransactionTooLargeException { + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service + " type=" + resolvedType + " conn=" + connection.asBinder() + " flags=0x" + Integer.toHexString(flags)); final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); @@ -751,7 +764,7 @@ public final class ActiveServices { try { if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) { - if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: " + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "BIND SERVICE WHILE RESTART PENDING: " + s); } @@ -819,7 +832,7 @@ public final class ActiveServices { mAm.updateOomAdjLocked(s.app); } - if (DEBUG_SERVICE) Slog.v(TAG, "Bind " + s + " with " + b + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b + ": received=" + b.intent.received + " apps=" + b.intent.apps.size() + " doRebind=" + b.intent.doRebind); @@ -857,7 +870,7 @@ public final class ActiveServices { void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) { final long origId = Binder.clearCallingIdentity(); try { - if (DEBUG_SERVICE) Slog.v(TAG, "PUBLISHING " + r + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r + " " + intent + ": " + service); if (r != null) { Intent.FilterComparison filter @@ -873,14 +886,14 @@ public final class ActiveServices { ConnectionRecord c = clist.get(i); if (!filter.equals(c.binding.intent.intent)) { if (DEBUG_SERVICE) Slog.v( - TAG, "Not publishing to: " + c); + TAG_SERVICE, "Not publishing to: " + c); if (DEBUG_SERVICE) Slog.v( - TAG, "Bound intent: " + c.binding.intent.intent); + TAG_SERVICE, "Bound intent: " + c.binding.intent.intent); if (DEBUG_SERVICE) Slog.v( - TAG, "Published intent: " + intent); + TAG_SERVICE, "Published intent: " + intent); continue; } - if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Publishing to: " + c); try { c.conn.connected(r.name, service); } catch (Exception e) { @@ -901,7 +914,7 @@ public final class ActiveServices { boolean unbindServiceLocked(IServiceConnection connection) { IBinder binder = connection.asBinder(); - if (DEBUG_SERVICE) Slog.v(TAG, "unbindService: conn=" + binder); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "unbindService: conn=" + binder); ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder); if (clist == null) { Slog.w(TAG, "Unbind failed: could not find connection for " @@ -945,7 +958,7 @@ public final class ActiveServices { Intent.FilterComparison filter = new Intent.FilterComparison(intent); IntentBindRecord b = r.bindings.get(filter); - if (DEBUG_SERVICE) Slog.v(TAG, "unbindFinished in " + r + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "unbindFinished in " + r + " at " + b + ": apps=" + (b != null ? b.apps.size() : 0)); @@ -963,7 +976,11 @@ public final class ActiveServices { break; } } - requestServiceBindingLocked(r, b, inFg, true); + try { + requestServiceBindingLocked(r, b, inFg, true); + } catch (TransactionTooLargeException e) { + // Don't pass this back to ActivityThread, it's unrelated. + } } else { // Note to tell the service the next time there is // a new client. @@ -1012,7 +1029,7 @@ public final class ActiveServices { String resolvedType, int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg) { ServiceRecord r = null; - if (DEBUG_SERVICE) Slog.v(TAG, "retrieveServiceLocked: " + service + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service + " type=" + resolvedType + " callingUid=" + callingUid); userId = mAm.handleIncomingUser(callingPid, callingUid, userId, @@ -1036,7 +1053,7 @@ public final class ActiveServices { ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; if (sInfo == null) { - Slog.w(TAG, "Unable to start service " + service + " U=" + userId + + Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId + ": not found"); return null; } @@ -1110,9 +1127,9 @@ public final class ActiveServices { } private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) { - if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING " + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING " + why + " of " + r + " in app " + r.app); - else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, ">>> EXECUTING " + else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING " + why + " of " + r.shortName); long now = SystemClock.uptimeMillis(); if (r.executeNesting == 0) { @@ -1137,8 +1154,8 @@ public final class ActiveServices { r.executingStart = now; } - private final boolean requestServiceBindingLocked(ServiceRecord r, - IntentBindRecord i, boolean execInFg, boolean rebind) { + private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, + boolean execInFg, boolean rebind) throws TransactionTooLargeException { if (r.app == null || r.app.thread == null) { // If service is not currently running, can't yet bind. return false; @@ -1154,8 +1171,17 @@ public final class ActiveServices { } i.hasBound = true; i.doRebind = false; + } catch (TransactionTooLargeException e) { + // Keep the executeNesting count accurate. + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e); + final boolean inDestroying = mDestroyingServices.contains(r); + serviceDoneExecutingLocked(r, inDestroying, inDestroying); + throw e; } catch (RemoteException e) { - if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while binding " + r); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r); + // Keep the executeNesting count accurate. + final boolean inDestroying = mDestroyingServices.contains(r); + serviceDoneExecutingLocked(r, inDestroying, inDestroying); return false; } } @@ -1280,7 +1306,11 @@ public final class ActiveServices { if (!mRestartingServices.contains(r)) { return; } - bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true); + try { + bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true); + } catch (TransactionTooLargeException e) { + // Ignore, it's been logged and nothing upstack cares. + } } private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid, @@ -1321,8 +1351,8 @@ public final class ActiveServices { } } - private final String bringUpServiceLocked(ServiceRecord r, - int intentFlags, boolean execInFg, boolean whileRestarting) { + private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, + boolean whileRestarting) throws TransactionTooLargeException { //Slog.i(TAG, "Bring up service:"); //r.dump(" "); @@ -1336,17 +1366,18 @@ public final class ActiveServices { return null; } - if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up " + r + " " + r.intent); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent); // We are now bringing the service up, so no longer in the // restarting state. if (mRestartingServices.remove(r)) { + r.resetRestartCounter(); clearRestartingIfNeededLocked(r); } // Make sure this service is no longer considered delayed, we are starting it now. if (r.delayed) { - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (bring up): " + r); getServiceMap(r.userId).mDelayedStartList.remove(r); r.delayed = false; } @@ -1386,6 +1417,8 @@ public final class ActiveServices { app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats); realStartServiceLocked(r, app, execInFg); return null; + } catch (TransactionTooLargeException e) { + throw e; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting service " + r.shortName, e); } @@ -1429,7 +1462,8 @@ public final class ActiveServices { // Oh and hey we've already been asked to stop! r.delayedStop = false; if (r.startRequested) { - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, + "Applying delayed stop (in bring up): " + r); stopServiceLocked(r); } } @@ -1437,7 +1471,8 @@ public final class ActiveServices { return null; } - private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) { + private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) + throws TransactionTooLargeException { for (int i=r.bindings.size()-1; i>=0; i--) { IntentBindRecord ibr = r.bindings.valueAt(i); if (!requestServiceBindingLocked(r, ibr, execInFg, false)) { @@ -1457,7 +1492,7 @@ public final class ActiveServices { r.app = app; r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); - app.services.add(r); + final boolean newService = app.services.add(r); bumpServiceExecutingLocked(r, execInFg, "create"); mAm.updateLruProcessLocked(app, false, null); mAm.updateOomAdjLocked(); @@ -1484,12 +1519,23 @@ public final class ActiveServices { } catch (DeadObjectException e) { Slog.w(TAG, "Application dead when creating service " + r); mAm.appDiedLocked(app); + throw e; } finally { if (!created) { - app.services.remove(r); - r.app = null; - scheduleServiceRestartLocked(r, false); - return; + // Keep the executeNesting count accurate. + final boolean inDestroying = mDestroyingServices.contains(r); + serviceDoneExecutingLocked(r, inDestroying, inDestroying); + + // Cleanup. + if (newService) { + app.services.remove(r); + r.app = null; + } + + // Retry. + if (!inDestroying) { + scheduleServiceRestartLocked(r, false); + } } } @@ -1508,7 +1554,7 @@ public final class ActiveServices { sendServiceArgsLocked(r, execInFg, true); if (r.delayed) { - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r); getServiceMap(r.userId).mDelayedStartList.remove(r); r.delayed = false; } @@ -1517,23 +1563,26 @@ public final class ActiveServices { // Oh and hey we've already been asked to stop! r.delayedStop = false; if (r.startRequested) { - if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (from start): " + r); + if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, + "Applying delayed stop (from start): " + r); stopServiceLocked(r); } } } private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, - boolean oomAdjusted) { + boolean oomAdjusted) throws TransactionTooLargeException { final int N = r.pendingStarts.size(); if (N == 0) { return; } while (r.pendingStarts.size() > 0) { + Exception caughtException = null; + ServiceRecord.StartItem si; try { - ServiceRecord.StartItem si = r.pendingStarts.remove(0); - if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to: " + si = r.pendingStarts.remove(0); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Sending arguments to: " + r + " " + r.intent + " args=" + si.intent); if (si.intent == null && N > 1) { // If somehow we got a dummy null intent in the middle, @@ -1562,13 +1611,26 @@ public final class ActiveServices { flags |= Service.START_FLAG_REDELIVERY; } r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); + } catch (TransactionTooLargeException e) { + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large: intent=" + + si.intent); + caughtException = e; } catch (RemoteException e) { - // Remote process gone... we'll let the normal cleanup take - // care of this. - if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r); - break; + // Remote process gone... we'll let the normal cleanup take care of this. + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r); + caughtException = e; } catch (Exception e) { Slog.w(TAG, "Unexpected exception", e); + caughtException = e; + } + + if (caughtException != null) { + // Keep nesting count correct + final boolean inDestroying = mDestroyingServices.contains(r); + serviceDoneExecutingLocked(r, inDestroying, inDestroying); + if (caughtException instanceof TransactionTooLargeException) { + throw (TransactionTooLargeException)caughtException; + } break; } } @@ -1635,7 +1697,7 @@ public final class ActiveServices { 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 + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down binding " + ibr + ": hasBound=" + ibr.hasBound); if (ibr.hasBound) { try { @@ -1653,7 +1715,7 @@ public final class ActiveServices { } } - if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down " + r + " " + r.intent); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down " + r + " " + r.intent); r.destroyTime = SystemClock.uptimeMillis(); if (LOG_SERVICE_START_STOP) { EventLogTags.writeAmDestroyService( @@ -1670,7 +1732,7 @@ public final class ActiveServices { for (int i=mPendingServices.size()-1; i>=0; i--) { if (mPendingServices.get(i) == r) { mPendingServices.remove(i); - if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Removed pending: " + r); } } @@ -1703,11 +1765,11 @@ public final class ActiveServices { } } else { if (DEBUG_SERVICE) Slog.v( - TAG, "Removed service that has no process: " + r); + TAG_SERVICE, "Removed service that has no process: " + r); } } else { if (DEBUG_SERVICE) Slog.v( - TAG, "Removed service that is not running: " + r); + TAG_SERVICE, "Removed service that is not running: " + r); } if (r.bindings.size() > 0) { @@ -1774,7 +1836,7 @@ public final class ActiveServices { } if (!c.serviceDead) { - if (DEBUG_SERVICE) Slog.v(TAG, "Disconnecting binding " + b.intent + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Disconnecting binding " + b.intent + ": shouldUnbind=" + b.intent.hasBound); if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0 && b.intent.hasBound) { @@ -1876,6 +1938,7 @@ public final class ActiveServices { } else if (r.executeNesting != 1) { Slog.wtfStack(TAG, "Service done with onDestroy, but executeNesting=" + r.executeNesting + ": " + r); + // Fake it to keep from ANR due to orphaned entry. r.executeNesting = 1; } } @@ -1901,19 +1964,20 @@ public final class ActiveServices { private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) { - if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r + ": nesting=" + r.executeNesting + ", inDestroying=" + inDestroying + ", app=" + r.app); - else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName); + else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, + "<<< DONE EXECUTING " + r.shortName); r.executeNesting--; if (r.executeNesting <= 0) { if (r.app != null) { - if (DEBUG_SERVICE) Slog.v(TAG, + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "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, + if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, "No more executingServices of " + r.shortName); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); } else if (r.executeFg) { @@ -1926,7 +1990,7 @@ public final class ActiveServices { } } if (inDestroying) { - if (DEBUG_SERVICE) Slog.v(TAG, + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "doneExecuting remove destroying " + r); mDestroyingServices.remove(r); r.bindings.clear(); @@ -2090,7 +2154,11 @@ public final class ActiveServices { if (sr.app != null && sr.app.thread != null) { // We always run in the foreground, since this is called as // part of the "remove task" UI operation. - sendServiceArgsLocked(sr, true, false); + try { + sendServiceArgsLocked(sr, true, false); + } catch (TransactionTooLargeException e) { + // Ignore, keep going. + } } } } @@ -2126,8 +2194,16 @@ public final class ActiveServices { } } - // First clear app state from services. - for (int i=app.services.size()-1; i>=0; i--) { + // Clean up any connections this application has to other services. + for (int i = app.connections.size() - 1; i >= 0; i--) { + ConnectionRecord r = app.connections.valueAt(i); + removeConnectionLocked(r, app, null); + } + updateServiceConnectionActivitiesLocked(app); + app.connections.clear(); + + // Clear app state from services. + for (int i = app.services.size() - 1; i >= 0; i--) { ServiceRecord sr = app.services.valueAt(i); synchronized (sr.stats.getBatteryStats()) { sr.stats.stopLaunchedLocked(); @@ -2140,13 +2216,13 @@ public final class ActiveServices { sr.executeNesting = 0; sr.forceClearTracker(); if (mDestroyingServices.remove(sr)) { - if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "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 + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Killing binding " + b + ": shouldUnbind=" + b.hasBound); b.binder = null; b.requested = b.received = b.hasBound = false; @@ -2187,14 +2263,6 @@ public final class ActiveServices { } } - // Clean up any connections this application has to other services. - for (int i=app.connections.size()-1; i>=0; i--) { - ConnectionRecord r = app.connections.valueAt(i); - removeConnectionLocked(r, app, null); - } - updateServiceConnectionActivitiesLocked(app); - app.connections.clear(); - ServiceMap smap = getServiceMap(app.userId); // Now do remaining service cleanup. @@ -2227,7 +2295,7 @@ public final class ActiveServices { EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, sr.userId, sr.crashCount, sr.shortName, app.pid); bringDownServiceLocked(sr); - } else if (!allowRestart) { + } else if (!allowRestart || !mAm.isUserRunningLocked(sr.userId, false)) { bringDownServiceLocked(sr); } else { boolean canceled = scheduleServiceRestartLocked(sr, true); @@ -2280,7 +2348,7 @@ public final class ActiveServices { if (sr.app == app) { sr.forceClearTracker(); mDestroyingServices.remove(i); - if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "killServices remove destroying " + sr); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java new file mode 100644 index 0000000..d64e39f --- /dev/null +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 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; + +/** + * Common class for the various debug {@link android.util.Log} output configuration in the activity + * manager package. + */ +class ActivityManagerDebugConfig { + + // All output logs in the activity manager package use the {@link #TAG_AM} string for tagging + // their log output. This makes it easy to identify the origin of the log message when sifting + // through a large amount of log output from multiple sources. However, it also makes trying + // to figure-out the origin of a log message while debugging the activity manager a little + // painful. By setting this constant to true, log messages from the activity manager package + // will be tagged with their class names instead fot the generic tag. + static final boolean TAG_WITH_CLASS_NAME = false; + + // While debugging it is sometimes useful to have the category name of the log appended to the + // base log tag to make sifting through logs with the same base tag easier. By setting this + // constant to true, the category name of the log point will be appended to the log tag. + static final boolean APPEND_CATEGORY_NAME = false; + + // Default log tag for the activity manager package. + static final String TAG_AM = "ActivityManager"; + + // Enable all debug log categories. + static final boolean DEBUG_ALL = false; + + // Available log categories in the activity manager package. + static final boolean DEBUG_BACKUP = DEBUG_ALL || false; + static final boolean DEBUG_BROADCAST = DEBUG_ALL || false; + static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false; + static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; + static final boolean DEBUG_CLEANUP = DEBUG_ALL || false; + static final boolean DEBUG_CONFIGURATION = DEBUG_ALL || false; + static final boolean DEBUG_FOCUS = false; + static final boolean DEBUG_IMMERSIVE = DEBUG_ALL || false; + static final boolean DEBUG_LOCKSCREEN = DEBUG_ALL || false; + static final boolean DEBUG_LRU = DEBUG_ALL || false; + static final boolean DEBUG_MU = DEBUG_ALL || false; + static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false; + static final boolean DEBUG_PAUSE = DEBUG_ALL || false; + static final boolean DEBUG_POWER = DEBUG_ALL || false; + static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; + static final boolean DEBUG_PROCESS_OBSERVERS = DEBUG_ALL || false; + static final boolean DEBUG_PROCESSES = DEBUG_ALL || false; + static final boolean DEBUG_PROVIDER = DEBUG_ALL || false; + static final boolean DEBUG_PSS = DEBUG_ALL || false; + static final boolean DEBUG_RECENTS = DEBUG_ALL || false; + static final boolean DEBUG_RESULTS = DEBUG_ALL || false; + static final boolean DEBUG_SERVICE = DEBUG_ALL || false; + static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false; + static final boolean DEBUG_STACK = DEBUG_ALL || false; + static final boolean DEBUG_SWITCH = DEBUG_ALL || false; + static final boolean DEBUG_TASKS = DEBUG_ALL || false; + static final boolean DEBUG_THUMBNAILS = DEBUG_ALL || false; + static final boolean DEBUG_TRANSITION = DEBUG_ALL || false; + static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false; + static final boolean DEBUG_USER_LEAVING = DEBUG_ALL || false; + static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false; + static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false; + + static final String POSTFIX_BACKUP = (APPEND_CATEGORY_NAME) ? "_Backup" : ""; + static final String POSTFIX_BROADCAST = (APPEND_CATEGORY_NAME) ? "_Broadcast" : ""; + static final String POSTFIX_CLEANUP = (APPEND_CATEGORY_NAME) ? "_Cleanup" : ""; + static final String POSTFIX_CONFIGURATION = (APPEND_CATEGORY_NAME) ? "_Configuration" : ""; + static final String POSTFIX_FOCUS = (APPEND_CATEGORY_NAME) ? "_Focus" : ""; + static final String POSTFIX_IMMERSIVE = (APPEND_CATEGORY_NAME) ? "_Immersive" : ""; + static final String POSTFIX_LOCKSCREEN = (APPEND_CATEGORY_NAME) ? "_LOCKSCREEN" : ""; + static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : ""; + static final String POSTFIX_MU = "_MU"; + static final String POSTFIX_OOM_ADJ = (APPEND_CATEGORY_NAME) ? "_OomAdj" : ""; + static final String POSTFIX_PAUSE = (APPEND_CATEGORY_NAME) ? "_Pause" : ""; + static final String POSTFIX_POWER = (APPEND_CATEGORY_NAME) ? "_Power" : ""; + static final String POSTFIX_PROCESS_OBSERVERS = (APPEND_CATEGORY_NAME) + ? "_ProcessObservers" : ""; + static final String POSTFIX_PROCESSES = (APPEND_CATEGORY_NAME) ? "_Processes" : ""; + static final String POSTFIX_PROVIDER = (APPEND_CATEGORY_NAME) ? "_Provider" : ""; + static final String POSTFIX_PSS = (APPEND_CATEGORY_NAME) ? "_Pss" : ""; + static final String POSTFIX_RESULTS = (APPEND_CATEGORY_NAME) ? "_Results" : ""; + static final String POSTFIX_RECENTS = (APPEND_CATEGORY_NAME) ? "_Recents" : ""; + static final String POSTFIX_SERVICE = (APPEND_CATEGORY_NAME) ? "_Service" : ""; + static final String POSTFIX_SERVICE_EXECUTING = + (APPEND_CATEGORY_NAME) ? "_ServiceExecuting" : ""; + static final String POSTFIX_STACK = (APPEND_CATEGORY_NAME) ? "_Stack" : ""; + static final String POSTFIX_SWITCH = (APPEND_CATEGORY_NAME) ? "_Switch" : ""; + static final String POSTFIX_TASKS = (APPEND_CATEGORY_NAME) ? "_Tasks" : ""; + static final String POSTFIX_THUMBNAILS = (APPEND_CATEGORY_NAME) ? "_Thumbnails" : ""; + static final String POSTFIX_TRANSITION = (APPEND_CATEGORY_NAME) ? "_Transition" : ""; + static final String POSTFIX_URI_PERMISSION = (APPEND_CATEGORY_NAME) ? "_UriPermission" : ""; + static final String POSTFIX_USER_LEAVING = (APPEND_CATEGORY_NAME) ? "_UserLeaving" : ""; + static final String POSTFIX_VISIBILITY = (APPEND_CATEGORY_NAME) ? "_Visibility" : ""; + +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e8f3757..4970e0f 100755..100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -27,10 +27,14 @@ import static com.android.internal.util.XmlUtils.writeBooleanAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST; -import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; -import static org.xmlpull.v1.XmlPullParser.START_TAG; +import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.START_TAG; import android.Manifest; import android.app.AppOpsManager; @@ -40,7 +44,6 @@ import android.app.IActivityContainerCallback; import android.app.IAppTask; import android.app.ITaskStackListener; import android.app.ProfilerInfo; -import android.app.admin.DevicePolicyManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; @@ -50,24 +53,32 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.BatteryStats; import android.os.PersistableBundle; +import android.os.PowerManager; +import android.os.TransactionTooLargeException; +import android.os.WorkSource; import android.os.storage.IMountService; import android.os.storage.StorageManager; import android.service.voice.IVoiceInteractionSession; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.DebugUtils; import android.util.SparseIntArray; +import android.view.Display; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.DumpHeapActivity; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ProcessMap; import com.android.internal.app.ProcessStats; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.MemInfoReader; @@ -91,6 +102,7 @@ import com.google.android.collect.Lists; import com.google.android.collect.Maps; import libcore.io.IoUtils; +import libcore.util.EmptyArray; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -101,6 +113,7 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; import android.app.ActivityManagerInternal; +import android.app.ActivityManagerInternal.SleepToken; import android.app.ActivityManagerNative; import android.app.ActivityOptions; import android.app.ActivityThread; @@ -169,6 +182,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.IPermissionController; +import android.os.IProcessInfoService; import android.os.IRemoteCallback; import android.os.IUserManager; import android.os.Looper; @@ -241,49 +255,37 @@ public final class ActivityManagerService extends ActivityManagerNative // File that stores last updated system version and called preboot receivers static final String CALLED_PRE_BOOTS_FILENAME = "called_pre_boots.dat"; - 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_BACKUP = localLOGV || false; - static final boolean DEBUG_BROADCAST = localLOGV || false; - static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; - static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false; - static final boolean DEBUG_CLEANUP = 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_LRU = 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_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 DEBUG_RECENTS = 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. + private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM; + private static final String TAG_BACKUP = TAG + POSTFIX_BACKUP; + private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST; + private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP; + private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; + private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; + private static final String TAG_IMMERSIVE = TAG + POSTFIX_IMMERSIVE; + private static final String TAG_LOCKSCREEN = TAG + POSTFIX_LOCKSCREEN; + private static final String TAG_LRU = TAG + POSTFIX_LRU; + private static final String TAG_MU = TAG + POSTFIX_MU; + private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ; + private static final String TAG_POWER = TAG + POSTFIX_POWER; + private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS; + private static final String TAG_PROCESSES = TAG + POSTFIX_PROCESSES; + private static final String TAG_PROVIDER = TAG + POSTFIX_PROVIDER; + private static final String TAG_PSS = TAG + POSTFIX_PSS; + private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; + private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE; + private static final String TAG_STACK = TAG + POSTFIX_STACK; + private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; + private static final String TAG_URI_PERMISSION = TAG + POSTFIX_URI_PERMISSION; + private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY; + + /** Control over CPU and battery monitoring */ + // write battery stats every 30 minutes. + static final long BATTERY_STATS_TIME = 30 * 60 * 1000; static final boolean MONITOR_CPU_USAGE = true; - static final long MONITOR_CPU_MIN_TIME = 5*1000; // don't sample cpu less than every 5 seconds. - static final long MONITOR_CPU_MAX_TIME = 0x0fffffff; // wait possibly forever for next cpu sample. + // don't sample cpu less than every 5 seconds. + static final long MONITOR_CPU_MIN_TIME = 5 * 1000; + // wait possibly forever for next cpu sample. + static final long MONITOR_CPU_MAX_TIME = 0x0fffffff; static final boolean MONITOR_THREAD_CPU_USAGE = false; // The flags that are set for all calls we make to the package manager. @@ -293,9 +295,6 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean IS_USER_BUILD = "user".equals(Build.TYPE); - // Maximum number recent bitmaps to keep in memory. - static final int MAX_RECENT_BITMAPS = 3; - // 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; @@ -402,24 +401,12 @@ public final class ActivityManagerService extends ActivityManagerNative BroadcastQueue broadcastQueueForIntent(Intent intent) { final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0; - if (DEBUG_BACKGROUND_BROADCAST) { - Slog.i(TAG, "Broadcast intent " + intent + " on " - + (isFg ? "foreground" : "background") - + " queue"); - } + if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST, + "Broadcast intent " + intent + " on " + + (isFg ? "foreground" : "background") + " queue"); return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue; } - BroadcastRecord broadcastRecordForReceiverLocked(IBinder receiver) { - for (BroadcastQueue queue : mBroadcastQueues) { - BroadcastRecord r = queue.getMatchingOrderedReceiver(receiver); - if (r != null) { - return r; - } - } - return null; - } - /** * Activity we have told the window manager to have key focus. */ @@ -428,8 +415,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * List of intents that were used to start the most recent tasks. */ - ArrayList<TaskRecord> mRecentTasks; - ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>(); + private final RecentTasks mRecentTasks; /** * For addAppTask: cached of the last activity component that was added. @@ -446,28 +432,43 @@ public final class ActivityManagerService extends ActivityManagerNative */ ActivityInfo mLastAddedTaskActivity; + /** + * List of packages whitelisted by DevicePolicyManager for locktask. Indexed by userId. + */ + SparseArray<String[]> mLockTaskPackages = new SparseArray<>(); + + /** + * The package name of the DeviceOwner. This package is not permitted to have its data cleared. + */ + String mDeviceOwnerName; + public class PendingAssistExtras extends Binder implements Runnable { public final ActivityRecord activity; public final Bundle extras; public final Intent intent; public final String hint; + public final IResultReceiver receiver; public final int userHandle; public boolean haveResult = false; public Bundle result = null; public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent, - String _hint, int _userHandle) { + String _hint, IResultReceiver _receiver, int _userHandle) { activity = _activity; extras = _extras; intent = _intent; hint = _hint; + receiver = _receiver; userHandle = _userHandle; } @Override public void run() { Slog.w(TAG, "getAssistContextExtras failed: timeout retrieving from " + activity); - synchronized (this) { - haveResult = true; - notifyAll(); + synchronized (ActivityManagerService.this) { + synchronized (this) { + haveResult = true; + notifyAll(); + } + pendingAssistExtrasTimedOutLocked(this); } } } @@ -994,16 +995,36 @@ public final class ActivityManagerService extends ActivityManagerNative private boolean mSleeping = false; /** + * The process state used for processes that are running the top activities. + * This changes between TOP and TOP_SLEEPING to following mSleeping. + */ + int mTopProcessState = ActivityManager.PROCESS_STATE_TOP; + + /** * Set while we are running a voice interaction. This overrides * sleeping while it is active. */ - private boolean mRunningVoice = false; + private IVoiceInteractionSession mRunningVoice; + + /** + * We want to hold a wake lock while running a voice interaction session, since + * this may happen with the screen off and we need to keep the CPU running to + * be able to continue to interact with the user. + */ + PowerManager.WakeLock mVoiceWakeLock; /** * State of external calls telling us if the device is awake or asleep. */ private int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE; + /** + * A list of tokens that cause the top activity to be put to sleep. + * They are used by components that may hide and block interaction with underlying + * activities. + */ + final ArrayList<SleepToken> mSleepTokens = new ArrayList<SleepToken>(); + static final int LOCK_SCREEN_HIDDEN = 0; static final int LOCK_SCREEN_LEAVING = 1; static final int LOCK_SCREEN_SHOWN = 2; @@ -1129,6 +1150,11 @@ public final class ActivityManagerService extends ActivityManagerNative boolean mAutoStopProfiler = false; int mProfileType = 0; String mOpenGlTraceApp = null; + final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>(); + String mMemWatchDumpProcName; + String mMemWatchDumpFile; + int mMemWatchDumpPid; + int mMemWatchDumpUid; final long[] mTmpLong = new long[1]; @@ -1211,7 +1237,7 @@ public final class ActivityManagerService extends ActivityManagerNative AppDeathRecipient(ProcessRecord app, int pid, IApplicationThread thread) { - if (localLOGV) Slog.v( + if (DEBUG_ALL) Slog.v( TAG, "New death recipient " + this + " for thread " + thread.asBinder()); mApp = app; @@ -1221,11 +1247,11 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void binderDied() { - if (localLOGV) Slog.v( + if (DEBUG_ALL) Slog.v( TAG, "Death received in " + this + " for thread " + mAppThread.asBinder()); synchronized(ActivityManagerService.this) { - appDiedLocked(mApp, mPid, mAppThread); + appDiedLocked(mApp, mPid, mAppThread, true); } } } @@ -1270,6 +1296,10 @@ public final class ActivityManagerService extends ActivityManagerNative static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47; static final int DISMISS_DIALOG_MSG = 48; static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG = 49; + static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 50; + static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51; + static final int DELETE_DUMPHEAP_MSG = 52; + static final int FOREGROUND_PROFILE_CHANGED_MSG = 53; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1294,10 +1324,11 @@ public final class ActivityManagerService extends ActivityManagerNative final ServiceThread mHandlerThread; final MainHandler mHandler; + final UiHandler mUiHandler; - final class MainHandler extends Handler { - public MainHandler(Looper looper) { - super(looper, null, true); + final class UiHandler extends Handler { + public UiHandler() { + super(com.android.server.UiThread.get().getLooper(), null, true); } @Override @@ -1410,15 +1441,6 @@ public final class ActivityManagerService extends ActivityManagerNative d.show(); ensureBootCompleted(); } break; - case UPDATE_CONFIGURATION_MSG: { - final ContentResolver resolver = mContext.getContentResolver(); - Settings.System.putConfiguration(resolver, (Configuration)msg.obj); - } break; - case GC_BACKGROUND_PROCESSES_MSG: { - synchronized (ActivityManagerService.this) { - performAppGcsIfAppropriateLocked(); - } - } break; case WAIT_FOR_DEBUGGER_MSG: { synchronized (ActivityManagerService.this) { ProcessRecord app = (ProcessRecord)msg.obj; @@ -1439,6 +1461,88 @@ public final class ActivityManagerService extends ActivityManagerNative } } } break; + case SHOW_UID_ERROR_MSG: { + if (mShowDialogs) { + AlertDialog d = new BaseErrorDialog(mContext); + d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); + d.setCancelable(false); + d.setTitle(mContext.getText(R.string.android_system_label)); + d.setMessage(mContext.getText(R.string.system_error_wipe_data)); + d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok), + obtainMessage(DISMISS_DIALOG_MSG, d)); + d.show(); + } + } break; + case SHOW_FINGERPRINT_ERROR_MSG: { + if (mShowDialogs) { + AlertDialog d = new BaseErrorDialog(mContext); + d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); + d.setCancelable(false); + d.setTitle(mContext.getText(R.string.android_system_label)); + d.setMessage(mContext.getText(R.string.system_error_manufacturer)); + d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok), + obtainMessage(DISMISS_DIALOG_MSG, d)); + d.show(); + } + } break; + case SHOW_COMPAT_MODE_DIALOG_MSG: { + synchronized (ActivityManagerService.this) { + ActivityRecord ar = (ActivityRecord) msg.obj; + if (mCompatModeDialog != null) { + if (mCompatModeDialog.mAppInfo.packageName.equals( + ar.info.applicationInfo.packageName)) { + return; + } + mCompatModeDialog.dismiss(); + mCompatModeDialog = null; + } + if (ar != null && false) { + if (mCompatModePackages.getPackageAskCompatModeLocked( + ar.packageName)) { + int mode = mCompatModePackages.computeCompatModeLocked( + ar.info.applicationInfo); + if (mode == ActivityManager.COMPAT_MODE_DISABLED + || mode == ActivityManager.COMPAT_MODE_ENABLED) { + mCompatModeDialog = new CompatModeDialog( + ActivityManagerService.this, mContext, + ar.info.applicationInfo); + mCompatModeDialog.show(); + } + } + } + } + break; + } + case START_USER_SWITCH_MSG: { + showUserSwitchDialog(msg.arg1, (String) msg.obj); + break; + } + case DISMISS_DIALOG_MSG: { + final Dialog d = (Dialog) msg.obj; + d.dismiss(); + break; + } + } + } + } + + final class MainHandler extends Handler { + public MainHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case UPDATE_CONFIGURATION_MSG: { + final ContentResolver resolver = mContext.getContentResolver(); + Settings.System.putConfiguration(resolver, (Configuration) msg.obj); + } break; + case GC_BACKGROUND_PROCESSES_MSG: { + synchronized (ActivityManagerService.this) { + performAppGcsIfAppropriateLocked(); + } + } break; case SERVICE_TIMEOUT_MSG: { if (mDidDexOpt) { mDidDexOpt = false; @@ -1503,30 +1607,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } } break; - case SHOW_UID_ERROR_MSG: { - if (mShowDialogs) { - AlertDialog d = new BaseErrorDialog(mContext); - d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); - d.setCancelable(false); - d.setTitle(mContext.getText(R.string.android_system_label)); - d.setMessage(mContext.getText(R.string.system_error_wipe_data)); - d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok), - mHandler.obtainMessage(DISMISS_DIALOG_MSG, d)); - d.show(); - } - } break; - case SHOW_FINGERPRINT_ERROR_MSG: { - if (mShowDialogs) { - AlertDialog d = new BaseErrorDialog(mContext); - d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); - d.setCancelable(false); - d.setTitle(mContext.getText(R.string.android_system_label)); - d.setMessage(mContext.getText(R.string.system_error_manufacturer)); - d.setButton(DialogInterface.BUTTON_POSITIVE, mContext.getText(R.string.ok), - mHandler.obtainMessage(DISMISS_DIALOG_MSG, d)); - d.show(); - } - } break; case PROC_START_TIMEOUT_MSG: { if (mDidDexOpt) { mDidDexOpt = false; @@ -1583,7 +1663,7 @@ public final class ActivityManagerService extends ActivityManagerNative notification.defaults = 0; // please be quiet notification.sound = null; notification.vibrate = null; - notification.color = mContext.getResources().getColor( + notification.color = mContext.getColor( com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(context, text, mContext.getText(R.string.heavy_weight_notification_detail), @@ -1627,34 +1707,6 @@ public final class ActivityManagerService extends ActivityManagerNative sendMessageDelayed(nmsg, POWER_CHECK_DELAY); } } break; - case SHOW_COMPAT_MODE_DIALOG_MSG: { - synchronized (ActivityManagerService.this) { - ActivityRecord ar = (ActivityRecord)msg.obj; - if (mCompatModeDialog != null) { - if (mCompatModeDialog.mAppInfo.packageName.equals( - ar.info.applicationInfo.packageName)) { - return; - } - mCompatModeDialog.dismiss(); - mCompatModeDialog = null; - } - if (ar != null && false) { - if (mCompatModePackages.getPackageAskCompatModeLocked( - ar.packageName)) { - int mode = mCompatModePackages.computeCompatModeLocked( - ar.info.applicationInfo); - if (mode == ActivityManager.COMPAT_MODE_DISABLED - || mode == ActivityManager.COMPAT_MODE_ENABLED) { - mCompatModeDialog = new CompatModeDialog( - ActivityManagerService.this, mContext, - ar.info.applicationInfo); - mCompatModeDialog.show(); - } - } - } - } - break; - } case DISPATCH_PROCESSES_CHANGED: { dispatchProcessesChanged(); break; @@ -1675,10 +1727,6 @@ public final class ActivityManagerService extends ActivityManagerNative thread.start(); break; } - case START_USER_SWITCH_MSG: { - showUserSwitchDialog(msg.arg1, (String) msg.obj); - break; - } case REPORT_USER_SWITCH_MSG: { dispatchUserSwitch((UserStartedState) msg.obj, msg.arg1, msg.arg2); break; @@ -1694,10 +1742,9 @@ public final class ActivityManagerService extends ActivityManagerNative case IMMERSIVE_MODE_LOCK_MSG: { final boolean nextState = (msg.arg1 != 0); if (mUpdateLock.isHeld() != nextState) { - if (DEBUG_IMMERSIVE) { - final ActivityRecord r = (ActivityRecord) msg.obj; - Slog.d(TAG, "Applying new update lock state '" + nextState + "' for " + r); - } + if (DEBUG_IMMERSIVE) Slog.d(TAG_IMMERSIVE, + "Applying new update lock state '" + nextState + + "' for " + (ActivityRecord)msg.obj); if (nextState) { mUpdateLock.acquire(); } else { @@ -1755,7 +1802,7 @@ public final class ActivityManagerService extends ActivityManagerNative } case ENTER_ANIMATION_COMPLETE_MSG: { synchronized (ActivityManagerService.this) { - ActivityRecord r = ActivityRecord.forToken((IBinder) msg.obj); + ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj); if (r != null && r.app != null && r.app.thread != null) { try { r.app.thread.scheduleEnterAnimationComplete(r.appToken); @@ -1786,11 +1833,6 @@ public final class ActivityManagerService extends ActivityManagerNative } break; } - case DISMISS_DIALOG_MSG: { - final Dialog d = (Dialog) msg.obj; - d.dismiss(); - break; - } case NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG: { synchronized (ActivityManagerService.this) { int i = mTaskStackListeners.beginBroadcast(); @@ -1807,6 +1849,111 @@ public final class ActivityManagerService extends ActivityManagerNative } break; } + case NOTIFY_CLEARTEXT_NETWORK_MSG: { + final int uid = msg.arg1; + final byte[] firstPacket = (byte[]) msg.obj; + + synchronized (mPidsSelfLocked) { + for (int i = 0; i < mPidsSelfLocked.size(); i++) { + final ProcessRecord p = mPidsSelfLocked.valueAt(i); + if (p.uid == uid) { + try { + p.thread.notifyCleartextNetwork(firstPacket); + } catch (RemoteException ignored) { + } + } + } + } + break; + } + case POST_DUMP_HEAP_NOTIFICATION_MSG: { + final String procName; + final int uid; + final long memLimit; + final String reportPackage; + synchronized (ActivityManagerService.this) { + procName = mMemWatchDumpProcName; + uid = mMemWatchDumpUid; + Pair<Long, String> val = mMemWatchProcesses.get(procName, uid); + if (val == null) { + val = mMemWatchProcesses.get(procName, 0); + } + if (val != null) { + memLimit = val.first; + reportPackage = val.second; + } else { + memLimit = 0; + reportPackage = null; + } + } + if (procName == null) { + return; + } + + if (DEBUG_PSS) Slog.d(TAG_PSS, + "Showing dump heap notification from " + procName + "/" + uid); + + INotificationManager inm = NotificationManager.getService(); + if (inm == null) { + return; + } + + String text = mContext.getString(R.string.dump_heap_notification, procName); + Notification notification = new Notification(); + notification.icon = com.android.internal.R.drawable.stat_sys_adb; + notification.when = 0; + notification.flags = Notification.FLAG_ONGOING_EVENT|Notification.FLAG_AUTO_CANCEL; + notification.tickerText = text; + notification.defaults = 0; // please be quiet + notification.sound = null; + notification.vibrate = null; + notification.color = mContext.getColor( + com.android.internal.R.color.system_notification_accent_color); + Intent deleteIntent = new Intent(); + deleteIntent.setAction(DumpHeapActivity.ACTION_DELETE_DUMPHEAP); + notification.deleteIntent = PendingIntent.getBroadcastAsUser(mContext, 0, + deleteIntent, 0, UserHandle.OWNER); + Intent intent = new Intent(); + intent.setClassName("android", DumpHeapActivity.class.getName()); + intent.putExtra(DumpHeapActivity.KEY_PROCESS, procName); + intent.putExtra(DumpHeapActivity.KEY_SIZE, memLimit); + if (reportPackage != null) { + intent.putExtra(DumpHeapActivity.KEY_DIRECT_LAUNCH, reportPackage); + } + int userId = UserHandle.getUserId(uid); + notification.setLatestEventInfo(mContext, text, + mContext.getText(R.string.dump_heap_notification_detail), + PendingIntent.getActivityAsUser(mContext, 0, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, + new UserHandle(userId))); + + try { + int[] outId = new int[1]; + inm.enqueueNotificationWithTag("android", "android", null, + R.string.dump_heap_notification, + notification, outId, userId); + } catch (RuntimeException e) { + Slog.w(ActivityManagerService.TAG, + "Error showing notification for dump heap", e); + } catch (RemoteException e) { + } + } break; + case DELETE_DUMPHEAP_MSG: { + revokeUriPermission(ActivityThread.currentActivityThread().getApplicationThread(), + DumpHeapActivity.JAVA_URI, + Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + UserHandle.myUserId()); + synchronized (ActivityManagerService.this) { + mMemWatchDumpFile = null; + mMemWatchDumpProcName = null; + mMemWatchDumpPid = -1; + mMemWatchDumpUid = -1; + } + } break; + case FOREGROUND_PROFILE_CHANGED_MSG: { + dispatchForegroundProfileChanged(msg.arg1); + } break; } } }; @@ -1848,11 +1995,16 @@ public final class ActivityManagerService extends ActivityManagerNative } memInfo.readMemInfo(); synchronized (ActivityManagerService.this) { - if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in " + if (DEBUG_PSS) Slog.d(TAG_PSS, "Collected native and kernel memory in " + (SystemClock.uptimeMillis()-start) + "ms"); - mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(), - memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(), - memInfo.getKernelUsedSizeKb(), nativeTotalPss); + final long cachedKb = memInfo.getCachedSizeKb(); + final long freeKb = memInfo.getFreeSizeKb(); + final long zramKb = memInfo.getZramTotalSizeKb(); + final long kernelKb = memInfo.getKernelUsedSizeKb(); + EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024, + kernelKb*1024, nativeTotalPss*1024); + mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, + nativeTotalPss); } } @@ -1865,8 +2017,9 @@ public final class ActivityManagerService extends ActivityManagerNative long lastPssTime; synchronized (ActivityManagerService.this) { if (mPendingPssProcesses.size() <= 0) { - if (mTestPssMode || DEBUG_PSS) Slog.d(TAG, "Collected PSS of " + num - + " processes in " + (SystemClock.uptimeMillis()-start) + "ms"); + if (mTestPssMode || DEBUG_PSS) Slog.d(TAG_PSS, + "Collected PSS of " + num + " processes in " + + (SystemClock.uptimeMillis() - start) + "ms"); mPendingPssProcesses.clear(); return; } @@ -1888,7 +2041,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (pss != 0 && proc.thread != null && proc.setProcState == procState && proc.pid == pid && proc.lastPssTime == lastPssTime) { num++; - recordPssSample(proc, procState, pss, tmp[0], + recordPssSampleLocked(proc, procState, pss, tmp[0], SystemClock.uptimeMillis()); } } @@ -1910,6 +2063,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceManager.addService("cpuinfo", new CpuBinder(this)); } ServiceManager.addService("permission", new PermissionController(this)); + ServiceManager.addService("processinfo", new ProcessInfoService(this)); ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS); @@ -2067,6 +2221,7 @@ public final class ActivityManagerService extends ActivityManagerNative android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/); mHandlerThread.start(); mHandler = new MainHandler(mHandlerThread.getLooper()); + mUiHandler = new UiHandler(); mFgBroadcastQueue = new BroadcastQueue(this, mHandler, "foreground", BROADCAST_FG_TIMEOUT, false); @@ -2084,7 +2239,7 @@ public final class ActivityManagerService extends ActivityManagerNative systemDir.mkdirs(); mBatteryStatsService = new BatteryStatsService(systemDir, mHandler); mBatteryStatsService.getActiveStatistics().readLocked(); - mBatteryStatsService.getActiveStatistics().writeAsyncLocked(); + mBatteryStatsService.scheduleWriteToDisk(); mOnBattery = DEBUG_POWER ? true : mBatteryStatsService.getActiveStatistics().getIsOnBattery(); mBatteryStatsService.getActiveStatistics().setCallback(this); @@ -2096,8 +2251,8 @@ public final class ActivityManagerService extends ActivityManagerNative mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml")); // User 0 is the first and only user that runs at boot. - mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true)); - mUserLru.add(Integer.valueOf(0)); + mStartedUsers.put(UserHandle.USER_OWNER, new UserStartedState(UserHandle.OWNER, true)); + mUserLru.add(UserHandle.USER_OWNER); updateStartedUserArrayLocked(); GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", @@ -2106,15 +2261,16 @@ public final class ActivityManagerService extends ActivityManagerNative mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations")); mConfiguration.setToDefaults(); - mConfiguration.locale = Locale.getDefault(); + mConfiguration.setLocale(Locale.getDefault()); mConfigurationSeq = mConfiguration.seq = 1; mProcessCpuTracker.init(); mCompatModePackages = new CompatModePackages(this, systemDir, mHandler); mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); - mStackSupervisor = new ActivityStackSupervisor(this); - mTaskPersister = new TaskPersister(systemDir, mStackSupervisor); + mRecentTasks = new RecentTasks(this); + mStackSupervisor = new ActivityStackSupervisor(this, mRecentTasks); + mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks); mProcessCpuThread = new Thread("CpuTracker") { @Override @@ -2171,6 +2327,9 @@ public final class ActivityManagerService extends ActivityManagerNative public void initPowerManagement() { mStackSupervisor.initPowerManagement(); mBatteryStatsService.initPowerManagement(); + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mVoiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*voice*"); + mVoiceWakeLock.setReferenceCounted(false); } @Override @@ -2236,31 +2395,33 @@ public final class ActivityManagerService extends ActivityManagerNative if (MONITOR_CPU_USAGE && mLastCpuTime.get() < (now-MONITOR_CPU_MIN_TIME)) { mLastCpuTime.set(now); - haveNewCpuStats = true; mProcessCpuTracker.update(); - //Slog.i(TAG, mProcessCpu.printCurrentState()); - //Slog.i(TAG, "Total CPU usage: " - // + mProcessCpu.getTotalCpuPercent() + "%"); + if (mProcessCpuTracker.hasGoodLastStats()) { + haveNewCpuStats = true; + //Slog.i(TAG, mProcessCpu.printCurrentState()); + //Slog.i(TAG, "Total CPU usage: " + // + mProcessCpu.getTotalCpuPercent() + "%"); - // Slog the cpu usage if the property is set. - if ("true".equals(SystemProperties.get("events.cpu"))) { - int user = mProcessCpuTracker.getLastUserTime(); - int system = mProcessCpuTracker.getLastSystemTime(); - int iowait = mProcessCpuTracker.getLastIoWaitTime(); - int irq = mProcessCpuTracker.getLastIrqTime(); - int softIrq = mProcessCpuTracker.getLastSoftIrqTime(); - int idle = mProcessCpuTracker.getLastIdleTime(); + // Slog the cpu usage if the property is set. + if ("true".equals(SystemProperties.get("events.cpu"))) { + 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; + int total = user + system + iowait + irq + softIrq + idle; + if (total == 0) total = 1; - EventLog.writeEvent(EventLogTags.CPU, - ((user+system+iowait+irq+softIrq) * 100) / total, - (user * 100) / total, - (system * 100) / total, - (iowait * 100) / total, - (irq * 100) / total, - (softIrq * 100) / total); + EventLog.writeEvent(EventLogTags.CPU, + ((user+system+iowait+irq+softIrq) * 100) / total, + (user * 100) / total, + (system * 100) / total, + (iowait * 100) / total, + (irq * 100) / total, + (softIrq * 100) / total); + } } } @@ -2269,8 +2430,10 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(bstats) { synchronized(mPidsSelfLocked) { if (haveNewCpuStats) { - if (mOnBattery) { - int perc = bstats.startAddingCpuLocked(); + final int perc = bstats.startAddingCpuLocked(); + if (perc >= 0) { + int remainUTime = 0; + int remainSTime = 0; int totalUTime = 0; int totalSTime = 0; final int N = mProcessCpuTracker.countStats(); @@ -2282,38 +2445,45 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord pr = mPidsSelfLocked.get(st.pid); int otherUTime = (st.rel_utime*perc)/100; int otherSTime = (st.rel_stime*perc)/100; - totalUTime += otherUTime; - totalSTime += otherSTime; + remainUTime += otherUTime; + remainSTime += otherSTime; + totalUTime += st.rel_utime; + totalSTime += st.rel_stime; if (pr != null) { BatteryStatsImpl.Uid.Proc ps = pr.curProcBatteryStats; if (ps == null || !ps.isActive()) { pr.curProcBatteryStats = ps = bstats.getProcessStatsLocked( pr.info.uid, pr.processName); } - ps.addCpuTimeLocked(st.rel_utime-otherUTime, - st.rel_stime-otherSTime); - ps.addSpeedStepTimes(cpuSpeedTimes); - pr.curCpuTime += (st.rel_utime+st.rel_stime) * 10; + ps.addCpuTimeLocked(st.rel_utime - otherUTime, + st.rel_stime - otherSTime, cpuSpeedTimes); + pr.curCpuTime += st.rel_utime + st.rel_stime; } else { BatteryStatsImpl.Uid.Proc ps = st.batteryStats; if (ps == null || !ps.isActive()) { st.batteryStats = ps = bstats.getProcessStatsLocked( bstats.mapUid(st.uid), st.name); } - ps.addCpuTimeLocked(st.rel_utime-otherUTime, - st.rel_stime-otherSTime); - ps.addSpeedStepTimes(cpuSpeedTimes); + ps.addCpuTimeLocked(st.rel_utime - otherUTime, + st.rel_stime - otherSTime, cpuSpeedTimes); } } - bstats.finishAddingCpuLocked(perc, totalUTime, - totalSTime, cpuSpeedTimes); + final int userTime = mProcessCpuTracker.getLastUserTime(); + final int systemTime = mProcessCpuTracker.getLastSystemTime(); + final int iowaitTime = mProcessCpuTracker.getLastIoWaitTime(); + final int irqTime = mProcessCpuTracker.getLastIrqTime(); + final int softIrqTime = mProcessCpuTracker.getLastSoftIrqTime(); + final int idleTime = mProcessCpuTracker.getLastIdleTime(); + bstats.finishAddingCpuLocked(perc, remainUTime, + remainSTime, totalUTime, totalSTime, userTime, systemTime, + iowaitTime, irqTime, softIrqTime, idleTime, cpuSpeedTimes); } } } if (mLastWriteTime < (now-BATTERY_STATS_TIME)) { mLastWriteTime = now; - mBatteryStatsService.getActiveStatistics().writeAsyncLocked(); + mBatteryStatsService.scheduleWriteToDisk(); } } } @@ -2336,6 +2506,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public void batterySendBroadcast(Intent intent) { + broadcastIntentLocked(null, null, intent, null, + null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1, + Process.SYSTEM_UID, UserHandle.USER_ALL); + } + /** * Initialize the application bind args. These are passed to each * process when the bindApplication() IPC is sent to the process. They're @@ -2359,19 +2536,31 @@ public final class ActivityManagerService extends ActivityManagerNative } final void setFocusedActivityLocked(ActivityRecord r, String reason) { - if (mFocusedActivity != r) { - if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r); + if (r != null && mFocusedActivity != r) { + if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedActivityLocked: r=" + r); + ActivityRecord last = mFocusedActivity; mFocusedActivity = r; if (r.task != null && r.task.voiceInteractor != null) { - startRunningVoiceLocked(); + startRunningVoiceLocked(r.task.voiceSession, r.info.applicationInfo.uid); } else { finishRunningVoiceLocked(); + if (last != null && last.task.voiceSession != null) { + // We had been in a voice interaction session, but now focused has + // move to something different. Just finish the session, we can't + // return to it and retain the proper state and synchronization with + // the voice interaction service. + finishVoiceTask(last.task.voiceSession); + } } - mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity"); - if (r != null) { + if (mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity")) { mWindowManager.setFocusedApp(r.appToken, true); } applyUpdateLockStateLocked(r); + if (last != null && last.userId != mFocusedActivity.userId) { + mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG); + mHandler.sendMessage(mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG, + mFocusedActivity.userId, 0)); + } } EventLog.writeEvent(EventLogTags.AM_FOCUSED_ACTIVITY, mCurrentUserId, mFocusedActivity == null ? "NULL" : mFocusedActivity.shortComponentName); @@ -2385,13 +2574,14 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void setFocusedStack(int stackId) { - if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: stackId=" + stackId); + if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedStack: stackId=" + stackId); synchronized (ActivityManagerService.this) { ActivityStack stack = mStackSupervisor.getStack(stackId); if (stack != null) { ActivityRecord r = stack.topRunningActivityLocked(null); if (r != null) { setFocusedActivityLocked(r, "setFocusedStack"); + mStackSupervisor.resumeTopActivitiesLocked(stack, null, null); } } } @@ -2409,9 +2599,9 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void notifyActivityDrawn(IBinder token) { - if (DEBUG_VISBILITY) Slog.d(TAG, "notifyActivityDrawn: token=" + token); + if (DEBUG_VISIBILITY) Slog.d(TAG_VISIBILITY, "notifyActivityDrawn: token=" + token); synchronized (this) { - ActivityRecord r= mStackSupervisor.isInAnyStackLocked(token); + ActivityRecord r = mStackSupervisor.isInAnyStackLocked(token); if (r != null) { r.task.stack.notifyActivityDrawnLocked(r); } @@ -2432,7 +2622,7 @@ public final class ActivityManagerService extends ActivityManagerNative Message msg = Message.obtain(); msg.what = SHOW_COMPAT_MODE_DIALOG_MSG; msg.obj = r.task.askedCompatMode ? null : r; - mHandler.sendMessage(msg); + mUiHandler.sendMessage(msg); } private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index, @@ -2466,7 +2656,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (index > 0) { index--; } - if (DEBUG_LRU) Slog.d(TAG, "Moving dep from " + lrui + " to " + index + if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index + " in LRU list: " + app); mLruProcesses.add(index, app); return index; @@ -2512,13 +2702,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (hasActivity) { final int N = mLruProcesses.size(); if (N > 0 && mLruProcesses.get(N-1) == app) { - if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top activity: " + app); + if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top activity: " + app); return; } } else { if (mLruProcessServiceStart > 0 && mLruProcesses.get(mLruProcessServiceStart-1) == app) { - if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top other: " + app); + if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top other: " + app); return; } } @@ -2528,7 +2718,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.persistent && lrui >= 0) { // We don't care about the position of persistent processes, as long as // they are in the list. - if (DEBUG_LRU) Slog.d(TAG, "Not moving, persistent: " + app); + if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, persistent: " + app); return; } @@ -2597,27 +2787,29 @@ public final class ActivityManagerService extends ActivityManagerNative int nextIndex; if (hasActivity) { final int N = mLruProcesses.size(); - if (app.activities.size() == 0 && mLruProcessActivityStart < (N-1)) { + if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) { // Process doesn't have activities, but has clients with // activities... move it up, but one below the top (the top // should always have a real activity). - if (DEBUG_LRU) Slog.d(TAG, "Adding to second-top of LRU activity list: " + app); - mLruProcesses.add(N-1, app); + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Adding to second-top of LRU activity list: " + app); + mLruProcesses.add(N - 1, app); // To keep it from spamming the LRU list (by making a bunch of clients), // we will push down any other entries owned by the app. final int uid = app.info.uid; - for (int i=N-2; i>mLruProcessActivityStart; i--) { + for (int i = N - 2; i > mLruProcessActivityStart; i--) { ProcessRecord subProc = mLruProcesses.get(i); if (subProc.info.uid == uid) { // We want to push this one down the list. If the process after // it is for the same uid, however, don't do so, because we don't // want them internally to be re-ordered. - if (mLruProcesses.get(i-1).info.uid != uid) { - if (DEBUG_LRU) Slog.d(TAG, "Pushing uid " + uid + " swapping at " + i - + ": " + mLruProcesses.get(i) + " : " + mLruProcesses.get(i-1)); + if (mLruProcesses.get(i - 1).info.uid != uid) { + if (DEBUG_LRU) Slog.d(TAG_LRU, + "Pushing uid " + uid + " swapping at " + i + ": " + + mLruProcesses.get(i) + " : " + mLruProcesses.get(i - 1)); ProcessRecord tmp = mLruProcesses.get(i); - mLruProcesses.set(i, mLruProcesses.get(i-1)); - mLruProcesses.set(i-1, tmp); + mLruProcesses.set(i, mLruProcesses.get(i - 1)); + mLruProcesses.set(i - 1, tmp); i--; } } else { @@ -2627,13 +2819,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } else { // Process has activities, put it at the very tipsy-top. - if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU activity list: " + app); + if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU activity list: " + app); mLruProcesses.add(app); } nextIndex = mLruProcessServiceStart; } else if (hasService) { // Process has services, put it at the top of the service list. - if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU service list: " + app); + if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU service list: " + app); mLruProcesses.add(mLruProcessActivityStart, app); nextIndex = mLruProcessServiceStart; mLruProcessActivityStart++; @@ -2644,7 +2836,7 @@ public final class ActivityManagerService extends ActivityManagerNative // If there is a client, don't allow the process to be moved up higher // in the list than that client. int clientIndex = mLruProcesses.lastIndexOf(client); - if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG, "Unknown client " + client + if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG_LRU, "Unknown client " + client + " when updating " + app); if (clientIndex <= lrui) { // Don't allow the client index restriction to push it down farther in the @@ -2655,7 +2847,7 @@ public final class ActivityManagerService extends ActivityManagerNative index = clientIndex; } } - if (DEBUG_LRU) Slog.d(TAG, "Adding at " + index + " of LRU list: " + app); + if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding at " + index + " of LRU list: " + app); mLruProcesses.add(index, app); nextIndex = index-1; mLruProcessActivityStart++; @@ -2707,7 +2899,7 @@ public final class ActivityManagerService extends ActivityManagerNative } 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 (DEBUG_PSS) Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + proc.lastCachedPss); if (proc.lastCachedPss >= mProcessList.getCachedRestoreThresholdKb()) { if (proc.baseProcessTracker != null) { proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss); @@ -2779,17 +2971,45 @@ public final class ActivityManagerService extends ActivityManagerNative if (!isolated) { app = getProcessRecordLocked(processName, info.uid, keepIfLarge); checkTime(startTime, "startProcess: after getProcessRecord"); + + if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) { + // If we are in the background, then check to see if this process + // is bad. If so, we will just silently fail. + if (mBadProcesses.get(info.processName, info.uid) != null) { + if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid + + "/" + info.processName); + return null; + } + } else { + // When the user is explicitly starting a process, then clear its + // crash count so that we won't make it bad until they see at + // least one crash dialog again, and make the process good again + // if it had been bad. + if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid + + "/" + info.processName); + mProcessCrashTimes.remove(info.processName, info.uid); + if (mBadProcesses.get(info.processName, info.uid) != null) { + EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, + UserHandle.getUserId(info.uid), info.uid, + info.processName); + mBadProcesses.remove(info.processName, info.uid); + if (app != null) { + app.bad = false; + } + } + } } else { // If this is an isolated process, it can't re-use an existing process. app = null; } + // We don't have to do anything more if: // (1) There is an existing application record; and // (2) The caller doesn't think it is dead, OR there is no thread // object attached to it so we know it couldn't have crashed; and // (3) There is a pid assigned to it, so it is either starting or // already running. - if (DEBUG_PROCESSES) Slog.v(TAG, "startProcess: name=" + processName + if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "startProcess: name=" + processName + " app=" + app + " knownToBeDead=" + knownToBeDead + " thread=" + (app != null ? app.thread : null) + " pid=" + (app != null ? app.pid : -1)); @@ -2797,7 +3017,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!knownToBeDead || app.thread == null) { // We already have the app running, or are waiting for it to // 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 (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "App already running: " + app); // If this is a new package in the process, add the package to the list app.addPackage(info.packageName, info.versionCode, mProcessStats); checkTime(startTime, "startProcess: done, added package to proc"); @@ -2806,7 +3026,7 @@ public final class ActivityManagerService extends ActivityManagerNative // An application record is attached to a previous process, // clean it up now. - if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, "App died: " + app); + if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG_PROCESSES, "App died: " + app); checkTime(startTime, "startProcess: bad proc running, killing"); Process.killProcessGroup(app.info.uid, app.pid); handleAppDiedLocked(app, true, true); @@ -2816,35 +3036,6 @@ public final class ActivityManagerService extends ActivityManagerNative String hostingNameStr = hostingName != null ? hostingName.flattenToShortString() : null; - if (!isolated) { - if ((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) { - // If we are in the background, then check to see if this process - // is bad. If so, we will just silently fail. - if (mBadProcesses.get(info.processName, info.uid) != null) { - if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid - + "/" + info.processName); - return null; - } - } else { - // When the user is explicitly starting a process, then clear its - // crash count so that we won't make it bad until they see at - // least one crash dialog again, and make the process good again - // if it had been bad. - if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid - + "/" + info.processName); - mProcessCrashTimes.remove(info.processName, info.uid); - if (mBadProcesses.get(info.processName, info.uid) != null) { - EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, - UserHandle.getUserId(info.uid), info.uid, - info.processName); - mBadProcesses.remove(info.processName, info.uid); - if (app != null) { - app.bad = false; - } - } - } - } - if (app == null) { checkTime(startTime, "startProcess: creating new process record"); app = newProcessRecordLocked(info, processName, isolated, isolatedUid); @@ -2873,7 +3064,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (!mProcessesOnHold.contains(app)) { mProcessesOnHold.add(app); } - if (DEBUG_PROCESSES) Slog.v(TAG, "System not ready, putting on hold: " + app); + if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, + "System not ready, putting on hold: " + app); checkTime(startTime, "startProcess: returning with proc on hold"); return app; } @@ -2908,7 +3100,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.setPid(0); } - if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG, + if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES, "startProcessLocked removing on hold: " + app); mProcessesOnHold.remove(app); @@ -2920,25 +3112,14 @@ public final class ActivityManagerService extends ActivityManagerNative int uid = app.uid; int[] gids = null; - int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; + int mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT; if (!app.isolated) { int[] permGids = null; try { checkTime(startTime, "startProcess: getting gids from package manager"); - final PackageManager pm = mContext.getPackageManager(); - permGids = pm.getPackageGids(app.info.packageName); - - if (Environment.isExternalStorageEmulated()) { - checkTime(startTime, "startProcess: checking external storage perm"); - if (pm.checkPermission( - android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, - app.info.packageName) == PERMISSION_GRANTED) { - mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL; - } else { - mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER; - } - } - } catch (PackageManager.NameNotFoundException e) { + permGids = AppGlobals.getPackageManager().getPackageGids(app.info.packageName, + app.userId); + } catch (RemoteException e) { Slog.w(TAG, "Unable to retrieve gids", e); } @@ -2946,7 +3127,7 @@ public final class ActivityManagerService extends ActivityManagerNative * Add shared application and profile GIDs so applications can share some * resources like shared libraries and access user-wide resources */ - if (permGids == null) { + if (ArrayUtils.isEmpty(permGids)) { gids = new int[2]; } else { gids = new int[permGids.length + 2]; @@ -2983,6 +3164,15 @@ public final class ActivityManagerService extends ActivityManagerNative if ("1".equals(SystemProperties.get("debug.checkjni"))) { debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; } + String jitDebugProperty = SystemProperties.get("debug.usejit"); + if ("true".equals(jitDebugProperty)) { + debugFlags |= Zygote.DEBUG_ENABLE_JIT; + } else if (!"false".equals(jitDebugProperty)) { + // If we didn't force disable by setting false, defer to the dalvik vm options. + if ("true".equals(SystemProperties.get("dalvik.vm.usejit"))) { + debugFlags |= Zygote.DEBUG_ENABLE_JIT; + } + } if ("1".equals(SystemProperties.get("debug.jni.logging"))) { debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; } @@ -3079,7 +3269,8 @@ public final class ActivityManagerService extends ActivityManagerNative } void updateUsageStats(ActivityRecord component, boolean resumed) { - if (DEBUG_SWITCH) Slog.d(TAG, "updateUsageStats: comp=" + component + "res=" + resumed); + if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, + "updateUsageStats: comp=" + component + "res=" + resumed); final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); if (resumed) { if (mUsageStatsService != null) { @@ -3287,6 +3478,35 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public int getPackageProcessState(String packageName) { + int procState = ActivityManager.PROCESS_STATE_NONEXISTENT; + synchronized (this) { + for (int i=mLruProcesses.size()-1; i>=0; i--) { + final ProcessRecord proc = mLruProcesses.get(i); + if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT + || procState > proc.setProcState) { + boolean found = false; + for (int j=proc.pkgList.size()-1; j>=0 && !found; j--) { + if (proc.pkgList.keyAt(j).equals(packageName)) { + procState = proc.setProcState; + found = true; + } + } + if (proc.pkgDeps != null && !found) { + for (int j=proc.pkgDeps.size()-1; j>=0; j--) { + if (proc.pkgDeps.valueAt(j).equals(packageName)) { + procState = proc.setProcState; + break; + } + } + } + } + } + } + return procState; + } + private void dispatchProcessesChanged() { int N; synchronized (this) { @@ -3297,7 +3517,8 @@ public final class ActivityManagerService extends ActivityManagerNative mPendingProcessChanges.toArray(mActiveProcessChanges); mAvailProcessChanges.addAll(mPendingProcessChanges); mPendingProcessChanges.clear(); - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "*** Delivering " + N + " process changes"); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "*** Delivering " + N + " process changes"); } int i = mProcessObservers.beginBroadcast(); @@ -3309,15 +3530,16 @@ public final class ActivityManagerService extends ActivityManagerNative for (int j=0; j<N; j++) { ProcessChangeItem item = mActiveProcessChanges[j]; if ((item.changes&ProcessChangeItem.CHANGE_ACTIVITIES) != 0) { - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "ACTIVITIES CHANGED pid=" - + item.pid + " uid=" + item.uid + ": " - + item.foregroundActivities); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "ACTIVITIES CHANGED pid=" + item.pid + " uid=" + + item.uid + ": " + item.foregroundActivities); observer.onForegroundActivitiesChanged(item.pid, item.uid, item.foregroundActivities); } if ((item.changes&ProcessChangeItem.CHANGE_PROCESS_STATE) != 0) { - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "PROCSTATE CHANGED pid=" - + item.pid + " uid=" + item.uid + ": " + item.processState); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "PROCSTATE CHANGED pid=" + item.pid + " uid=" + item.uid + + ": " + item.processState); observer.onProcessStateChanged(item.pid, item.uid, item.processState); } } @@ -3462,10 +3684,10 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public int startActivityIntentSender(IApplicationThread caller, - IntentSender intent, Intent fillInIntent, String resolvedType, - IBinder resultTo, String resultWho, int requestCode, - int flagsMask, int flagsValues, Bundle options) { + public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, + Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, + int requestCode, int flagsMask, int flagsValues, Bundle options) + throws TransactionTooLargeException { enforceNotIsolatedCaller("startActivityIntentSender"); // Refuse possible leaked file descriptors if (fillInIntent != null && fillInIntent.hasFileDescriptors()) { @@ -3519,6 +3741,19 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake) { + synchronized (this) { + if (mRunningVoice != null && mRunningVoice.asBinder() == session.asBinder()) { + if (keepAwake) { + mVoiceWakeLock.acquire(); + } else { + mVoiceWakeLock.release(); + } + } + } + } + + @Override public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) { // Refuse possible leaked file descriptors @@ -3642,7 +3877,7 @@ public final class ActivityManagerService extends ActivityManagerNative final Intent intent; final int userId; synchronized (this) { - task = recentTaskForIdLocked(taskId); + task = mRecentTasks.taskForIdLocked(taskId); if (task == null) { throw new IllegalArgumentException("Task " + taskId + " not found."); } @@ -3700,518 +3935,6 @@ public final class ActivityManagerService extends ActivityManagerNative return ret; } - //explicitly remove thd old information in mRecentTasks when removing existing user. - private void removeRecentTasksForUserLocked(int userId) { - if(userId <= 0) { - Slog.i(TAG, "Can't remove recent task on user " + userId); - return; - } - - for (int i = mRecentTasks.size() - 1; i >= 0; --i) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.userId == userId) { - if(DEBUG_TASKS) Slog.i(TAG, "remove RecentTask " + tr - + " when finishing user" + userId); - mRecentTasks.remove(i); - tr.removedFromRecents(); - } - } - - // Remove tasks from persistent storage. - notifyTaskPersisterLocked(null, true); - } - - // Sort by taskId - private Comparator<TaskRecord> mTaskRecordComparator = new Comparator<TaskRecord>() { - @Override - public int compare(TaskRecord lhs, TaskRecord rhs) { - return rhs.taskId - lhs.taskId; - } - }; - - // Extract the affiliates of the chain containing mRecentTasks[start]. - private int processNextAffiliateChainLocked(int start) { - final TaskRecord startTask = mRecentTasks.get(start); - final int affiliateId = startTask.mAffiliatedTaskId; - - // Quick identification of isolated tasks. I.e. those not launched behind. - if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null && - startTask.mNextAffiliate == null) { - // There is still a slim chance that there are other tasks that point to this task - // and that the chain is so messed up that this task no longer points to them but - // the gain of this optimization outweighs the risk. - startTask.inRecents = true; - return start + 1; - } - - // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents. - mTmpRecents.clear(); - for (int i = mRecentTasks.size() - 1; i >= start; --i) { - final TaskRecord task = mRecentTasks.get(i); - if (task.mAffiliatedTaskId == affiliateId) { - mRecentTasks.remove(i); - mTmpRecents.add(task); - } - } - - // Sort them all by taskId. That is the order they were create in and that order will - // always be correct. - Collections.sort(mTmpRecents, mTaskRecordComparator); - - // Go through and fix up the linked list. - // The first one is the end of the chain and has no next. - final TaskRecord first = mTmpRecents.get(0); - first.inRecents = true; - if (first.mNextAffiliate != null) { - Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate); - first.setNextAffiliate(null); - notifyTaskPersisterLocked(first, false); - } - // Everything in the middle is doubly linked from next to prev. - final int tmpSize = mTmpRecents.size(); - for (int i = 0; i < tmpSize - 1; ++i) { - final TaskRecord next = mTmpRecents.get(i); - final TaskRecord prev = mTmpRecents.get(i + 1); - if (next.mPrevAffiliate != prev) { - Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate + - " setting prev=" + prev); - next.setPrevAffiliate(prev); - notifyTaskPersisterLocked(next, false); - } - if (prev.mNextAffiliate != next) { - Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate + - " setting next=" + next); - prev.setNextAffiliate(next); - notifyTaskPersisterLocked(prev, false); - } - prev.inRecents = true; - } - // The last one is the beginning of the list and has no prev. - final TaskRecord last = mTmpRecents.get(tmpSize - 1); - if (last.mPrevAffiliate != null) { - Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate); - last.setPrevAffiliate(null); - notifyTaskPersisterLocked(last, false); - } - - // Insert the group back into mRecentTasks at start. - mRecentTasks.addAll(start, mTmpRecents); - - // Let the caller know where we left off. - return start + tmpSize; - } - - /** - * Update the recent tasks lists: make sure tasks should still be here (their - * applications / activities still exist), update their availability, fixup ordering - * of affiliations. - */ - void cleanupRecentTasksLocked(int userId) { - if (mRecentTasks == null) { - // Happens when called from the packagemanager broadcast before boot. - return; - } - - final HashMap<ComponentName, ActivityInfo> availActCache = new HashMap<>(); - final HashMap<String, ApplicationInfo> availAppCache = new HashMap<>(); - final IPackageManager pm = AppGlobals.getPackageManager(); - final ActivityInfo dummyAct = new ActivityInfo(); - final ApplicationInfo dummyApp = new ApplicationInfo(); - - int N = mRecentTasks.size(); - - int[] users = userId == UserHandle.USER_ALL - ? getUsersLocked() : new int[] { userId }; - for (int user : users) { - for (int i = 0; i < N; i++) { - TaskRecord task = mRecentTasks.get(i); - if (task.userId != user) { - // Only look at tasks for the user ID of interest. - continue; - } - if (task.autoRemoveRecents && task.getTopActivity() == null) { - // This situation is broken, and we should just get rid of it now. - mRecentTasks.remove(i); - task.removedFromRecents(); - i--; - N--; - Slog.w(TAG, "Removing auto-remove without activity: " + task); - continue; - } - // Check whether this activity is currently available. - if (task.realActivity != null) { - ActivityInfo ai = availActCache.get(task.realActivity); - if (ai == null) { - try { - ai = pm.getActivityInfo(task.realActivity, - PackageManager.GET_UNINSTALLED_PACKAGES - | PackageManager.GET_DISABLED_COMPONENTS, user); - } catch (RemoteException e) { - // Will never happen. - continue; - } - if (ai == null) { - ai = dummyAct; - } - availActCache.put(task.realActivity, ai); - } - if (ai == dummyAct) { - // This could be either because the activity no longer exists, or the - // app is temporarily gone. For the former we want to remove the recents - // entry; for the latter we want to mark it as unavailable. - ApplicationInfo app = availAppCache.get(task.realActivity.getPackageName()); - if (app == null) { - try { - app = pm.getApplicationInfo(task.realActivity.getPackageName(), - PackageManager.GET_UNINSTALLED_PACKAGES - | PackageManager.GET_DISABLED_COMPONENTS, user); - } catch (RemoteException e) { - // Will never happen. - continue; - } - if (app == null) { - app = dummyApp; - } - availAppCache.put(task.realActivity.getPackageName(), app); - } - if (app == dummyApp || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { - // Doesn't exist any more! Good-bye. - mRecentTasks.remove(i); - task.removedFromRecents(); - i--; - N--; - Slog.w(TAG, "Removing no longer valid recent: " + task); - continue; - } else { - // Otherwise just not available for now. - if (task.isAvailable) { - if (DEBUG_RECENTS) Slog.d(TAG, "Making recent unavailable: " - + task); - } - task.isAvailable = false; - } - } else { - if (!ai.enabled || !ai.applicationInfo.enabled - || (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { - if (task.isAvailable) { - if (DEBUG_RECENTS) Slog.d(TAG, "Making recent unavailable: " - + task + " (enabled=" + ai.enabled + "/" - + ai.applicationInfo.enabled + " flags=" - + Integer.toHexString(ai.applicationInfo.flags) + ")"); - } - task.isAvailable = false; - } else { - if (!task.isAvailable) { - if (DEBUG_RECENTS) Slog.d(TAG, "Making recent available: " - + task); - } - task.isAvailable = true; - } - } - } - } - } - - // Verify the affiliate chain for each task. - for (int i = 0; i < N; i = processNextAffiliateChainLocked(i)) { - } - - mTmpRecents.clear(); - // mRecentTasks is now in sorted, affiliated order. - } - - private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) { - int N = mRecentTasks.size(); - TaskRecord top = task; - int topIndex = taskIndex; - while (top.mNextAffiliate != null && topIndex > 0) { - top = top.mNextAffiliate; - topIndex--; - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: adding affilliates starting at " - + topIndex + " from intial " + taskIndex); - // Find the end of the chain, doing a sanity check along the way. - boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId; - int endIndex = topIndex; - TaskRecord prev = top; - while (endIndex < N) { - TaskRecord cur = mRecentTasks.get(endIndex); - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: looking at next chain @" - + endIndex + " " + cur); - if (cur == top) { - // Verify start of the chain. - if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": first task has next affiliate: " + prev); - sane = false; - break; - } - } else { - // Verify middle of the chain's next points back to the one before. - if (cur.mNextAffiliate != prev - || cur.mNextAffiliateTaskId != prev.taskId) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": middle task " + cur + " @" + endIndex - + " has bad next affiliate " - + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId - + ", expected " + prev); - sane = false; - break; - } - } - if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) { - // Chain ends here. - if (cur.mPrevAffiliate != null) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": last task " + cur + " has previous affiliate " - + cur.mPrevAffiliate); - sane = false; - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: end of chain @" + endIndex); - break; - } else { - // Verify middle of the chain's prev points to a valid item. - if (cur.mPrevAffiliate == null) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": task " + cur + " has previous affiliate " - + cur.mPrevAffiliate + " but should be id " - + cur.mPrevAffiliate); - sane = false; - break; - } - } - if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": task " + cur + " has affiliated id " - + cur.mAffiliatedTaskId + " but should be " - + task.mAffiliatedTaskId); - sane = false; - break; - } - prev = cur; - endIndex++; - if (endIndex >= N) { - Slog.wtf(TAG, "Bad chain ran off index " + endIndex - + ": last task " + prev); - sane = false; - break; - } - } - if (sane) { - if (endIndex < taskIndex) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": did not extend to task " + task + " @" + taskIndex); - sane = false; - } - } - if (sane) { - // All looks good, we can just move all of the affiliated tasks - // to the top. - for (int i=topIndex; i<=endIndex; i++) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: moving affiliated " + task - + " from " + i + " to " + (i-topIndex)); - TaskRecord cur = mRecentTasks.remove(i); - mRecentTasks.add(i-topIndex, cur); - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: done moving tasks " + topIndex - + " to " + endIndex); - return true; - } - - // Whoops, couldn't do it. - return false; - } - - final void addRecentTaskLocked(TaskRecord task) { - final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId - || task.mNextAffiliateTaskId != INVALID_TASK_ID - || task.mPrevAffiliateTaskId != INVALID_TASK_ID; - - int N = mRecentTasks.size(); - // Quick case: check if the top-most recent task is the same. - if (!isAffiliated && N > 0 && mRecentTasks.get(0) == task) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: already at top: " + task); - return; - } - // Another quick case: check if this is part of a set of affiliated - // tasks that are at the top. - if (isAffiliated && N > 0 && task.inRecents - && task.mAffiliatedTaskId == mRecentTasks.get(0).mAffiliatedTaskId) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: affiliated " + mRecentTasks.get(0) - + " at top when adding " + task); - return; - } - // Another quick case: never add voice sessions. - if (task.voiceSession != null) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: not adding voice interaction " + task); - return; - } - - boolean needAffiliationFix = false; - - // Slightly less quick case: the task is already in recents, so all we need - // to do is move it. - if (task.inRecents) { - int taskIndex = mRecentTasks.indexOf(task); - if (taskIndex >= 0) { - if (!isAffiliated) { - // Simple case: this is not an affiliated task, so we just move it to the front. - mRecentTasks.remove(taskIndex); - mRecentTasks.add(0, task); - notifyTaskPersisterLocked(task, false); - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: moving to top " + task - + " from " + taskIndex); - return; - } else { - // More complicated: need to keep all affiliated tasks together. - if (moveAffiliatedTasksToFront(task, taskIndex)) { - // All went well. - return; - } - - // Uh oh... something bad in the affiliation chain, try to rebuild - // everything and then go through our general path of adding a new task. - needAffiliationFix = true; - } - } else { - Slog.wtf(TAG, "Task with inRecent not in recents: " + task); - needAffiliationFix = true; - } - } - - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: trimming tasks for " + task); - trimRecentsForTaskLocked(task, true); - - N = mRecentTasks.size(); - while (N >= ActivityManager.getMaxRecentTasksStatic()) { - final TaskRecord tr = mRecentTasks.remove(N - 1); - tr.removedFromRecents(); - N--; - } - task.inRecents = true; - if (!isAffiliated || needAffiliationFix) { - // If this is a simple non-affiliated task, or we had some failure trying to - // handle it as part of an affilated task, then just place it at the top. - mRecentTasks.add(0, task); - } else if (isAffiliated) { - // If this is a new affiliated task, then move all of the affiliated tasks - // to the front and insert this new one. - TaskRecord other = task.mNextAffiliate; - if (other == null) { - other = task.mPrevAffiliate; - } - if (other != null) { - int otherIndex = mRecentTasks.indexOf(other); - if (otherIndex >= 0) { - // Insert new task at appropriate location. - int taskIndex; - if (other == task.mNextAffiliate) { - // We found the index of our next affiliation, which is who is - // before us in the list, so add after that point. - taskIndex = otherIndex+1; - } else { - // We found the index of our previous affiliation, which is who is - // after us in the list, so add at their position. - taskIndex = otherIndex; - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: new affiliated task added at " - + taskIndex + ": " + task); - mRecentTasks.add(taskIndex, task); - - // Now move everything to the front. - if (moveAffiliatedTasksToFront(task, taskIndex)) { - // All went well. - return; - } - - // Uh oh... something bad in the affiliation chain, try to rebuild - // everything and then go through our general path of adding a new task. - needAffiliationFix = true; - } else { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: couldn't find other affiliation " - + other); - needAffiliationFix = true; - } - } else { - if (DEBUG_RECENTS) Slog.d(TAG, - "addRecent: adding affiliated task without next/prev:" + task); - needAffiliationFix = true; - } - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: adding " + task); - - if (needAffiliationFix) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: regrouping affiliations"); - cleanupRecentTasksLocked(task.userId); - } - } - - /** - * If needed, remove oldest existing entries in recents that are for the same kind - * of task as the given one. - */ - int trimRecentsForTaskLocked(TaskRecord task, boolean doTrim) { - int N = mRecentTasks.size(); - final Intent intent = task.intent; - final boolean document = intent != null && intent.isDocument(); - - int maxRecents = task.maxRecents - 1; - for (int i=0; i<N; i++) { - final TaskRecord tr = mRecentTasks.get(i); - if (task != tr) { - if (task.userId != tr.userId) { - continue; - } - if (i > MAX_RECENT_BITMAPS) { - tr.freeLastThumbnail(); - } - final Intent trIntent = tr.intent; - if ((task.affinity == null || !task.affinity.equals(tr.affinity)) && - (intent == null || !intent.filterEquals(trIntent))) { - continue; - } - final boolean trIsDocument = trIntent != null && trIntent.isDocument(); - if (document && trIsDocument) { - // These are the same document activity (not necessarily the same doc). - if (maxRecents > 0) { - --maxRecents; - continue; - } - // Hit the maximum number of documents for this task. Fall through - // and remove this document from recents. - } else if (document || trIsDocument) { - // Only one of these is a document. Not the droid we're looking for. - continue; - } - } - - if (!doTrim) { - // If the caller is not actually asking for a trim, just tell them we reached - // a point where the trim would happen. - return i; - } - - // Either task and tr are the same or, their affinities match or their intents match - // and neither of them is a document, or they are documents using the same activity - // and their maxRecents has been reached. - tr.disposeThumbnail(); - mRecentTasks.remove(i); - if (task != tr) { - tr.removedFromRecents(); - } - i--; - N--; - if (task.intent == null) { - // If the new recent task we are adding is not fully - // specified, then replace it with the existing recent task. - task = tr; - } - notifyTaskPersisterLocked(tr, false); - } - - return -1; - } - @Override public void reportActivityFullyDrawn(IBinder token) { synchronized (this) { @@ -4230,6 +3953,10 @@ public final class ActivityManagerService extends ActivityManagerNative if (r == null) { return; } + if (r.task != null && r.task.mResizeable) { + // Fixed screen orientation isn't supported with resizeable activities. + return; + } final long origId = Binder.clearCallingIdentity(); mWindowManager.setAppOrientation(r.appToken, requestedOrientation); Configuration config = mWindowManager.updateOrientationFromAppTokens( @@ -4285,13 +4012,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (rootR == null) { Slog.w(TAG, "Finishing task with all activities already finished"); } - // Do not allow task to finish in Lock Task mode. - if (tr == mStackSupervisor.mLockTaskModeTask) { - if (rootR == r) { - Slog.i(TAG, "Not finishing task in lock task mode"); - mStackSupervisor.showLockTaskToast(); - return false; - } + // Do not allow task to finish if last task in lockTask mode. Launchable apps can + // finish themselves. + if (tr.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE && rootR == r && + mStackSupervisor.isLastLockedTask(tr)) { + Slog.i(TAG, "Not finishing task in lock task mode"); + mStackSupervisor.showLockTaskToast(); + return false; } if (mController != null) { // Find the first activity that is not finishing. @@ -4354,11 +4081,10 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>( - mHeavyWeightProcess.activities); - for (int i=0; i<activities.size(); i++) { + ArrayList<ActivityRecord> activities = new ArrayList<>(mHeavyWeightProcess.activities); + for (int i = 0; i < activities.size(); i++) { ActivityRecord r = activities.get(i); - if (!r.finishing) { + if (!r.finishing && r.isInStackLocked()) { r.task.stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "finish-heavy", true); } @@ -4446,20 +4172,18 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); try { ActivityRecord r = ActivityRecord.isInStackLocked(token); - - ActivityRecord rootR = r.task.getRootActivity(); - // Do not allow task to finish in Lock Task mode. - if (r.task == mStackSupervisor.mLockTaskModeTask) { - if (rootR == r) { - mStackSupervisor.showLockTaskToast(); - return false; - } + if (r == null) { + return false; } - boolean res = false; - if (r != null) { - res = r.task.stack.finishActivityAffinityLocked(r); + + // Do not allow the last non-launchable task to finish in Lock Task mode. + final TaskRecord task = r.task; + if (task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE && + mStackSupervisor.isLastLockedTask(task) && task.getRootActivity() == r) { + mStackSupervisor.showLockTaskToast(); + return false; } - return res; + return task.stack.finishActivityAffinityLocked(r); } finally { Binder.restoreCallingIdentity(origId); } @@ -4485,7 +4209,7 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); try { ActivityRecord r = ActivityRecord.isInStackLocked(token); - if (r.task == null || r.task.stack == null) { + if (r == null) { return false; } return r.task.stack.safelyDestroyActivityLocked(r, "app-req"); @@ -4573,17 +4297,13 @@ public final class ActivityManagerService extends ActivityManagerNative finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info); } - if (!restarting) { - 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) { - mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); - } - } + if (!restarting && hasVisibleActivities && !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. + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); } } @@ -4670,10 +4390,11 @@ public final class ActivityManagerService extends ActivityManagerNative } final void appDiedLocked(ProcessRecord app) { - appDiedLocked(app, app.pid, app.thread); + appDiedLocked(app, app.pid, app.thread, false); } - final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) { + final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread, + boolean fromBinderDied) { // First check if this ProcessRecord is actually active for the pid. synchronized (mPidsSelfLocked) { ProcessRecord curProc = mPidsSelfLocked.get(pid); @@ -4689,7 +4410,9 @@ public final class ActivityManagerService extends ActivityManagerNative } if (!app.killed) { - Process.killProcessQuiet(pid); + if (!fromBinderDied) { + Process.killProcessQuiet(pid); + } Process.killProcessGroup(app.info.uid, pid); app.killed = true; } @@ -4710,9 +4433,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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()); + if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, + "Dying app: " + app + ", pid: " + pid + ", thread: " + thread.asBinder()); handleAppDiedLocked(app, false, true); if (doOomAdj) { @@ -4727,7 +4449,7 @@ public final class ActivityManagerService extends ActivityManagerNative + ") has died and restarted (pid " + app.pid + ")."); EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName); } else if (DEBUG_PROCESSES) { - Slog.d(TAG, "Received spurious death notification for thread " + Slog.d(TAG_PROCESSES, "Received spurious death notification for thread " + thread.asBinder()); } } @@ -5082,20 +4804,20 @@ public final class ActivityManagerService extends ActivityManagerNative map.put("activity", activity); } - mHandler.sendMessage(msg); + mUiHandler.sendMessage(msg); } } final void showLaunchWarningLocked(final ActivityRecord cur, final ActivityRecord next) { if (!mLaunchWarningShown) { mLaunchWarningShown = true; - mHandler.post(new Runnable() { + mUiHandler.post(new Runnable() { @Override public void run() { synchronized (ActivityManagerService.this) { final Dialog d = new LaunchWarningWindow(mContext, cur, next); d.show(); - mHandler.postDelayed(new Runnable() { + mUiHandler.postDelayed(new Runnable() { @Override public void run() { synchronized (ActivityManagerService.this) { @@ -5114,6 +4836,9 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer, int userId) { enforceNotIsolatedCaller("clearApplicationUserData"); + if (packageName != null && packageName.equals(mDeviceOwnerName)) { + throw new SecurityException("Clearing DeviceOwner data is forbidden."); + } int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); userId = handleIncomingUser(pid, uid, @@ -5762,9 +5487,8 @@ public final class ActivityManagerService extends ActivityManagerNative boolean callerWillRestart, boolean allowRestart, String reason) { final String name = app.processName; final int uid = app.uid; - if (DEBUG_PROCESSES) Slog.d( - TAG, "Force removing proc " + app.toShortString() + " (" + name - + "/" + uid + ")"); + if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES, + "Force removing proc " + app.toShortString() + " (" + name + "/" + uid + ")"); mProcessNames.remove(name, uid); mIsolatedProcesses.remove(app.uid); @@ -5784,17 +5508,20 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.isolated) { mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); } - app.kill(reason, true); - handleAppDiedLocked(app, true, allowRestart); - removeLruProcessLocked(app); - + boolean willRestart = false; if (app.persistent && !app.isolated) { if (!callerWillRestart) { - addAppLocked(app.info, false, null /* ABI override */); + willRestart = true; } else { needRestart = true; } } + app.kill(reason, true); + handleAppDiedLocked(app, willRestart, allowRestart); + if (willRestart) { + removeLruProcessLocked(app); + addAppLocked(app.info, false, null /* ABI override */); + } } else { mRemovedProcesses.add(app); } @@ -5833,6 +5560,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Take care of any services that are waiting for the process. mServices.processStartTimedOutLocked(app); app.kill("start timeout", true); + removeLruProcessLocked(app); if (mBackupTarget != null && mBackupTarget.app.pid == pid) { Slog.w(TAG, "Unattached app died before backup, skipping"); try { @@ -5892,7 +5620,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Tell the process all about itself. - if (localLOGV) Slog.v( + if (DEBUG_ALL) Slog.v( TAG, "Binding process pid " + pid + " to record " + app); final String processName = app.processName; @@ -5928,7 +5656,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.i(TAG, "Launching preboot mode app: " + app); } - if (localLOGV) Slog.v( + if (DEBUG_ALL) Slog.v( TAG, "New app record " + app + " thread=" + thread.asBinder() + " pid=" + pid); try { @@ -5974,7 +5702,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.instrumentationClass != null) { ensurePackageDexOpt(app.instrumentationClass.getPackageName()); } - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Binding proc " + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc " + processName + " with config " + mConfiguration); ApplicationInfo appInfo = app.instrumentationInfo != null ? app.instrumentationInfo : app.info; @@ -6007,7 +5735,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Remove this record from the list of starting applications. mPersistentStartingProcesses.remove(app); - if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG, + if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES, "Attach application locked removing on hold: " + app); mProcessesOnHold.remove(app); @@ -6049,7 +5777,8 @@ public final class ActivityManagerService extends ActivityManagerNative // Check whether the next backup agent is in this process... if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.uid) { - if (DEBUG_BACKUP) Slog.v(TAG, "New app is backup target, launching agent for " + app); + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, + "New app is backup target, launching agent for " + app); ensurePackageDexOpt(mBackupTarget.appInfo.packageName); try { thread.scheduleCreateBackupAgent(mBackupTarget.appInfo, @@ -6123,7 +5852,10 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void showBootMessage(final CharSequence msg, final boolean always) { - enforceNotIsolatedCaller("showBootMessage"); + if (Binder.getCallingUid() != Process.myUid()) { + // These days only the core system can call this, so apps can't get in + // the way of what we show about running them. + } mWindowManager.showBootMessage(msg, always); } @@ -6177,7 +5909,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (String pkg : pkgs) { synchronized (ActivityManagerService.this) { if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, - 0, "finished booting")) { + 0, "query restart")) { setResultCode(Activity.RESULT_OK); return; } @@ -6187,6 +5919,19 @@ public final class ActivityManagerService extends ActivityManagerNative } }, pkgFilter); + IntentFilter dumpheapFilter = new IntentFilter(); + dumpheapFilter.addAction(DumpHeapActivity.ACTION_DELETE_DUMPHEAP); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getBooleanExtra(DumpHeapActivity.EXTRA_DELAY_DELETE, false)) { + mHandler.sendEmptyMessageDelayed(POST_DUMP_HEAP_NOTIFICATION_MSG, 5*60*1000); + } else { + mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG); + } + } + }, dumpheapFilter); + // Let system services know. mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED); @@ -6198,7 +5943,7 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(mProcessesOnHold); for (int ip=0; ip<NP; ip++) { - if (DEBUG_PROCESSES) Slog.v(TAG, "Starting process on hold: " + if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "Starting process on hold: " + procs.get(ip)); startProcessLocked(procs.get(ip), "on-hold", null); } @@ -6316,7 +6061,7 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public final void activityStopped(IBinder token, Bundle icicle, PersistableBundle persistentState, CharSequence description) { - if (localLOGV) Slog.v(TAG, "Activity stopped: token=" + token); + if (DEBUG_ALL) Slog.v(TAG, "Activity stopped: token=" + token); // Refuse possible leaked file descriptors if (icicle != null && icicle.hasFileDescriptors()) { @@ -6339,7 +6084,7 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public final void activityDestroyed(IBinder token) { - if (DEBUG_SWITCH) Slog.v(TAG, "ACTIVITY DESTROYED: " + token); + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token); synchronized (this) { ActivityStack stack = ActivityRecord.getStackLocked(token); if (stack != null) { @@ -6495,8 +6240,7 @@ public final class ActivityManagerService extends ActivityManagerNative int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) { - if (DEBUG_MU) - Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid); + if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid); ActivityRecord activity = null; if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { activity = ActivityRecord.isInStackLocked(token); @@ -6673,31 +6417,38 @@ public final class ActivityManagerService extends ActivityManagerNative } try { PendingIntentRecord res = (PendingIntentRecord)pendingResult; - Intent intent = res.key.requestIntent; - if (intent != null) { - if (res.lastTag != null && res.lastTagPrefix == prefix && (res.lastTagPrefix == null - || res.lastTagPrefix.equals(prefix))) { - return res.lastTag; - } - res.lastTagPrefix = prefix; - StringBuilder sb = new StringBuilder(128); - if (prefix != null) { - sb.append(prefix); - } - if (intent.getAction() != null) { - sb.append(intent.getAction()); - } else if (intent.getComponent() != null) { - intent.getComponent().appendShortString(sb); - } else { - sb.append("?"); - } - return res.lastTag = sb.toString(); + synchronized (this) { + return getTagForIntentSenderLocked(res, prefix); } } catch (ClassCastException e) { } return null; } + String getTagForIntentSenderLocked(PendingIntentRecord res, String prefix) { + final Intent intent = res.key.requestIntent; + if (intent != null) { + if (res.lastTag != null && res.lastTagPrefix == prefix && (res.lastTagPrefix == null + || res.lastTagPrefix.equals(prefix))) { + return res.lastTag; + } + res.lastTagPrefix = prefix; + final StringBuilder sb = new StringBuilder(128); + if (prefix != null) { + sb.append(prefix); + } + if (intent.getAction() != null) { + sb.append(intent.getAction()); + } else if (intent.getComponent() != null) { + intent.getComponent().appendShortString(sb); + } else { + sb.append("?"); + } + return res.lastTag = sb.toString(); + } + return null; + } + @Override public void setProcessLimit(int max) { enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT, @@ -6742,7 +6493,7 @@ public final class ActivityManagerService extends ActivityManagerNative "setProcessForeground()"); synchronized(this) { boolean changed = false; - + synchronized (mPidsSelfLocked) { ProcessRecord pr = mPidsSelfLocked.get(pid); if (pr == null && isForeground) { @@ -6778,13 +6529,52 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + if (changed) { updateOomAdjLocked(); } } } - + + // ========================================================= + // PROCESS INFO + // ========================================================= + + static class ProcessInfoService extends IProcessInfoService.Stub { + final ActivityManagerService mActivityManagerService; + ProcessInfoService(ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } + + @Override + public void getProcessStatesFromPids(/*in*/ int[] pids, /*out*/ int[] states) { + mActivityManagerService.getProcessStatesForPIDs(/*in*/ pids, /*out*/ states); + } + } + + /** + * For each PID in the given input array, write the current process state + * for that process into the output array, or -1 to indicate that no + * process with the given PID exists. + */ + public void getProcessStatesForPIDs(/*in*/ int[] pids, /*out*/ int[] states) { + if (pids == null) { + throw new NullPointerException("pids"); + } else if (states == null) { + throw new NullPointerException("states"); + } else if (pids.length != states.length) { + throw new IllegalArgumentException("input and output arrays have different lengths!"); + } + + synchronized (mPidsSelfLocked) { + for (int i = 0; i < pids.length; i++) { + ProcessRecord pr = mPidsSelfLocked.get(pids[i]); + states[i] = (pr == null) ? ActivityManager.PROCESS_STATE_NONEXISTENT : + pr.curProcState; + } + } + } + // ========================================================= // PERMISSIONS // ========================================================= @@ -6834,7 +6624,7 @@ public final class ActivityManagerService extends ActivityManagerNative * permission is automatically denied. (Internally a null permission * string is used when calling {@link #checkComponentPermission} in cases * when only uid-based security is needed.) - * + * * This can be called with or without the global lock held. */ @Override @@ -6842,7 +6632,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (permission == null) { return PackageManager.PERMISSION_DENIED; } - return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true); + return checkComponentPermission(permission, pid, uid, -1, true); } @Override @@ -6862,7 +6652,7 @@ public final class ActivityManagerService extends ActivityManagerNative pid = tlsIdentity.pid; } - return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true); + return checkComponentPermission(permission, pid, uid, -1, true); } /** @@ -6899,7 +6689,7 @@ public final class ActivityManagerService extends ActivityManagerNative */ private final boolean checkHoldingPermissionsLocked( IPackageManager pm, ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "checkHoldingPermissionsLocked: uri=" + grantUri + " uid=" + uid); if (UserHandle.getUserId(uid) != grantUri.sourceUserId) { if (ActivityManager.checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true) @@ -6947,8 +6737,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (pp.match(path)) { if (!readMet) { final String pprperm = pp.getReadPermission(); - if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for " - + pprperm + " for " + pp.getPath() + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, + "Checking read perm for " + pprperm + " for " + pp.getPath() + ": match=" + pp.match(path) + " check=" + pm.checkUidPermission(pprperm, uid)); if (pprperm != null) { @@ -6962,8 +6752,8 @@ public final class ActivityManagerService extends ActivityManagerNative } if (!writeMet) { final String ppwperm = pp.getWritePermission(); - if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm " - + ppwperm + " for " + pp.getPath() + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, + "Checking write perm " + ppwperm + " for " + pp.getPath() + ": match=" + pp.match(path) + " check=" + pm.checkUidPermission(ppwperm, uid)); if (ppwperm != null) { @@ -7108,15 +6898,15 @@ public final class ActivityManagerService extends ActivityManagerNative } if (targetPkg != null) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Checking grant " + targetPkg + " permission to " + grantUri); } - + final IPackageManager pm = AppGlobals.getPackageManager(); // If this is not a content: uri, we can't do anything with it. if (!ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Can't grant URI permission for non-content URI: " + grantUri); return -1; } @@ -7134,7 +6924,7 @@ public final class ActivityManagerService extends ActivityManagerNative try { targetUid = pm.getPackageUid(targetPkg, UserHandle.getUserId(callingUid)); if (targetUid < 0) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Can't grant URI permission no uid for: " + targetPkg); return -1; } @@ -7147,7 +6937,7 @@ public final class ActivityManagerService extends ActivityManagerNative // First... does the target actually need this permission? if (checkHoldingPermissionsLocked(pm, pi, grantUri, targetUid, modeFlags)) { // No need to grant the target this permission. - if (DEBUG_URI_PERMISSION) Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Target " + targetPkg + " already has full permission to " + grantUri); return -1; } @@ -7244,7 +7034,7 @@ public final class ActivityManagerService extends ActivityManagerNative // to the uri, and the target doesn't. Let's now give this to // the target. - if (DEBUG_URI_PERMISSION) Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Granting " + targetPkg + "/" + targetUid + " permission to " + grantUri); final String authority = grantUri.uri.getAuthority(); @@ -7302,7 +7092,7 @@ public final class ActivityManagerService extends ActivityManagerNative */ NeededUriGrants checkGrantUriPermissionFromIntentLocked(int callingUid, String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Checking URI perm to data=" + (intent != null ? intent.getData() : null) + " clip=" + (intent != null ? intent.getClipData() : null) + " from " + intent + "; flags=0x" @@ -7336,10 +7126,9 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } if (targetUid < 0) { - if (DEBUG_URI_PERMISSION) { - Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg - + " on user " + targetUserId); - } + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, + "Can't grant URI permission no uid for: " + targetPkg + + " on user " + targetUserId); return null; } } @@ -7446,7 +7235,7 @@ public final class ActivityManagerService extends ActivityManagerNative final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get( perm.targetUid); if (perms != null) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Removing " + perm.targetUid + " permission to " + perm.uri); perms.remove(perm.uri); @@ -7458,7 +7247,8 @@ public final class ActivityManagerService extends ActivityManagerNative } private void revokeUriPermissionLocked(int callingUid, GrantUri grantUri, final int modeFlags) { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + grantUri); + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, + "Revoking all granted permissions to " + grantUri); final IPackageManager pm = AppGlobals.getPackageManager(); final String authority = grantUri.uri.getAuthority(); @@ -7480,9 +7270,9 @@ public final class ActivityManagerService extends ActivityManagerNative final UriPermission perm = it.next(); if (perm.uri.sourceUserId == grantUri.sourceUserId && perm.uri.uri.isPathPrefixMatch(grantUri.uri)) { - if (DEBUG_URI_PERMISSION) - Slog.v(TAG, "Revoking non-owned " + perm.targetUid + - " permission to " + perm.uri); + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, + "Revoking non-owned " + perm.targetUid + + " permission to " + perm.uri); persistChanged |= perm.revokeModes( modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false); if (perm.modeFlags == 0) { @@ -7512,8 +7302,7 @@ public final class ActivityManagerService extends ActivityManagerNative final UriPermission perm = it.next(); if (perm.uri.sourceUserId == grantUri.sourceUserId && perm.uri.uri.isPathPrefixMatch(grantUri.uri)) { - if (DEBUG_URI_PERMISSION) - Slog.v(TAG, + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "Revoking " + perm.targetUid + " permission to " + perm.uri); persistChanged |= perm.revokeModes( modeFlags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, true); @@ -7559,7 +7348,6 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - final IPackageManager pm = AppGlobals.getPackageManager(); final String authority = uri.getAuthority(); final ProviderInfo pi = getProviderInfoLocked(authority, userId); if (pi == null) { @@ -7699,7 +7487,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private void writeGrantedUriPermissions() { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, "writeGrantedUriPermissions()"); + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "writeGrantedUriPermissions()"); // Snapshot permissions so we can persist without lock ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList(); @@ -7747,7 +7535,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private void readGrantedUriPermissionsLocked() { - if (DEBUG_URI_PERMISSION) Slog.v(TAG, "readGrantedUriPermissions()"); + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "readGrantedUriPermissions()"); final long now = System.currentTimeMillis(); @@ -7925,9 +7713,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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); - } + if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, + "Trimming grant created at " + perm.persistedCreateTime); perm.releasePersistableModes(~0); removeUriPermissionIfNeededLocked(perm); @@ -7995,7 +7782,7 @@ public final class ActivityManagerService extends ActivityManagerNative msg.what = WAIT_FOR_DEBUGGER_MSG; msg.obj = app; msg.arg1 = waiting ? 1 : 0; - mHandler.sendMessage(msg); + mUiHandler.sendMessage(msg); } } @@ -8015,7 +7802,7 @@ public final class ActivityManagerService extends ActivityManagerNative outInfo.foregroundAppThreshold = mProcessList.getMemLevel( ProcessList.FOREGROUND_APP_ADJ); } - + // ========================================================= // TASK MANAGEMENT // ========================================================= @@ -8028,7 +7815,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { ArrayList<IAppTask> list = new ArrayList<IAppTask>(); try { - if (localLOGV) Slog.v(TAG, "getAppTasks"); + if (DEBUG_ALL) Slog.v(TAG, "getAppTasks"); final int N = mRecentTasks.size(); for (int i = 0; i < N; i++) { @@ -8062,7 +7849,7 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>(); synchronized(this) { - if (localLOGV) Slog.v( + if (DEBUG_ALL) Slog.v( TAG, "getTasks: max=" + maxNum + ", flags=" + flags); final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(), @@ -8096,6 +7883,27 @@ public final class ActivityManagerService extends ActivityManagerNative rti.lastActiveTime = tr.lastActiveTime; rti.affiliatedTaskId = tr.mAffiliatedTaskId; rti.affiliatedTaskColor = tr.mAffiliatedTaskColor; + rti.numActivities = 0; + + ActivityRecord base = null; + ActivityRecord top = null; + ActivityRecord tmp; + + for (int i = tr.mActivities.size() - 1; i >= 0; --i) { + tmp = tr.mActivities.get(i); + if (tmp.finishing) { + continue; + } + base = tmp; + if (top == null || (top.state == ActivityState.INITIALIZING)) { + top = base; + } + rti.numActivities++; + } + + rti.baseActivity = (base != null) ? base.intent.getComponent() : null; + rti.topActivity = (top != null) ? top.intent.getComponent() : null; + return rti; } @@ -8121,7 +7929,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (!allowed) { Slog.w(TAG, caller + ": caller " + callingUid - + " does not hold GET_TASKS; limiting output"); + + " does not hold REAL_GET_TASKS; limiting output"); } return allowed; } @@ -8141,24 +7949,23 @@ public final class ActivityManagerService extends ActivityManagerNative android.Manifest.permission.GET_DETAILED_TASKS) == PackageManager.PERMISSION_GRANTED; - final int N = mRecentTasks.size(); - ArrayList<ActivityManager.RecentTaskInfo> res - = new ArrayList<ActivityManager.RecentTaskInfo>( - maxNum < N ? maxNum : N); + final int recentsCount = mRecentTasks.size(); + ArrayList<ActivityManager.RecentTaskInfo> res = + new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount); final Set<Integer> includedUsers; if (includeProfiles) { includedUsers = getProfileIdsLocked(userId); } else { - includedUsers = new HashSet<Integer>(); + includedUsers = new HashSet<>(); } includedUsers.add(Integer.valueOf(userId)); - for (int i=0; i<N && maxNum > 0; i++) { + for (int i = 0; i < recentsCount && maxNum > 0; i++) { TaskRecord tr = mRecentTasks.get(i); // Only add calling user or related users recent tasks if (!includedUsers.contains(Integer.valueOf(tr.userId))) { - if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, not user: " + tr); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr); continue; } @@ -8177,25 +7984,27 @@ public final class ActivityManagerService extends ActivityManagerNative // If the caller doesn't have the GET_TASKS permission, then only // allow them to see a small subset of tasks -- their own and home. if (!tr.isHomeTask() && tr.effectiveUid != callingUid) { - if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, not allowed: " + tr); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr); continue; } } if ((flags & ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS) != 0) { if (tr.stack != null && tr.stack.isHomeStack()) { - if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, home stack task: " + tr); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, home stack task: " + tr); continue; } } if (tr.autoRemoveRecents && tr.getTopActivity() == null) { // Don't include auto remove tasks that are finished or finishing. - if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, auto-remove without activity: " - + tr); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, auto-remove without activity: " + tr); continue; } if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0 && !tr.isAvailable) { - if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, unavail real act: " + tr); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "Skipping, unavail real act: " + tr); continue; } @@ -8212,23 +8021,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } - 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; - } - } - return null; - } - @Override public ActivityManager.TaskThumbnail getTaskThumbnail(int id) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, "getTaskThumbnail()"); - TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id); + TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id, false); if (tr != null) { return tr.getTaskThumbnailLocked(); } @@ -8293,7 +8091,7 @@ public final class ActivityManagerService extends ActivityManagerNative TaskRecord task = new TaskRecord(this, mStackSupervisor.getNextTaskId(), ainfo, intent, description); - int trimIdx = trimRecentsForTaskLocked(task, false); + int trimIdx = mRecentTasks.trimForTaskLocked(task, false); if (trimIdx >= 0) { // If this would have caused a trim, then we'll abort because that // means it would be added at the end of the list but then just removed. @@ -8339,6 +8137,41 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void setTaskResizeable(int taskId, boolean resizeable) { + synchronized (this) { + TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, false); + if (task == null) { + Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found"); + return; + } + if (task.mResizeable != resizeable) { + task.mResizeable = resizeable; + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); + mStackSupervisor.resumeTopActivitiesLocked(); + } + } + } + + @Override + public void resizeTask(int taskId, Rect bounds) { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "resizeTask()"); + long ident = Binder.clearCallingIdentity(); + try { + synchronized (this) { + TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); + if (task == null) { + Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found"); + return; + } + mStackSupervisor.resizeTaskLocked(task, bounds); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public Bitmap getTaskDescriptionIcon(String filename) { if (!FileUtils.isValidExtFilename(filename) || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) { @@ -8473,7 +8306,7 @@ public final class ActivityManagerService extends ActivityManagerNative * @return Returns true if the given task was found and removed. */ private boolean removeTaskByIdLocked(int taskId, boolean killProcess) { - TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId); + TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId, false); if (tr != null) { tr.removeTaskActivitiesLocked(); cleanUpRemovedTaskLocked(tr, killProcess); @@ -8505,10 +8338,9 @@ public final class ActivityManagerService extends ActivityManagerNative */ @Override public void moveTaskToFront(int taskId, int flags, Bundle options) { - enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, - "moveTaskToFront()"); + enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()"); - if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving taskId=" + taskId); + if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToFront: moving taskId=" + taskId); synchronized(this) { moveTaskToFrontLocked(taskId, flags, options); } @@ -8543,40 +8375,10 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityOptions.abort(options); } - @Override - public void moveTaskToBack(int taskId) { - enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, - "moveTaskToBack()"); - - synchronized(this) { - TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId); - if (tr != null) { - if (tr == mStackSupervisor.mLockTaskModeTask) { - mStackSupervisor.showLockTaskToast(); - return; - } - 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(), -1, -1, "Task to back")) { - return; - } - } - final long origId = Binder.clearCallingIdentity(); - try { - stack.moveTaskToBackLocked(taskId); - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - } - /** * Moves an activity, and all of the other activities within the same task, to the bottom * of the history stack. The activity's order within the task is unchanged. - * + * * @param token A reference to the activity we wish to move * @param nonRoot If false then this only works if the activity is the root * of a task; if true it will work for any activity in a task. @@ -8589,9 +8391,9 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); try { int taskId = ActivityRecord.getTaskForActivityLocked(token, !nonRoot); - if (taskId >= 0) { - if ((mStackSupervisor.mLockTaskModeTask != null) - && (mStackSupervisor.mLockTaskModeTask.taskId == taskId)) { + final TaskRecord task = mRecentTasks.taskForIdLocked(taskId); + if (task != null) { + if (mStackSupervisor.isLockedTask(task)) { mStackSupervisor.showLockTaskToast(); return false; } @@ -8634,7 +8436,7 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public IActivityContainer createActivityContainer(IBinder parentActivityToken, + public IActivityContainer createVirtualActivityContainer(IBinder parentActivityToken, IActivityContainerCallback callback) throws RemoteException { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, "createActivityContainer()"); @@ -8642,14 +8444,14 @@ public final class ActivityManagerService extends ActivityManagerNative if (parentActivityToken == null) { throw new IllegalArgumentException("parent token must not be null"); } - ActivityRecord r = ActivityRecord.forToken(parentActivityToken); + ActivityRecord r = ActivityRecord.forTokenLocked(parentActivityToken); if (r == null) { return null; } if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } - return mStackSupervisor.createActivityContainer(r, callback); + return mStackSupervisor.createVirtualActivityContainer(r, callback); } } @@ -8663,14 +8465,27 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public IActivityContainer getEnclosingActivityContainer(IBinder activityToken) - throws RemoteException { + public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "createStackOnDisplay()"); + synchronized (this) { + final int stackId = mStackSupervisor.getNextStackId(); + final ActivityStack stack = mStackSupervisor.createStackOnDisplay(stackId, displayId); + if (stack == null) { + return null; + } + return stack.mActivityContainer; + } + } + + @Override + public int getActivityDisplayId(IBinder activityToken) throws RemoteException { synchronized (this) { ActivityStack stack = ActivityRecord.getStackLocked(activityToken); - if (stack != null) { - return stack.mActivityContainer; + if (stack != null && stack.mActivityContainer.isAttachedLocked()) { + return stack.mActivityContainer.getDisplayId(); } - return null; + return Display.DEFAULT_DISPLAY; } } @@ -8685,8 +8500,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { long ident = Binder.clearCallingIdentity(); try { - if (DEBUG_STACK) Slog.d(TAG, "moveTaskToStack: moving task=" + taskId + " to stackId=" - + stackId + " toTop=" + toTop); + if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId + + " to stackId=" + stackId + " toTop=" + toTop); mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop); } finally { Binder.restoreCallingIdentity(ident); @@ -8695,12 +8510,14 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public void resizeStack(int stackBoxId, Rect bounds) { + public void resizeStack(int stackId, Rect bounds) { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, - "resizeStackBox()"); + "resizeStack()"); long ident = Binder.clearCallingIdentity(); try { - mWindowManager.resizeStack(stackBoxId, bounds); + synchronized (this) { + mStackSupervisor.resizeStackLocked(stackId, bounds); + } } finally { Binder.restoreCallingIdentity(ident); } @@ -8741,7 +8558,7 @@ public final class ActivityManagerService extends ActivityManagerNative long ident = Binder.clearCallingIdentity(); try { synchronized (this) { - TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId); + TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(taskId, false); return tr != null && tr.stack != null && tr.stack.isHomeStack(); } } finally { @@ -8756,47 +8573,63 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private boolean isLockTaskAuthorized(String pkg) { - final DevicePolicyManager dpm = (DevicePolicyManager) - mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); - try { - int uid = mContext.getPackageManager().getPackageUid(pkg, - Binder.getCallingUserHandle().getIdentifier()); - return (uid == Binder.getCallingUid()) && dpm != null && dpm.isLockTaskPermitted(pkg); - } catch (NameNotFoundException e) { - return false; + @Override + public void updateDeviceOwner(String packageName) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { + throw new SecurityException("updateDeviceOwner called from non-system process"); + } + synchronized (this) { + mDeviceOwnerName = packageName; } } - void startLockTaskMode(TaskRecord task) { - final String pkg; + @Override + public void updateLockTaskPackages(int userId, String[] packages) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { + throw new SecurityException("updateLockTaskPackage called from non-system process"); + } synchronized (this) { - pkg = task.intent.getComponent().getPackageName(); + mLockTaskPackages.put(userId, packages); + mStackSupervisor.onLockTaskPackagesUpdatedLocked(); } - boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID; - if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) { - StatusBarManagerInternal statusBarManager = LocalServices.getService( - StatusBarManagerInternal.class); - if (statusBarManager != null) { - statusBarManager.showScreenPinningRequest(); - } + } + + + void startLockTaskModeLocked(TaskRecord task) { + if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { return; } + + // isSystemInitiated is used to distinguish between locked and pinned mode, as pinned mode + // is initiated by system after the pinning request was shown and locked mode is initiated + // by an authorized app directly + final int callingUid = Binder.getCallingUid(); + boolean isSystemInitiated = callingUid == Process.SYSTEM_UID; long ident = Binder.clearCallingIdentity(); try { - synchronized (this) { - // Since we lost lock on task, make sure it is still there. - task = mStackSupervisor.anyTaskForIdLocked(task.taskId); - if (task != null) { - if (!isSystemInitiated - && ((mStackSupervisor.getFocusedStack() == null) - || (task != mStackSupervisor.getFocusedStack().topTask()))) { - throw new IllegalArgumentException("Invalid task, not in foreground"); + final ActivityStack stack = mStackSupervisor.getFocusedStack(); + if (!isSystemInitiated) { + task.mLockTaskUid = callingUid; + if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) { + // startLockTask() called by app and task mode is lockTaskModeDefault. + StatusBarManagerInternal statusBarManager = + LocalServices.getService(StatusBarManagerInternal.class); + if (statusBarManager != null) { + statusBarManager.showScreenPinningRequest(); } - mStackSupervisor.setLockTaskModeLocked(task, !isSystemInitiated, - "startLockTask"); + return; + } + + if (stack == null || task != stack.topTask()) { + throw new IllegalArgumentException("Invalid task, not in foreground"); } } + mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated ? + ActivityManager.LOCK_TASK_MODE_PINNED : + ActivityManager.LOCK_TASK_MODE_LOCKED, + "startLockTask"); } finally { Binder.restoreCallingIdentity(ident); } @@ -8804,37 +8637,25 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void startLockTaskMode(int taskId) { - final TaskRecord task; - long ident = Binder.clearCallingIdentity(); - try { - synchronized (this) { - task = mStackSupervisor.anyTaskForIdLocked(taskId); + synchronized (this) { + final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); + if (task != null) { + startLockTaskModeLocked(task); } - } finally { - Binder.restoreCallingIdentity(ident); - } - if (task != null) { - startLockTaskMode(task); } } @Override public void startLockTaskMode(IBinder token) { - final TaskRecord task; - long ident = Binder.clearCallingIdentity(); - try { - synchronized (this) { - final ActivityRecord r = ActivityRecord.forToken(token); - if (r == null) { - return; - } - task = r.task; + synchronized (this) { + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + if (r == null) { + return; + } + final TaskRecord task = r.task; + if (task != null) { + startLockTaskModeLocked(task); } - } finally { - Binder.restoreCallingIdentity(ident); - } - if (task != null) { - startLockTaskMode(task); } } @@ -8844,11 +8665,12 @@ public final class ActivityManagerService extends ActivityManagerNative "startLockTaskModeOnCurrent"); long ident = Binder.clearCallingIdentity(); try { - ActivityRecord r = null; synchronized (this) { - r = mStackSupervisor.topRunningActivityLocked(); + ActivityRecord r = mStackSupervisor.topRunningActivityLocked(); + if (r != null) { + startLockTaskModeLocked(r.task); + } } - startLockTaskMode(r.task); } finally { Binder.restoreCallingIdentity(ident); } @@ -8856,30 +8678,23 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void stopLockTaskMode() { - // Verify that the user matches the package of the intent for the TaskRecord - // we are locked to or systtem. This will ensure the same caller for startLockTaskMode - // and stopLockTaskMode. - final int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID) { - try { - String pkg = - mStackSupervisor.mLockTaskModeTask.intent.getComponent().getPackageName(); - int uid = mContext.getPackageManager().getPackageUid(pkg, - Binder.getCallingUserHandle().getIdentifier()); - if (uid != callingUid) { - throw new SecurityException("Invalid uid, expected " + uid); - } - } catch (NameNotFoundException e) { - Log.d(TAG, "stopLockTaskMode " + e); - return; - } + final TaskRecord lockTask = mStackSupervisor.getLockedTaskLocked(); + if (lockTask == null) { + // Our work here is done. + return; + } + // Ensure the same caller for startLockTaskMode and stopLockTaskMode. + if (getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED && + Binder.getCallingUid() != lockTask.mLockTaskUid) { + throw new SecurityException("Invalid uid, expected " + lockTask.mLockTaskUid); } long ident = Binder.clearCallingIdentity(); try { Log.d(TAG, "stopLockTaskMode"); // Stop lock task synchronized (this) { - mStackSupervisor.setLockTaskModeLocked(null, false, "stopLockTask"); + mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE, + "stopLockTask"); } } finally { Binder.restoreCallingIdentity(ident); @@ -8900,8 +8715,24 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public boolean isInLockTaskMode() { + return getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE; + } + + @Override + public int getLockTaskModeState() { + synchronized (this) { + return mStackSupervisor.getLockTaskModeState(); + } + } + + @Override + public void showLockTaskEscapeMessage(IBinder token) { synchronized (this) { - return mStackSupervisor.isInLockTaskMode(); + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + if (r == null) { + return; + } + mStackSupervisor.showLockTaskEscapeMessageLocked(r.task); } } @@ -8917,8 +8748,8 @@ public final class ActivityManagerService extends ActivityManagerNative STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { } - if (DEBUG_MU) - Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.uid); + if (DEBUG_MU) Slog.v(TAG_MU, + "generateApplicationProvidersLocked, app.info.uid = " + app.uid); int userId = app.userId; if (providers != null) { int N = providers.size(); @@ -8928,7 +8759,7 @@ public final class ActivityManagerService extends ActivityManagerNative (ProviderInfo)providers.get(i); boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags); - if (singleton && UserHandle.getUserId(app.uid) != 0) { + if (singleton && UserHandle.getUserId(app.uid) != UserHandle.USER_OWNER) { // This is a singleton provider, but a user besides the // default user is asking to initialize a process it runs // in... well, no, it doesn't actually run in this process, @@ -8945,8 +8776,8 @@ public final class ActivityManagerService extends ActivityManagerNative cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton); mProviderMap.putProviderByClass(comp, cpr); } - if (DEBUG_MU) - Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid); + if (DEBUG_MU) Slog.v(TAG_MU, + "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid); app.pubProviders.put(cpi.name, cpr); if (!cpi.multiprocess || !"android".equals(cpi.packageName)) { // Don't add this if it is a platform component that is marked @@ -9002,7 +8833,7 @@ public final class ActivityManagerService extends ActivityManagerNative == PackageManager.PERMISSION_GRANTED) { return null; } - + PathPermission[] pps = cpi.pathPermissions; if (pps != null) { int i = pps.length; @@ -9084,7 +8915,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<r.conProviders.size(); i++) { ContentProviderConnection conn = r.conProviders.get(i); if (conn.provider == cpr) { - if (DEBUG_PROVIDER) Slog.v(TAG, + if (DEBUG_PROVIDER) Slog.v(TAG_PROVIDER, "Adding provider requested by " + r.processName + " from process " + cpr.info.processName + ": " + cpr.name.flattenToShortString() @@ -9120,7 +8951,7 @@ public final class ActivityManagerService extends ActivityManagerNative ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) { if (conn != null) { cpr = conn.provider; - if (DEBUG_PROVIDER) Slog.v(TAG, + if (DEBUG_PROVIDER) Slog.v(TAG_PROVIDER, "Removing provider requested by " + conn.client.processName + " from process " + cpr.info.processName + ": " + cpr.name.flattenToShortString() @@ -9248,7 +9079,7 @@ public final class ActivityManagerService extends ActivityManagerNative checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); boolean success = updateOomAdjLocked(cpr.proc); checkTime(startTime, "getContentProviderImpl: after updateOomAdj"); - if (DEBUG_PROVIDER) Slog.i(TAG, "Adjust success: " + success); + if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success); // NOTE: there is still a race here where a signal could be // pending on the process even though we managed to update its // adj level. Not sure what to do about this, but at least @@ -9258,8 +9089,7 @@ public final class ActivityManagerService extends ActivityManagerNative // has been killed on us. We need to wait for a new // process to be started, and make sure its death // doesn't kill our process. - Slog.i(TAG, - "Existing provider " + cpr.name.flattenToShortString() + Slog.i(TAG, "Existing provider " + cpr.name.flattenToShortString() + " is crashing; detaching " + r); boolean lastRef = decProviderCountLocked(conn, cpr, token, stable); checkTime(startTime, "getContentProviderImpl: before appDied"); @@ -9370,18 +9200,16 @@ public final class ActivityManagerService extends ActivityManagerNative return cpr.newHolder(null); } - if (DEBUG_PROVIDER) { - RuntimeException e = new RuntimeException("here"); - Slog.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + (r != null ? r.uid : null) - + " pruid " + cpr.appInfo.uid + "): " + cpr.info.name, e); - } + if (DEBUG_PROVIDER) Slog.w(TAG_PROVIDER, "LAUNCHING REMOTE PROVIDER (myuid " + + (r != null ? r.uid : null) + " pruid " + cpr.appInfo.uid + "): " + + cpr.info.name + " callers=" + Debug.getCallers(6)); // This is single process, and our app is now connecting to it. // See if we are already in the process of launching this // provider. final int N = mLaunchingProviders.size(); int i; - for (i=0; i<N; i++) { + for (i = 0; i < N; i++) { if (mLaunchingProviders.get(i) == cpr) { break; } @@ -9410,14 +9238,15 @@ public final class ActivityManagerService extends ActivityManagerNative 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); - } - checkTime(startTime, "getContentProviderImpl: scheduling install"); - proc.pubProviders.put(cpi.name, cpr); - try { - proc.thread.scheduleInstallProvider(cpi); - } catch (RemoteException e) { + if (DEBUG_PROVIDER) Slog.d(TAG_PROVIDER, + "Installing in existing process " + proc); + if (!proc.pubProviders.containsKey(cpi.name)) { + checkTime(startTime, "getContentProviderImpl: scheduling install"); + proc.pubProviders.put(cpi.name, cpr); + try { + proc.thread.scheduleInstallProvider(cpi); + } catch (RemoteException e) { + } } } else { checkTime(startTime, "getContentProviderImpl: before start process"); @@ -9473,10 +9302,9 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } try { - if (DEBUG_MU) { - Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp=" - + cpr.launchingApp); - } + if (DEBUG_MU) Slog.v(TAG_MU, + "Waiting to start provider " + cpr + + " launchingApp=" + cpr.launchingApp); if (conn != null) { conn.waiting = true; } @@ -9567,7 +9395,7 @@ public final class ActivityManagerService extends ActivityManagerNative ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId); if(cpr == null) { //remove from mProvidersByClass - if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list"); + if(DEBUG_ALL) Slog.v(TAG, name+" content provider not found in providers list"); return; } @@ -9588,7 +9416,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) { if (providers == null) { @@ -9598,8 +9426,7 @@ public final class ActivityManagerService extends ActivityManagerNative enforceNotIsolatedCaller("publishContentProviders"); synchronized (this) { final ProcessRecord r = getRecordForAppLocked(caller); - if (DEBUG_MU) - Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid); + if (DEBUG_MU) Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid); if (r == null) { throw new SecurityException( "Unable to find app for caller " + caller @@ -9616,8 +9443,7 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } ContentProviderRecord dst = r.pubProviders.get(src.info.name); - if (DEBUG_MU) - Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid); + if (DEBUG_MU) Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid); if (dst != null) { ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name); mProviderMap.putProviderByClass(comp, dst); @@ -9982,10 +9808,9 @@ public final class ActivityManagerService extends ActivityManagerNative } finally { // Ensure that whatever happens, we clean up the identity state sCallerIdentity.remove(); + // Ensure we're done with the provider. + removeContentProviderExternalUnchecked(name, null, userId); } - - // We've got the fd now, so we're done with the provider. - removeContentProviderExternalUnchecked(name, null, userId); } else { Slog.d(TAG, "Failed to get provider for authority '" + name + "'"); } @@ -10010,8 +9835,8 @@ public final class ActivityManagerService extends ActivityManagerNative } void finishRunningVoiceLocked() { - if (mRunningVoice) { - mRunningVoice = false; + if (mRunningVoice != null) { + mRunningVoice = null; updateSleepIfNeededLocked(); } } @@ -10019,10 +9844,14 @@ public final class ActivityManagerService extends ActivityManagerNative void updateSleepIfNeededLocked() { if (mSleeping && !shouldSleepLocked()) { mSleeping = false; + mTopProcessState = ActivityManager.PROCESS_STATE_TOP; mStackSupervisor.comeOutOfSleepIfNeededLocked(); + updateOomAdjLocked(); } else if (!mSleeping && shouldSleepLocked()) { mSleeping = true; + mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING; mStackSupervisor.goingToSleepLocked(); + updateOomAdjLocked(); // Initialize the wake times of all processes. checkExcessivePowerUsageLocked(false); @@ -10034,19 +9863,18 @@ public final class ActivityManagerService extends ActivityManagerNative private boolean shouldSleepLocked() { // Resume applications while running a voice interactor. - if (mRunningVoice) { + if (mRunningVoice != null) { return false; } + // TODO: Transform the lock screen state into a sleep token instead. switch (mWakefulness) { case PowerManagerInternal.WAKEFULNESS_AWAKE: case PowerManagerInternal.WAKEFULNESS_DREAMING: - // If we're interactive but applications are already paused then defer - // resuming them until the lock screen is hidden. - return mSleeping && mLockScreenShown != LOCK_SCREEN_HIDDEN; case PowerManagerInternal.WAKEFULNESS_DOZING: - // If we're dozing then pause applications whenever the lock screen is shown. - return mLockScreenShown != LOCK_SCREEN_HIDDEN; + // Pause applications whenever the lock screen is shown or any sleep + // tokens have been acquired. + return (mLockScreenShown != LOCK_SCREEN_HIDDEN || !mSleepTokens.isEmpty()); case PowerManagerInternal.WAKEFULNESS_ASLEEP: default: // If we're asleep then pause applications unconditionally. @@ -10071,6 +9899,11 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void notifyCleartextNetwork(int uid, byte[] firstPacket) { + mHandler.obtainMessage(NOTIFY_CLEARTEXT_NETWORK_MSG, uid, 0, firstPacket).sendToTarget(); + } + + @Override public boolean shutdown(int timeout) { if (checkCallingPermission(android.Manifest.permission.SHUTDOWN) != PackageManager.PERMISSION_GRANTED) { @@ -10100,7 +9933,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public final void activitySlept(IBinder token) { - if (localLOGV) Slog.v(TAG, "Activity slept: token=" + token); + if (DEBUG_ALL) Slog.v(TAG, "Activity slept: token=" + token); final long origId = Binder.clearCallingIdentity(); @@ -10124,16 +9957,20 @@ public final class ActivityManagerService extends ActivityManagerNative } void logLockScreen(String msg) { - if (DEBUG_LOCKSCREEN) Slog.d(TAG, Debug.getCallers(2) + ":" + msg + if (DEBUG_LOCKSCREEN) Slog.d(TAG_LOCKSCREEN, Debug.getCallers(2) + ":" + msg + " mLockScreenShown=" + lockScreenShownToString() + " mWakefulness=" + PowerManagerInternal.wakefulnessToString(mWakefulness) + " mSleeping=" + mSleeping); } - void startRunningVoiceLocked() { - if (!mRunningVoice) { - mRunningVoice = true; - updateSleepIfNeededLocked(); + void startRunningVoiceLocked(IVoiceInteractionSession session, int targetUid) { + mVoiceWakeLock.setWorkSource(new WorkSource(targetUid)); + if (mRunningVoice == null || mRunningVoice.asBinder() != session.asBinder()) { + if (mRunningVoice == null) { + mVoiceWakeLock.acquire(); + updateSleepIfNeededLocked(); + } + mRunningVoice = session; } } @@ -10167,7 +10004,7 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException("Requires permission " + android.Manifest.permission.STOP_APP_SWITCHES); } - + synchronized(this) { mAppSwitchesAllowedTime = SystemClock.uptimeMillis() + APP_SWITCH_DELAY_TIME; @@ -10177,14 +10014,14 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME); } } - + public void resumeAppSwitches() { if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires permission " + android.Manifest.permission.STOP_APP_SWITCHES); } - + synchronized(this) { // Note that we don't execute any pending app switches... we will // let those wait until either the timeout, or the next start @@ -10192,7 +10029,7 @@ public final class ActivityManagerService extends ActivityManagerNative mAppSwitchesAllowedTime = 0; } } - + boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid, int callingPid, int callingUid, String name) { if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) { @@ -10220,7 +10057,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, name + " request from " + sourceUid + " stopped"); return false; } - + public void setDebugApp(String packageName, boolean waitForDebugger, boolean persistent) { enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP, @@ -10436,8 +10273,9 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } + @Override public Bundle getAssistContextExtras(int requestType) { - PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, + PendingAssistExtras pae = enqueueAssistContext(requestType, null, null, null, UserHandle.getCallingUserId()); if (pae == null) { return null; @@ -10449,30 +10287,30 @@ public final class ActivityManagerService extends ActivityManagerNative } catch (InterruptedException e) { } } - if (pae.result != null) { - pae.extras.putBundle(Intent.EXTRA_ASSIST_CONTEXT, pae.result); - } } synchronized (this) { + buildAssistBundleLocked(pae, pae.result); mPendingAssistExtras.remove(pae); mHandler.removeCallbacks(pae); } return pae.extras; } + @Override + public void requestAssistContextExtras(int requestType, IResultReceiver receiver) { + enqueueAssistContext(requestType, null, null, receiver, UserHandle.getCallingUserId()); + } + private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint, - int userHandle) { + IResultReceiver receiver, int userHandle) { enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO, - "getAssistContextExtras()"); - PendingAssistExtras pae; - Bundle extras = new Bundle(); + "enqueueAssistContext()"); synchronized (this) { - ActivityRecord activity = getFocusedStack().mResumedActivity; + ActivityRecord activity = getFocusedStack().topActivity(); if (activity == null) { - Slog.w(TAG, "getAssistContextExtras failed: no resumed activity"); + Slog.w(TAG, "getAssistContextExtras failed: no top activity"); return null; } - extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName); if (activity.app == null || activity.app.thread == null) { Slog.w(TAG, "getAssistContextExtras failed: no process for " + activity); return null; @@ -10481,7 +10319,11 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, "getAssistContextExtras failed: request process same as " + activity); return null; } - pae = new PendingAssistExtras(activity, extras, intent, hint, userHandle); + PendingAssistExtras pae; + Bundle extras = new Bundle(); + extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName); + extras.putInt(Intent.EXTRA_ASSIST_UID, activity.app.uid); + pae = new PendingAssistExtras(activity, extras, intent, hint, receiver, userHandle); try { activity.app.thread.requestAssistContextExtras(activity.appToken, pae, requestType); @@ -10495,13 +10337,33 @@ public final class ActivityManagerService extends ActivityManagerNative } } + void pendingAssistExtrasTimedOutLocked(PendingAssistExtras pae) { + mPendingAssistExtras.remove(pae); + if (pae.receiver != null) { + // Caller wants result sent back to them. + try { + pae.receiver.send(0, null); + } catch (RemoteException e) { + } + } + } + + private void buildAssistBundleLocked(PendingAssistExtras pae, Bundle result) { + if (result != null) { + pae.extras.putBundle(Intent.EXTRA_ASSIST_CONTEXT, result); + } + if (pae.hint != null) { + pae.extras.putBoolean(pae.hint, true); + } + } + public void reportAssistContextExtras(IBinder token, Bundle extras) { PendingAssistExtras pae = (PendingAssistExtras)token; synchronized (pae) { pae.result = extras; pae.haveResult = true; pae.notifyAll(); - if (pae.intent == null) { + if (pae.intent == null && pae.receiver == null) { // Caller is just waiting for the result. return; } @@ -10509,17 +10371,23 @@ public final class ActivityManagerService extends ActivityManagerNative // We are now ready to launch the assist activity. synchronized (this) { + buildAssistBundleLocked(pae, extras); boolean exists = mPendingAssistExtras.remove(pae); mHandler.removeCallbacks(pae); if (!exists) { // Timed out. return; } + if (pae.receiver != null) { + // Caller wants result sent back to them. + try { + pae.receiver.send(0, pae.extras); + } catch (RemoteException e) { + } + return; + } } - pae.intent.replaceExtras(extras); - if (pae.hint != null) { - pae.intent.putExtra(pae.hint, true); - } + pae.intent.replaceExtras(pae.extras); pae.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -10532,7 +10400,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public boolean launchAssistIntent(Intent intent, int requestType, String hint, int userHandle) { - return enqueueAssistContext(requestType, intent, hint, userHandle) != null; + return enqueueAssistContext(requestType, intent, hint, null, userHandle) != null; } public void registerProcessObserver(IProcessObserver observer) { @@ -10561,7 +10429,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final boolean translucentChanged = r.changeWindowTranslucency(true); if (translucentChanged) { - r.task.stack.releaseBackgroundResources(); + r.task.stack.releaseBackgroundResources(r); mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); } mWindowManager.setAppFullscreen(token, true); @@ -10588,7 +10456,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final boolean translucentChanged = r.changeWindowTranslucency(false); if (translucentChanged) { - r.task.stack.convertToTranslucent(r); + r.task.stack.convertActivityToTranslucent(r); } mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); mWindowManager.setAppFullscreen(token, false); @@ -10660,9 +10528,7 @@ public final class ActivityManagerService extends ActivityManagerNative // update associated state if we're frontmost if (r == mFocusedActivity) { - if (DEBUG_IMMERSIVE) { - Slog.d(TAG, "Frontmost changed immersion: "+ r); - } + if (DEBUG_IMMERSIVE) Slog.d(TAG_IMMERSIVE, "Frontmost changed immersion: "+ r); applyUpdateLockStateLocked(r); } } @@ -10729,25 +10595,53 @@ public final class ActivityManagerService extends ActivityManagerNative Context.WINDOW_SERVICE)).addView(v, lp); } - public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg) { + public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) { if (!(sender instanceof PendingIntentRecord)) { return; } - BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + final PendingIntentRecord rec = (PendingIntentRecord)sender; + final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); synchronized (stats) { if (mBatteryStatsService.isOnBattery()) { mBatteryStatsService.enforceCallingPermission(); - PendingIntentRecord rec = (PendingIntentRecord)sender; int MY_UID = Binder.getCallingUid(); int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid; BatteryStatsImpl.Uid.Pkg pkg = stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid, sourcePkg != null ? sourcePkg : rec.key.packageName); - pkg.incWakeupsLocked(); + pkg.noteWakeupAlarmLocked(tag); } } } + public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) { + if (!(sender instanceof PendingIntentRecord)) { + return; + } + final PendingIntentRecord rec = (PendingIntentRecord)sender; + final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + mBatteryStatsService.enforceCallingPermission(); + int MY_UID = Binder.getCallingUid(); + int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid; + mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid); + } + } + + public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) { + if (!(sender instanceof PendingIntentRecord)) { + return; + } + final PendingIntentRecord rec = (PendingIntentRecord)sender; + final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + mBatteryStatsService.enforceCallingPermission(); + int MY_UID = Binder.getCallingUid(); + int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid; + mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid); + } + } + public boolean killPids(int[] pids, String pReason, boolean secure) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("killPids only available to the system"); @@ -10951,7 +10845,8 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord proc = mLruProcesses.get(i); if (proc.notCachedSinceIdle) { - if (proc.setProcState > ActivityManager.PROCESS_STATE_TOP + if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP + && proc.setProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) { if (doKilling && proc.initialIdlePss != 0 && proc.lastPss > ((proc.initialIdlePss*3)/2)) { @@ -11011,7 +10906,8 @@ public final class ActivityManagerService extends ActivityManagerNative // This happens before any activities are started, so we can // change mConfiguration in-place. updateConfigurationLocked(configuration, null, false, true); - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Initial config: " + mConfiguration); + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, + "Initial config: " + mConfiguration); } } @@ -11105,9 +11001,68 @@ public final class ActivityManagerService extends ActivityManagerNative } } + final class PreBootContinuation extends IIntentReceiver.Stub { + final Intent intent; + final Runnable onFinishCallback; + final ArrayList<ComponentName> doneReceivers; + final List<ResolveInfo> ris; + final int[] users; + int lastRi = -1; + int curRi = 0; + int curUser = 0; + + PreBootContinuation(Intent _intent, Runnable _onFinishCallback, + ArrayList<ComponentName> _doneReceivers, List<ResolveInfo> _ris, int[] _users) { + intent = _intent; + onFinishCallback = _onFinishCallback; + doneReceivers = _doneReceivers; + ris = _ris; + users = _users; + } + + void go() { + if (lastRi != curRi) { + ActivityInfo ai = ris.get(curRi).activityInfo; + ComponentName comp = new ComponentName(ai.packageName, ai.name); + intent.setComponent(comp); + doneReceivers.add(comp); + lastRi = curRi; + CharSequence label = ai.loadLabel(mContext.getPackageManager()); + showBootMessage(mContext.getString(R.string.android_preparing_apk, label), false); + } + Slog.i(TAG, "Pre-boot of " + intent.getComponent().toShortString() + + " for user " + users[curUser]); + EventLogTags.writeAmPreBoot(users[curUser], intent.getComponent().getPackageName()); + broadcastIntentLocked(null, null, intent, null, this, + 0, null, null, null, AppOpsManager.OP_NONE, + true, false, MY_PID, Process.SYSTEM_UID, + users[curUser]); + } + + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, + boolean sticky, int sendingUser) { + curUser++; + if (curUser >= users.length) { + curUser = 0; + curRi++; + if (curRi >= ris.size()) { + // All done sending broadcasts! + if (onFinishCallback != null) { + // The raw IIntentReceiver interface is called + // with the AM lock held, so redispatch to + // execute our code without the lock. + mHandler.post(onFinishCallback); + } + return; + } + } + go(); + } + } + private boolean deliverPreBootCompleted(final Runnable onFinishCallback, ArrayList<ComponentName> doneReceivers, int userId) { - boolean waitingUpdate = false; Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); List<ResolveInfo> ris = null; try { @@ -11115,71 +11070,51 @@ public final class ActivityManagerService extends ActivityManagerNative intent, null, 0, userId); } catch (RemoteException e) { } - if (ris != null) { - for (int i=ris.size()-1; i>=0; i--) { - if ((ris.get(i).activityInfo.applicationInfo.flags - &ApplicationInfo.FLAG_SYSTEM) == 0) { - ris.remove(i); - } - } - intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE); - - // For User 0, load the version number. When delivering to a new user, deliver - // to all receivers. - if (userId == UserHandle.USER_OWNER) { - ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers(); - for (int i=0; i<ris.size(); i++) { - ActivityInfo ai = ris.get(i).activityInfo; - ComponentName comp = new ComponentName(ai.packageName, ai.name); - if (lastDoneReceivers.contains(comp)) { - // We already did the pre boot receiver for this app with the current - // platform version, so don't do it again... - ris.remove(i); - i--; - // ...however, do keep it as one that has been done, so we don't - // forget about it when rewriting the file of last done receivers. - doneReceivers.add(comp); - } - } + if (ris == null) { + return false; + } + for (int i=ris.size()-1; i>=0; i--) { + if ((ris.get(i).activityInfo.applicationInfo.flags + &ApplicationInfo.FLAG_SYSTEM) == 0) { + ris.remove(i); } + } + intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE); - // If primary user, send broadcast to all available users, else just to userId - final int[] users = userId == UserHandle.USER_OWNER ? getUsersLocked() - : new int[] { userId }; - for (int i = 0; i < ris.size(); i++) { + // For User 0, load the version number. When delivering to a new user, deliver + // to all receivers. + if (userId == UserHandle.USER_OWNER) { + ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers(); + for (int i=0; i<ris.size(); i++) { ActivityInfo ai = ris.get(i).activityInfo; ComponentName comp = new ComponentName(ai.packageName, ai.name); - doneReceivers.add(comp); - intent.setComponent(comp); - for (int j=0; j<users.length; j++) { - IIntentReceiver finisher = null; - // On last receiver and user, set up a completion callback - if (i == ris.size() - 1 && j == users.length - 1 && onFinishCallback != null) { - finisher = new IIntentReceiver.Stub() { - public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean ordered, - boolean sticky, int sendingUser) { - // The raw IIntentReceiver interface is called - // with the AM lock held, so redispatch to - // execute our code without the lock. - mHandler.post(onFinishCallback); - } - }; - } - Slog.i(TAG, "Sending system update to " + intent.getComponent() - + " for user " + users[j]); - broadcastIntentLocked(null, null, intent, null, finisher, - 0, null, null, null, AppOpsManager.OP_NONE, - true, false, MY_PID, Process.SYSTEM_UID, - users[j]); - if (finisher != null) { - waitingUpdate = true; - } + if (false && lastDoneReceivers.contains(comp)) { + // We already did the pre boot receiver for this app with the current + // platform version, so don't do it again... + ris.remove(i); + i--; + // ...however, do keep it as one that has been done, so we don't + // forget about it when rewriting the file of last done receivers. + doneReceivers.add(comp); } } } - return waitingUpdate; + if (ris.size() <= 0) { + return false; + } + + // If primary user, send broadcast to all available users, else just to userId + final int[] users = userId == UserHandle.USER_OWNER ? getUsersLocked() + : new int[] { userId }; + if (users.length <= 0) { + return false; + } + + PreBootContinuation cont = new PreBootContinuation(intent, onFinishCallback, doneReceivers, + ris, users); + cont.go(); + return true; } public void systemReady(final Runnable goingCallback) { @@ -11197,12 +11132,11 @@ public final class ActivityManagerService extends ActivityManagerNative // security checks. updateCurrentProfileIdsLocked(); - if (mRecentTasks == null) { - mRecentTasks = mTaskPersister.restoreTasksLocked(); - mTaskPersister.restoreTasksFromOtherDeviceLocked(); - cleanupRecentTasksLocked(UserHandle.USER_ALL); - mTaskPersister.startPersisting(); - } + mRecentTasks.clear(); + mRecentTasks.addAll(mTaskPersister.restoreTasksLocked()); + mTaskPersister.restoreTasksFromOtherDeviceLocked(); + mRecentTasks.cleanupLocked(UserHandle.USER_ALL); + mTaskPersister.startPersisting(); // Check to see if there are any update receivers to run. if (!mDidUpdate) { @@ -11215,9 +11149,10 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (ActivityManagerService.this) { mDidUpdate = true; } - writeLastDonePreBootReceivers(doneReceivers); - showBootMessage(mContext.getText(R.string.android_upgrading_complete), + showBootMessage(mContext.getText( + R.string.android_upgrading_complete), false); + writeLastDonePreBootReceivers(doneReceivers); systemReady(goingCallback); } }, doneReceivers, UserHandle.USER_OWNER); @@ -11244,7 +11179,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + synchronized(this) { if (procsToKill != null) { for (int i=procsToKill.size()-1; i>=0; i--) { @@ -11253,20 +11188,20 @@ public final class ActivityManagerService extends ActivityManagerNative removeProcessLocked(proc, true, false, "system update done"); } } - + // Now that we have cleaned up any update processes, we // are ready to start launching real processes and know that // we won't trample on them any more. mProcessesReady = true; } - + Slog.i(TAG, "System now ready"); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_AMS_READY, SystemClock.uptimeMillis()); synchronized(this) { // Make sure we have no pre-ready processes sitting around. - + if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) { ResolveInfo ri = mContext.getPackageManager() .resolveActivity(new Intent(Intent.ACTION_FACTORY_TEST), @@ -11295,7 +11230,7 @@ public final class ActivityManagerService extends ActivityManagerNative Message msg = Message.obtain(); msg.what = SHOW_FACTORY_ERROR_MSG; msg.getData().putCharSequence("msg", errorMsg); - mHandler.sendMessage(msg); + mUiHandler.sendMessage(msg); } } } @@ -11345,14 +11280,14 @@ public final class ActivityManagerService extends ActivityManagerNative if (AppGlobals.getPackageManager().hasSystemUidErrors()) { Slog.e(TAG, "UIDs on the system are inconsistent, you need to wipe your" + " data partition or your device will be unstable."); - mHandler.obtainMessage(SHOW_UID_ERROR_MSG).sendToTarget(); + mUiHandler.obtainMessage(SHOW_UID_ERROR_MSG).sendToTarget(); } } catch (RemoteException e) { } - if (!Build.isFingerprintConsistent()) { + if (!Build.isBuildConsistent()) { Slog.e(TAG, "Build fingerprint is not consistent, warning user"); - mHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget(); + mUiHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget(); } long ident = Binder.clearCallingIdentity(); @@ -11394,7 +11329,7 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace); startAppProblemLocked(app); app.stopFreezingAllLocked(); - return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace); + return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace); } private void makeAppNotRespondingLocked(ProcessRecord app, @@ -11406,12 +11341,12 @@ public final class ActivityManagerService extends ActivityManagerNative startAppProblemLocked(app); app.stopFreezingAllLocked(); } - + /** * Generate a process error record, suitable for attachment to a ProcessRecord. - * + * * @param app The ProcessRecord in which the error occurred. - * @param condition Crashing, Application Not Responding, etc. Values are defined in + * @param condition Crashing, Application Not Responding, etc. Values are defined in * ActivityManager.AppErrorStateInfo * @param activity The activity associated with the crash, if known. * @param shortMsg Short message describing the crash. @@ -11420,7 +11355,7 @@ public final class ActivityManagerService extends ActivityManagerNative * * @return Returns a fully-formed AppErrorStateInfo record. */ - private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app, + private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app, int condition, String activity, String shortMsg, String longMsg, String stackTrace) { ActivityManager.ProcessErrorStateInfo report = new ActivityManager.ProcessErrorStateInfo(); @@ -11449,14 +11384,15 @@ public final class ActivityManagerService extends ActivityManagerNative app.waitDialog = null; } if (app.pid > 0 && app.pid != MY_PID) { - handleAppCrashLocked(app, null, null, null); + handleAppCrashLocked(app, "user-terminated" /*reason*/, + null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/); app.kill("user request after error", true); } } } - private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg, - String stackTrace) { + private boolean handleAppCrashLocked(ProcessRecord app, String reason, + String shortMsg, String longMsg, String stackTrace) { long now = SystemClock.uptimeMillis(); Long crashTime; @@ -11497,7 +11433,7 @@ public final class ActivityManagerService extends ActivityManagerNative } mStackSupervisor.resumeTopActivitiesLocked(); } else { - mStackSupervisor.finishTopRunningActivityLocked(app); + mStackSupervisor.finishTopRunningActivityLocked(app, reason); } // Bump up the crash count of any services currently running in the proc. @@ -11638,7 +11574,7 @@ public final class ActivityManagerService extends ActivityManagerNative data.put("violationMask", violationMask); data.put("info", info); msg.obj = data; - mHandler.sendMessage(msg); + mUiHandler.sendMessage(msg); Binder.restoreCallingIdentity(origId); } @@ -11700,8 +11636,12 @@ public final class ActivityManagerService extends ActivityManagerNative sb.append("\n"); if (info.crashInfo != null && info.crashInfo.stackTrace != null) { sb.append(info.crashInfo.stackTrace); + sb.append("\n"); + } + if (info.message != null) { + sb.append(info.message); + sb.append("\n"); } - sb.append("\n"); // Only buffer up to ~64k. Various logging bits truncate // things at 128k. @@ -12089,7 +12029,7 @@ public final class ActivityManagerService extends ActivityManagerNative data.put("result", result); data.put("app", r); msg.obj = data; - mHandler.sendMessage(msg); + mUiHandler.sendMessage(msg); Binder.restoreCallingIdentity(origId); } @@ -12189,14 +12129,14 @@ public final class ActivityManagerService extends ActivityManagerNative } else if (app.notResponding) { report = app.notRespondingReport; } - + if (report != null) { if (errList == null) { errList = new ArrayList<ActivityManager.ProcessErrorStateInfo>(1); } errList.add(report); } else { - Slog.w(TAG, "Missing app error report, app = " + app.processName + + Slog.w(TAG, "Missing app error report, app = " + app.processName + " crashing = " + app.crashing + " notResponding = " + app.notResponding); } @@ -12241,21 +12181,28 @@ public final class ActivityManagerService extends ActivityManagerNative public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() { enforceNotIsolatedCaller("getRunningAppProcesses"); + + final int callingUid = Binder.getCallingUid(); + // Lazy instantiation of list List<ActivityManager.RunningAppProcessInfo> runList = null; final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, - Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED; - int userId = UserHandle.getUserId(Binder.getCallingUid()); + callingUid) == PackageManager.PERMISSION_GRANTED; + final int userId = UserHandle.getUserId(callingUid); + final boolean allUids = isGetTasksAllowed( + "getRunningAppProcesses", Binder.getCallingPid(), callingUid); + synchronized (this) { // Iterate across all processes - for (int i=mLruProcesses.size()-1; i>=0; i--) { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord app = mLruProcesses.get(i); - if (!allUsers && app.userId != userId) { + if ((!allUsers && app.userId != userId) + || (!allUids && app.uid != callingUid)) { continue; } if ((app.thread != null) && (!app.crashing && !app.notResponding)) { // Generate process state info for running application - ActivityManager.RunningAppProcessInfo currApp = + ActivityManager.RunningAppProcessInfo currApp = new ActivityManager.RunningAppProcessInfo(app.processName, app.pid, app.getPackageList()); fillInProcMemInfo(app, currApp); @@ -12274,7 +12221,7 @@ public final class ActivityManagerService extends ActivityManagerNative //Slog.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance // + " lru=" + currApp.lru); if (runList == null) { - runList = new ArrayList<ActivityManager.RunningAppProcessInfo>(); + runList = new ArrayList<>(); } runList.add(currApp); } @@ -12337,7 +12284,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean dumpAll = false; boolean dumpClient = false; String dumpPackage = null; - + int opti = 0; while (opti < args.length) { String opt = args[opti]; @@ -12827,12 +12774,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + if (mForegroundProcesses.size() > 0) { synchronized (mPidsSelfLocked) { boolean printed = false; for (int i=0; i<mForegroundProcesses.size(); i++) { - ProcessRecord r = mPidsSelfLocked.get( + ProcessRecord r = mPidsSelfLocked.get( mForegroundProcesses.valueAt(i).pid); if (dumpPackage != null && (r == null || !r.pkgList.containsKey(dumpPackage))) { @@ -12850,7 +12797,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + if (mPersistentStartingProcesses.size() > 0) { if (needSep) pw.println(); needSep = true; @@ -12868,7 +12815,7 @@ public final class ActivityManagerService extends ActivityManagerNative dumpProcessList(pw, this, mRemovedProcesses, " ", "Removed Norm", "Removed PERS", dumpPackage); } - + if (mProcessesOnHold.size() > 0) { if (needSep) pw.println(); needSep = true; @@ -12879,7 +12826,7 @@ public final class ActivityManagerService extends ActivityManagerNative } needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage); - + if (mProcessCrashTimes.getMap().size() > 0) { boolean printed = false; long now = SystemClock.uptimeMillis(); @@ -13056,10 +13003,14 @@ public final class ActivityManagerService extends ActivityManagerNative if (dumpPackage == null) { pw.println(" mWakefulness=" + PowerManagerInternal.wakefulnessToString(mWakefulness)); + pw.println(" mSleepTokens=" + mSleepTokens); pw.println(" mSleeping=" + mSleeping + " mLockScreenShown=" + lockScreenShownToString()); - pw.println(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice - + " mTestPssMode=" + mTestPssMode); + pw.println(" mShuttingDown=" + mShuttingDown + " mTestPssMode=" + mTestPssMode); + if (mRunningVoice != null) { + pw.println(" mRunningVoice=" + mRunningVoice); + pw.println(" mVoiceWakeLock" + mVoiceWakeLock); + } } if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { @@ -13074,6 +13025,34 @@ public final class ActivityManagerService extends ActivityManagerNative + " mOrigWaitForDebugger=" + mOrigWaitForDebugger); } } + if (mMemWatchProcesses.getMap().size() > 0) { + pw.println(" Mem watch processes:"); + final ArrayMap<String, SparseArray<Pair<Long, String>>> procs + = mMemWatchProcesses.getMap(); + for (int i=0; i<procs.size(); i++) { + final String proc = procs.keyAt(i); + final SparseArray<Pair<Long, String>> uids = procs.valueAt(i); + for (int j=0; j<uids.size(); j++) { + if (needSep) { + pw.println(); + needSep = false; + } + StringBuilder sb = new StringBuilder(); + sb.append(" ").append(proc).append('/'); + UserHandle.formatUid(sb, uids.keyAt(j)); + Pair<Long, String> val = uids.valueAt(j); + sb.append(": "); DebugUtils.sizeValueToString(val.first, sb); + if (val.second != null) { + sb.append(", report to ").append(val.second); + } + pw.println(sb.toString()); + } + } + pw.print(" mMemWatchDumpProcName="); pw.println(mMemWatchDumpProcName); + pw.print(" mMemWatchDumpFile="); pw.println(mMemWatchDumpFile); + pw.print(" mMemWatchDumpPid="); pw.print(mMemWatchDumpPid); + pw.print(" mMemWatchDumpUid="); pw.println(mMemWatchDumpUid); + } if (mOpenGlTraceApp != null) { if (dumpPackage == null || dumpPackage.equals(mOpenGlTraceApp)) { if (needSep) { @@ -13251,7 +13230,7 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<String> strings; ArrayList<Integer> objects; boolean all; - + ItemMatcher() { all = true; } @@ -13336,7 +13315,7 @@ public final class ActivityManagerService extends ActivityManagerNative protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll) { ArrayList<ActivityRecord> activities; - + synchronized (this) { activities = mStackSupervisor.getDumpActivitiesLocked(name); } @@ -13459,7 +13438,7 @@ public final class ActivityManagerService extends ActivityManagerNative } needSep = true; - + if (!onlyHistory && mStickyBroadcasts != null && dumpPackage == null) { for (int user=0; user<mStickyBroadcasts.size(); user++) { if (needSep) { @@ -13494,7 +13473,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + if (!onlyHistory && dumpAll) { pw.println(); for (BroadcastQueue queue : mBroadcastQueues) { @@ -13506,7 +13485,7 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = true; printedAnything = true; } - + if (!printedAnything) { pw.println(" (nothing)"); } @@ -13756,8 +13735,9 @@ public final class ActivityManagerService extends ActivityManagerNative 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(" lastPss="); DebugUtils.printSizeValue(pw, r.lastPss*1024); + pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, r.lastCachedPss*1024); + pw.println(); pw.print(prefix); pw.print(" "); pw.print("cached="); pw.print(r.cached); @@ -13846,7 +13826,7 @@ public final class ActivityManagerService extends ActivityManagerNative long realtime = SystemClock.elapsedRealtime(); pw.println("Applications Graphics Acceleration Info:"); pw.println("Uptime: " + uptime + " Realtime: " + realtime); - + for (int i = procs.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = procs.get(i); if (r.thread != null) { @@ -14071,7 +14051,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean isCompact = false; boolean localOnly = false; boolean packages = false; - + int opti = 0; while (opti < args.length) { String opt = args[opti]; @@ -14096,7 +14076,7 @@ public final class ActivityManagerService extends ActivityManagerNative } else if ("-h".equals(opt)) { 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(" -d: include dalvik details."); pw.println(" -c: dump in a compact machine-parseable representation."); pw.println(" --oom: only show processes organized by oom adj."); pw.println(" --local: only collect details locally, don't call process."); @@ -14109,7 +14089,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println("Unknown argument: " + opt + "; use -h for help"); } } - + final boolean isCheckinRequest = scanArgs(args, "--checkin"); long uptime = SystemClock.uptimeMillis(); long realtime = SystemClock.elapsedRealtime(); @@ -14183,6 +14163,8 @@ public final class ActivityManagerService extends ActivityManagerNative final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>(); long nativePss = 0; long dalvikPss = 0; + long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : + EmptyArray.LONG; long otherPss = 0; long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; @@ -14260,6 +14242,9 @@ public final class ActivityManagerService extends ActivityManagerNative nativePss += mi.nativePss; dalvikPss += mi.dalvikPss; + for (int j=0; j<dalvikSubitemPss.length; j++) { + dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); + } otherPss += mi.otherPss; for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { long mem = mi.getOtherPss(j); @@ -14318,6 +14303,10 @@ public final class ActivityManagerService extends ActivityManagerNative nativePss += mi.nativePss; dalvikPss += mi.dalvikPss; + for (int j=0; j<dalvikSubitemPss.length; j++) { + dalvikSubitemPss[j] += mi.getOtherPss( + Debug.MemoryInfo.NUM_OTHER_STATS + j); + } otherPss += mi.otherPss; for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { long mem = mi.getOtherPss(j); @@ -14336,7 +14325,16 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<MemItem> catMems = new ArrayList<MemItem>(); catMems.add(new MemItem("Native", "Native", nativePss, -1)); - catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, -2)); + final MemItem dalvikItem = new MemItem("Dalvik", "Dalvik", dalvikPss, -2); + if (dalvikSubitemPss.length > 0) { + dalvikItem.subitems = new ArrayList<MemItem>(); + for (int j=0; j<dalvikSubitemPss.length; j++) { + final String name = Debug.MemoryInfo.getOtherLabel( + Debug.MemoryInfo.NUM_OTHER_STATS + j); + dalvikItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j], j)); + } + } + catMems.add(dalvikItem); catMems.add(new MemItem("Unknown", "Unknown", otherPss, -3)); for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { String label = Debug.MemoryInfo.getOtherLabel(j); @@ -14380,9 +14378,14 @@ public final class ActivityManagerService extends ActivityManagerNative memInfo.readMemInfo(); if (nativeProcTotalPss > 0) { synchronized (this) { - mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(), - memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(), - memInfo.getKernelUsedSizeKb(), nativeProcTotalPss); + final long cachedKb = memInfo.getCachedSizeKb(); + final long freeKb = memInfo.getFreeSizeKb(); + final long zramKb = memInfo.getZramTotalSizeKb(); + final long kernelKb = memInfo.getKernelUsedSizeKb(); + EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024, + kernelKb*1024, nativeProcTotalPss*1024); + mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, + nativeProcTotalPss); } } if (!brief) { @@ -14805,7 +14808,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - for (int i=0; i<cpr.connections.size(); i++) { + for (int i = cpr.connections.size() - 1; i >= 0; i--) { ContentProviderConnection conn = cpr.connections.get(i); if (conn.waiting) { // If this connection is waiting for the provider, then we don't @@ -14897,10 +14900,11 @@ public final class ActivityManagerService extends ActivityManagerNative boolean restart = false; // Remove published content providers. - for (int i=app.pubProviders.size()-1; i>=0; i--) { + 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) { + boolean inLaunching = removeDyingProviderLocked(app, cpr, always); + if ((inLaunching || always) && !cpr.connections.isEmpty()) { // We left the provider in the launching list, need to // restart it. restart = true; @@ -14918,7 +14922,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Unregister from connected content providers. if (!app.conProviders.isEmpty()) { - for (int i=0; i<app.conProviders.size(); i++) { + for (int i = app.conProviders.size() - 1; i >= 0; i--) { ContentProviderConnection conn = app.conProviders.get(i); conn.provider.connections.remove(conn); stopAssociationLocked(app.uid, app.processName, conn.provider.uid, @@ -14933,9 +14937,8 @@ public final class ActivityManagerService extends ActivityManagerNative // XXX Commented out for now. Trying to figure out a way to reproduce // the actual situation to identify what is actually going on. if (false) { - for (int i=0; i<mLaunchingProviders.size(); i++) { - ContentProviderRecord cpr = (ContentProviderRecord) - mLaunchingProviders.get(i); + for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) { + ContentProviderRecord cpr = mLaunchingProviders.get(i); if (cpr.connections.size() <= 0 && !cpr.hasExternalProcessHandles()) { synchronized (cpr) { cpr.launchingApp = null; @@ -14948,14 +14951,14 @@ public final class ActivityManagerService extends ActivityManagerNative skipCurrentReceiverLocked(app); // Unregister any receivers. - for (int i=app.receivers.size()-1; i>=0; i--) { + 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 " + if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App " + mBackupTarget.appInfo + " died during backup"); try { IBackupManager bm = IBackupManager.Stub.asInterface( @@ -14966,7 +14969,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - for (int i = mPendingProcessChanges.size()-1; i>=0; i--) { + for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) { ProcessChangeItem item = mPendingProcessChanges.get(i); if (item.pid == app.pid) { mPendingProcessChanges.remove(i); @@ -14982,7 +14985,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (!app.persistent || app.isolated) { - if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, + if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "Removing non-persistent process during cleanup: " + app); mProcessNames.remove(app.processName, app.uid); mIsolatedProcesses.remove(app.uid); @@ -15000,8 +15003,8 @@ public final class ActivityManagerService extends ActivityManagerNative restart = true; } } - if ((DEBUG_PROCESSES || DEBUG_CLEANUP) && mProcessesOnHold.contains(app)) Slog.v(TAG, - "Clean-up removing on hold: " + app); + if ((DEBUG_PROCESSES || DEBUG_CLEANUP) && mProcessesOnHold.contains(app)) Slog.v( + TAG_CLEANUP, "Clean-up removing on hold: " + app); mProcessesOnHold.remove(app); if (app == mHomeProcess) { @@ -15041,24 +15044,20 @@ public final class ActivityManagerService extends ActivityManagerNative // and if any run in this process then either schedule a restart of // the process or kill the client waiting for it if this process has // gone bad. - int NL = mLaunchingProviders.size(); boolean restart = false; - for (int i=0; i<NL; i++) { + for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) { ContentProviderRecord cpr = mLaunchingProviders.get(i); if (cpr.launchingApp == app) { - if (!alwaysBad && !app.bad) { + if (!alwaysBad && !app.bad && !cpr.connections.isEmpty()) { restart = true; } else { removeDyingProviderLocked(app, cpr, true); - // cpr should have been removed from mLaunchingProviders - NL = mLaunchingProviders.size(); - i--; } } } return restart; } - + // ========================================================= // SERVICES // ========================================================= @@ -15082,15 +15081,15 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public ComponentName startService(IApplicationThread caller, Intent service, - String resolvedType, int userId) { + String resolvedType, int userId) throws TransactionTooLargeException { enforceNotIsolatedCaller("startService"); // Refuse possible leaked file descriptors if (service != null && service.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); } - if (DEBUG_SERVICE) - Slog.v(TAG, "startService: " + service + " type=" + resolvedType); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, + "startService: " + service + " type=" + resolvedType); synchronized(this) { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -15102,11 +15101,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } - ComponentName startServiceInPackage(int uid, - Intent service, String resolvedType, int userId) { + ComponentName startServiceInPackage(int uid, Intent service, String resolvedType, int userId) + throws TransactionTooLargeException { synchronized(this) { - if (DEBUG_SERVICE) - Slog.v(TAG, "startServiceInPackage: " + service + " type=" + resolvedType); + if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, + "startServiceInPackage: " + service + " type=" + resolvedType); final long origId = Binder.clearCallingIdentity(); ComponentName res = mServices.startServiceLocked(null, service, resolvedType, -1, uid, userId); @@ -15140,7 +15139,7 @@ public final class ActivityManagerService extends ActivityManagerNative return mServices.peekServiceLocked(service, resolvedType); } } - + @Override public boolean stopServiceToken(ComponentName className, IBinder token, int startId) { @@ -15285,10 +15284,9 @@ public final class ActivityManagerService extends ActivityManagerNative result = UserHandle.isSameApp(aInfo.uid, Process.PHONE_UID) || (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0; } - if (DEBUG_MU) { - Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo - + ", " + className + ", 0x" + Integer.toHexString(flags) + ") = " + result); - } + if (DEBUG_MU) Slog.v(TAG_MU, + "isSingleton(" + componentProcessName + ", " + aInfo + ", " + className + ", 0x" + + Integer.toHexString(flags) + ") = " + result); return result; } @@ -15307,9 +15305,9 @@ public final class ActivityManagerService extends ActivityManagerNative == PackageManager.PERMISSION_GRANTED; } - public int bindService(IApplicationThread caller, IBinder token, - Intent service, String resolvedType, - IServiceConnection connection, int flags, int userId) { + public int bindService(IApplicationThread caller, IBinder token, Intent service, + String resolvedType, IServiceConnection connection, int flags, int userId) + throws TransactionTooLargeException { enforceNotIsolatedCaller("bindService"); // Refuse possible leaked file descriptors @@ -15363,16 +15361,17 @@ public final class ActivityManagerService extends ActivityManagerNative mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res); } } - + // ========================================================= // BACKUP AND RESTORE // ========================================================= - + // Cause the target app to be launched if necessary and its backup agent // instantiated. The backup agent will invoke backupAgentCreated() on the // activity manager to announce its creation. public boolean bindBackupAgent(ApplicationInfo app, int backupMode) { - if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode); + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, + "bindBackupAgent: app=" + app + " mode=" + backupMode); enforceCallingPermission("android.permission.CONFIRM_FULL_BACKUP", "bindBackupAgent"); synchronized(this) { @@ -15415,7 +15414,7 @@ public final class ActivityManagerService extends ActivityManagerNative // If the process is already attached, schedule the creation of the backup agent now. // If it is not yet live, this will be done when it attaches to the framework. if (proc.thread != null) { - if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc already running: " + proc); + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc); try { proc.thread.scheduleCreateBackupAgent(app, compatibilityInfoForPackageLocked(app), backupMode); @@ -15423,20 +15422,20 @@ public final class ActivityManagerService extends ActivityManagerNative // Will time out on the backup manager side } } else { - if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc not running, waiting for attach"); + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc not running, waiting for attach"); } // Invariants: at this point, the target app process exists and the application // is either already running or in the process of coming up. mBackupTarget and // mBackupAppName describe the app, so that when it binds back to the AM we // know that it's scheduled for a backup-agent operation. } - + return true; } @Override public void clearPendingBackup() { - if (DEBUG_BACKUP) Slog.v(TAG, "clearPendingBackup"); + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "clearPendingBackup"); enforceCallingPermission("android.permission.BACKUP", "clearPendingBackup"); synchronized (this) { @@ -15447,7 +15446,7 @@ public final class ActivityManagerService extends ActivityManagerNative // A backup agent has just come up public void backupAgentCreated(String agentPackageName, IBinder agent) { - if (DEBUG_BACKUP) Slog.v(TAG, "backupAgentCreated: " + agentPackageName + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName + " = " + agent); synchronized(this) { @@ -15474,7 +15473,7 @@ public final class ActivityManagerService extends ActivityManagerNative // done with this agent public void unbindBackupAgent(ApplicationInfo appInfo) { - if (DEBUG_BACKUP) Slog.v(TAG, "unbindBackupAgent: " + appInfo); + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "unbindBackupAgent: " + appInfo); if (appInfo == null) { Slog.w(TAG, "unbind backup agent for null app"); return; @@ -15516,30 +15515,6 @@ public final class ActivityManagerService extends ActivityManagerNative // BROADCASTS // ========================================================= - private final List getStickiesLocked(String action, IntentFilter filter, - List cur, int userId) { - final ContentResolver resolver = mContext.getContentResolver(); - ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); - if (stickies == null) { - return cur; - } - final ArrayList<Intent> list = stickies.get(action); - if (list == null) { - return cur; - } - int N = list.size(); - for (int i=0; i<N; i++) { - Intent intent = list.get(i); - if (filter.match(resolver, intent, true, TAG) >= 0) { - if (cur == null) { - cur = new ArrayList<Intent>(); - } - cur.add(intent); - } - } - return cur; - } - boolean isPendingBroadcastProcessLocked(int pid) { return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid) || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid); @@ -15564,10 +15539,11 @@ public final class ActivityManagerService extends ActivityManagerNative public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String permission, int userId) { enforceNotIsolatedCaller("registerReceiver"); + ArrayList<Intent> stickyIntents = null; + ProcessRecord callerApp = null; int callingUid; int callingPid; synchronized(this) { - ProcessRecord callerApp = null; if (caller != null) { callerApp = getRecordForAppLocked(caller); if (callerApp == null) { @@ -15590,39 +15566,67 @@ public final class ActivityManagerService extends ActivityManagerNative callingPid = Binder.getCallingPid(); } - userId = this.handleIncomingUser(callingPid, callingUid, userId, + userId = handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage); - List allSticky = null; + Iterator<String> actions = filter.actionsIterator(); + if (actions == null) { + ArrayList<String> noAction = new ArrayList<String>(1); + noAction.add(null); + actions = noAction.iterator(); + } + + // Collect stickies of users + int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) }; + while (actions.hasNext()) { + String action = actions.next(); + for (int id : userIds) { + ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id); + if (stickies != null) { + ArrayList<Intent> intents = stickies.get(action); + if (intents != null) { + if (stickyIntents == null) { + stickyIntents = new ArrayList<Intent>(); + } + stickyIntents.addAll(intents); + } + } + } + } + } + ArrayList<Intent> allSticky = null; + if (stickyIntents != null) { + final ContentResolver resolver = mContext.getContentResolver(); // Look for any matching sticky broadcasts... - Iterator actions = filter.actionsIterator(); - if (actions != null) { - while (actions.hasNext()) { - String action = (String)actions.next(); - allSticky = getStickiesLocked(action, filter, allSticky, - UserHandle.USER_ALL); - allSticky = getStickiesLocked(action, filter, allSticky, - UserHandle.getUserId(callingUid)); + for (int i = 0, N = stickyIntents.size(); i < N; i++) { + Intent intent = stickyIntents.get(i); + // If intent has scheme "content", it will need to acccess + // provider that needs to lock mProviderMap in ActivityThread + // and also it may need to wait application response, so we + // cannot lock ActivityManagerService here. + if (filter.match(resolver, intent, true, TAG) >= 0) { + if (allSticky == null) { + allSticky = new ArrayList<Intent>(); + } + allSticky.add(intent); } - } else { - allSticky = getStickiesLocked(null, filter, allSticky, - UserHandle.USER_ALL); - allSticky = getStickiesLocked(null, filter, allSticky, - UserHandle.getUserId(callingUid)); } + } - // The first sticky in the list is returned directly back to - // the client. - Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null; - - if (DEBUG_BROADCAST) Slog.v(TAG, "Register receiver " + filter - + ": " + sticky); + // The first sticky in the list is returned directly back to the client. + Intent sticky = allSticky != null ? allSticky.get(0) : null; + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Register receiver " + filter + ": " + sticky); + if (receiver == null) { + return sticky; + } - if (receiver == null) { - return sticky; + synchronized (this) { + if (callerApp != null && (callerApp.thread == null + || callerApp.thread.asBinder() != caller.asBinder())) { + // Original caller already died + return null; } - ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); if (rl == null) { @@ -15683,7 +15687,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public void unregisterReceiver(IIntentReceiver receiver) { - if (DEBUG_BROADCAST) Slog.v(TAG, "Unregister receiver: " + receiver); + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Unregister receiver: " + receiver); final long origId = Binder.clearCallingIdentity(); try { @@ -15692,11 +15696,11 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { 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); + final BroadcastRecord r = rl.curBroadcast; + if (r != null && r == r.queue.getMatchingOrderedReceiver(r)) { + final boolean doNext = r.queue.finishReceiverLocked( + r, r.resultCode, r.resultData, r.resultExtras, + r.resultAbort, false); if (doNext) { doTrim = true; r.queue.processNextBroadcast(false); @@ -15733,7 +15737,7 @@ public final class ActivityManagerService extends ActivityManagerNative mReceiverResolver.removeFilter(rl.get(i)); } } - + private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); @@ -15761,7 +15765,7 @@ public final class ActivityManagerService extends ActivityManagerNative } List<ResolveInfo> newReceivers = AppGlobals.getPackageManager() .queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user); - if (user != 0 && newReceivers != null) { + if (user != UserHandle.USER_OWNER && newReceivers != null) { // If this is not the primary user, we need to check for // any receivers that should be filtered out. for (int i=0; i<newReceivers.size(); i++) { @@ -15834,9 +15838,9 @@ public final class ActivityManagerService extends ActivityManagerNative // By default broadcasts do not go to stopped apps. intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); - if (DEBUG_BROADCAST_LIGHT) Slog.v( - TAG, (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent - + " ordered=" + ordered + " userid=" + userId); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, + (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent + + " ordered=" + ordered + " userid=" + userId); if ((resultTo != null) && !ordered) { Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!"); } @@ -15955,14 +15959,14 @@ public final class ActivityManagerService extends ActivityManagerNative forceStopPackageLocked(list[i], -1, false, true, true, false, false, userId, "storage unmount"); } - cleanupRecentTasksLocked(UserHandle.USER_ALL); + mRecentTasks.cleanupLocked(UserHandle.USER_ALL); sendPackageBroadcastLocked( IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list, userId); } break; case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: - cleanupRecentTasksLocked(UserHandle.USER_ALL); + mRecentTasks.cleanupLocked(UserHandle.USER_ALL); break; case Intent.ACTION_PACKAGE_REMOVED: case Intent.ACTION_PACKAGE_CHANGED: @@ -15992,6 +15996,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (userId == UserHandle.USER_OWNER) { mTaskPersister.removeFromPackageCache(ssp); } + mBatteryStatsService.notePackageUninstalled(ssp); } } else { removeTasksByRemovedPackageComponentsLocked(ssp, userId); @@ -16018,6 +16023,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (userId == UserHandle.USER_OWNER) { mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp); } + try { + ApplicationInfo ai = AppGlobals.getPackageManager(). + getApplicationInfo(ssp, 0, 0); + mBatteryStatsService.notePackageInstalled(ssp, + ai != null ? ai.versionCode : 0); + } catch (RemoteException e) { + } } break; case Intent.ACTION_TIMEZONE_CHANGED: @@ -16157,10 +16169,10 @@ public final class ActivityManagerService extends ActivityManagerNative final boolean replacePending = (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0; - - if (DEBUG_BROADCAST) Slog.v(TAG, "Enqueing broadcast: " + intent.getAction() + + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueing broadcast: " + intent.getAction() + " replacePending=" + replacePending); - + int NR = registeredReceivers != null ? registeredReceivers.size() : 0; if (!ordered && NR > 0) { // If we are not serializing this broadcast, then send the @@ -16171,8 +16183,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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); + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r); final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r); if (!replaced) { queue.enqueueParallelBroadcastLocked(r); @@ -16261,14 +16272,13 @@ public final class ActivityManagerService extends ActivityManagerNative 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 + + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r + ": prev had " + queue.mOrderedBroadcasts.size()); - if (DEBUG_BROADCAST) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq); - } - boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); + if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST, + "Enqueueing broadcast " + r.intent.getAction()); + + boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { queue.enqueueOrderedBroadcastLocked(r); queue.scheduleBroadcastsLocked(); @@ -16387,17 +16397,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private final boolean finishReceiverLocked(IBinder receiver, int resultCode, - 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, false); - } - void backgroundServicesFinishedLocked(int userId) { for (BroadcastQueue queue : mBroadcastQueues) { queue.backgroundServicesFinishedLocked(userId); @@ -16405,8 +16404,8 @@ public final class ActivityManagerService extends ActivityManagerNative } public void finishReceiver(IBinder who, int resultCode, String resultData, - Bundle resultExtras, boolean resultAbort) { - if (DEBUG_BROADCAST) Slog.v(TAG, "Finish receiver: " + who); + Bundle resultExtras, boolean resultAbort, int flags) { + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Finish receiver: " + who); // Refuse possible leaked file descriptors if (resultExtras != null && resultExtras.hasFileDescriptors()) { @@ -16419,7 +16418,9 @@ public final class ActivityManagerService extends ActivityManagerNative BroadcastRecord r; synchronized(this) { - r = broadcastRecordForReceiverLocked(who); + BroadcastQueue queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0 + ? mFgBroadcastQueue : mBgBroadcastQueue; + r = queue.getMatchingOrderedReceiver(who); if (r != null) { doNext = r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, true); @@ -16434,7 +16435,7 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(origId); } } - + // ========================================================= // INSTRUMENTATION // ========================================================= @@ -16504,17 +16505,17 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } - + /** - * Report errors that occur while attempting to start Instrumentation. Always writes the + * Report errors that occur while attempting to start Instrumentation. Always writes the * error to the logs, but if somebody is watching, send the report there too. This enables * the "am" command to report errors with more information. - * + * * @param watcher The IInstrumentationWatcher. Null if there isn't one. * @param cn The component name of the instrumentation. * @param report The error report. */ - private void reportStartInstrumentationFailure(IInstrumentationWatcher watcher, + private void reportStartInstrumentationFailure(IInstrumentationWatcher watcher, ComponentName cn, String report) { Slog.w(TAG, report); try { @@ -16584,7 +16585,7 @@ public final class ActivityManagerService extends ActivityManagerNative // ========================================================= // CONFIGURATION // ========================================================= - + public ConfigurationInfo getDeviceConfigurationInfo() { ConfigurationInfo config = new ConfigurationInfo(); synchronized (this) { @@ -16608,6 +16609,15 @@ public final class ActivityManagerService extends ActivityManagerNative return mStackSupervisor.getFocusedStack(); } + @Override + public int getFocusedStackId() throws RemoteException { + ActivityStack focusedStack = getFocusedStack(); + if (focusedStack != null) { + return focusedStack.getStackId(); + } + return -1; + } + public Configuration getConfiguration() { Configuration ci; synchronized(this) { @@ -16672,16 +16682,16 @@ public final class ActivityManagerService extends ActivityManagerNative Configuration newConfig = new Configuration(mConfiguration); changes = newConfig.updateFrom(values); if (changes != 0) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { - Slog.i(TAG, "Updating configuration to: " + values); - } - + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.i(TAG_CONFIGURATION, + "Updating configuration to: " + values); + EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes); - if (values.locale != null && !initLocale) { - saveLocaleLocked(values.locale, - !values.locale.equals(mConfiguration.locale), - values.userSetLocale); + if (!initLocale && values.locale != null && values.userSetLocale) { + final String languageTag = values.locale.toLanguageTag(); + SystemProperties.set("persist.sys.locale", languageTag); + mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, + values.locale)); } mConfigurationSeq++; @@ -16695,7 +16705,7 @@ public final class ActivityManagerService extends ActivityManagerNative //mUsageStatsService.noteStartConfig(newConfig); final Configuration configCopy = new Configuration(mConfiguration); - + // TODO: If our config changes, should we auto dismiss any currently // showing dialogs? mShowDialogs = shouldShowDialogs(newConfig); @@ -16724,7 +16734,7 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord app = mLruProcesses.get(i); try { if (app.thread != null) { - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc " + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc " + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(configCopy); } @@ -16784,32 +16794,15 @@ public final class ActivityManagerService extends ActivityManagerNative */ private static final boolean shouldShowDialogs(Configuration config) { return !(config.keyboard == Configuration.KEYBOARD_NOKEYS - && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH); - } - - /** - * Save the locale. You must be inside a synchronized (this) block. - */ - private void saveLocaleLocked(Locale l, boolean isDiff, boolean isPersist) { - if(isDiff) { - SystemProperties.set("user.language", l.getLanguage()); - SystemProperties.set("user.region", l.getCountry()); - } - - if(isPersist) { - SystemProperties.set("persist.sys.language", l.getLanguage()); - SystemProperties.set("persist.sys.country", l.getCountry()); - SystemProperties.set("persist.sys.localevar", l.getVariant()); - - mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, l)); - } + && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH + && config.navigation == Configuration.NAVIGATION_NONAV); } @Override public boolean shouldUpRecreateTask(IBinder token, String destAffinity) { synchronized (this) { - ActivityRecord srec = ActivityRecord.forToken(token); - if (srec.task != null && srec.task.stack != null) { + ActivityRecord srec = ActivityRecord.forTokenLocked(token); + if (srec != null) { return srec.task.stack.shouldUpRecreateTaskLocked(srec, destAffinity); } } @@ -16820,16 +16813,19 @@ public final class ActivityManagerService extends ActivityManagerNative Intent resultData) { synchronized (this) { - final ActivityStack stack = ActivityRecord.getStackLocked(token); - if (stack != null) { - return stack.navigateUpToLocked(token, destIntent, resultCode, resultData); + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + if (r != null) { + return r.task.stack.navigateUpToLocked(r, destIntent, resultCode, resultData); } return false; } } public int getLaunchedFromUid(IBinder activityToken) { - ActivityRecord srec = ActivityRecord.forToken(activityToken); + ActivityRecord srec; + synchronized (this) { + srec = ActivityRecord.forTokenLocked(activityToken); + } if (srec == null) { return -1; } @@ -16837,7 +16833,10 @@ public final class ActivityManagerService extends ActivityManagerNative } public String getLaunchedFromPackage(IBinder activityToken) { - ActivityRecord srec = ActivityRecord.forToken(activityToken); + ActivityRecord srec; + synchronized (this) { + srec = ActivityRecord.forTokenLocked(activityToken); + } if (srec == null) { return null; } @@ -16987,6 +16986,8 @@ public final class ActivityManagerService extends ActivityManagerNative app.systemNoUi = false; + final int PROCESS_STATE_TOP = mTopProcessState; + // Determine the importance of the process, starting with most // important to least, and assign an appropriate OOM adjustment. int adj; @@ -17000,7 +17001,7 @@ public final class ActivityManagerService extends ActivityManagerNative schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "top-activity"; foregroundActivities = true; - procState = ActivityManager.PROCESS_STATE_TOP; + procState = PROCESS_STATE_TOP; } else if (app.instrumentationClass != null) { // Don't want to kill running instrumentation. adj = ProcessList.FOREGROUND_APP_ADJ; @@ -17053,8 +17054,8 @@ public final class ActivityManagerService extends ActivityManagerNative adj = ProcessList.VISIBLE_APP_ADJ; app.adjType = "visible"; } - if (procState > ActivityManager.PROCESS_STATE_TOP) { - procState = ActivityManager.PROCESS_STATE_TOP; + if (procState > PROCESS_STATE_TOP) { + procState = PROCESS_STATE_TOP; } schedGroup = Process.THREAD_GROUP_DEFAULT; app.cached = false; @@ -17066,8 +17067,8 @@ public final class ActivityManagerService extends ActivityManagerNative adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "pausing"; } - if (procState > ActivityManager.PROCESS_STATE_TOP) { - procState = ActivityManager.PROCESS_STATE_TOP; + if (procState > PROCESS_STATE_TOP) { + procState = PROCESS_STATE_TOP; } schedGroup = Process.THREAD_GROUP_DEFAULT; app.cached = false; @@ -17106,7 +17107,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.foregroundServices) { // The user is aware of this app, so make it visible. adj = ProcessList.PERCEPTIBLE_APP_ADJ; - procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; app.cached = false; app.adjType = "fg-service"; schedGroup = Process.THREAD_GROUP_DEFAULT; @@ -17177,7 +17178,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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); + if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app); adj = ProcessList.BACKUP_APP_ADJ; if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) { procState = ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; @@ -17546,7 +17547,7 @@ public final class ActivityManagerService extends ActivityManagerNative } app.curRawAdj = adj; - + //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid + // " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj); if (adj > app.maxAdj) { @@ -17572,11 +17573,12 @@ public final class ActivityManagerService extends ActivityManagerNative /** * Record new PSS sample for a process. */ - void recordPssSample(ProcessRecord proc, int procState, long pss, long uss, long now) { + void recordPssSampleLocked(ProcessRecord proc, int procState, long pss, long uss, long now) { + EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024); proc.lastPssTime = now; proc.baseProcessTracker.addPss(pss, uss, true, proc.pkgList); - if (DEBUG_PSS) Slog.d(TAG, "PSS of " + proc.toShortString() - + ": " + pss + " lastPss=" + proc.lastPss + if (DEBUG_PSS) Slog.d(TAG_PSS, + "PSS of " + proc.toShortString() + ": " + pss + " lastPss=" + proc.lastPss + " state=" + ProcessList.makeProcStateString(procState)); if (proc.initialIdlePss == 0) { proc.initialIdlePss = pss; @@ -17585,6 +17587,80 @@ public final class ActivityManagerService extends ActivityManagerNative if (procState >= ActivityManager.PROCESS_STATE_HOME) { proc.lastCachedPss = pss; } + + final SparseArray<Pair<Long, String>> watchUids + = mMemWatchProcesses.getMap().get(proc.processName); + Long check = null; + if (watchUids != null) { + Pair<Long, String> val = watchUids.get(proc.uid); + if (val == null) { + val = watchUids.get(0); + } + if (val != null) { + check = val.first; + } + } + if (check != null) { + if ((pss * 1024) >= check && proc.thread != null && mMemWatchDumpProcName == null) { + boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); + if (!isDebuggable) { + if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + isDebuggable = true; + } + } + if (isDebuggable) { + Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting"); + final ProcessRecord myProc = proc; + final File heapdumpFile = DumpHeapProvider.getJavaFile(); + mMemWatchDumpProcName = proc.processName; + mMemWatchDumpFile = heapdumpFile.toString(); + mMemWatchDumpPid = proc.pid; + mMemWatchDumpUid = proc.uid; + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + revokeUriPermission(ActivityThread.currentActivityThread() + .getApplicationThread(), + DumpHeapActivity.JAVA_URI, + Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + UserHandle.myUserId()); + ParcelFileDescriptor fd = null; + try { + heapdumpFile.delete(); + fd = ParcelFileDescriptor.open(heapdumpFile, + ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_TRUNCATE | + ParcelFileDescriptor.MODE_WRITE_ONLY | + ParcelFileDescriptor.MODE_APPEND); + IApplicationThread thread = myProc.thread; + if (thread != null) { + try { + if (DEBUG_PSS) Slog.d(TAG_PSS, + "Requesting dump heap from " + + myProc + " to " + heapdumpFile); + thread.dumpHeap(true, heapdumpFile.toString(), fd); + } catch (RemoteException e) { + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + if (fd != null) { + try { + fd.close(); + } catch (IOException e) { + } + } + } + } + }); + } else { + Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + + ", but debugging not enabled"); + } + } + } } /** @@ -17597,7 +17673,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (mPendingPssProcesses.size() == 0) { mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); } - if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of: " + proc); + if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting PSS of: " + proc); proc.pssProcState = procState; mPendingPssProcesses.add(proc); } @@ -17612,13 +17688,17 @@ public final class ActivityManagerService extends ActivityManagerNative return; } } - if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs! memLowered=" + memLowered); + if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting PSS of all procs! memLowered=" + memLowered); mLastFullPssTime = now; mFullPssPending = true; mPendingPssProcesses.ensureCapacity(mLruProcesses.size()); mPendingPssProcesses.clear(); - for (int i=mLruProcesses.size()-1; i>=0; i--) { + for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord app = mLruProcesses.get(i); + if (app.thread == null + || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) { + continue; + } if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { app.pssProcState = app.setProcState; app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, @@ -17658,7 +17738,7 @@ public final class ActivityManagerService extends ActivityManagerNative // whatever. } } - + /** * Returns true if things are idle enough to perform GCs. */ @@ -17672,7 +17752,7 @@ public final class ActivityManagerService extends ActivityManagerNative return !processingBroadcasts && (isSleeping() || mStackSupervisor.allResumedActivitiesIdle()); } - + /** * Perform GCs on all processes that are waiting for it, but only * if things are idle. @@ -17701,11 +17781,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + scheduleAppGcsLocked(); } } - + /** * If all looks good, perform GCs on all processes waiting for them. */ @@ -17723,12 +17803,12 @@ public final class ActivityManagerService extends ActivityManagerNative */ final void scheduleAppGcsLocked() { mHandler.removeMessages(GC_BACKGROUND_PROCESSES_MSG); - + if (mProcessesToGc.size() > 0) { // Schedule a GC for the time to the next process. ProcessRecord proc = mProcessesToGc.get(0); Message msg = mHandler.obtainMessage(GC_BACKGROUND_PROCESSES_MSG); - + long when = proc.lastRequestedGc + GC_MIN_INTERVAL; long now = SystemClock.uptimeMillis(); if (when < (now+GC_TIMEOUT)) { @@ -17737,7 +17817,7 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendMessageAtTime(msg, when); } } - + /** * Add a process to the array of processes waiting to be GCed. Keeps the * list in sorted order by the last GC time. The process can't already be @@ -17757,7 +17837,7 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessesToGc.add(0, proc); } } - + /** * Set up to ask a process to GC itself. This will either do it * immediately, or put it on the list of processes to gc the next @@ -17824,7 +17904,7 @@ public final class ActivityManagerService extends ActivityManagerNative sb.append(" ("); sb.append((wtimeUsed*100)/realtimeSince); sb.append("%)"); - Slog.i(TAG, sb.toString()); + Slog.i(TAG_POWER, sb.toString()); sb.setLength(0); sb.append("CPU for "); app.toShortString(sb); @@ -17835,7 +17915,7 @@ public final class ActivityManagerService extends ActivityManagerNative sb.append(" ("); sb.append((cputimeUsed*100)/uptimeSince); sb.append("%)"); - Slog.i(TAG, sb.toString()); + Slog.i(TAG_POWER, sb.toString()); } // If a process has held a wake lock for more // than 50% of the time during this period, @@ -17876,15 +17956,15 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.curAdj != app.setAdj) { ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj); - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v( - TAG, "Set " + app.pid + " " + app.processName + - " adj " + app.curAdj + ": " + app.adjType); + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, + "Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": " + + app.adjType); app.setAdj = app.curAdj; } if (app.setSchedGroup != app.curSchedGroup) { app.setSchedGroup = app.curSchedGroup; - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Setting process group of " + app.processName + " to " + app.curSchedGroup); if (app.waitingToKill != null && @@ -17934,8 +18014,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState, - app.setProcState)) { + if (app.setProcState == ActivityManager.PROCESS_STATE_NONEXISTENT + || ProcessList.procStatesDifferForMem(app.curProcState, app.setProcState)) { if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) { // Experimental code to more aggressively collect pss while // running test... the problem is that this tends to collect @@ -17943,7 +18023,7 @@ public final class ActivityManagerService extends ActivityManagerNative // states, which well tend to give noisy data. long start = SystemClock.uptimeMillis(); long pss = Debug.getPss(app.pid, mTmpLong, null); - recordPssSample(app, app.curProcState, pss, mTmpLong[0], now); + recordPssSampleLocked(app, app.curProcState, pss, mTmpLong[0], now); mPendingPssProcesses.remove(app); Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState + " to " + app.curProcState + ": " @@ -17952,7 +18032,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.lastStateTime = now; app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, mTestPssMode, isSleeping(), now); - if (DEBUG_PSS) Slog.d(TAG, "Process state change from " + if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from " + ProcessList.makeProcStateString(app.setProcState) + " to " + ProcessList.makeProcStateString(app.curProcState) + " next pss in " + (app.nextPssTime-now) + ": " + app); @@ -17963,12 +18043,11 @@ public final class ActivityManagerService extends ActivityManagerNative requestPssLocked(app, app.setProcState); app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false, mTestPssMode, isSleeping(), now); - } else if (false && DEBUG_PSS) { - Slog.d(TAG, "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now)); - } + } else if (false && DEBUG_PSS) Slog.d(TAG_PSS, + "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now)); } if (app.setProcState != app.curProcState) { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Proc state change of " + app.processName + " to " + app.curProcState); boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE; @@ -17986,6 +18065,10 @@ public final class ActivityManagerService extends ActivityManagerNative app.lastCpuTime = app.curCpuTime; } + // Inform UsageStats of important process state change + // Must be called before updating setProcState + maybeUpdateUsageStats(app); + app.setProcState = app.curProcState; if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) { app.notCachedSinceIdle = false; @@ -17998,13 +18081,15 @@ public final class ActivityManagerService extends ActivityManagerNative } if (changes != 0) { - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Changes in " + app + ": " + changes); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Changes in " + app + ": " + changes); int i = mPendingProcessChanges.size()-1; ProcessChangeItem item = null; while (i >= 0) { item = mPendingProcessChanges.get(i); if (item.pid == app.pid) { - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Re-using existing item: " + item); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Re-using existing item: " + item); break; } i--; @@ -18014,16 +18099,18 @@ public final class ActivityManagerService extends ActivityManagerNative final int NA = mAvailProcessChanges.size(); if (NA > 0) { item = mAvailProcessChanges.remove(NA-1); - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Retreiving available item: " + item); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Retreiving available item: " + item); } else { item = new ProcessChangeItem(); - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Allocating new item: " + item); + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Allocating new item: " + item); } item.changes = 0; item.pid = app.pid; item.uid = app.info.uid; if (mPendingProcessChanges.size() == 0) { - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!"); mHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget(); } @@ -18032,8 +18119,8 @@ public final class ActivityManagerService extends ActivityManagerNative item.changes |= changes; item.processState = app.repProcState; item.foregroundActivities = app.repForegroundActivities; - if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Item " - + Integer.toHexString(System.identityHashCode(item)) + if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, + "Item " + Integer.toHexString(System.identityHashCode(item)) + " " + app.toShortString() + ": changes=" + item.changes + " procState=" + item.processState + " foreground=" + item.foregroundActivities @@ -18044,6 +18131,28 @@ public final class ActivityManagerService extends ActivityManagerNative return success; } + private void maybeUpdateUsageStats(ProcessRecord app) { + if (DEBUG_USAGE_STATS) { + Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList()) + + "] state changes: old = " + app.setProcState + ", new = " + + app.curProcState); + } + if (mUsageStatsService == null) { + return; + } + if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + && (app.setProcState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + || app.setProcState < 0)) { + String[] packages = app.getPackageList(); + if (packages != null) { + for (int i = 0; i < packages.length; i++) { + mUsageStatsService.reportEvent(packages[i], app.userId, + UsageEvents.Event.INTERACTION); + } + } + } + } + private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) { if (proc.thread != null) { if (proc.baseProcessTracker != null) { @@ -18233,7 +18342,7 @@ public final class ActivityManagerService extends ActivityManagerNative // step that cached level. app.curRawAdj = curCachedAdj; app.curAdj = app.modifyRawOomAdj(curCachedAdj); - if (DEBUG_LRU && false) Slog.d(TAG, "Assigning activity LRU #" + i + if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj + ")"); if (curCachedAdj != nextCachedAdj) { @@ -18256,7 +18365,7 @@ public final class ActivityManagerService extends ActivityManagerNative // state is still as a service), which is what we want. app.curRawAdj = curEmptyAdj; app.curAdj = app.modifyRawOomAdj(curEmptyAdj); - if (DEBUG_LRU && false) Slog.d(TAG, "Assigning empty LRU #" + i + if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj + ")"); if (curEmptyAdj != nextEmptyAdj) { @@ -18346,13 +18455,13 @@ public final class ActivityManagerService extends ActivityManagerNative // 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 (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "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!"); + if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!"); } } mLastMemoryLevel = memFactor; @@ -18392,9 +18501,8 @@ public final class ActivityManagerService extends ActivityManagerNative && !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); + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, + "Trimming memory of " + app.processName + " to " + curLevel); app.thread.scheduleTrimMemory(curLevel); } catch (RemoteException e) { } @@ -18429,7 +18537,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND && app.thread != null) { try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of heavy-weight " + app.processName + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); app.thread.scheduleTrimMemory( @@ -18447,7 +18555,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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, + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui " + app.processName + " to " + level); app.thread.scheduleTrimMemory(level); @@ -18458,7 +18566,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) { try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName + " to " + fgTrimLevel); app.thread.scheduleTrimMemory(fgTrimLevel); @@ -18484,7 +18592,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN && app.thread != null) { try { - if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ, "Trimming memory of ui hidden " + app.processName + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); app.thread.scheduleTrimMemory( @@ -18519,12 +18627,12 @@ public final class ActivityManagerService extends ActivityManagerNative } if (DEBUG_OOM_ADJ) { + final long duration = SystemClock.uptimeMillis() - now; if (false) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.d(TAG, "Did OOM ADJ in " + (SystemClock.uptimeMillis()-now) + "ms", here); + Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms", + new RuntimeException("here").fillInStackTrace()); } else { - Slog.d(TAG, "Did OOM ADJ in " + (SystemClock.uptimeMillis()-now) + "ms"); + Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms"); } } } @@ -18763,6 +18871,61 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize, + String reportPackage) { + if (processName != null) { + enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP, + "setDumpHeapDebugLimit()"); + } else { + if (!Build.IS_DEBUGGABLE) { + throw new SecurityException("Not running a debuggable build"); + } + synchronized (mPidsSelfLocked) { + ProcessRecord proc = mPidsSelfLocked.get(Binder.getCallingPid()); + if (proc == null) { + throw new SecurityException("No process found for calling pid " + + Binder.getCallingPid()); + } + processName = proc.processName; + uid = proc.uid; + if (reportPackage != null && !proc.pkgList.containsKey(reportPackage)) { + throw new SecurityException("Package " + reportPackage + " is not running in " + + proc); + } + } + } + synchronized (this) { + if (maxMemSize > 0) { + mMemWatchProcesses.put(processName, uid, new Pair(maxMemSize, reportPackage)); + } else { + if (uid != 0) { + mMemWatchProcesses.remove(processName, uid); + } else { + mMemWatchProcesses.getMap().remove(processName); + } + } + } + } + + @Override + public void dumpHeapFinished(String path) { + synchronized (this) { + if (Binder.getCallingPid() != mMemWatchDumpPid) { + Slog.w(TAG, "dumpHeapFinished: Calling pid " + Binder.getCallingPid() + + " does not match last pid " + mMemWatchDumpPid); + return; + } + if (mMemWatchDumpFile == null || !mMemWatchDumpFile.equals(path)) { + Slog.w(TAG, "dumpHeapFinished: Calling path " + path + + " does not match last path " + mMemWatchDumpFile); + return; + } + if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path); + mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG); + } + } + /** In this method we try to acquire our lock to make sure that we have not deadlocked */ public void monitor() { synchronized (this) { } @@ -18826,8 +18989,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private Set getProfileIdsLocked(int userId) { - Set userIds = new HashSet<Integer>(); + private Set<Integer> getProfileIdsLocked(int userId) { + Set<Integer> userIds = new HashSet<Integer>(); final List<UserInfo> profiles = getUserManagerLocked().getProfiles( userId, false /* enabledOnly */); for (UserInfo user : profiles) { @@ -18853,8 +19016,8 @@ public final class ActivityManagerService extends ActivityManagerNative userName = userInfo.name; mTargetUserId = userId; } - mHandler.removeMessages(START_USER_SWITCH_MSG); - mHandler.sendMessage(mHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName)); + mUiHandler.removeMessages(START_USER_SWITCH_MSG); + mUiHandler.sendMessage(mUiHandler.obtainMessage(START_USER_SWITCH_MSG, userId, 0, userName)); return true; } @@ -18886,7 +19049,8 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } - mStackSupervisor.setLockTaskModeLocked(null, false, "startUser"); + mStackSupervisor.setLockTaskModeLocked(null, ActivityManager.LOCK_TASK_MODE_NONE, + "startUser"); final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId); if (userInfo == null) { @@ -19032,6 +19196,18 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } + void dispatchForegroundProfileChanged(int userId) { + final int N = mUserSwitchObservers.beginBroadcast(); + for (int i = 0; i < N; i++) { + try { + mUserSwitchObservers.getBroadcastItem(i).onForegroundProfileSwitch(userId); + } catch (RemoteException e) { + // Ignore + } + } + mUserSwitchObservers.finishBroadcast(); + } + void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) { long ident = Binder.clearCallingIdentity(); try { @@ -19302,7 +19478,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - if (userId <= 0) { + if (userId < 0 || userId == UserHandle.USER_OWNER) { throw new IllegalArgumentException("Can't stop primary user " + userId); } enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId); @@ -19420,7 +19596,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // Explicitly remove the old information in mRecentTasks. - removeRecentTasksForUserLocked(userId); + mRecentTasks.removeTasksForUserLocked(userId); } for (int i=0; i<callbacks.size(); i++) { @@ -19548,14 +19724,6 @@ public final class ActivityManagerService extends ActivityManagerNative mUserSwitchObservers.unregister(observer); } - private boolean userExists(int userId) { - if (userId == 0) { - return true; - } - UserManagerService ums = getUserManagerLocked(); - return ums != null ? (ums.getUserInfo(userId) != null) : false; - } - int[] getUsersLocked() { UserManagerService ums = getUserManagerLocked(); return ums != null ? ums.getUserIds() : new int[] { 0 }; @@ -19577,8 +19745,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (info == null) return null; ApplicationInfo newInfo = new ApplicationInfo(info); newInfo.uid = applyUserId(info.uid, userId); - newInfo.dataDir = USER_DATA_DIR + userId + "/" - + info.packageName; + newInfo.dataDir = PackageManager.getDataDirForUser(info.volumeUuid, info.packageName, + userId).getAbsolutePath(); return newInfo; } @@ -19605,6 +19773,42 @@ public final class ActivityManagerService extends ActivityManagerNative return ActivityManagerService.this.startIsolatedProcess(entryPoint, entryPointArgs, processName, abiOverride, uid, crashHandler); } + + @Override + public SleepToken acquireSleepToken(String tag) { + Preconditions.checkNotNull(tag); + + synchronized (ActivityManagerService.this) { + SleepTokenImpl token = new SleepTokenImpl(tag); + mSleepTokens.add(token); + updateSleepIfNeededLocked(); + return token; + } + } + } + + private final class SleepTokenImpl extends SleepToken { + private final String mTag; + private final long mAcquireTime; + + public SleepTokenImpl(String tag) { + mTag = tag; + mAcquireTime = SystemClock.uptimeMillis(); + } + + @Override + public void release() { + synchronized (ActivityManagerService.this) { + if (mSleepTokens.remove(this)) { + updateSleepIfNeededLocked(); + } + } + } + + @Override + public String toString() { + return "{\"" + mTag + "\", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}"; + } } /** @@ -19651,7 +19855,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (ActivityManagerService.this) { long origId = Binder.clearCallingIdentity(); try { - TaskRecord tr = recentTaskForIdLocked(mTaskId); + TaskRecord tr = mRecentTasks.taskForIdLocked(mTaskId); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } @@ -19678,7 +19882,7 @@ public final class ActivityManagerService extends ActivityManagerNative TaskRecord tr; IApplicationThread appThread; synchronized (ActivityManagerService.this) { - tr = recentTaskForIdLocked(mTaskId); + tr = mRecentTasks.taskForIdLocked(mTaskId); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } @@ -19699,7 +19903,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (ActivityManagerService.this) { long origId = Binder.clearCallingIdentity(); try { - TaskRecord tr = recentTaskForIdLocked(mTaskId); + TaskRecord tr = mRecentTasks.taskForIdLocked(mTaskId); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b1b2a5c..f3b18f5 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.TaskPersister.DEBUG_PERSISTER; import static com.android.server.am.TaskPersister.DEBUG_RESTORER; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; @@ -71,11 +72,14 @@ import java.util.Objects; * An entry in the history stack, representing an activity. */ final class ActivityRecord { - static final String TAG = ActivityManagerService.TAG; + private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_AM; + private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; + private static final String TAG_THUMBNAILS = TAG + POSTFIX_THUMBNAILS; + + private static final boolean SHOW_ACTIVITY_START_TIME = true; static final boolean DEBUG_SAVED_STATE = ActivityStackSupervisor.DEBUG_SAVED_STATE; final public static String RECENTS_PACKAGE_NAME = "com.android.systemui.recents"; - private static final String TAG_ACTIVITY = "activity"; private static final String ATTR_ID = "id"; private static final String TAG_INTENT = "intent"; private static final String ATTR_USERID = "user_id"; @@ -127,6 +131,10 @@ final class ActivityRecord { long pauseTime; // last time we started pausing the activity long launchTickTime; // base time for launch tick messages Configuration configuration; // configuration activity was last running in + // Overridden configuration by the activity stack + // WARNING: Reference points to {@link ActivityStack#mOverrideConfig}, so its internal state + // should never be altered directly. + Configuration stackConfigOverride; CompatibilityInfo compat;// last used compatibility mode ActivityRecord resultTo; // who started this entry, so will get our reply final String resultWho; // additional identifier for use by resultTo. @@ -154,7 +162,6 @@ final class ActivityRecord { int launchMode; // the launch mode activity attribute. boolean visible; // does this activity's window need to be shown? boolean sleeping; // have we told the activity to sleep? - boolean waitingVisible; // true if waiting for a new act to become vis boolean nowVisible; // is this activity's window visible? boolean idle; // has the activity gone idle? boolean hasBeenLaunched;// has this activity ever been launched? @@ -205,6 +212,7 @@ final class ActivityRecord { pw.print(" icon=0x"); pw.print(Integer.toHexString(icon)); pw.print(" theme=0x"); pw.println(Integer.toHexString(theme)); pw.print(prefix); pw.print("config="); pw.println(configuration); + pw.print(prefix); pw.print("stackConfigOverride="); pw.println(stackConfigOverride); if (resultTo != null || resultWho != null) { pw.print(prefix); pw.print("resultTo="); pw.print(resultTo); pw.print(" resultWho="); pw.print(resultWho); @@ -293,6 +301,7 @@ final class ActivityRecord { else TimeUtils.formatDuration(startTime, now, pw); pw.println(); } + final boolean waitingVisible = mStackSupervisor.mWaitingVisibleActivities.contains(this); if (lastVisibleTime != 0 || waitingVisible || nowVisible) { pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible); pw.print(" nowVisible="); pw.print(nowVisible); @@ -312,44 +321,83 @@ final class ActivityRecord { } static class Token extends IApplicationToken.Stub { - final WeakReference<ActivityRecord> weakActivity; + private final WeakReference<ActivityRecord> weakActivity; + private final ActivityManagerService mService; - Token(ActivityRecord activity) { - weakActivity = new WeakReference<ActivityRecord>(activity); + Token(ActivityRecord activity, ActivityManagerService service) { + weakActivity = new WeakReference<>(activity); + mService = service; } - @Override public void windowsDrawn() { - ActivityRecord activity = weakActivity.get(); - if (activity != null) { - activity.windowsDrawn(); + @Override + public void windowsDrawn() { + synchronized (mService) { + ActivityRecord r = tokenToActivityRecordLocked(this); + if (r != null) { + r.windowsDrawnLocked(); + } } } - @Override public void windowsVisible() { - ActivityRecord activity = weakActivity.get(); - if (activity != null) { - activity.windowsVisible(); + @Override + public void windowsVisible() { + synchronized (mService) { + ActivityRecord r = tokenToActivityRecordLocked(this); + if (r != null) { + r.windowsVisibleLocked(); + } } } - @Override public void windowsGone() { - ActivityRecord activity = weakActivity.get(); - if (activity != null) { - activity.windowsGone(); + @Override + public void windowsGone() { + synchronized (mService) { + ActivityRecord r = tokenToActivityRecordLocked(this); + if (r != null) { + if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsGone(): " + r); + r.nowVisible = false; + return; + } } } - @Override public boolean keyDispatchingTimedOut(String reason) { - ActivityRecord activity = weakActivity.get(); - return activity != null && activity.keyDispatchingTimedOut(reason); + @Override + public boolean keyDispatchingTimedOut(String reason) { + ActivityRecord r; + ActivityRecord anrActivity; + ProcessRecord anrApp; + synchronized (mService) { + r = tokenToActivityRecordLocked(this); + if (r == null) { + return false; + } + anrActivity = r.getWaitingHistoryRecordLocked(); + anrApp = r != null ? r.app : null; + } + return mService.inputDispatchingTimedOut(anrApp, anrActivity, r, false, reason); + } + + @Override + public long getKeyDispatchingTimeout() { + synchronized (mService) { + ActivityRecord r = tokenToActivityRecordLocked(this); + if (r == null) { + return 0; + } + r = r.getWaitingHistoryRecordLocked(); + return ActivityManagerService.getInputDispatchingTimeoutLocked(r); + } } - @Override public long getKeyDispatchingTimeout() { - ActivityRecord activity = weakActivity.get(); - if (activity != null) { - return activity.getKeyDispatchingTimeout(); + private static final ActivityRecord tokenToActivityRecordLocked(Token token) { + if (token == null) { + return null; } - return 0; + ActivityRecord r = token.weakActivity.get(); + if (r == null || r.task == null || r.task.stack == null) { + return null; + } + return r; } @Override @@ -364,11 +412,11 @@ final class ActivityRecord { } } - static ActivityRecord forToken(IBinder token) { + static ActivityRecord forTokenLocked(IBinder token) { try { - return token != null ? ((Token)token).weakActivity.get() : null; + return Token.tokenToActivityRecordLocked((Token)token); } catch (ClassCastException e) { - Slog.w(ActivityManagerService.TAG, "Bad activity token: " + token, e); + Slog.w(TAG, "Bad activity token: " + token, e); return null; } } @@ -384,7 +432,7 @@ final class ActivityRecord { boolean _componentSpecified, ActivityStackSupervisor supervisor, ActivityContainer container, Bundle options) { service = _service; - appToken = new Token(this); + appToken = new Token(this, service); info = aInfo; launchedFromUid = _launchedFromUid; launchedFromPackage = _launchedFromPackage; @@ -394,6 +442,8 @@ final class ActivityRecord { resolvedType = _resolvedType; componentSpecified = _componentSpecified; configuration = _configuration; + stackConfigOverride = (container != null) + ? container.mStack.mOverrideConfig : Configuration.EMPTY; resultTo = _resultTo; resultWho = _resultWho; requestCode = _reqCode; @@ -407,7 +457,6 @@ final class ActivityRecord { keysPaused = false; inHistory = false; visible = true; - waitingVisible = false; nowVisible = false; idle = false; hasBeenLaunched = false; @@ -474,10 +523,16 @@ final class ActivityRecord { AttributeCache.Entry ent = AttributeCache.instance().get(packageName, realTheme, com.android.internal.R.styleable.Window, userId); + final boolean translucent = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowIsTranslucent, false) + || (!ent.array.hasValue( + com.android.internal.R.styleable.Window_windowIsTranslucent) + && ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowSwipeToDismiss, + false)); fullscreen = ent != null && !ent.array.getBoolean( com.android.internal.R.styleable.Window_windowIsFloating, false) - && !ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsTranslucent, false); + && !translucent; noDisplay = ent != null && ent.array.getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, false); @@ -514,13 +569,8 @@ final class ActivityRecord { } void setTask(TaskRecord newTask, TaskRecord taskToAffiliateWith) { - if (task != null && task.removeActivity(this)) { - if (task != newTask) { - task.stack.removeTask(task, "setTask"); - } else { - Slog.d(TAG, "!!! REMOVE THIS LOG !!! setTask: nearly removed stack=" + - (newTask == null ? null : newTask.stack)); - } + if (task != null && task.removeActivity(this) && task != newTask && task.stack != null) { + task.stack.removeTask(task, "setTask"); } task = newTask; setTaskToAffiliateWith(taskToAffiliateWith); @@ -566,6 +616,10 @@ final class ActivityRecord { return inHistory; } + boolean isInStackLocked() { + return task != null && task.stack != null && task.stack.isInStackLocked(this) != null; + } + boolean isHomeActivity() { return mActivityType == HOME_ACTIVITY_TYPE; } @@ -585,9 +639,10 @@ final class ActivityRecord { (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0); } - void makeFinishing() { + void makeFinishingLocked() { if (!finishing) { - if (this == task.stack.getVisibleBehindActivity()) { + if (task != null && task.stack != null + && this == task.stack.getVisibleBehindActivity()) { // A finishing activity should not remain as visible in the background mStackSupervisor.requestVisibleBehindLocked(this, false); } @@ -656,8 +711,9 @@ final class ActivityRecord { // stack. final ReferrerIntent rintent = new ReferrerIntent(intent, referrer); boolean unsent = true; - if ((state == ActivityState.RESUMED || (service.isSleeping() - && task.stack.topRunningActivityLocked(null) == this)) + if ((state == ActivityState.RESUMED + || (service.isSleeping() && task.stack != null + && task.stack.topRunningActivityLocked(null) == this)) && app != null && app.thread != null) { try { ArrayList<ReferrerIntent> ar = new ArrayList<>(1); @@ -665,11 +721,9 @@ final class ActivityRecord { app.thread.scheduleNewIntent(ar, appToken); unsent = false; } catch (RemoteException e) { - Slog.w(ActivityManagerService.TAG, - "Exception thrown sending new intent to " + this, e); + Slog.w(TAG, "Exception thrown sending new intent to " + this, e); } catch (NullPointerException e) { - Slog.w(ActivityManagerService.TAG, - "Exception thrown sending new intent to " + this, e); + Slog.w(TAG, "Exception thrown sending new intent to " + this, e); } } if (unsent) { @@ -707,6 +761,17 @@ final class ActivityRecord { pendingOptions.getCustomExitResId(), pendingOptions.getOnAnimationStartListener()); break; + case ActivityOptions.ANIM_CLIP_REVEAL: + service.mWindowManager.overridePendingAppTransitionClipReveal( + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getWidth(), pendingOptions.getHeight()); + if (intent.getSourceBounds() == null) { + intent.setSourceBounds(new Rect(pendingOptions.getStartX(), + pendingOptions.getStartY(), + pendingOptions.getStartX()+pendingOptions.getWidth(), + pendingOptions.getStartY()+pendingOptions.getHeight())); + } + break; case ActivityOptions.ANIM_SCALE_UP: service.mWindowManager.overridePendingAppTransitionScaleUp( pendingOptions.getStartX(), pendingOptions.getStartY(), @@ -798,7 +863,7 @@ final class ActivityRecord { void updateThumbnailLocked(Bitmap newThumbnail, CharSequence description) { if (newThumbnail != null) { - if (ActivityManagerService.DEBUG_THUMBNAILS) Slog.i(ActivityManagerService.TAG, + if (DEBUG_THUMBNAILS) Slog.i(TAG_THUMBNAILS, "Setting thumbnail of " + this + " to " + newThumbnail); boolean thumbnailUpdated = task.setLastThumbnail(newThumbnail); if (thumbnailUpdated && isPersistable()) { @@ -819,19 +884,27 @@ final class ActivityRecord { } boolean continueLaunchTickingLocked() { - if (launchTickTime != 0) { - 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; + if (launchTickTime == 0) { + return false; } - return false; + + final ActivityStack stack = task.stack; + if (stack == null) { + return false; + } + + 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; } void finishLaunchTickingLocked() { launchTickTime = 0; - task.stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG); + final ActivityStack stack = task.stack; + if (stack != null) { + stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG); + } } // IApplicationToken @@ -862,12 +935,12 @@ final class ActivityRecord { if (displayStartTime != 0) { reportLaunchTimeLocked(curTime); } - if (fullyDrawnStartTime != 0) { - final ActivityStack stack = task.stack; + final ActivityStack stack = task.stack; + if (fullyDrawnStartTime != 0 && stack != null) { final long thisTime = curTime - fullyDrawnStartTime; final long totalTime = stack.mFullyDrawnStartTime != 0 ? (curTime - stack.mFullyDrawnStartTime) : thisTime; - if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) { + if (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, @@ -883,22 +956,25 @@ final class ActivityRecord { TimeUtils.formatDuration(totalTime, sb); sb.append(")"); } - Log.i(ActivityManagerService.TAG, sb.toString()); + Log.i(TAG, sb.toString()); } if (totalTime > 0) { //service.mUsageStatsService.noteFullyDrawnTime(realActivity, (int) totalTime); } - fullyDrawnStartTime = 0; stack.mFullyDrawnStartTime = 0; } + fullyDrawnStartTime = 0; } private void reportLaunchTimeLocked(final long curTime) { final ActivityStack stack = task.stack; + if (stack == null) { + return; + } final long thisTime = curTime - displayStartTime; final long totalTime = stack.mLaunchStartTime != 0 ? (curTime - stack.mLaunchStartTime) : thisTime; - if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) { + if (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, @@ -914,7 +990,7 @@ final class ActivityRecord { TimeUtils.formatDuration(totalTime, sb); sb.append(")"); } - Log.i(ActivityManagerService.TAG, sb.toString()); + Log.i(TAG, sb.toString()); } mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime); if (totalTime > 0) { @@ -924,100 +1000,62 @@ final class ActivityRecord { stack.mLaunchStartTime = 0; } - public void windowsDrawn() { - synchronized(service) { - if (displayStartTime != 0) { - reportLaunchTimeLocked(SystemClock.uptimeMillis()); - } - mStackSupervisor.sendWaitingVisibleReportLocked(this); - startTime = 0; - finishLaunchTickingLocked(); - if (task != null) { - task.hasBeenVisible = true; - } + void windowsDrawnLocked() { + if (displayStartTime != 0) { + reportLaunchTimeLocked(SystemClock.uptimeMillis()); + } + mStackSupervisor.sendWaitingVisibleReportLocked(this); + startTime = 0; + finishLaunchTickingLocked(); + if (task != null) { + task.hasBeenVisible = true; } } - public void windowsVisible() { - synchronized(service) { - mStackSupervisor.reportActivityVisibleLocked(this); - if (ActivityManagerService.DEBUG_SWITCH) Log.v( - ActivityManagerService.TAG, "windowsVisible(): " + this); - if (!nowVisible) { - nowVisible = true; - lastVisibleTime = SystemClock.uptimeMillis(); - if (!idle) { - // 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. - 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 = mStackSupervisor.mWaitingVisibleActivities.size(); - if (N > 0) { - for (int i=0; i<N; i++) { - ActivityRecord r = mStackSupervisor.mWaitingVisibleActivities.get(i); - r.waitingVisible = false; - if (ActivityManagerService.DEBUG_SWITCH) Log.v( - ActivityManagerService.TAG, - "Was waiting for visible: " + r); - } - mStackSupervisor.mWaitingVisibleActivities.clear(); - mStackSupervisor.scheduleIdleLocked(); + void windowsVisibleLocked() { + mStackSupervisor.reportActivityVisibleLocked(this); + if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "windowsVisibleLocked(): " + this); + if (!nowVisible) { + nowVisible = true; + lastVisibleTime = SystemClock.uptimeMillis(); + if (!idle) { + // 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. + 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 size = mStackSupervisor.mWaitingVisibleActivities.size(); + if (size > 0) { + for (int i = 0; i < size; i++) { + ActivityRecord r = mStackSupervisor.mWaitingVisibleActivities.get(i); + if (DEBUG_SWITCH) Log.v(TAG_SWITCH, "Was waiting for visible: " + r); } + mStackSupervisor.mWaitingVisibleActivities.clear(); + mStackSupervisor.scheduleIdleLocked(); } - service.scheduleAppGcsLocked(); } + service.scheduleAppGcsLocked(); } } - public void windowsGone() { - if (ActivityManagerService.DEBUG_SWITCH) Log.v( - 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; - if (r.waitingVisible) { + ActivityRecord getWaitingHistoryRecordLocked() { + // First find the real culprit... if this activity is waiting for + // another activity to start or has stopped, then the key dispatching + // timeout should not be caused by this. + if (mStackSupervisor.mWaitingVisibleActivities.contains(this) || stopped) { final ActivityStack stack = mStackSupervisor.getFocusedStack(); - // Hmmm, who might we be waiting for? - r = stack.mResumedActivity; + // Try to use the one which is closest to top. + ActivityRecord r = stack.mResumedActivity; if (r == null) { r = stack.mPausingActivity; } - // Both of those null? Fall back to 'this' again - if (r == null) { - r = this; + if (r != null) { + return r; } } - - return r; - } - - 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, reason); - } - - /** Returns the key dispatching timeout for this application token. */ - public long getKeyDispatchingTimeout() { - synchronized(service) { - ActivityRecord r = getWaitingHistoryRecordLocked(); - return ActivityManagerService.getInputDispatchingTimeoutLocked(r); - } + return this; } /** @@ -1047,14 +1085,14 @@ final class ActivityRecord { } static void activityResumedLocked(IBinder token) { - final ActivityRecord r = ActivityRecord.forToken(token); + final ActivityRecord r = ActivityRecord.forTokenLocked(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); + final ActivityRecord r = ActivityRecord.forTokenLocked(token); if (r == null) { return INVALID_TASK_ID; } @@ -1067,11 +1105,8 @@ final class ActivityRecord { } static ActivityRecord isInStackLocked(IBinder token) { - final ActivityRecord r = ActivityRecord.forToken(token); - if (r != null) { - return r.task.stack.isInStackLocked(token); - } - return null; + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + return (r != null) ? r.task.stack.isInStackLocked(r) : null; } static ActivityStack getStackLocked(IBinder token) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 7908da0..33f915f 100755..100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -16,19 +16,9 @@ package com.android.server.am; -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 android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; + +import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; @@ -97,6 +87,20 @@ import java.util.Objects; */ final class ActivityStack { + private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM; + private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP; + private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; + private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE; + private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; + private static final String TAG_STACK = TAG + POSTFIX_STACK; + private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; + private static final String TAG_TASKS = TAG + POSTFIX_TASKS; + private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION; + private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; + private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY; + + private static final boolean VALIDATE_TOKENS = false; + // Ticks during which we check progress while waiting for an app to launch. static final int LAUNCH_TICK = 500; @@ -130,8 +134,6 @@ final class ActivityStack { // convertToTranslucent(). static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000; - static final boolean SCREENSHOT_FORCE_565 = ActivityManager.isLowRamDeviceStatic(); - enum ActivityState { INITIALIZING, RESUMED, @@ -146,30 +148,31 @@ final class ActivityStack { final ActivityManagerService mService; final WindowManagerService mWindowManager; + private final RecentTasks mRecentTasks; /** * The back history of all previous (and possibly still * running) activities. It contains #TaskRecord objects. */ - private ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>(); + private ArrayList<TaskRecord> mTaskHistory = new ArrayList<>(); /** * Used for validating app tokens with window manager. */ - final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<TaskGroup>(); + final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<>(); /** * List of running activities, sorted by recent usage. * The first entry in the list is the least recently used. * It contains HistoryRecord objects. */ - final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>(); + final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>(); /** * Animations that for the current transition have requested not to * be considered for the transition animation. */ - final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<ActivityRecord>(); + final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>(); /** * When we are in the process of pausing an activity, before starting the @@ -219,6 +222,9 @@ final class ActivityStack { */ boolean mConfigWillChange; + // Whether or not this stack covers the entire screen; by default stacks are full screen + boolean mFullscreen = true; + long mLaunchStartTime = 0; long mFullyDrawnStartTime = 0; @@ -234,6 +240,11 @@ final class ActivityStack { /** Run all ActivityStacks through this */ final ActivityStackSupervisor mStackSupervisor; + Configuration mOverrideConfig; + /** True if the stack was forced to full screen because {@link TaskRecord#mResizeable} is false + * and the stack was previously resized. */ + private boolean mForcedFullscreen = false; + static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1; static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2; static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3; @@ -255,9 +266,7 @@ final class ActivityStack { final Handler mHandler; final class ActivityStackHandler extends Handler { - //public Handler() { - // if (localLOGV) Slog.v(TAG, "Handler started!"); - //} + ActivityStackHandler(Looper looper) { super(looper); } @@ -337,7 +346,12 @@ final class ActivityStack { return count; } - ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer) { + int numTasks() { + return mTaskHistory.size(); + } + + ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer, + RecentTasks recentTasks) { mActivityContainer = activityContainer; mStackSupervisor = activityContainer.getOuter(); mService = mStackSupervisor.mService; @@ -345,22 +359,13 @@ final class ActivityStack { mWindowManager = mService.mWindowManager; mStackId = activityContainer.mStackId; mCurrentUser = mService.mCurrentUserId; - } - - /** - * Checks whether the userid is a profile of the current user. - */ - private boolean isCurrentProfileLocked(int userId) { - if (userId == mCurrentUser) return true; - for (int i = 0; i < mService.mCurrentProfileIds.length; i++) { - if (mService.mCurrentProfileIds[i] == userId) return true; - } - return false; + mRecentTasks = recentTasks; + mOverrideConfig = Configuration.EMPTY; } boolean okToShowLocked(ActivityRecord r) { - return isCurrentProfileLocked(r.userId) - || (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0; + return mStackSupervisor.isCurrentProfileLocked(r.userId) + || (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0; } final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { @@ -446,14 +451,20 @@ final class ActivityStack { } ActivityRecord isInStackLocked(IBinder token) { - final ActivityRecord r = ActivityRecord.forToken(token); - if (r != null) { - final TaskRecord task = r.task; - if (task != null && task.mActivities.contains(r) && mTaskHistory.contains(task)) { - if (task.stack != this) Slog.w(TAG, + final ActivityRecord r = ActivityRecord.forTokenLocked(token); + return isInStackLocked(r); + } + + ActivityRecord isInStackLocked(ActivityRecord r) { + if (r == null) { + return null; + } + final TaskRecord task = r.task; + if (task != null && task.stack != null + && 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 r; } return null; } @@ -475,11 +486,22 @@ final class ActivityStack { final void moveToFront(String reason) { if (isAttached()) { + final boolean homeStack = isHomeStack() + || (mActivityContainer.mParentActivity != null + && mActivityContainer.mParentActivity.isHomeActivity()); + ActivityStack lastFocusStack = null; + if (!homeStack) { + // Need to move this stack to the front before calling + // {@link ActivityStackSupervisor#moveHomeStack} below. + lastFocusStack = mStacks.get(mStacks.size() - 1); + mStacks.remove(this); + mStacks.add(this); + } + // TODO(multi-display): Focus stack currently adjusted in call to move home stack. + // Needs to also work if focus is moving to the non-home display. if (isOnHomeDisplay()) { - mStackSupervisor.moveHomeStack(isHomeStack(), reason); + mStackSupervisor.moveHomeStack(homeStack, reason, lastFocusStack); } - mStacks.remove(this); - mStacks.add(this); final TaskRecord task = topTask(); if (task != null) { mWindowManager.moveTaskToTop(task.taskId); @@ -507,23 +529,23 @@ final class ActivityStack { // If documentData is non-null then it must match the existing task data. Uri documentData = isDocument ? intent.getData() : null; - if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + target + " in " + this); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); if (task.voiceSession != null) { // We never match voice sessions; those always run independently. - if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": voice session"); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": voice session"); continue; } if (task.userId != userId) { // Looking for a different task. - if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user"); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "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); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": mismatch root " + r); continue; } @@ -542,34 +564,32 @@ final class ActivityStack { taskDocumentData = null; } - if (DEBUG_TASKS) Slog.d(TAG, "Comparing existing cls=" + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Comparing existing cls=" + taskIntent.getComponent().flattenToShortString() + "/aff=" + r.task.rootAffinity + " to new cls=" + intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity); if (!isDocument && !taskIsDocument && task.rootAffinity != null) { if (task.rootAffinity.equals(target.taskAffinity)) { - if (DEBUG_TASKS) Slog.d(TAG, "Found matching affinity!"); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity!"); return r; } } else if (taskIntent != null && taskIntent.getComponent() != null && taskIntent.getComponent().compareTo(cls) == 0 && Objects.equals(documentData, taskDocumentData)) { - if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!"); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching class!"); //dump(); - if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: " - + r.intent); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, + "For Intent " + intent + " bringing to top: " + r.intent); return r; } else if (affinityIntent != null && affinityIntent.getComponent() != null && affinityIntent.getComponent().compareTo(cls) == 0 && Objects.equals(documentData, taskDocumentData)) { - if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!"); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching class!"); //dump(); - if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: " - + r.intent); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, + "For Intent " + intent + " bringing to top: " + r.intent); return r; - } else if (DEBUG_TASKS) { - Slog.d(TAG, "Not a match: " + task); - } + } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task); } return null; @@ -588,13 +608,16 @@ final class ActivityStack { final int userId = UserHandle.getUserId(info.applicationInfo.uid); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { - TaskRecord task = mTaskHistory.get(taskNdx); - if (!isCurrentProfileLocked(task.userId)) { - return null; - } + final TaskRecord task = mTaskHistory.get(taskNdx); + final boolean notCurrentUserTask = + !mStackSupervisor.isCurrentProfileLocked(task.userId); final ArrayList<ActivityRecord> activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { ActivityRecord r = activities.get(activityNdx); + if (notCurrentUserTask && (r.info.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) { + return null; + } if (!r.finishing && r.intent.getComponent().equals(cls) && r.userId == userId) { //Slog.i(TAG, "Found matching class!"); //dump(); @@ -619,9 +642,13 @@ final class ActivityStack { // Move userId's tasks to the top. int index = mTaskHistory.size(); for (int i = 0; i < index; ) { - TaskRecord task = mTaskHistory.get(i); - if (isCurrentProfileLocked(task.userId)) { - if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() + + final TaskRecord task = mTaskHistory.get(i); + + // NOTE: If {@link TaskRecord#topRunningActivityLocked} return is not null then it is + // okay to show the activity when locked. + if (mStackSupervisor.isCurrentProfileLocked(task.userId) + || task.topRunningActivityLocked(null) != null) { + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "switchUserLocked: stack=" + getStackId() + " moving " + task + " to top"); mTaskHistory.remove(i); mTaskHistory.add(task); @@ -643,7 +670,7 @@ final class ActivityStack { r.stopped = false; mResumedActivity = r; r.task.touchActiveTime(); - mService.addRecentTaskLocked(r.task); + mRecentTasks.addLocked(r.task); completeResumeLocked(r); mStackSupervisor.checkReadyForSleepLocked(); setLaunchTime(r); @@ -708,14 +735,15 @@ final class ActivityStack { 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"); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep needs to pause " + mResumedActivity); + if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, + "Sleep => pause with userLeaving=false"); startPausingLocked(false, true, false, false); return true; } 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); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still waiting to pause " + mPausingActivity); return true; } @@ -757,7 +785,7 @@ final class ActivityStack { if (w > 0) { if (DEBUG_SCREENSHOTS) Slog.d(TAG, "\tTaking screenshot"); return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY, - w, h, SCREENSHOT_FORCE_565); + w, h); } Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h); return null; @@ -780,8 +808,14 @@ final class ActivityStack { final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, boolean resuming, boolean dontWait) { if (mPausingActivity != null) { - Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity); - completePauseLocked(false); + Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity + + " state=" + mPausingActivity.state); + if (!mService.isSleeping()) { + // Avoid recursion among check for sleep and complete pause during sleeping. + // Because activity will be paused immediately after resume, just let pause + // be completed by the order of activity paused from clients. + completePauseLocked(false); + } } ActivityRecord prev = mResumedActivity; if (prev == null) { @@ -798,7 +832,7 @@ final class ActivityStack { } if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSING: " + prev); - else if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev); + else if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Start pausing: " + prev); mResumedActivity = null; mPausingActivity = prev; mLastPausedActivity = prev; @@ -816,7 +850,7 @@ final class ActivityStack { mService.updateCpuStats(); if (prev.app != null && prev.app.thread != null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + prev); try { EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY, prev.userId, System.identityHashCode(prev), @@ -839,7 +873,7 @@ final class ActivityStack { // If we are not going to sleep, we want to ensure the device is // awake until the next activity is started. - if (!mService.isSleepingOrShuttingDown()) { + if (!uiSleeping && !mService.isSleepingOrShuttingDown()) { mStackSupervisor.acquireLaunchWakelock(); } @@ -850,8 +884,8 @@ final class ActivityStack { // key dispatch; the same activity will pick it up again on wakeup. if (!uiSleeping) { prev.pauseKeyDispatchingLocked(); - } else { - if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off"); + } else if (DEBUG_PAUSE) { + Slog.v(TAG_PAUSE, "Key dispatch not paused for screen off"); } if (dontWait) { @@ -868,14 +902,14 @@ final class ActivityStack { msg.obj = prev; prev.pauseTime = SystemClock.uptimeMillis(); mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); - if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete..."); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete..."); return true; } } else { // 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."); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Activity not running, resuming next."); if (!resuming) { mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null); } @@ -884,8 +918,8 @@ final class ActivityStack { } final void activityPausedLocked(IBinder token, boolean timeout) { - if (DEBUG_PAUSE) Slog.v( - TAG, "Activity paused: token=" + token + ", timeout=" + timeout); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, + "Activity paused: token=" + token + ", timeout=" + timeout); final ActivityRecord r = isInStackLocked(token); if (r != null) { @@ -899,6 +933,11 @@ final class ActivityStack { r.userId, System.identityHashCode(r), r.shortComponentName, mPausingActivity != null ? mPausingActivity.shortComponentName : "(none)"); + if (r.finishing && r.state == ActivityState.PAUSING) { + if (DEBUG_PAUSE) Slog.v(TAG, + "Executing finish of failed to pause activity: " + r); + finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false); + } } } } @@ -946,20 +985,18 @@ final class ActivityStack { private void completePauseLocked(boolean resumeNext) { ActivityRecord prev = mPausingActivity; - if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev); if (prev != null) { prev.state = ActivityState.PAUSED; if (prev.finishing) { - if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev); prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false); } else if (prev.app != null) { - if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev); - if (prev.waitingVisible) { - prev.waitingVisible = false; - mStackSupervisor.mWaitingVisibleActivities.remove(prev); - if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v( - TAG, "Complete pause, no longer waiting: " + prev); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending stop: " + prev); + if (mStackSupervisor.mWaitingVisibleActivities.remove(prev)) { + if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(TAG_PAUSE, + "Complete pause, no longer waiting: " + prev); } if (prev.configDestroy) { // The previous is being paused because the configuration @@ -967,7 +1004,7 @@ final class ActivityStack { // To juggle the fact that we are also starting a new // instance right now, we need to first completely stop // the current instance before starting the new one. - if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Destroying after pause: " + prev); destroyActivityLocked(prev, true, "pause-config"); } else if (!hasVisibleBehindActivity()) { // If we were visible then resumeTopActivities will release resources before @@ -979,16 +1016,20 @@ final class ActivityStack { // then give up on things going idle and start clearing // 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"); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "To many pending stops, forcing idle"); mStackSupervisor.scheduleIdleLocked(); } else { mStackSupervisor.checkReadyForSleepLocked(); } } } else { - if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "App died during pause, not stopping: " + prev); prev = null; } + // It is possible the activity was freezing the screen before it was paused. + // In that case go ahead and remove the freeze this activity has on the screen + // since it is no longer visible. + prev.stopFreezingScreenLocked(true /*force*/); mPausingActivity = null; } @@ -1083,7 +1124,7 @@ final class ActivityStack { } } - private void setVisibile(ActivityRecord r, boolean visible) { + private void setVisible(ActivityRecord r, boolean visible) { r.visible = visible; mWindowManager.setAppVisibility(r.appToken, visible); final ArrayList<ActivityContainer> containers = r.mChildContainers; @@ -1116,7 +1157,8 @@ final class ActivityStack { final int numStacks = mStacks.size(); while (stackNdx < numStacks) { - tasks = mStacks.get(stackNdx).mTaskHistory; + ActivityStack historyStack = mStacks.get(stackNdx); + tasks = historyStack.mTaskHistory; final int numTasks = tasks.size(); while (taskNdx < numTasks) { activities = tasks.get(taskNdx).mActivities; @@ -1124,7 +1166,7 @@ final class ActivityStack { while (activityNdx < numActivities) { final ActivityRecord activity = activities.get(activityNdx); if (!activity.finishing) { - return activity.fullscreen ? null : activity; + return historyStack.mFullscreen && activity.fullscreen ? null : activity; } ++activityNdx; } @@ -1138,9 +1180,26 @@ final class ActivityStack { return null; } + private ActivityStack getNextVisibleStackLocked() { + ArrayList<ActivityStack> stacks = mStacks; + final ActivityRecord parent = mActivityContainer.mParentActivity; + if (parent != null) { + stacks = parent.task.stack.mStacks; + } + if (stacks != null) { + for (int i = stacks.size() - 1; i >= 0; --i) { + ActivityStack stack = stacks.get(i); + if (stack != this && stack.isStackVisibleLocked()) { + return stack; + } + } + } + return null; + } + // Checks if any of the stacks above this one has a fullscreen activity behind it. // If so, this stack is hidden, otherwise it is visible. - private boolean isStackVisible() { + private boolean isStackVisibleLocked() { if (!isAttached()) { return false; } @@ -1155,11 +1214,18 @@ final class ActivityStack { * wallpaper to be shown behind it. */ for (int i = mStacks.indexOf(this) + 1; i < mStacks.size(); i++) { - final ArrayList<TaskRecord> tasks = mStacks.get(i).getAllTasks(); - for (int taskNdx = 0; taskNdx < tasks.size(); taskNdx++) { + ActivityStack stack = mStacks.get(i); + // stack above isn't full screen, so, we assume we're still visible. at some point + // we should look at the stack bounds to see if we're occluded even if the stack + // isn't fullscreen + if (!stack.mFullscreen) { + continue; + } + final ArrayList<TaskRecord> tasks = stack.getAllTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = tasks.get(taskNdx); final ArrayList<ActivityRecord> activities = task.mActivities; - for (int activityNdx = 0; activityNdx < activities.size(); activityNdx++) { + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = activities.get(activityNdx); // Conditions for an activity to obscure the stack we're @@ -1188,8 +1254,8 @@ final class ActivityStack { if (top == null) { return; } - if (DEBUG_VISBILITY) Slog.v( - TAG, "ensureActivitiesVisible behind " + top + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "ensureActivitiesVisible behind " + top + " configChanges=0x" + Integer.toHexString(configChanges)); if (mTranslucentActivityWaiting != top) { @@ -1205,7 +1271,8 @@ final class ActivityStack { // If the top activity is not fullscreen, then we need to // make sure any activities under it are now visible. boolean aboveTop = true; - boolean behindFullscreen = !isStackVisible(); + boolean behindFullscreen = !isStackVisibleLocked(); + boolean noStackActivityResumed = (isInStackLocked(starting) == null); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); @@ -1222,8 +1289,8 @@ final class ActivityStack { // mLaunchingBehind: Activities launching behind are at the back of the task stack // but must be drawn initially for the animation as though they were visible. if (!behindFullscreen || r.mLaunchTaskBehind) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Make visible? " + r + " finishing=" + r.finishing + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Make visible? " + r + " finishing=" + r.finishing + " state=" + r.state); // First: if this is not the current activity being started, make @@ -1233,26 +1300,29 @@ final class ActivityStack { } if (r.app == null || r.app.thread == null) { - // 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); + // This activity needs to be visible, but isn't even running... + // get it started and resume if no other stack in this stack is resumed. + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Start and freeze screen for " + r); if (r != starting) { r.startFreezingScreenLocked(r.app, configChanges); } if (!r.visible || r.mLaunchTaskBehind) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Starting and making visible: " + r); - setVisibile(r, true); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Starting and making visible: " + r); + setVisible(r, true); } if (r != starting) { - mStackSupervisor.startSpecificActivityLocked(r, false, false); + mStackSupervisor.startSpecificActivityLocked( + r, noStackActivityResumed, false); + noStackActivityResumed = false; } } 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); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Skipping: already visible at " + r); r.stopFreezingScreenLocked(false); try { if (r.returningOptions != null) { @@ -1268,14 +1338,14 @@ final class ActivityStack { 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); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Making visible and scheduling visibility: " + r); try { if (mTranslucentActivityWaiting != null) { r.updateOptionsLocked(r.returningOptions); mUndrawnActivitiesBelowTopTranslucent.add(r); } - setVisibile(r, true); + setVisible(r, true); r.sleeping = false; r.app.pendingUiClean = true; r.app.thread.scheduleWindowVisibility(r.appToken, true); @@ -1294,29 +1364,28 @@ final class ActivityStack { if (r.fullscreen) { // At this point, nothing else needs to be shown - if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r); behindFullscreen = true; } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) { - if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r); behindFullscreen = true; } } else { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Make invisible? " + r + " finishing=" + r.finishing - + " state=" + r.state - + " behindFullscreen=" + behindFullscreen); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "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_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r); try { - setVisibile(r, false); + setVisible(r, 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); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Scheduling invisibility: " + r); r.app.thread.scheduleWindowVisibility(r.appToken, false); } break; @@ -1328,7 +1397,7 @@ final class ActivityStack { // This case created for transitioning activities from // translucent to opaque {@link Activity#convertToOpaque}. if (getVisibleBehindActivity() == r) { - releaseBackgroundResources(); + releaseBackgroundResources(r); } else { if (!mStackSupervisor.mStoppingActivities.contains(r)) { mStackSupervisor.mStoppingActivities.add(r); @@ -1347,7 +1416,7 @@ final class ActivityStack { + r.intent.getComponent(), e); } } else { - if (DEBUG_VISBILITY) Slog.v(TAG, "Already invisible: " + r); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r); } } } @@ -1360,7 +1429,7 @@ final class ActivityStack { } } - void convertToTranslucent(ActivityRecord r) { + void convertActivityToTranslucent(ActivityRecord r) { mTranslucentActivityWaiting = r; mUndrawnActivitiesBelowTopTranslucent.clear(); mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT); @@ -1417,7 +1486,8 @@ final class ActivityStack { } if (r.state == ActivityState.INITIALIZING && r.mStartingWindowShown) { - if (DEBUG_VISBILITY) Slog.w(TAG, "Found orphaned starting window " + r); + if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, + "Found orphaned starting window " + r); r.mStartingWindowShown = false; mWindowManager.removeAppStartingWindow(r.appToken); } @@ -1459,8 +1529,8 @@ final class ActivityStack { return result; } - final boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) { - if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(""); + private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) { + if (DEBUG_LOCKSCREEN) mService.logLockScreen(""); if (!mService.mBooting && !mService.mBooted) { // Not ready yet! @@ -1487,8 +1557,17 @@ final class ActivityStack { final TaskRecord prevTask = prev != null ? prev.task : null; if (next == null) { - // There are no more activities! Let's just start up the - // Launcher... + // There are no more activities! + final String reason = "noMoreActivities"; + if (!mFullscreen) { + // Try to move focus to the next visible stack with a running activity if this + // stack is not covering the entire screen. + final ActivityStack stack = getNextVisibleStackLocked(); + if (adjustFocusToNextVisibleStackLocked(stack, reason)) { + return mStackSupervisor.resumeTopActivitiesLocked(stack, prev, null); + } + } + // Let's just start up the Launcher... ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home"); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); @@ -1496,7 +1575,7 @@ final class ActivityStack { final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ? HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo(); return isOnHomeDisplay() && - mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, "noMoreActivities"); + mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, reason); } next.delayedResume = false; @@ -1525,10 +1604,11 @@ final class ActivityStack { // Now the task above it has to return to the home task instead. final int taskNdx = mTaskHistory.indexOf(prevTask) + 1; mTaskHistory.get(taskNdx).setTaskToReturnTo(HOME_ACTIVITY_TYPE); - } else { - if (DEBUG_STATES && isOnHomeDisplay()) Slog.d(TAG, + } else if (!isOnHomeDisplay()) { + return false; + } else if (!isHomeStack()){ + if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Launching home next"); - // Only resume home if on home display final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ? HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo(); return isOnHomeDisplay() && @@ -1568,12 +1648,12 @@ final class ActivityStack { next.sleeping = false; mStackSupervisor.mWaitingVisibleActivities.remove(next); - if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next); + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next); // If we are currently pausing an activity, then don't do anything // until that is done. if (!mStackSupervisor.allPausedActivitiesComplete()) { - if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE, "resumeTopActivityLocked: Skip resume: some activity pausing."); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return false; @@ -1608,6 +1688,8 @@ final class ActivityStack { } } + mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid); + // We need to start pausing the current activity so the top one // can be resumed... boolean dontWaitForPause = (next.info.flags&ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0; @@ -1638,16 +1720,16 @@ final class ActivityStack { if (DEBUG_STATES) Slog.d(TAG, "no-history finish of " + mLastNoHistoryActivity + " on new resume"); requestFinishActivityLocked(mLastNoHistoryActivity.appToken, Activity.RESULT_CANCELED, - null, "no-history", false); + null, "resume-no-history", false); mLastNoHistoryActivity = null; } if (prev != null && prev != next) { - if (!prev.waitingVisible && next != null && !next.nowVisible) { - prev.waitingVisible = true; + if (!mStackSupervisor.mWaitingVisibleActivities.contains(prev) + && next != null && !next.nowVisible) { mStackSupervisor.mWaitingVisibleActivities.add(prev); - if (DEBUG_SWITCH) Slog.v( - TAG, "Resuming top, waiting visible to hide: " + prev); + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, + "Resuming top, waiting visible to hide: " + prev); } else { // The next activity is already visible, so hide the previous // activity's windows right now so we can show the new one ASAP. @@ -1659,15 +1741,16 @@ final class ActivityStack { // new one is found to be full-screen or not. if (prev.finishing) { mWindowManager.setAppVisibility(prev.appToken, false); - if (DEBUG_SWITCH) Slog.v(TAG, "Not waiting for visible to hide: " - + prev + ", waitingVisible=" - + (prev != null ? prev.waitingVisible : null) + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, + "Not waiting for visible to hide: " + prev + ", waitingVisible=" + + mStackSupervisor.mWaitingVisibleActivities.contains(prev) + ", nowVisible=" + next.nowVisible); } else { - if (DEBUG_SWITCH) Slog.v(TAG, "Previous already visible but still waiting to hide: " - + prev + ", waitingVisible=" - + (prev != null ? prev.waitingVisible : null) - + ", nowVisible=" + next.nowVisible); + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, + "Previous already visible but still waiting to hide: " + prev + + ", waitingVisible=" + + mStackSupervisor.mWaitingVisibleActivities.contains(prev) + + ", nowVisible=" + next.nowVisible); } } } @@ -1689,7 +1772,7 @@ final class ActivityStack { boolean anim = true; if (prev != null) { if (prev.finishing) { - if (DEBUG_TRANSITION) Slog.v(TAG, + if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev); if (mNoAnimActivities.contains(prev)) { anim = false; @@ -1702,7 +1785,8 @@ final class ActivityStack { 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_TRANSITION, + "Prepare open transition: prev=" + prev); if (mNoAnimActivities.contains(next)) { anim = false; mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); @@ -1719,7 +1803,7 @@ final class ActivityStack { mWindowManager.setAppVisibility(prev.appToken, false); } } else { - if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: no previous"); + if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous"); if (mNoAnimActivities.contains(next)) { anim = false; mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); @@ -1741,7 +1825,7 @@ final class ActivityStack { ActivityStack lastStack = mStackSupervisor.getLastStack(); if (next.app != null && next.app.thread != null) { - if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next); + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next); // This activity is now becoming visible. mWindowManager.setAppVisibility(next.appToken, true); @@ -1759,7 +1843,7 @@ final class ActivityStack { next.state = ActivityState.RESUMED; mResumedActivity = next; next.task.touchActiveTime(); - mService.addRecentTaskLocked(next.task); + mRecentTasks.addLocked(next.task); mService.updateLruProcessLocked(next.app, true, null); updateLRUListLocked(next); mService.updateOomAdjLocked(); @@ -1806,9 +1890,8 @@ final class ActivityStack { if (a != null) { final int N = a.size(); if (!next.finishing && N > 0) { - if (DEBUG_RESULTS) Slog.v( - TAG, "Delivering results to " + next - + ": " + a); + if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, + "Delivering results to " + next + ": " + a); next.app.thread.scheduleSendResult(next.appToken, a); } } @@ -1823,7 +1906,7 @@ final class ActivityStack { next.sleeping = false; mService.showAskCompatModeDialogLocked(next); next.app.pendingUiClean = true; - next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP); + next.app.forceProcessStateUpTo(mService.mTopProcessState); next.clearOptionsLocked(); next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState, mService.isNextTransitionForward(), resumeAnimOptions); @@ -1885,7 +1968,7 @@ final class ActivityStack { next.labelRes, next.icon, next.logo, next.windowFlags, null, true); } - if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next); + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next); } if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Restarting " + next); mStackSupervisor.startSpecificActivityLocked(next, true, true); @@ -1895,9 +1978,31 @@ final class ActivityStack { return true; } - private void insertTaskAtTop(TaskRecord task) { + private TaskRecord getNextTask(TaskRecord targetTask) { + final int index = mTaskHistory.indexOf(targetTask); + if (index >= 0) { + final int numTasks = mTaskHistory.size(); + for (int i = index + 1; i < numTasks; ++i) { + TaskRecord task = mTaskHistory.get(i); + if (task.userId == targetTask.userId) { + return task; + } + } + } + return null; + } + + private void insertTaskAtTop(TaskRecord task, ActivityRecord newActivity) { + // If the moving task is over home stack, transfer its return type to next task + if (task.isOverHomeStack()) { + final TaskRecord nextTask = getNextTask(task); + if (nextTask != null) { + nextTask.setTaskToReturnTo(task.getTaskToReturnTo()); + } + } + // If this is being moved to the top by another activity or being launched from the home - // activity, set mOnTopOfHome accordingly. + // activity, set mTaskToReturnTo accordingly. if (isOnHomeDisplay()) { ActivityStack lastStack = mStackSupervisor.getLastStack(); final boolean fromHome = lastStack.isHomeStack(); @@ -1915,10 +2020,15 @@ final class ActivityStack { mTaskHistory.remove(task); // Now put task at top. int taskNdx = mTaskHistory.size(); - if (!isCurrentProfileLocked(task.userId)) { + final boolean notShownWhenLocked = + (newActivity != null && (newActivity.info.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) + || (newActivity == null && task.topRunningActivityLocked(null) == null); + if (!mStackSupervisor.isCurrentProfileLocked(task.userId) && notShownWhenLocked) { // Put non-current user tasks below current user tasks. while (--taskNdx >= 0) { - if (!isCurrentProfileLocked(mTaskHistory.get(taskNdx).userId)) { + final TaskRecord tmpTask = mTaskHistory.get(taskNdx); + if (!mStackSupervisor.isCurrentProfileLocked(tmpTask.userId) + || tmpTask.topRunningActivityLocked(null) == null) { break; } } @@ -1937,7 +2047,7 @@ final class ActivityStack { // Last activity in task had been removed or ActivityManagerService is reusing task. // Insert or replace. // Might not even be in. - insertTaskAtTop(rTask); + insertTaskAtTop(rTask, r); mWindowManager.moveTaskToTop(taskId); } TaskRecord task = null; @@ -1961,7 +2071,7 @@ final class ActivityStack { r.putInHistory(); 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.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); if (VALIDATE_TOKENS) { @@ -1985,7 +2095,7 @@ final class ActivityStack { // activity if (task == r.task && mTaskHistory.indexOf(task) != (mTaskHistory.size() - 1)) { mStackSupervisor.mUserLeaving = false; - if (DEBUG_USER_LEAVING) Slog.v(TAG, + if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, "startActivity() behind front, mUserLeaving=false"); } @@ -2010,9 +2120,9 @@ final class ActivityStack { if (proc == null || proc.thread == null) { showStartingIcon = true; } - if (DEBUG_TRANSITION) Slog.v(TAG, + if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: starting " + r); - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition); mNoAnimActivities.add(r); } else { @@ -2025,7 +2135,7 @@ final class ActivityStack { } 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, + (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); boolean doShow = true; if (newTask) { @@ -2077,7 +2187,7 @@ final class ActivityStack { // because there is nothing for it to animate on top of. 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, + (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); ActivityOptions.abort(options); options = null; @@ -2182,18 +2292,18 @@ final class ActivityStack { // same task affinity as the one we are moving, // then merge it into the same task. targetTask = bottom.task; - if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target + " out to bottom task " + bottom.task); } else { targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info, null, null, null, false); targetTask.affinityIntent = target.intent; - if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target + " out to new task " + target.task); } final int targetTaskId = targetTask.taskId; - mWindowManager.setAppGroupId(target.appToken, targetTaskId); + mWindowManager.setAppTask(target.appToken, targetTaskId); boolean noOptions = canMoveOptions; final int start = replyChainEnd < 0 ? i : replyChainEnd; @@ -2213,12 +2323,12 @@ final class ActivityStack { if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task=" + task + " adding to task=" + targetTask + " Callers=" + Debug.getCallers(4)); - if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p - + " out to target's task " + target.task); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, + "Pushing next activity " + p + " out to target's task " + target.task); p.setTask(targetTask, null); targetTask.addActivityAtBottom(p); - mWindowManager.setAppGroupId(p.appToken, targetTaskId); + mWindowManager.setAppTask(p.appToken, targetTaskId); } mWindowManager.moveTaskToBottom(targetTaskId); @@ -2237,7 +2347,7 @@ final class ActivityStack { // 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; + end = activities.size() - 1; } else if (replyChainEnd < 0) { end = i; } else { @@ -2256,9 +2366,10 @@ final class ActivityStack { noOptions = false; } } - if (DEBUG_TASKS) Slog.w(TAG, + if (DEBUG_TASKS) Slog.w(TAG_TASKS, "resetTaskIntendedTask: calling finishActivity on " + p); - if (finishActivityLocked(p, Activity.RESULT_CANCELED, null, "reset", false)) { + if (finishActivityLocked( + p, Activity.RESULT_CANCELED, null, "reset-task", false)) { end--; srcPos--; } @@ -2329,13 +2440,15 @@ final class ActivityStack { // in a task that is not currently on top.) if (forceReset || finishOnTaskLaunch) { final int start = replyChainEnd >= 0 ? replyChainEnd : i; - if (DEBUG_TASKS) Slog.v(TAG, "Finishing task at index " + start + " to " + i); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, + "Finishing task at index " + start + " to " + i); for (int srcPos = start; srcPos >= i; --srcPos) { final ActivityRecord p = activities.get(srcPos); if (p.finishing) { continue; } - finishActivityLocked(p, Activity.RESULT_CANCELED, null, "reset", false); + finishActivityLocked( + p, Activity.RESULT_CANCELED, null, "move-affinity", false); } } else { if (taskInsertionPoint < 0) { @@ -2344,8 +2457,9 @@ final class ActivityStack { } final int start = replyChainEnd >= 0 ? replyChainEnd : i; - if (DEBUG_TASKS) Slog.v(TAG, "Reparenting from task=" + affinityTask + ":" - + start + "-" + i + " to task=" + task + ":" + taskInsertionPoint); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, + "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); @@ -2354,9 +2468,9 @@ final class ActivityStack { 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); - mWindowManager.setAppGroupId(p.appToken, taskId); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p + + " from " + srcPos + " in to resetting task " + task); + mWindowManager.setAppTask(p.appToken, taskId); } mWindowManager.moveTaskToTop(taskId); if (VALIDATE_TOKENS) { @@ -2422,9 +2536,11 @@ final class ActivityStack { } int taskNdx = mTaskHistory.indexOf(task); - do { - taskTop = mTaskHistory.get(taskNdx--).getTopActivity(); - } while (taskTop == null && taskNdx >= 0); + if (taskNdx >= 0) { + do { + taskTop = mTaskHistory.get(taskNdx--).getTopActivity(); + } while (taskTop == null && taskNdx >= 0); + } if (topOptions != null) { // If we got some ActivityOptions from an activity on top that @@ -2468,22 +2584,50 @@ final class ActivityStack { private void adjustFocusedActivityLocked(ActivityRecord r, String reason) { if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) { ActivityRecord next = topRunningActivityLocked(null); + final String myReason = reason + " adjustFocus"; if (next != r) { final TaskRecord task = r.task; if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) { - mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(), - reason + " adjustFocus"); + // For non-fullscreen stack, we want to move the focus to the next visible + // stack to prevent the home screen from moving to the top and obscuring + // other visible stacks. + if (!mFullscreen + && adjustFocusToNextVisibleStackLocked(null, myReason)) { + return; + } + // Move the home stack to the top if this stack is fullscreen or there is no + // other visible stack. + if (mStackSupervisor.moveHomeStackTaskToTop( + task.getTaskToReturnTo(), myReason)) { + // Activity focus was already adjusted. Nothing else to do... + return; + } } } - ActivityRecord top = mStackSupervisor.topRunningActivityLocked(); + + final ActivityRecord top = mStackSupervisor.topRunningActivityLocked(); if (top != null) { - mService.setFocusedActivityLocked(top, reason + " adjustTopFocus"); + mService.setFocusedActivityLocked(top, myReason); } } } + private boolean adjustFocusToNextVisibleStackLocked(ActivityStack inStack, String reason) { + final ActivityStack stack = (inStack != null) ? inStack : getNextVisibleStackLocked(); + final String myReason = reason + " adjustFocusToNextVisibleStack"; + if (stack == null) { + return false; + } + final ActivityRecord top = stack.topRunningActivityLocked(null); + if (top == null) { + return false; + } + mService.setFocusedActivityLocked(top, myReason); + return true; + } + final void stopActivityLocked(ActivityRecord r) { - if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r); + if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + r); if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { if (!r.finishing) { @@ -2492,7 +2636,7 @@ final class ActivityStack { Slog.d(TAG, "no-history finish of " + r); } requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, - "no-history", false); + "stop-no-history", false); } else { if (DEBUG_STATES) Slog.d(TAG, "Not finishing noHistory " + r + " on stop because we're just sleeping"); @@ -2508,8 +2652,8 @@ final class ActivityStack { if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r + " (stop requested)"); r.state = ActivityState.STOPPING; - if (DEBUG_VISBILITY) Slog.v( - TAG, "Stopping visible=" + r.visible + " for " + r); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, + "Stopping visible=" + r.visible + " for " + r); if (!r.visible) { mWindowManager.setAppVisibility(r.appToken, false); } @@ -2571,16 +2715,16 @@ final class ActivityStack { mService.updateOomAdjLocked(); } - final void finishTopRunningActivityLocked(ProcessRecord app) { + final void finishTopRunningActivityLocked(ProcessRecord app, String reason) { 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 1 " + 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); + finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, 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. @@ -2600,9 +2744,9 @@ final class ActivityStack { || r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { if (!r.isHomeActivity() || mService.mHomeProcess != r.app) { - Slog.w(TAG, " Force finishing activity 2 " + Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false); + finishActivityLocked(r, Activity.RESULT_CANCELED, null, reason, false); } } } @@ -2646,7 +2790,7 @@ final class ActivityStack { // send the result ActivityRecord resultTo = r.resultTo; if (resultTo != null) { - if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo + if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Adding result to " + resultTo + " who=" + r.resultWho + " req=" + r.requestCode + " res=" + resultCode + " data=" + resultData); if (resultTo.userId != r.userId) { @@ -2663,7 +2807,7 @@ final class ActivityStack { resultData); r.resultTo = null; } - else if (DEBUG_RESULTS) Slog.v(TAG, "No result destination from " + r); + else if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "No result destination from " + r); // Make sure this HistoryRecord is not holding on to other resources, // because clients have remote IPC references to this object so we @@ -2685,7 +2829,7 @@ final class ActivityStack { return false; } - r.makeFinishing(); + r.makeFinishingLocked(); final TaskRecord task = r.task; EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, r.userId, System.identityHashCode(r), @@ -2711,7 +2855,7 @@ final class ActivityStack { if (mResumedActivity == r) { boolean endTask = index <= 0; - if (DEBUG_VISBILITY || DEBUG_TRANSITION) Slog.v(TAG, + if (DEBUG_VISIBILITY || DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + r); mWindowManager.prepareAppTransition(endTask ? AppTransition.TRANSIT_TASK_CLOSE @@ -2721,21 +2865,22 @@ final class ActivityStack { 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"); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish needs to pause: " + r); + if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, + "finish() => pause with userLeaving=false"); startPausingLocked(false, false, false, false); } if (endTask) { - mStackSupervisor.endLockTaskModeIfTaskEnding(task); + mStackSupervisor.removeLockedTaskLocked(task); } } else if (r.state != ActivityState.PAUSING) { // 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); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + r); return finishCurrentActivityLocked(r, FINISH_AFTER_PAUSE, oomAdj) == null; } else { - if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + r); } return false; @@ -2788,12 +2933,12 @@ final class ActivityStack { || prevState == ActivityState.INITIALIZING) { // If this activity is already stopped, we can just finish // it right now. - r.makeFinishing(); + r.makeFinishingLocked(); boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm"); if (activityRemoved) { mStackSupervisor.resumeTopActivitiesLocked(); } - if (DEBUG_CONTAINERS) Slog.d(TAG, + if (DEBUG_CONTAINERS) Slog.d(TAG, "destroyActivityLocked: finishCurrentActivityLocked r=" + r + " destroy returned removed=" + activityRemoved); return activityRemoved ? null : r; @@ -2801,7 +2946,7 @@ final class ActivityStack { // 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); + if (DEBUG_ALL) Slog.v(TAG, "Enqueueing pending finish: " + r); mStackSupervisor.mFinishingActivities.add(r); r.resumeKeyDispatchingLocked(); mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null); @@ -2864,9 +3009,8 @@ final class ActivityStack { return false; } - final boolean navigateUpToLocked(IBinder token, Intent destIntent, int resultCode, + final boolean navigateUpToLocked(ActivityRecord srec, 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); @@ -2939,7 +3083,7 @@ final class ActivityStack { foundParentInTask = false; } requestFinishActivityLocked(parent.appToken, resultCode, - resultData, "navigate-up", true); + resultData, "navigate-top", true); } } Binder.restoreCallingIdentity(origId); @@ -2951,6 +3095,8 @@ final class ActivityStack { * representation) and cleaning things up as a result of its hosting * processing going away, in which case there is no remaining client-side * state to destroy so only the cleanup here is needed. + * + * Note: Call before #removeActivityFromHistoryLocked. */ final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices, boolean setState) { @@ -3011,7 +3157,7 @@ final class ActivityStack { private void removeActivityFromHistoryLocked(ActivityRecord r, String reason) { mStackSupervisor.removeChildActivityContainers(r); finishActivityResultsLocked(r, Activity.RESULT_CANCELED, null); - r.makeFinishing(); + r.makeFinishingLocked(); if (DEBUG_ADD_REMOVE) { RuntimeException here = new RuntimeException("here"); here.fillInStackTrace(); @@ -3029,7 +3175,7 @@ final class ActivityStack { } final TaskRecord task = r.task; if (task != null && task.removeActivity(r)) { - if (DEBUG_STACK) Slog.i(TAG, + if (DEBUG_STACK) Slog.i(TAG_STACK, "removeActivityFromHistoryLocked: last activity removed from " + this); if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.isOverHomeStack()) { @@ -3082,7 +3228,7 @@ final class ActivityStack { continue; } if (r.isDestroyable()) { - if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Destroying " + r + " in state " + r.state + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); if (destroyActivityLocked(r, true, reason)) { @@ -3098,8 +3244,8 @@ final class ActivityStack { final boolean safelyDestroyActivityLocked(ActivityRecord r, String reason) { if (r.isDestroyable()) { - if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state - + " resumed=" + mResumedActivity + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, + "Destroying " + r + " in state " + r.state + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); return destroyActivityLocked(r, true, reason); } @@ -3157,9 +3303,9 @@ final class ActivityStack { * but then create a new client-side object for this same HistoryRecord. */ final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) { - if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v( - TAG, "Removing activity from " + reason + ": token=" + r - + ", app=" + (r.app != null ? r.app.processName : "(null)")); + if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v(TAG_SWITCH, + "Removing activity from " + reason + ": token=" + r + + ", app=" + (r.app != null ? r.app.processName : "(null)")); EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, r.userId, System.identityHashCode(r), r.task.taskId, r.shortComponentName, reason); @@ -3191,7 +3337,7 @@ final class ActivityStack { boolean skipDestroy = false; try { - if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r); + if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r); r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing, r.configChangeFlags); } catch (Exception e) { @@ -3252,13 +3398,13 @@ final class ActivityStack { final void activityDestroyedLocked(IBinder token, String reason) { final long origId = Binder.clearCallingIdentity(); try { - ActivityRecord r = ActivityRecord.forToken(token); + ActivityRecord r = ActivityRecord.forTokenLocked(token); if (r != null) { mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); } if (DEBUG_CONTAINERS) Slog.d(TAG, "activityDestroyedLocked: r=" + r); - if (isInStackLocked(token) != null) { + if (isInStackLocked(r) != null) { if (r.state == ActivityState.DESTROYING) { cleanUpActivityLocked(r, true, false); removeActivityFromHistoryLocked(r, reason); @@ -3270,10 +3416,9 @@ final class ActivityStack { } } - void releaseBackgroundResources() { + void releaseBackgroundResources(ActivityRecord r) { if (hasVisibleBehindActivity() && !mHandler.hasMessages(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG)) { - final ActivityRecord r = getVisibleBehindActivity(); if (r == topRunningActivityLocked(null)) { // Don't release the top activity if it has requested to run behind the next // activity. @@ -3323,15 +3468,14 @@ final class ActivityStack { 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"); + if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, + "Removing app " + app + " from list " + listName + " with " + i + " entries"); while (i > 0) { i--; ActivityRecord r = list.get(i); - if (DEBUG_CLEANUP) Slog.v(TAG, "Record #" + i + " " + r); + if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "Record #" + i + " " + r); if (r.app == app) { - if (DEBUG_CLEANUP) Slog.v(TAG, "---> REMOVING this entry!"); + if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "---> REMOVING this entry!"); list.remove(i); removeTimeoutsForActivityLocked(r); } @@ -3353,17 +3497,20 @@ final class ActivityStack { // Clean out the history list. int i = numActivities(); - if (DEBUG_CLEANUP) Slog.v( - TAG, "Removing app " + app + " from history with " + i + " entries"); + if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, + "Removing app " + app + " from history with " + i + " entries"); 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 (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, + "Record #" + i + " " + r + ": app=" + r.app); if (r.app == app) { - boolean remove; + if (r.visible) { + hasVisibleActivities = true; + } + final boolean remove; if ((!r.haveState && !r.stateNotNeeded) || r.finishing) { // Don't currently have state for the activity, or // it is finishing -- always remove it. @@ -3397,16 +3544,11 @@ final class ActivityStack { mService.updateUsageStats(r, false); } } - removeActivityFromHistoryLocked(r, "appDied"); - } else { // We have the current state for this activity, so // it can be restarted later when needed. - if (localLOGV) Slog.v( + if (DEBUG_ALL) 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; @@ -3417,8 +3559,10 @@ final class ActivityStack { r.icicle = null; } } - cleanUpActivityLocked(r, true, true); + if (remove) { + removeActivityFromHistoryLocked(r, "appDied"); + } } } } @@ -3455,27 +3599,25 @@ final class ActivityStack { for (int taskNdx = top; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); if (task.taskType == homeStackTaskType) { - if (DEBUG_TASKS || DEBUG_STACK) - Slog.d(TAG, "moveHomeStackTaskToTop: moving " + task); + if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG_STACK, + "moveHomeStackTaskToTop: moving " + task); mTaskHistory.remove(taskNdx); mTaskHistory.add(top, task); updateTaskMovement(task, true); - mWindowManager.moveTaskToTop(task.taskId); return; } } } - final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord source, Bundle options, + final void moveTaskToFrontLocked(TaskRecord tr, boolean noAnimation, Bundle options, String reason) { - if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr); final int numTasks = mTaskHistory.size(); final int index = mTaskHistory.indexOf(tr); if (numTasks == 0 || index < 0) { // nothing to do! - if (source != null && - (source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + if (noAnimation) { ActivityOptions.abort(options); } else { updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options); @@ -3485,14 +3627,15 @@ final class ActivityStack { // Shift all activities with this task up to the top // of the stack, keeping them in the same internal order. - insertTaskAtTop(tr); - moveToFront(reason); + insertTaskAtTop(tr, null); - if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to front transition: task=" + tr); - if (source != null && - (source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + // Set focus to the top running activity of this stack. + ActivityRecord r = topRunningActivityLocked(null); + mService.setFocusedActivityLocked(r, reason); + + if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to front transition: task=" + tr); + if (noAnimation) { mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); - ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); } @@ -3528,8 +3671,7 @@ final class ActivityStack { } Slog.i(TAG, "moveTaskToBack: " + tr); - - mStackSupervisor.endLockTaskModeIfTaskEnding(tr); + mStackSupervisor.removeLockedTaskLocked(tr); // 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 @@ -3554,9 +3696,17 @@ final class ActivityStack { } } - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to back transition: task=" + taskId); + if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId); + boolean prevIsHome = false; + if (tr.isOverHomeStack()) { + final TaskRecord nextTask = getNextTask(tr); + if (nextTask != null) { + nextTask.setTaskToReturnTo(tr.getTaskToReturnTo()); + } else { + prevIsHome = true; + } + } mTaskHistory.remove(tr); mTaskHistory.add(0, tr); updateTaskMovement(tr, false); @@ -3583,7 +3733,8 @@ final class ActivityStack { } final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null; - if (task == tr && tr.isOverHomeStack() || numTasks <= 1 && isOnHomeDisplay()) { + if (prevIsHome || task == tr && tr.isOverHomeStack() + || numTasks <= 1 && isOnHomeDisplay()) { if (!mService.mBooting && !mService.mBooted) { // Not ready yet! return false; @@ -3618,26 +3769,46 @@ final class ActivityStack { final boolean ensureActivityConfigurationLocked(ActivityRecord r, int globalChanges) { if (mConfigWillChange) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Skipping config check (will change): " + r); return true; } - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Ensuring correct configuration: " + r); + // Make sure the current stack override configuration is supported by the top task + // before continuing. + final TaskRecord topTask = topTask(); + if (topTask != null && ((topTask.mResizeable && mForcedFullscreen) + || (!topTask.mResizeable && !mFullscreen))) { + final boolean prevFullscreen = mFullscreen; + final Configuration newOverrideConfig = + mWindowManager.forceStackToFullscreen(mStackId, !topTask.mResizeable); + updateOverrideConfiguration(newOverrideConfig); + mForcedFullscreen = !prevFullscreen && mFullscreen; + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, + "Updated stack config to support task=" + topTask + + " resizeable=" + topTask.mResizeable + + " mForcedFullscreen=" + mForcedFullscreen + + " prevFullscreen=" + prevFullscreen + + " mFullscreen=" + mFullscreen); + } + // Short circuit: if the two configurations are the exact same // object (the common case), then there is nothing to do. Configuration newConfig = mService.mConfiguration; - if (r.configuration == newConfig && !r.forceNewConfig) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (r.configuration == newConfig + && r.stackConfigOverride == mOverrideConfig + && !r.forceNewConfig) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "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, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Configuration doesn't matter in finishing " + r); r.stopFreezingScreenLocked(false); return true; @@ -3645,16 +3816,33 @@ final class ActivityStack { // 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; + final Configuration oldConfig = r.configuration; + final Configuration oldStackOverride = r.stackConfigOverride; r.configuration = newConfig; + r.stackConfigOverride = mOverrideConfig; // Determine what has changed. May be nothing, if this is a config // that has come back from the app after going idle. In that case // we just want to leave the official config object now in the // activity and do nothing else. - final int changes = oldConfig.diff(newConfig); + int stackChanges = oldStackOverride.diff(mOverrideConfig); + if (stackChanges == 0) { + // {@link Configuration#diff} doesn't catch changes from unset values. + // Check for changes we care about. + if (oldStackOverride.orientation != mOverrideConfig.orientation) { + stackChanges |= ActivityInfo.CONFIG_ORIENTATION; + } + if (oldStackOverride.screenHeightDp != mOverrideConfig.screenHeightDp + || oldStackOverride.screenWidthDp != mOverrideConfig.screenWidthDp) { + stackChanges |= ActivityInfo.CONFIG_SCREEN_SIZE; + } + if (oldStackOverride.smallestScreenWidthDp != mOverrideConfig.smallestScreenWidthDp) { + stackChanges |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; + } + } + final int changes = oldConfig.diff(newConfig) | stackChanges; if (changes == 0 && !r.forceNewConfig) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Configuration no differences in " + r); return true; } @@ -3662,7 +3850,7 @@ final class ActivityStack { // If the activity isn't currently running, just leave the new // configuration and it will pick that up next time it starts. if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Configuration doesn't matter not running " + r); r.stopFreezingScreenLocked(false); r.forceNewConfig = false; @@ -3670,26 +3858,25 @@ final class ActivityStack { } // 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" - + Integer.toHexString(changes) + ", handles=0x" - + Integer.toHexString(r.info.getRealConfigChanged()) - + ", newConfig=" + newConfig); - } + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, + "Checking to restart " + r.info.name + ": changed=0x" + + Integer.toHexString(changes) + ", handles=0x" + + Integer.toHexString(r.info.getRealConfigChanged()) + ", newConfig=" + newConfig); + if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) { // Aha, the activity isn't handling the change, so DIE DIE DIE. r.configChangeFlags |= changes; r.startFreezingScreenLocked(r.app, globalChanges); r.forceNewConfig = false; if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is destroying non-running " + r); destroyActivityLocked(r, true, "config"); } else if (r.state == ActivityState.PAUSING) { // A little annoying: we are waiting for this activity to // finish pausing. Let's not do anything now, but just // flag that it needs to be restarted when done pausing. - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is skipping already pausing " + r); r.configDestroy = true; return true; @@ -3698,12 +3885,12 @@ final class ActivityStack { // and we need to restart the top, resumed activity. // Instead of doing the normal handshaking, just say // "restart!". - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is relaunching resumed " + r); relaunchActivityLocked(r, r.configChangeFlags, true); r.configChangeFlags = 0; } else { - if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is relaunching non-resumed " + r); relaunchActivityLocked(r, r.configChangeFlags, false); r.configChangeFlags = 0; @@ -3714,15 +3901,15 @@ final class ActivityStack { 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 - // process when they happen so it can just use whatever configuration - // it last got. + // Default case: the activity can handle this new configuration, so hand it over. + // NOTE: We only forward the stack override configuration as the system level configuration + // changes is always sent to all processes when they happen so it can just use whatever + // system level configuration it last got. if (r.app != null && r.app.thread != null) { try { - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r); - r.app.thread.scheduleActivityConfigurationChanged(r.appToken); + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending new config to " + r); + r.app.thread.scheduleActivityConfigurationChanged( + r.appToken, new Configuration(mOverrideConfig)); } catch (RemoteException e) { // If process died, whatever. } @@ -3732,16 +3919,15 @@ final class ActivityStack { return true; } - private boolean relaunchActivityLocked(ActivityRecord r, - int changes, boolean andResume) { + private boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume) { List<ResultInfo> results = null; List<ReferrerIntent> newIntents = null; if (andResume) { results = r.results; newIntents = r.newIntents; } - if (DEBUG_SWITCH) Slog.v(TAG, "Relaunching: " + r - + " with results=" + results + " newIntents=" + newIntents + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, + "Relaunching: " + r + " with results=" + results + " newIntents=" + newIntents + " andResume=" + andResume); EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY : EventLogTags.AM_RELAUNCH_ACTIVITY, r.userId, System.identityHashCode(r), @@ -3752,17 +3938,17 @@ final class ActivityStack { mStackSupervisor.removeChildActivityContainers(r); try { - if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG, - (andResume ? "Relaunching to RESUMED " : "Relaunching to PAUSED ") - + r); + if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, + "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + r); r.forceNewConfig = false; - r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, - changes, !andResume, new Configuration(mService.mConfiguration)); + r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes, + !andResume, new Configuration(mService.mConfiguration), + new Configuration(mOverrideConfig)); // Note: don't need to call pauseIfSleepingLocked() here, because // the caller will only pass in 'andResume' if this activity is // currently resumed, which implies we aren't sleeping. } catch (RemoteException e) { - if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG, "Relaunch failed", e); + if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e); } if (andResume) { @@ -3790,7 +3976,7 @@ final class ActivityStack { } } } - final ActivityRecord r = ActivityRecord.forToken(token); + final ActivityRecord r = ActivityRecord.forTokenLocked(token); if (r == null) { return false; } @@ -3842,7 +4028,7 @@ final class ActivityStack { } } didSomething = true; - Slog.i(TAG, " Force finishing activity 3 " + r); + Slog.i(TAG, " Force finishing activity " + r); if (samePackage) { if (r.app != null) { r.app.removed = true; @@ -3863,21 +4049,28 @@ final class ActivityStack { } void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) { + boolean focusedStack = mStackSupervisor.getFocusedStack() == this; + boolean topTask = true; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); + if (task.getTopActivity() == null) { + continue; + } ActivityRecord r = null; ActivityRecord top = null; + ActivityRecord tmp; int numActivities = 0; int numRunning = 0; final ArrayList<ActivityRecord> activities = task.mActivities; - if (activities.isEmpty()) { - continue; - } if (!allowed && !task.isHomeTask() && task.effectiveUid != callingUid) { continue; } for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { - r = activities.get(activityNdx); + tmp = activities.get(activityNdx); + if (tmp.finishing) { + continue; + } + r = tmp; // Initialize state for next task if needed. if (top == null || (top.state == ActivityState.INITIALIZING)) { @@ -3891,7 +4084,7 @@ final class ActivityStack { numRunning++; } - if (localLOGV) Slog.v( + if (DEBUG_ALL) Slog.v( TAG, r.intent.getComponent().flattenToShortString() + ": task=" + r.task); } @@ -3901,22 +4094,25 @@ final class ActivityStack { ci.baseActivity = r.intent.getComponent(); ci.topActivity = top.intent.getComponent(); ci.lastActiveTime = task.lastActiveTime; + if (focusedStack && topTask) { + // Give the latest time to ensure foreground task can be sorted + // at the first, because lastActiveTime of creating task is 0. + ci.lastActiveTime = System.currentTimeMillis(); + topTask = false; + } if (top.task != null) { ci.description = top.task.lastDescription; } ci.numActivities = numActivities; ci.numRunning = numRunning; - //System.out.println( - // "#" + maxNum + ": " + " descr=" + ci.description); list.add(ci); } } public void unhandledBackLocked() { final int top = mTaskHistory.size() - 1; - if (DEBUG_SWITCH) Slog.d( - TAG, "Performing unhandledBack(): top activity at " + top); + if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Performing unhandledBack(): top activity at " + top); if (top >= 0) { final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities; int activityTop = activities.size() - 1; @@ -3934,7 +4130,7 @@ final class ActivityStack { */ boolean handleAppDiedLocked(ProcessRecord app) { if (mPausingActivity != null && mPausingActivity.app == app) { - if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG, + if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG_PAUSE, "App died while pausing: " + mPausingActivity); mPausingActivity = null; } @@ -3952,7 +4148,7 @@ final class ActivityStack { 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 4 " + Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); // Force the destroy to skip right to removal. r.app = null; @@ -4033,8 +4229,22 @@ final class ActivityStack { } void removeTask(TaskRecord task, String reason) { - mStackSupervisor.endLockTaskModeIfTaskEnding(task); - mWindowManager.removeTask(task.taskId); + removeTask(task, reason, true /* notMoving */); + } + + /** + * Removes the input task from this stack. + * @param task to remove. + * @param reason for removal. + * @param notMoving task to another stack. In the case we are moving we don't want to perform + * some operations on the task like removing it from window manager or recents. + */ + void removeTask(TaskRecord task, String reason, boolean notMoving) { + if (notMoving) { + mStackSupervisor.removeLockedTaskLocked(task); + mWindowManager.removeTask(task.taskId); + } + final ActivityRecord r = mResumedActivity; if (r != null && r.task == task) { mResumedActivity = null; @@ -4051,7 +4261,7 @@ final class ActivityStack { mTaskHistory.remove(task); updateTaskMovement(task, true); - if (task.mActivities.isEmpty()) { + if (notMoving && task.mActivities.isEmpty()) { final boolean isVoiceSession = task.voiceSession != null; if (isVoiceSession) { try { @@ -4062,22 +4272,30 @@ final class ActivityStack { if (task.autoRemoveFromRecents() || isVoiceSession) { // Task creator asked to remove this when done, or this task was a voice // interaction, so it should not remain on the recent tasks list. - mService.mRecentTasks.remove(task); + mRecentTasks.remove(task); task.removedFromRecents(); } } if (mTaskHistory.isEmpty()) { - if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this); + if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this); + final boolean notHomeStack = !isHomeStack(); if (isOnHomeDisplay()) { - mStackSupervisor.moveHomeStack(!isHomeStack(), reason + " leftTaskHistoryEmpty"); + String myReason = reason + " leftTaskHistoryEmpty"; + if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) { + mStackSupervisor.moveHomeStack(notHomeStack, myReason); + } } if (mStacks != null) { mStacks.remove(this); mStacks.add(0, this); } - mActivityContainer.onTaskListEmptyLocked(); + if (notHomeStack) { + mActivityContainer.onTaskListEmptyLocked(); + } } + + task.stack = null; } TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, @@ -4090,13 +4308,13 @@ final class ActivityStack { } ArrayList<TaskRecord> getAllTasks() { - return new ArrayList<TaskRecord>(mTaskHistory); + return new ArrayList<>(mTaskHistory); } void addTask(final TaskRecord task, final boolean toTop, boolean moving) { task.stack = this; if (toTop) { - insertTaskAtTop(task); + insertTaskAtTop(task, null); } else { mTaskHistory.add(0, task); updateTaskMovement(task, false); @@ -4118,4 +4336,20 @@ final class ActivityStack { return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this)) + " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}"; } + + boolean updateOverrideConfiguration(Configuration newConfig) { + Configuration oldConfig = mOverrideConfig; + mOverrideConfig = (newConfig == null) ? Configuration.EMPTY : newConfig; + // We override the configuration only when the stack's dimensions are different from + // the display. In this manner, we know that if the override configuration is empty, + // the stack is necessarily full screen. + mFullscreen = Configuration.EMPTY.equals(mOverrideConfig); + return !mOverrideConfig.equals(oldConfig); + } + + void onLockTaskPackagesUpdatedLocked() { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + mTaskHistory.get(taskNdx).setLockTaskAuth(); + } + } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 32787d8..8c98f9f 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -17,24 +17,26 @@ package com.android.server.am; import static android.Manifest.permission.START_ANY_ACTIVITY; +import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; +import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; +import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; +import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; 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_RECENTS; -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.ActivityManagerDebugConfig.*; import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG; -import static com.android.server.am.ActivityManagerService.TAG; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; +import static com.android.server.am.ActivityStack.ActivityState.*; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE; +import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED; import android.app.Activity; import android.app.ActivityManager; @@ -63,12 +65,14 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.graphics.Point; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.VirtualDisplay; import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -82,7 +86,9 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.TransactionTooLargeException; import android.os.UserHandle; +import android.os.WorkSource; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.voice.IVoiceInteractionSession; @@ -114,7 +120,18 @@ import java.util.ArrayList; import java.util.List; public final class ActivityStackSupervisor implements DisplayListener { - static final boolean DEBUG = ActivityManagerService.DEBUG || false; + private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM; + private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; + private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; + private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE; + private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; + private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; + private static final String TAG_STACK = TAG + POSTFIX_STACK; + private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; + private static final String TAG_TASKS = TAG + POSTFIX_TASKS; + private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING; + + static final boolean DEBUG = DEBUG_ALL || false; static final boolean DEBUG_ADD_REMOVE = DEBUG || false; static final boolean DEBUG_APP = DEBUG || false; static final boolean DEBUG_CONTAINERS = DEBUG || false; @@ -148,8 +165,8 @@ public final class ActivityStackSupervisor implements DisplayListener { static final int LOCK_TASK_START_MSG = FIRST_SUPERVISOR_STACK_MSG + 9; static final int LOCK_TASK_END_MSG = FIRST_SUPERVISOR_STACK_MSG + 10; static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11; - static final int CONTAINER_TASK_LIST_EMPTY_TIMEOUT = FIRST_SUPERVISOR_STACK_MSG + 12; - static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 13; + static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12; + static final int SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG = FIRST_SUPERVISOR_STACK_MSG + 13; private final static String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay"; @@ -166,6 +183,8 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityManagerService mService; + private final RecentTasks mRecentTasks; + final ActivityStackSupervisorHandler mHandler; /** Short cut */ @@ -196,32 +215,30 @@ public final class ActivityStackSupervisor implements DisplayListener { /** 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>(); + final ArrayList<ActivityRecord> mWaitingVisibleActivities = new ArrayList<>(); /** List of processes waiting to find out about the next visible activity. */ - final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible = - new ArrayList<IActivityManager.WaitResult>(); + final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible = new ArrayList<>(); /** List of processes waiting to find out about the next launched activity. */ - final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched = - new ArrayList<IActivityManager.WaitResult>(); + final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched = new ArrayList<>(); /** 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>(); + final ArrayList<ActivityRecord> mStoppingActivities = new ArrayList<>(); /** 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>(); + final ArrayList<ActivityRecord> mFinishingActivities = new ArrayList<>(); /** List of activities that are in the process of going to sleep. */ - final ArrayList<ActivityRecord> mGoingToSleepActivities = new ArrayList<ActivityRecord>(); + final ArrayList<ActivityRecord> mGoingToSleepActivities = new ArrayList<>(); /** Used on user changes */ - final ArrayList<UserStartedState> mStartingUsers = new ArrayList<UserStartedState>(); + final ArrayList<UserStartedState> mStartingUsers = new ArrayList<>(); /** Used to queue up any background users being started */ - final ArrayList<UserStartedState> mStartingBackgroundUsers = new ArrayList<UserStartedState>(); + final ArrayList<UserStartedState> mStartingBackgroundUsers = new ArrayList<>(); /** Set to indicate whether to issue an onUserLeaving callback when a newly launched activity * is being brought in front of us. */ @@ -254,27 +271,28 @@ public final class ActivityStackSupervisor implements DisplayListener { // TODO: Add listener for removal of references. /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */ - private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<ActivityContainer>(); + private SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>(); /** Mapping from displayId to display current state */ - private final SparseArray<ActivityDisplay> mActivityDisplays = - new SparseArray<ActivityDisplay>(); + private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>(); InputManagerInternal mInputManagerInternal; - /** If non-null then the task specified remains in front and no other tasks may be started - * until the task exits or #stopLockTaskMode() is called. */ - TaskRecord mLockTaskModeTask; - /** Whether lock task has been entered by an authorized app and cannot - * be exited. */ - private boolean mLockTaskIsLocked; + /** The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks + * may be finished until there is only one entry left. If this is empty the system is not + * in lockTask mode. */ + ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>(); + /** Store the current lock task mode. Possible values: + * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED}, + * {@link ActivityManager#LOCK_TASK_MODE_PINNED} + */ + private int mLockTaskModeState; /** * Notifies the user when entering/exiting lock-task. */ private LockTaskNotify mLockTaskNotify; - final ArrayList<PendingActivityLaunch> mPendingActivityLaunches - = new ArrayList<PendingActivityLaunch>(); + final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>(); /** Used to keep resumeTopActivityLocked() from being entered recursively */ boolean inResumeTopActivity; @@ -298,8 +316,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - public ActivityStackSupervisor(ActivityManagerService service) { + public ActivityStackSupervisor(ActivityManagerService service, RecentTasks recentTasks) { mService = service; + mRecentTasks = recentTasks; mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper()); } @@ -310,8 +329,7 @@ public final class ActivityStackSupervisor implements DisplayListener { void initPowerManagement() { PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE); mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); - mLaunchingActivity = - pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); + mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*launch*"); mLaunchingActivity.setReferenceCounted(false); } @@ -372,7 +390,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } void notifyActivityDrawnForKeyguard() { - if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(""); + if (DEBUG_LOCKSCREEN) mService.logLockScreen(""); mWindowManager.notifyActivityDrawnForKeyguard(); } @@ -384,36 +402,47 @@ public final class ActivityStackSupervisor implements DisplayListener { return mLastFocusedStack; } - // TODO: Split into two methods isFrontStack for any visible stack and isFrontmostStack for the - // top of all visible stacks. + /** Top of all visible stacks is/should always be equal to the focused stack. + * Use {@link ActivityStack#isStackVisibleLocked} to determine if a specific + * stack is visible or not. */ boolean isFrontStack(ActivityStack stack) { + if (stack == null) { + return false; + } + final ActivityRecord parent = stack.mActivityContainer.mParentActivity; if (parent != null) { stack = parent.task.stack; } - ArrayList<ActivityStack> stacks = stack.mStacks; - if (stacks != null && !stacks.isEmpty()) { - return stack == stacks.get(stacks.size() - 1); - } - return false; + return stack == mFocusedStack; } void moveHomeStack(boolean toFront, String reason) { + moveHomeStack(toFront, reason, null); + } + + void moveHomeStack(boolean toFront, String reason, ActivityStack lastFocusedStack) { ArrayList<ActivityStack> stacks = mHomeStack.mStacks; final int topNdx = stacks.size() - 1; if (topNdx <= 0) { return; } - ActivityStack topStack = stacks.get(topNdx); - final boolean homeInFront = topStack == mHomeStack; - if (homeInFront != toFront) { - mLastFocusedStack = topStack; + + // The home stack should either be at the top or bottom of the stack list. + if ((toFront && (stacks.get(topNdx) != mHomeStack)) + || (!toFront && (stacks.get(0) != mHomeStack))) { + if (DEBUG_STACK) Slog.d(TAG_STACK, "moveHomeTask: topStack old=" + + ((lastFocusedStack != null) ? lastFocusedStack : stacks.get(topNdx)) + + " new=" + mFocusedStack); stacks.remove(mHomeStack); stacks.add(toFront ? topNdx : 0, mHomeStack); - mFocusedStack = stacks.get(topNdx); - if (DEBUG_STACK) Slog.d(TAG, "moveHomeTask: topStack old=" + topStack + " new=" - + mFocusedStack); } + + if (lastFocusedStack != null) { + mLastFocusedStack = lastFocusedStack; + } + mFocusedStack = stacks.get(topNdx); + EventLog.writeEvent(EventLogTags.AM_HOME_STACK_MOVED, mCurrentUser, toFront ? 1 : 0, stacks.get(topNdx).getStackId(), mFocusedStack == null ? -1 : mFocusedStack.getStackId(), reason); @@ -426,13 +455,21 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - void moveHomeStackTaskToTop(int homeStackTaskType, String reason) { + /** Returns true if the focus activity was adjusted to the home stack top activity. */ + boolean moveHomeStackTaskToTop(int homeStackTaskType, String reason) { if (homeStackTaskType == RECENTS_ACTIVITY_TYPE) { mWindowManager.showRecentApps(); - return; + return false; } - moveHomeStack(true, reason); + mHomeStack.moveHomeStackTaskToTop(homeStackTaskType); + + final ActivityRecord top = getHomeActivity(); + if (top == null) { + return false; + } + mService.setFocusedActivityLocked(top, reason); + return true; } boolean resumeHomeStackTask(int homeStackTaskType, ActivityRecord prev, String reason) { @@ -445,14 +482,14 @@ public final class ActivityStackSupervisor implements DisplayListener { mWindowManager.showRecentApps(); return false; } - moveHomeStackTaskToTop(homeStackTaskType, reason); + if (prev != null) { prev.task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE); } - ActivityRecord r = mHomeStack.topRunningActivityLocked(null); - // if (r != null && (r.isHomeActivity() || r.isRecentsActivity())) { - if (r != null && r.isHomeActivity()) { + mHomeStack.moveHomeStackTaskToTop(homeStackTaskType); + ActivityRecord r = getHomeActivity(); + if (r != null) { mService.setFocusedActivityLocked(r, reason); return resumeTopActivitiesLocked(mHomeStack, prev, null); } @@ -460,6 +497,16 @@ public final class ActivityStackSupervisor implements DisplayListener { } TaskRecord anyTaskForIdLocked(int id) { + return anyTaskForIdLocked(id, true); + } + + /** + * Returns a {@link TaskRecord} for the input id if available. Null otherwise. + * @param id Id of the task we would like returned. + * @param restoreFromRecents If the id was not in the active list, but was found in recents, + * restore the task from recents to the active list. + */ + TaskRecord anyTaskForIdLocked(int id, boolean restoreFromRecents) { int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; @@ -473,18 +520,23 @@ public final class ActivityStackSupervisor implements DisplayListener { } // Don't give up! Look in recents. - if (DEBUG_RECENTS) Slog.v(TAG, "Looking for task id=" + id + " in recents"); - TaskRecord task = mService.recentTaskForIdLocked(id); + if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents"); + TaskRecord task = mRecentTasks.taskForIdLocked(id); if (task == null) { - if (DEBUG_RECENTS) Slog.d(TAG, "\tDidn't find task id=" + id + " in recents"); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "\tDidn't find task id=" + id + " in recents"); return null; } + if (!restoreFromRecents) { + return task; + } + if (!restoreRecentTaskLocked(task)) { - if (DEBUG_RECENTS) Slog.w(TAG, "Couldn't restore task id=" + id + " found in recents"); + if (DEBUG_RECENTS) Slog.w(TAG_RECENTS, + "Couldn't restore task id=" + id + " found in recents"); return null; } - if (DEBUG_RECENTS) Slog.w(TAG, "Restored task id=" + id + " from in recents"); + if (DEBUG_RECENTS) Slog.w(TAG_RECENTS, "Restored task id=" + id + " from in recents"); return task; } @@ -514,12 +566,12 @@ public final class ActivityStackSupervisor implements DisplayListener { if (mCurTaskId <= 0) { mCurTaskId = 1; } - } while (anyTaskForIdLocked(mCurTaskId) != null); + } while (anyTaskForIdLocked(mCurTaskId, false) != null); return mCurTaskId; } ActivityRecord resumedAppLocked() { - ActivityStack stack = getFocusedStack(); + ActivityStack stack = mFocusedStack; if (stack == null) { return null; } @@ -592,14 +644,14 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityStack stack = stacks.get(stackNdx); if (isFrontStack(stack)) { final ActivityRecord r = stack.mResumedActivity; - if (r != null && r.state != ActivityState.RESUMED) { + if (r != null && r.state != RESUMED) { return false; } } } } // TODO: Not sure if this should check if all Paused are complete too. - if (DEBUG_STACK) Slog.d(TAG, + if (DEBUG_STACK) Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from=" + mLastFocusedStack + " to=" + mFocusedStack); mLastFocusedStack = mFocusedStack; @@ -607,17 +659,21 @@ public final class ActivityStackSupervisor implements DisplayListener { } boolean allResumedActivitiesVisible() { + boolean foundResumed = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); final ActivityRecord r = stack.mResumedActivity; - if (r != null && (!r.nowVisible || r.waitingVisible)) { - return false; + if (r != null) { + if (!r.nowVisible || mWaitingVisibleActivities.contains(r)) { + return false; + } + foundResumed = true; } } } - return true; + return foundResumed; } /** @@ -649,9 +705,7 @@ public final class ActivityStackSupervisor implements DisplayListener { for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); final ActivityRecord r = stack.mPausingActivity; - if (r != null && r.state != ActivityState.PAUSED - && r.state != ActivityState.STOPPED - && r.state != ActivityState.STOPPING) { + if (r != null && r.state != PAUSED && r.state != STOPPED && r.state != STOPPING) { if (DEBUG_STATES) { Slog.d(TAG, "allPausedActivitiesComplete: r=" + r + " state=" + r.state); pausing = false; @@ -723,7 +777,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } ActivityRecord topRunningActivityLocked() { - final ActivityStack focusedStack = getFocusedStack(); + final ActivityStack focusedStack = mFocusedStack; ActivityRecord r = focusedStack.topRunningActivityLocked(null); if (r != null) { return r; @@ -752,7 +806,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<RunningTaskInfo>(); + ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>(); runningTaskLists.add(stackTaskList); stack.getTasksLocked(stackTaskList, callingUid, allowed); } @@ -850,11 +904,16 @@ public final class ActivityStackSupervisor implements DisplayListener { intent = new Intent(intent); // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, - profilerInfo, userId); + ActivityInfo aInfo = + resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId); ActivityContainer container = (ActivityContainer)iContainer; synchronized (mService) { + if (container != null && container.mParentActivity != null && + container.mParentActivity.state != RESUMED) { + // Cannot start a child activity if the parent is not resumed. + return ActivityManager.START_CANCELED; + } final int realCallingPid = Binder.getCallingPid(); final int realCallingUid = Binder.getCallingUid(); int callingPid; @@ -869,19 +928,19 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityStack stack; if (container == null || container.mStack.isOnHomeDisplay()) { - stack = getFocusedStack(); + stack = mFocusedStack; } else { stack = container.mStack; } - stack.mConfigWillChange = config != null - && mService.mConfiguration.diff(config) != 0; - if (DEBUG_CONFIGURATION) Slog.v(TAG, + stack.mConfigWillChange = config != null && mService.mConfiguration.diff(config) != 0; + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "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) { + (aInfo.applicationInfo.privateFlags + &ApplicationInfo.PRIVATE_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)) { @@ -964,7 +1023,7 @@ public final class ActivityStackSupervisor implements DisplayListener { mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); stack.mConfigWillChange = false; - if (DEBUG_CONFIGURATION) Slog.v(TAG, + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Updating to new configuration after starting activity."); mService.updateConfigurationLocked(config, null, false, false); } @@ -981,7 +1040,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } while (!outResult.timeout && outResult.who == null); } else if (res == ActivityManager.START_TASK_TO_FRONT) { ActivityRecord r = stack.topRunningActivityLocked(null); - if (r.nowVisible && r.state == ActivityState.RESUMED) { + if (r.nowVisible && r.state == RESUMED) { outResult.timeout = false; outResult.who = new ComponentName(r.info.packageName, r.info.name); outResult.totalTime = 0; @@ -1052,8 +1111,8 @@ public final class ActivityStackSupervisor implements DisplayListener { aInfo = mService.getActivityInfoForUser(aInfo, userId); if (aInfo != null && - (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_CANT_SAVE_STATE) - != 0) { + (aInfo.applicationInfo.privateFlags + & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { throw new IllegalArgumentException( "FLAG_CANT_SAVE_STATE not supported here"); } @@ -1086,12 +1145,13 @@ public final class ActivityStackSupervisor implements DisplayListener { 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); + if (andResume) { + r.startFreezingScreenLocked(app, 0); + mWindowManager.setAppVisibility(r.appToken, true); - // schedule launch ticks to collect information about slow apps. - r.startLaunchTickingLocked(); + // 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 @@ -1111,7 +1171,7 @@ public final class ActivityStackSupervisor implements DisplayListener { r.launchCount++; r.lastLaunchTime = SystemClock.uptimeMillis(); - if (localLOGV) Slog.v(TAG, "Launching: " + r); + if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r); int idx = app.activities.indexOf(r); if (idx < 0) { @@ -1120,7 +1180,12 @@ public final class ActivityStackSupervisor implements DisplayListener { mService.updateLruProcessLocked(app, true, null); mService.updateOomAdjLocked(); - final ActivityStack stack = r.task.stack; + final TaskRecord task = r.task; + if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) { + setLockTaskModeLocked(task, LOCK_TASK_MODE_LOCKED, "lockTaskLaunchMode attribute"); + } + + final ActivityStack stack = task.stack; try { if (app.thread == null) { throw new RemoteException(); @@ -1131,60 +1196,62 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 (DEBUG_SWITCH) Slog.v(TAG_SWITCH, + "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); + 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.mHomeProcess = 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; + ProfilerInfo profilerInfo = null; 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; - } - } - 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) { + final String profileFile = mService.mProfileFile; + if (profileFile != null) { + ParcelFileDescriptor profileFd = mService.mProfileFd; + if (profileFd != null) { + try { + profileFd = profileFd.dup(); + } catch (IOException e) { + if (profileFd != null) { + try { + profileFd.close(); + } catch (IOException o) { + } + profileFd = null; + } + } } - profileFd = null; + + profilerInfo = new ProfilerInfo(profileFile, profileFd, + mService.mSamplingInterval, mService.mAutoStopProfiler); } } } - ProfilerInfo profilerInfo = profileFile != null - ? new ProfilerInfo(profileFile, profileFd, mService.mSamplingInterval, - mService.mAutoStopProfiler) : null; - app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP); + if (andResume) { + app.hasShownUi = true; + app.pendingUiClean = true; + } + app.forceProcessStateUpTo(mService.mTopProcessState); app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration), - r.compat, r.launchedFromPackage, r.task.voiceInteractor, app.repProcState, - r.icicle, r.persistentState, results, newIntents, !andResume, - mService.isNextTransitionForward(), profilerInfo); + new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage, + task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results, + newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo); - if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + if ((app.info.privateFlags&ApplicationInfo.PRIVATE_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 @@ -1240,7 +1307,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // other state. if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (starting in stopped state)"); - r.state = ActivityState.STOPPED; + r.state = STOPPED; r.stopped = true; } @@ -1317,8 +1384,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; + 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 uid " + callingUid + " on display " + (container == null ? (mFocusedStack == null ? @@ -1331,8 +1399,8 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityRecord resultRecord = null; if (resultTo != null) { sourceRecord = isInAnyStackLocked(resultTo); - if (DEBUG_RESULTS) Slog.v( - TAG, "Will send result to " + resultTo + " " + sourceRecord); + if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, + "Will send result to " + resultTo + " " + sourceRecord); if (sourceRecord != null) { if (requestCode >= 0 && !sourceRecord.finishing) { resultRecord = sourceRecord; @@ -1342,7 +1410,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final int launchFlags = intent.getFlags(); - if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) { + 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) { @@ -1350,6 +1418,9 @@ public final class ActivityStackSupervisor implements DisplayListener { return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; } resultRecord = sourceRecord.resultTo; + if (resultRecord != null && !resultRecord.isInStackLocked()) { + resultRecord = null; + } resultWho = sourceRecord.resultWho; requestCode = sourceRecord.requestCode; sourceRecord.resultTo = null; @@ -1383,6 +1454,13 @@ public final class ActivityStackSupervisor implements DisplayListener { err = ActivityManager.START_CLASS_NOT_FOUND; } + if (err == ActivityManager.START_SUCCESS + && !isCurrentProfileLocked(userId) + && (aInfo.flags & FLAG_SHOW_FOR_ALL_USERS) == 0) { + // Trying to launch a background activity that doesn't show for all users. + err = ActivityManager.START_NOT_CURRENT_USER_ACTIVITY; + } + if (err == ActivityManager.START_SUCCESS && sourceRecord != null && sourceRecord.task.voiceSession != null) { // If this activity is being launched as part of a voice session, we need @@ -1486,7 +1564,7 @@ public final class ActivityStackSupervisor implements DisplayListener { outActivity[0] = r; } - final ActivityStack stack = getFocusedStack(); + final ActivityStack stack = mFocusedStack; if (voiceSession == null && (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, @@ -1525,25 +1603,28 @@ public final class ActivityStackSupervisor implements DisplayListener { return err; } - ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) { + ActivityStack computeStackFocus(ActivityRecord r, boolean newTask) { final TaskRecord task = r.task; // On leanback only devices we should keep all activities in the same stack. if (!mLeanbackOnlyDevice && (r.isApplicationActivity() || (task != null && task.isApplicationTask()))) { - if (task != null) { - final ActivityStack taskStack = task.stack; - if (taskStack.isOnHomeDisplay()) { - if (mFocusedStack != taskStack) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Setting " + - "focused stack to r=" + r + " task=" + task); - mFocusedStack = taskStack; + + ActivityStack stack; + + if (task != null && task.stack != null) { + stack = task.stack; + if (stack.isOnHomeDisplay()) { + if (mFocusedStack != stack) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Setting " + "focused stack to r=" + r + + " task=" + task); } else { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, - "adjustStackFocus: Focused stack already=" + mFocusedStack); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Focused stack already=" + mFocusedStack); } } - return taskStack; + return stack; } final ActivityContainer container = r.mInitialActivityContainer; @@ -1555,48 +1636,45 @@ public final class ActivityStackSupervisor implements DisplayListener { if (mFocusedStack != mHomeStack && (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, - "adjustStackFocus: Have a focused stack=" + mFocusedStack); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Have a focused stack=" + mFocusedStack); return mFocusedStack; } final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks; for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = homeDisplayStacks.get(stackNdx); + stack = homeDisplayStacks.get(stackNdx); if (!stack.isHomeStack()) { - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, - "adjustStackFocus: Setting focused stack=" + stack); - mFocusedStack = stack; - return mFocusedStack; + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, + "computeStackFocus: Setting focused stack=" + stack); + return stack; } } // Need to create an app stack for this user. - int stackId = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY); - if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r + - " stackId=" + stackId); - mFocusedStack = getStack(stackId); - return mFocusedStack; + stack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r=" + + r + " stackId=" + stack.mStackId); + return stack; } return mHomeStack; } - void setFocusedStack(ActivityRecord r, String reason) { - if (r != null) { - final TaskRecord task = r.task; - boolean isHomeActivity = !r.isApplicationActivity(); - if (!isHomeActivity && task != null) { - isHomeActivity = !task.isApplicationTask(); - } - if (!isHomeActivity && task != null) { - final ActivityRecord parent = task.stack.mActivityContainer.mParentActivity; - isHomeActivity = parent != null && parent.isHomeActivity(); - } - moveHomeStack(isHomeActivity, reason); + boolean setFocusedStack(ActivityRecord r, String reason) { + if (r == null) { + // Not sure what you are trying to do, but it is not going to work... + return false; + } + final TaskRecord task = r.task; + if (task == null || task.stack == null) { + Slog.w(TAG, "Can't set focus stack for r=" + r + " task=" + task); + return false; } + task.stack.moveToFront(reason); + return true; } - final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, + final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, Bundle options, TaskRecord inTask) { final Intent intent = r.intent; @@ -1643,7 +1721,8 @@ public final class ActivityStackSupervisor implements DisplayListener { && !launchSingleTask && !launchSingleInstance && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0; - if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 + && r.resultTo.task.stack != null) { // 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 @@ -1672,7 +1751,8 @@ public final class ActivityStackSupervisor implements DisplayListener { // 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 (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING, + "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 @@ -1691,7 +1771,7 @@ public final class ActivityStackSupervisor implements DisplayListener { if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { ActivityRecord checkedCaller = sourceRecord; if (checkedCaller == null) { - checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop); + checkedCaller = mFocusedStack.topRunningNonDelayedActivityLocked(notTop); } if (!checkedCaller.realActivity.equals(r.realActivity)) { // Caller is not the same as launcher, so always needed. @@ -1807,6 +1887,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityStack targetStack; intent.setFlags(launchFlags); + final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0; // We may want to try to place the new activity in to an existing task. We always // do this if the target activity is singleTask or singleInstance; we will also do @@ -1836,11 +1917,6 @@ public final class ActivityStackSupervisor implements DisplayListener { 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); - targetStack.moveToFront("intentActivityFound"); if (intentActivity.task.intent == null) { // This task was started because of movement of // the activity based on affinity... now that we @@ -1848,29 +1924,31 @@ public final class ActivityStackSupervisor implements DisplayListener { // base intent. intentActivity.task.setIntent(r); } + targetStack = intentActivity.task.stack; + targetStack.mLastPausedActivity = null; // 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); + final ActivityStack focusStack = getFocusedStack(); + ActivityRecord curTop = (focusStack == null) + ? null : focusStack.topRunningNonDelayedActivityLocked(notTop); boolean movedToFront = false; if (curTop != null && (curTop.task != intentActivity.task || - curTop.task != lastStack.topTask())) { + curTop.task != focusStack.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. + // We really do want to push this one into the user's face, right now. if (launchTaskBehind && sourceRecord != null) { intentActivity.setTaskToAffiliateWith(sourceRecord.task); } movedHome = true; - targetStack.moveTaskToFrontLocked(intentActivity.task, r, options, - "bringingFoundTaskToFront"); + targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation, + options, "bringingFoundTaskToFront"); + movedToFront = true; if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { @@ -1878,15 +1956,20 @@ public final class ActivityStackSupervisor implements DisplayListener { intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); } options = null; - movedToFront = true; } } + if (!movedToFront) { + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack + + " from " + intentActivity); + targetStack.moveToFront("intentActivityFound"); + } + // 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) { + 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 @@ -1904,16 +1987,15 @@ public final class ActivityStackSupervisor implements DisplayListener { } 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)) { + if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) + == (FLAG_ACTIVITY_NEW_TASK | 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); - } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 + } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0 || launchSingleInstance || launchSingleTask) { // In this situation we want to remove all activities // from the task up to the one being started. In most @@ -1933,15 +2015,22 @@ public final class ActivityStackSupervisor implements DisplayListener { r, top.task); top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); } 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. + // 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. + // Now pretend like this activity is being started by the top of its + // task, so it is put in the right place. sourceRecord = intentActivity; + TaskRecord task = sourceRecord.task; + if (task != null && task.stack == null) { + // Target stack got cleared when we all activities were removed + // above. Go ahead and reset it. + targetStack = computeStackFocus(sourceRecord, false /* newTask */); + targetStack.addTask( + task, !launchTaskBehind /* toTop */, false /* moving */); + } + } } else if (r.realActivity.equals(intentActivity.task.realActivity)) { // In this case the top activity on the task is the @@ -2014,7 +2103,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // 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(); + ActivityStack topStack = mFocusedStack; ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); if (top != null && r.resultTo == null) { if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { @@ -2044,7 +2133,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } } else { - if (r.resultTo != null) { + if (r.resultTo != null && r.resultTo.task.stack != null) { r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, r.requestCode, Activity.RESULT_CANCELED, null); } @@ -2061,30 +2150,29 @@ public final class ActivityStackSupervisor implements DisplayListener { // Should this be considered a new task? if (r.resultTo == null && inTask == null && !addingToTask && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - if (isLockTaskModeViolation(reuseTask)) { - Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); - return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; - } newTask = true; - targetStack = adjustStackFocus(r, newTask); - if (!launchTaskBehind) { - targetStack.moveToFront("startingNewTask"); - } + targetStack = computeStackFocus(r, newTask); + targetStack.moveToFront("startingNewTask"); + if (reuseTask == null) { r.setTask(targetStack.createTaskRecord(getNextTaskId(), newTaskInfo != null ? newTaskInfo : r.info, newTaskIntent != null ? newTaskIntent : intent, voiceSession, voiceInteractor, !launchTaskBehind /* toTop */), taskToAffiliate); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + - r.task); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, + "Starting new activity " + r + " in new task " + r.task); } else { r.setTask(reuseTask, taskToAffiliate); } + if (isLockTaskModeViolation(r.task)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } 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)) { + (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, so before starting // their own activity we will bring home to the front. r.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); @@ -2100,7 +2188,8 @@ public final class ActivityStackSupervisor implements DisplayListener { targetStack.moveToFront("sourceStackToFront"); final TaskRecord topTask = targetStack.topTask(); if (topTask != sourceTask) { - targetStack.moveTaskToFrontLocked(sourceTask, r, options, "sourceTaskToFront"); + targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options, + "sourceTaskToFront"); } if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { // In this case, we are adding the activity to an existing @@ -2143,7 +2232,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // to keep the new one in the same task as the one that is starting // it. r.setTask(sourceTask, null); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + " in existing task " + r.task + " from source " + sourceRecord); } else if (inTask != null) { @@ -2154,7 +2243,7 @@ public final class ActivityStackSupervisor implements DisplayListener { return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } targetStack = inTask.stack; - targetStack.moveTaskToFrontLocked(inTask, r, options, "inTaskToFront"); + targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, "inTaskToFront"); // Check whether we should actually launch the new activity in to the task, // or just reuse the current activity on top. @@ -2182,20 +2271,20 @@ public final class ActivityStackSupervisor implements DisplayListener { } r.setTask(inTask, null); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + " in explicit 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. - targetStack = adjustStackFocus(r, newTask); + targetStack = computeStackFocus(r, newTask); targetStack.moveToFront("addingToTopTask"); ActivityRecord prev = targetStack.topActivity(); r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, null, null, true), null); mWindowManager.moveTaskToTop(r.task.taskId); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + r + " in new guessed " + r.task); } @@ -2235,6 +2324,10 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + void setLaunchSource(int uid) { + mLaunchingActivity.setWorkSource(new WorkSource(uid)); + } + void acquireLaunchWakelock() { if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { throw new IllegalStateException("Calling must be system uid"); @@ -2267,7 +2360,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // Checked. final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, Configuration config) { - if (localLOGV) Slog.v(TAG, "Activity idle: " + token); + if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token); ArrayList<ActivityRecord> stops = null; ArrayList<ActivityRecord> finishes = null; @@ -2277,7 +2370,7 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean booting = false; boolean activityRemoved = false; - ActivityRecord r = ActivityRecord.forToken(token); + ActivityRecord r = ActivityRecord.forTokenLocked(token); if (r != null) { if (DEBUG_IDLE) Slog.d(TAG, "activityIdleInternalLocked: Callers=" + Debug.getCallers(4)); @@ -2325,13 +2418,13 @@ public final class ActivityStackSupervisor implements DisplayListener { // 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); + if ((NF = mFinishingActivities.size()) > 0) { + finishes = new ArrayList<>(mFinishingActivities); mFinishingActivities.clear(); } if (mStartingUsers.size() > 0) { - startingUsers = new ArrayList<UserStartedState>(mStartingUsers); + startingUsers = new ArrayList<>(mStartingUsers); mStartingUsers.clear(); } @@ -2340,10 +2433,12 @@ public final class ActivityStackSupervisor implements DisplayListener { 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); + if (stack != null) { + if (r.finishing) { + stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false); + } else { + stack.stopActivityLocked(r); + } } } @@ -2351,7 +2446,10 @@ public final class ActivityStackSupervisor implements DisplayListener { // 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, "finish-idle"); + final ActivityStack stack = r.task.stack; + if (stack != null) { + activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle"); + } } if (!booting) { @@ -2465,13 +2563,14 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target, Bundle targetOptions) { if (targetStack == null) { - targetStack = getFocusedStack(); + targetStack = mFocusedStack; } // Do targetStack first. boolean result = false; if (isFrontStack(targetStack)) { result = targetStack.resumeTopActivityLocked(target, targetOptions); } + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { @@ -2488,13 +2587,13 @@ public final class ActivityStackSupervisor implements DisplayListener { return result; } - void finishTopRunningActivityLocked(ProcessRecord app) { + void finishTopRunningActivityLocked(ProcessRecord app, String reason) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; final int numStacks = stacks.size(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { final ActivityStack stack = stacks.get(stackNdx); - stack.finishTopRunningActivityLocked(app); + stack.finishTopRunningActivityLocked(app, reason); } } } @@ -2519,9 +2618,14 @@ public final class ActivityStackSupervisor implements DisplayListener { // we'll just indicate that this task returns to the home task. task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); } - task.stack.moveTaskToFrontLocked(task, null, options, reason); - if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack=" - + task.stack); + if (task.stack == null) { + Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task=" + + task + " to front. Stack is null"); + return; + } + task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options, reason); + if (DEBUG_STACK) Slog.d(TAG_STACK, + "findTaskToMoveToFront: moved to front of stack=" + task.stack); } ActivityStack getStack(int stackId) { @@ -2565,7 +2669,7 @@ public final class ActivityStackSupervisor implements DisplayListener { return null; } - ActivityContainer createActivityContainer(ActivityRecord parentActivity, + ActivityContainer createVirtualActivityContainer(ActivityRecord parentActivity, IActivityContainerCallback callback) { ActivityContainer activityContainer = new VirtualActivityContainer(parentActivity, callback); @@ -2596,16 +2700,85 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - private int createStackOnDisplay(int stackId, int displayId) { + void resizeStackLocked(int stackId, Rect bounds) { + final ActivityStack stack = getStack(stackId); + if (stack == null) { + Slog.w(TAG, "resizeStack: stackId " + stackId + " not found."); + return; + } + + final ActivityRecord r = stack.topRunningActivityLocked(null); + if (r != null && !r.task.mResizeable) { + Slog.w(TAG, "resizeStack: top task " + r.task + " not resizeable."); + return; + } + + final Configuration overrideConfig = mWindowManager.resizeStack(stackId, bounds); + if (stack.updateOverrideConfiguration(overrideConfig)) { + if (r != null) { + final boolean updated = stack.ensureActivityConfigurationLocked(r, 0); + // And we need to make sure at this point that all other activities + // are made visible with the correct configuration. + ensureActivitiesVisibleLocked(r, 0); + if (!updated) { + resumeTopActivitiesLocked(stack, null, null); + } + } + } + } + + /** Makes sure the input task is in a stack with the specified bounds by either resizing the + * current task stack if it only has one entry, moving the task to a stack that matches the + * bounds, or creating a new stack with the required bounds. Also, makes the task resizeable.*/ + void resizeTaskLocked(TaskRecord task, Rect bounds) { + task.mResizeable = true; + final ActivityStack currentStack = task.stack; + if (currentStack.isHomeStack()) { + // Can't move task off the home stack. Sorry! + return; + } + + final int matchingStackId = mWindowManager.getStackIdWithBounds(bounds); + if (matchingStackId != -1) { + // There is already a stack with the right bounds! + if (currentStack != null && currentStack.mStackId == matchingStackId) { + // Nothing to do here. Already in the right stack... + return; + } + // Move task to stack with matching bounds. + moveTaskToStackLocked(task.taskId, matchingStackId, true); + return; + } + + if (currentStack != null && currentStack.numTasks() == 1) { + // Just resize the current stack since this is the task in it. + resizeStackLocked(currentStack.mStackId, bounds); + return; + } + + // Create new stack and move the task to it. + final int displayId = (currentStack != null && currentStack.mDisplayId != -1) + ? currentStack.mDisplayId : Display.DEFAULT_DISPLAY; + ActivityStack newStack = createStackOnDisplay(getNextStackId(), displayId); + + if (newStack == null) { + Slog.e(TAG, "resizeTaskLocked: Can't create stack for task=" + task); + return; + } + moveTaskToStackLocked(task.taskId, newStack.mStackId, true); + resizeStackLocked(newStack.mStackId, bounds); + } + + ActivityStack createStackOnDisplay(int stackId, int displayId) { ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); if (activityDisplay == null) { - return -1; + return null; } ActivityContainer activityContainer = new ActivityContainer(stackId); mActivityContainers.put(stackId, activityContainer); activityContainer.attachToDisplayLocked(activityDisplay); - return stackId; + return activityContainer.mStack; } int getNextStackId() { @@ -2631,7 +2804,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks; for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack tmpStack = homeDisplayStacks.get(stackNdx); - if (!tmpStack.isHomeStack()) { + if (!tmpStack.isHomeStack() && tmpStack.mFullscreen) { stack = tmpStack; break; } @@ -2642,29 +2815,29 @@ public final class ActivityStackSupervisor implements DisplayListener { // We couldn't find a stack to restore the task to. Possible if are restoring recents // before an application stack is created...Go ahead and create one on the default // display. - stack = getStack(createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY)); + stack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY); // Restore home stack to top. moveHomeStack(true, "restoreRecentTask"); - if (DEBUG_RECENTS) - Slog.v(TAG, "Created stack=" + stack + " for recents restoration."); + if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, + "Created stack=" + stack + " for recents restoration."); } if (stack == null) { // What does this mean??? Not sure how we would get here... - if (DEBUG_RECENTS) - Slog.v(TAG, "Unable to find/create stack to restore recent task=" + task); + if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, + "Unable to find/create stack to restore recent task=" + task); return false; } stack.addTask(task, false, false); - if (DEBUG_RECENTS) - Slog.v(TAG, "Added restored task=" + task + " to stack=" + stack); + if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, + "Added restored task=" + task + " to stack=" + stack); final ArrayList<ActivityRecord> activities = task.mActivities; for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = activities.get(activityNdx); mWindowManager.addAppToken(0, r.appToken, task.taskId, stack.mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, + (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind); } @@ -2674,6 +2847,7 @@ public final class ActivityStackSupervisor implements DisplayListener { void moveTaskToStackLocked(int taskId, int stackId, boolean toTop) { final TaskRecord task = anyTaskForIdLocked(taskId); if (task == null) { + Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId); return; } final ActivityStack stack = getStack(stackId); @@ -2681,25 +2855,27 @@ public final class ActivityStackSupervisor implements DisplayListener { Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId); return; } - task.stack.removeTask(task, "moveTaskToStack"); + mWindowManager.moveTaskToStack(taskId, stackId, toTop); + if (task.stack != null) { + task.stack.removeTask(task, "moveTaskToStack", false /* notMoving */); + } stack.addTask(task, toTop, true); - mWindowManager.addTask(taskId, stackId, toTop); resumeTopActivitiesLocked(); } ActivityRecord findTaskLocked(ActivityRecord r) { - if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + r); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = stacks.get(stackNdx); if (!r.isApplicationActivity() && !stack.isHomeStack()) { - if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: (home activity) " + stack); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping stack: (home activity) " + stack); continue; } if (!stack.mActivityContainer.isEligibleForNewTasks()) { - if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: (new task not allowed) " + - stack); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, + "Skipping stack: (new task not allowed) " + stack); continue; } final ActivityRecord ar = stack.findTaskLocked(r); @@ -2708,7 +2884,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } } } - if (DEBUG_TASKS) Slog.d(TAG, "No task found"); + if (DEBUG_TASKS) Slog.d(TAG_TASKS, "No task found"); return null; } @@ -2817,7 +2993,7 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 " + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still need to stop " + mStoppingActivities.size() + " activities"); scheduleIdleLocked(); dontSleep = true; @@ -2825,7 +3001,7 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 " + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still need to sleep " + mGoingToSleepActivities.size() + " activities"); dontSleep = true; } @@ -2933,7 +3109,7 @@ public final class ActivityStackSupervisor implements DisplayListener { r.mLaunchTaskBehind = false; final TaskRecord task = r.task; task.setLastThumbnail(task.stack.screenshotActivities(r)); - mService.addRecentTaskLocked(task); + mRecentTasks.addLocked(task); mService.notifyTaskStackChangedLocked(); mWindowManager.setAppVisibility(r.appToken, false); } @@ -2976,16 +3152,14 @@ public final class ActivityStackSupervisor implements DisplayListener { // First, if we find an activity that is in the process of being destroyed, // then we just aren't going to do anything for now; we want things to settle // down before we try to prune more activities. - if (r.finishing || r.state == ActivityState.DESTROYING - || r.state == ActivityState.DESTROYED) { + if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) { if (DEBUG_RELEASE) Slog.d(TAG, "Abort release; already destroying: " + r); return; } // Don't consider any activies that are currently not in a state where they // can be destroyed. - if (r.visible || !r.stopped || !r.haveState - || r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING - || r.state == ActivityState.PAUSED || r.state == ActivityState.STOPPING) { + if (r.visible || !r.stopped || !r.haveState || r.state == RESUMED || r.state == PAUSING + || r.state == PAUSED || r.state == STOPPING) { if (DEBUG_RELEASE) Slog.d(TAG, "Not releasing in-use activity: " + r); continue; } @@ -3024,7 +3198,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } boolean switchUserLocked(int userId, UserStartedState uss) { - mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId()); + mUserStackInFront.put(mCurrentUser, mFocusedStack.getStackId()); final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID); mCurrentUser = userId; @@ -3068,40 +3242,43 @@ public final class ActivityStackSupervisor implements DisplayListener { mStartingBackgroundUsers.add(uss); } - final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) { - int N = mStoppingActivities.size(); - if (N <= 0) return null; + /** Checks whether the userid is a profile of the current user. */ + boolean isCurrentProfileLocked(int userId) { + if (userId == mCurrentUser) return true; + for (int i = 0; i < mService.mCurrentProfileIds.length; i++) { + if (mService.mCurrentProfileIds[i] == userId) return true; + } + return false; + } + final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) { 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) { + for (int activityNdx = mStoppingActivities.size() - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord s = mStoppingActivities.get(activityNdx); + final boolean waitingVisible = mWaitingVisibleActivities.contains(s); + if (DEBUG_ALL) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible + + " waitingVisible=" + waitingVisible + " finishing=" + s.finishing); + if (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); + if (DEBUG_ALL) 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 ((!waitingVisible || mService.isSleepingOrShuttingDown()) && remove) { + if (DEBUG_ALL) Slog.v(TAG, "Ready to stop: " + s); if (stops == null) { - stops = new ArrayList<ActivityRecord>(); + stops = new ArrayList<>(); } stops.add(s); - mStoppingActivities.remove(i); - N--; - i--; + mStoppingActivities.remove(activityNdx); } } @@ -3109,39 +3286,34 @@ public final class ActivityStackSupervisor implements DisplayListener { } void validateTopActivitiesLocked() { - // FIXME -/* for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = stacks.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); + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + final ActivityRecord r = stack.topRunningActivityLocked(null); + final ActivityState state = r == null ? 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 != INITIALIZING && state != 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); + } 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 == INITIALIZING || state == RESUMED)) Slog.e(TAG, + "validateTop...: activity in back resumed r=" + r + " state=" + state); } } } -*/ } public void dump(PrintWriter pw, String prefix) { @@ -3151,10 +3323,11 @@ public final class ActivityStackSupervisor implements DisplayListener { pw.print(prefix); pw.println("mCurTaskId=" + mCurTaskId); pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront); pw.print(prefix); pw.println("mActivityContainers=" + mActivityContainers); + pw.print(prefix); pw.println("mLockTaskModeTasks" + mLockTaskModeTasks); } ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) { - return getFocusedStack().getDumpActivitiesLocked(name); + return mFocusedStack.getDumpActivitiesLocked(name); } static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage, @@ -3367,7 +3540,7 @@ public final class ActivityStackSupervisor implements DisplayListener { mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0)); } - public void handleDisplayAddedLocked(int displayId) { + private void handleDisplayAdded(int displayId) { boolean newDisplay; synchronized (mService) { newDisplay = mActivityDisplays.get(displayId) == null; @@ -3385,7 +3558,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - public void handleDisplayRemovedLocked(int displayId) { + private void handleDisplayRemoved(int displayId) { synchronized (mService) { ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); if (activityDisplay != null) { @@ -3399,7 +3572,7 @@ public final class ActivityStackSupervisor implements DisplayListener { mWindowManager.onDisplayRemoved(displayId); } - public void handleDisplayChangedLocked(int displayId) { + private void handleDisplayChanged(int displayId) { synchronized (mService) { ActivityDisplay activityDisplay = mActivityDisplays.get(displayId); if (activityDisplay != null) { @@ -3409,7 +3582,7 @@ public final class ActivityStackSupervisor implements DisplayListener { mWindowManager.onDisplayChanged(displayId); } - StackInfo getStackInfo(ActivityStack stack) { + private StackInfo getStackInfoLocked(ActivityStack stack) { StackInfo info = new StackInfo(); mWindowManager.getStackBounds(stack.mStackId, info.bounds); info.displayId = Display.DEFAULT_DISPLAY; @@ -3435,7 +3608,7 @@ public final class ActivityStackSupervisor implements DisplayListener { StackInfo getStackInfoLocked(int stackId) { ActivityStack stack = getStack(stackId); if (stack != null) { - return getStackInfo(stack); + return getStackInfoLocked(stack); } return null; } @@ -3445,60 +3618,143 @@ public final class ActivityStackSupervisor implements DisplayListener { for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) { ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) { - list.add(getStackInfo(stacks.get(ndx))); + list.add(getStackInfoLocked(stacks.get(ndx))); } } return list; } + TaskRecord getLockedTaskLocked() { + final int top = mLockTaskModeTasks.size() - 1; + if (top >= 0) { + return mLockTaskModeTasks.get(top); + } + return null; + } + + boolean isLockedTask(TaskRecord task) { + return mLockTaskModeTasks.contains(task); + } + + boolean isLastLockedTask(TaskRecord task) { + return mLockTaskModeTasks.size() == 1 && mLockTaskModeTasks.contains(task); + } + + void removeLockedTaskLocked(final TaskRecord task) { + if (mLockTaskModeTasks.remove(task) && mLockTaskModeTasks.isEmpty()) { + // Last one. + final Message lockTaskMsg = Message.obtain(); + lockTaskMsg.arg1 = task.userId; + lockTaskMsg.what = LOCK_TASK_END_MSG; + mHandler.sendMessage(lockTaskMsg); + } + } + void showLockTaskToast() { - mLockTaskNotify.showToast(mLockTaskIsLocked); + mLockTaskNotify.showToast(mLockTaskModeState); } - void setLockTaskModeLocked(TaskRecord task, boolean isLocked, String reason) { + void showLockTaskEscapeMessageLocked(TaskRecord task) { + if (mLockTaskModeTasks.contains(task)) { + mHandler.sendEmptyMessage(SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG); + } + } + + void setLockTaskModeLocked(TaskRecord task, int lockTaskModeState, String reason) { if (task == null) { // Take out of lock task mode if necessary - if (mLockTaskModeTask != null) { - final Message lockTaskMsg = Message.obtain(); - lockTaskMsg.arg1 = mLockTaskModeTask.userId; - lockTaskMsg.what = LOCK_TASK_END_MSG; - mLockTaskModeTask = null; - mHandler.sendMessage(lockTaskMsg); + final TaskRecord lockedTask = getLockedTaskLocked(); + if (lockedTask != null) { + removeLockedTaskLocked(lockedTask); + if (!mLockTaskModeTasks.isEmpty()) { + // There are locked tasks remaining, can only finish this task, not unlock it. + lockedTask.performClearTaskLocked(); + resumeTopActivitiesLocked(); + return; + } } return; } + + // Should have already been checked, but do it again. + if (task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) { + return; + } if (isLockTaskModeViolation(task)) { - Slog.e(TAG, "setLockTaskMode: Attempt to start a second Lock Task Mode task."); + Slog.e(TAG, "setLockTaskMode: Attempt to start an unauthorized lock task."); return; } - mLockTaskModeTask = task; + + if (mLockTaskModeTasks.isEmpty()) { + // First locktask. + final Message lockTaskMsg = Message.obtain(); + lockTaskMsg.obj = task.intent.getComponent().getPackageName(); + lockTaskMsg.arg1 = task.userId; + lockTaskMsg.what = LOCK_TASK_START_MSG; + lockTaskMsg.arg2 = lockTaskModeState; + mHandler.sendMessage(lockTaskMsg); + } + // Add it or move it to the top. + mLockTaskModeTasks.remove(task); + mLockTaskModeTasks.add(task); + + if (task.mLockTaskUid == -1) { + task.mLockTaskUid = task.mCallingUid; + } findTaskToMoveToFrontLocked(task, 0, null, reason); resumeTopActivitiesLocked(); - - final Message lockTaskMsg = Message.obtain(); - lockTaskMsg.obj = mLockTaskModeTask.intent.getComponent().getPackageName(); - lockTaskMsg.arg1 = mLockTaskModeTask.userId; - lockTaskMsg.what = LOCK_TASK_START_MSG; - lockTaskMsg.arg2 = !isLocked ? 1 : 0; - mHandler.sendMessage(lockTaskMsg); } boolean isLockTaskModeViolation(TaskRecord task) { - return mLockTaskModeTask != null && mLockTaskModeTask != task; + if (getLockedTaskLocked() == task) { + return false; + } + final int lockTaskAuth = task.mLockTaskAuth; + switch (lockTaskAuth) { + case LOCK_TASK_AUTH_DONT_LOCK: + return !mLockTaskModeTasks.isEmpty(); + case LOCK_TASK_AUTH_LAUNCHABLE: + case LOCK_TASK_AUTH_WHITELISTED: + return false; + case LOCK_TASK_AUTH_PINNABLE: + // Pinnable tasks can't be launched on top of locktask tasks. + return !mLockTaskModeTasks.isEmpty(); + default: + Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth); + return true; + } } - void endLockTaskModeIfTaskEnding(TaskRecord task) { - if (mLockTaskModeTask != null && mLockTaskModeTask == task) { - final Message lockTaskMsg = Message.obtain(); - lockTaskMsg.arg1 = mLockTaskModeTask.userId; - lockTaskMsg.what = LOCK_TASK_END_MSG; - mLockTaskModeTask = null; - mHandler.sendMessage(lockTaskMsg); + void onLockTaskPackagesUpdatedLocked() { + boolean didSomething = false; + for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord lockedTask = mLockTaskModeTasks.get(taskNdx); + if (lockedTask.mLockTaskMode != LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED) { + continue; + } + final boolean wasLaunchable = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE; + lockedTask.setLockTaskAuth(); + if (wasLaunchable && lockedTask.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE) { + // Lost whitelisting authorization. End it now. + removeLockedTaskLocked(lockedTask); + lockedTask.performClearTaskLocked(); + didSomething = true; + } + } + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.onLockTaskPackagesUpdatedLocked(); + } + } + if (didSomething) { + resumeTopActivitiesLocked(); } } - boolean isInLockTaskMode() { - return mLockTaskModeTask != null; + int getLockTaskModeState() { + return mLockTaskModeState; } private final class ActivityStackSupervisorHandler extends Handler { @@ -3565,13 +3821,13 @@ public final class ActivityStackSupervisor implements DisplayListener { } } break; case HANDLE_DISPLAY_ADDED: { - handleDisplayAddedLocked(msg.arg1); + handleDisplayAdded(msg.arg1); } break; case HANDLE_DISPLAY_CHANGED: { - handleDisplayChangedLocked(msg.arg1); + handleDisplayChanged(msg.arg1); } break; case HANDLE_DISPLAY_REMOVED: { - handleDisplayRemovedLocked(msg.arg1); + handleDisplayRemoved(msg.arg1); } break; case CONTAINER_CALLBACK_VISIBILITY: { final ActivityContainer container = (ActivityContainer) msg.obj; @@ -3590,13 +3846,17 @@ public final class ActivityStackSupervisor implements DisplayListener { mLockTaskNotify = new LockTaskNotify(mService.mContext); } mLockTaskNotify.show(true); - mLockTaskIsLocked = msg.arg2 == 0; + mLockTaskModeState = msg.arg2; if (getStatusBarService() != null) { - int flags = - StatusBarManager.DISABLE_MASK ^ StatusBarManager.DISABLE_BACK; - if (!mLockTaskIsLocked) { - flags ^= StatusBarManager.DISABLE_HOME - | StatusBarManager.DISABLE_RECENT; + int flags = 0; + if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) { + flags = StatusBarManager.DISABLE_MASK + & (~StatusBarManager.DISABLE_BACK); + } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { + flags = StatusBarManager.DISABLE_MASK + & (~StatusBarManager.DISABLE_BACK) + & (~StatusBarManager.DISABLE_HOME) + & (~StatusBarManager.DISABLE_RECENT); } getStatusBarService().disable(flags, mToken, mService.mContext.getPackageName()); @@ -3630,7 +3890,7 @@ public final class ActivityStackSupervisor implements DisplayListener { boolean shouldLockKeyguard = Settings.Secure.getInt( mService.mContext.getContentResolver(), Settings.Secure.LOCK_TO_APP_EXIT_LOCKED) != 0; - if (!mLockTaskIsLocked && shouldLockKeyguard) { + if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) { mWindowManager.lockNow(null); mWindowManager.dismissKeyguard(); new LockPatternUtils(mService.mContext) @@ -3641,7 +3901,15 @@ public final class ActivityStackSupervisor implements DisplayListener { } } catch (RemoteException ex) { throw new RuntimeException(ex); + } finally { + mLockTaskModeState = LOCK_TASK_MODE_NONE; + } + } break; + case SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG: { + if (mLockTaskNotify == null) { + mLockTaskNotify = new LockTaskNotify(mService.mContext); } + mLockTaskNotify.showToast(LOCK_TASK_MODE_PINNED); } break; case CONTAINER_CALLBACK_TASK_LIST_EMPTY: { final ActivityContainer container = (ActivityContainer) msg.obj; @@ -3653,18 +3921,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } } } break; - case CONTAINER_TASK_LIST_EMPTY_TIMEOUT: { - synchronized (mService) { - Slog.w(TAG, "Timeout waiting for all activities in task to finish. " + - msg.obj); - final ActivityContainer container = (ActivityContainer) msg.obj; - container.mStack.finishAllActivitiesLocked(true); - container.onTaskListEmptyLocked(); - } - } break; case LAUNCH_TASK_BEHIND_COMPLETE: { synchronized (mService) { - ActivityRecord r = ActivityRecord.forToken((IBinder) msg.obj); + ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj); if (r != null) { handleLaunchTaskBehindCompleteLocked(r); } @@ -3696,14 +3955,14 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityContainer(int stackId) { synchronized (mService) { mStackId = stackId; - mStack = new ActivityStack(this); + mStack = new ActivityStack(this, mRecentTasks); mIdString = "ActivtyContainer{" + mStackId + "}"; - if (DEBUG_STACK) Slog.d(TAG, "Creating " + this); + if (DEBUG_STACK) Slog.d(TAG_STACK, "Creating " + this); } } void attachToDisplayLocked(ActivityDisplay activityDisplay) { - if (DEBUG_STACK) Slog.d(TAG, "attachToDisplayLocked: " + this + if (DEBUG_STACK) Slog.d(TAG_STACK, "attachToDisplayLocked: " + this + " to display=" + activityDisplay); mActivityDisplay = activityDisplay; mStack.mDisplayId = activityDisplay.mDisplayId; @@ -3735,6 +3994,13 @@ public final class ActivityStackSupervisor implements DisplayListener { } @Override + public int getStackId() { + synchronized (mService) { + return mStackId; + } + } + + @Override public boolean injectEvent(InputEvent event) { final long origId = Binder.clearCallingIdentity(); try { @@ -3759,10 +4025,6 @@ public final class ActivityStackSupervisor implements DisplayListener { } mContainerState = CONTAINER_STATE_FINISHING; - final Message msg = - mHandler.obtainMessage(CONTAINER_TASK_LIST_EMPTY_TIMEOUT, this); - mHandler.sendMessageDelayed(msg, 2000); - long origId = Binder.clearCallingIdentity(); try { mStack.finishAllActivitiesLocked(false); @@ -3774,7 +4036,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } protected void detachLocked() { - if (DEBUG_STACK) Slog.d(TAG, "detachLocked: " + this + " from display=" + if (DEBUG_STACK) Slog.d(TAG_STACK, "detachLocked: " + this + " from display=" + mActivityDisplay + " Callers=" + Debug.getCallers(2)); if (mActivityDisplay != null) { mActivityDisplay.detachActivitiesLocked(mStack); @@ -3788,43 +4050,45 @@ public final class ActivityStackSupervisor implements DisplayListener { @Override public final int startActivity(Intent intent) { mService.enforceNotIsolatedCaller("ActivityContainer.startActivity"); - int userId = mService.handleIncomingUser(Binder.getCallingPid(), + final int userId = mService.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), mCurrentUser, false, ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null); + // TODO: Switch to user app stacks here. - intent.addFlags(FORCE_NEW_TASK_FLAGS); String mimeType = intent.getType(); - if (mimeType == null && intent.getData() != null - && "content".equals(intent.getData().getScheme())) { - mimeType = mService.getProviderMimeType(intent.getData(), userId); + final Uri data = intent.getData(); + if (mimeType == null && data != null && "content".equals(data.getScheme())) { + mimeType = mService.getProviderMimeType(data, userId); } - return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, 0, - 0, null, null, null, null, userId, this, null); + checkEmbeddedAllowedInner(userId, intent, mimeType); + + intent.addFlags(FORCE_NEW_TASK_FLAGS); + return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, + 0, 0, null, null, null, null, userId, this, null); } @Override - public final int startActivityIntentSender(IIntentSender intentSender) { + public final int startActivityIntentSender(IIntentSender intentSender) + throws TransactionTooLargeException { mService.enforceNotIsolatedCaller("ActivityContainer.startActivityIntentSender"); if (!(intentSender instanceof PendingIntentRecord)) { throw new IllegalArgumentException("Bad PendingIntent object"); } - return ((PendingIntentRecord)intentSender).sendInner(0, null, null, null, null, null, - null, 0, FORCE_NEW_TASK_FLAGS, FORCE_NEW_TASK_FLAGS, null, this); - } - - private void checkEmbeddedAllowedInner(Intent intent, String resolvedType) { - int userId = mService.handleIncomingUser(Binder.getCallingPid(), + final int userId = mService.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), mCurrentUser, false, ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null); - if (resolvedType == null) { - resolvedType = intent.getType(); - if (resolvedType == null && intent.getData() != null - && "content".equals(intent.getData().getScheme())) { - resolvedType = mService.getProviderMimeType(intent.getData(), userId); - } - } + + final PendingIntentRecord pendingIntent = (PendingIntentRecord) intentSender; + checkEmbeddedAllowedInner(userId, pendingIntent.key.requestIntent, + pendingIntent.key.requestResolvedType); + + return pendingIntent.sendInner(0, null, null, null, null, null, null, 0, + FORCE_NEW_TASK_FLAGS, FORCE_NEW_TASK_FLAGS, null, this); + } + + private void checkEmbeddedAllowedInner(int userId, Intent intent, String resolvedType) { ActivityInfo aInfo = resolveActivity(intent, resolvedType, 0, null, userId); if (aInfo != null && (aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) { throw new SecurityException( @@ -3832,23 +4096,6 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - /** Throw a SecurityException if allowEmbedded is not true */ - @Override - public final void checkEmbeddedAllowed(Intent intent) { - checkEmbeddedAllowedInner(intent, null); - } - - /** Throw a SecurityException if allowEmbedded is not true */ - @Override - public final void checkEmbeddedAllowedIntentSender(IIntentSender intentSender) { - if (!(intentSender instanceof PendingIntentRecord)) { - throw new IllegalArgumentException("Bad PendingIntent object"); - } - PendingIntentRecord pendingIntent = (PendingIntentRecord) intentSender; - checkEmbeddedAllowedInner(pendingIntent.key.requestIntent, - pendingIntent.key.requestResolvedType); - } - @Override public IBinder asBinder() { return this; @@ -3897,6 +4144,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } void onTaskListEmptyLocked() { + detachLocked(); + deleteActivityContainer(this); + mHandler.obtainMessage(CONTAINER_CALLBACK_TASK_LIST_EMPTY, this).sendToTarget(); } @Override @@ -3962,8 +4212,8 @@ public final class ActivityStackSupervisor implements DisplayListener { setSurfaceIfReadyLocked(); - if (DEBUG_STACK) Slog.d(TAG, "setSurface: " + this + " to display=" - + virtualActivityDisplay); + if (DEBUG_STACK) Slog.d(TAG_STACK, + "setSurface: " + this + " to display=" + virtualActivityDisplay); } @Override @@ -3985,15 +4235,8 @@ public final class ActivityStackSupervisor implements DisplayListener { return false; } - void onTaskListEmptyLocked() { - mHandler.removeMessages(CONTAINER_TASK_LIST_EMPTY_TIMEOUT, this); - detachLocked(); - deleteActivityContainer(this); - mHandler.obtainMessage(CONTAINER_CALLBACK_TASK_LIST_EMPTY, this).sendToTarget(); - } - private void setSurfaceIfReadyLocked() { - if (DEBUG_STACK) Slog.v(TAG, "setSurfaceIfReadyLocked: mDrawn=" + mDrawn + + if (DEBUG_STACK) Slog.v(TAG_STACK, "setSurfaceIfReadyLocked: mDrawn=" + mDrawn + " mContainerState=" + mContainerState + " mSurface=" + mSurface); if (mDrawn && mSurface != null && mContainerState == CONTAINER_STATE_NO_SURFACE) { ((VirtualActivityDisplay) mActivityDisplay).setSurface(mSurface); @@ -4036,13 +4279,13 @@ public final class ActivityStackSupervisor implements DisplayListener { } void attachActivities(ActivityStack stack) { - if (DEBUG_STACK) Slog.v(TAG, "attachActivities: attaching " + stack + " to displayId=" - + mDisplayId); + if (DEBUG_STACK) Slog.v(TAG_STACK, + "attachActivities: attaching " + stack + " to displayId=" + mDisplayId); mStacks.add(stack); } void detachActivitiesLocked(ActivityStack stack) { - if (DEBUG_STACK) Slog.v(TAG, "detachActivitiesLocked: detaching " + stack + if (DEBUG_STACK) Slog.v(TAG_STACK, "detachActivitiesLocked: detaching " + stack + " from displayId=" + mDisplayId); mStacks.remove(stack); } diff --git a/services/core/java/com/android/server/am/AppBindRecord.java b/services/core/java/com/android/server/am/AppBindRecord.java index 65273c9..df833ad 100644 --- a/services/core/java/com/android/server/am/AppBindRecord.java +++ b/services/core/java/com/android/server/am/AppBindRecord.java @@ -19,7 +19,6 @@ package com.android.server.am; import android.util.ArraySet; import java.io.PrintWriter; -import java.util.Iterator; /** * An association between a service and one of its client applications. diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c0928c7..58665d7 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -16,20 +16,24 @@ package com.android.server.am; +import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.net.wifi.IWifiManager; +import android.net.wifi.WifiActivityEnergyInfo; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; +import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PowerManagerInternal; import android.os.Process; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; @@ -38,15 +42,16 @@ import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.PowerProfile; +import com.android.server.FgThread; import com.android.server.LocalServices; import java.io.File; import java.io.FileDescriptor; -import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.List; @@ -60,15 +65,51 @@ public final class BatteryStatsService extends IBatteryStats.Stub static final String TAG = "BatteryStatsService"; static IBatteryStats sService; - final BatteryStatsImpl mStats; + final BatteryStatsHandler mHandler; Context mContext; - private boolean mBluetoothPendingStats; - private BluetoothHeadset mBluetoothHeadset; PowerManagerInternal mPowerManagerInternal; + class BatteryStatsHandler extends Handler implements BatteryStatsImpl.ExternalStatsSync { + public static final int MSG_SYNC_EXTERNAL_STATS = 1; + public static final int MSG_WRITE_TO_DISK = 2; + + public BatteryStatsHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SYNC_EXTERNAL_STATS: + updateExternalStats((String)msg.obj); + break; + + case MSG_WRITE_TO_DISK: + updateExternalStats("write"); + synchronized (mStats) { + mStats.writeAsyncLocked(); + } + break; + } + } + + @Override + public void scheduleSync(String reason) { + if (!hasMessages(MSG_SYNC_EXTERNAL_STATS)) { + Message msg = Message.obtain(this, MSG_SYNC_EXTERNAL_STATS, reason); + sendMessage(msg); + } + } + } + BatteryStatsService(File systemDir, Handler handler) { - mStats = new BatteryStatsImpl(systemDir, handler); + // Our handler here will be accessing the disk, use a different thread than + // what the ActivityManagerService gave us (no I/O on that one!). + mHandler = new BatteryStatsHandler(FgThread.getHandler().getLooper()); + + // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through. + mStats = new BatteryStatsImpl(systemDir, handler, mHandler); } public void publish(Context context) { @@ -78,6 +119,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mStats.setRadioScanningTimeout(mContext.getResources().getInteger( com.android.internal.R.integer.config_radioScanningTimeout) * 1000L); + mStats.setPowerProfile(new PowerProfile(context)); } /** @@ -87,12 +129,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void initPowerManagement() { mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mPowerManagerInternal.registerLowPowerModeObserver(this); - mStats.noteLowPowerMode(mPowerManagerInternal.getLowPowerModeEnabled()); + mStats.notePowerSaveMode(mPowerManagerInternal.getLowPowerModeEnabled()); (new WakeupReasonThread()).start(); } public void shutdown() { Slog.w("BatteryStats", "Writing battery stats before shutdown..."); + + updateExternalStats("shutdown"); synchronized (mStats) { mStats.shutdownLocked(); } @@ -110,7 +154,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub @Override public void onLowPowerModeChanged(boolean enabled) { synchronized (mStats) { - mStats.noteLowPowerMode(enabled); + mStats.notePowerSaveMode(enabled); } } @@ -123,6 +167,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub return mStats; } + /** + * Schedules a write to disk to occur. This will cause the BatteryStatsImpl + * object to update with the latest info, then write to disk. + */ + public void scheduleWriteToDisk() { + mHandler.sendEmptyMessage(BatteryStatsHandler.MSG_WRITE_TO_DISK); + } + // These are for direct use by the activity manager... void addIsolatedUid(int isolatedUid, int appUid) { @@ -175,7 +227,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub //Slog.i("foo", "SENDING BATTERY INFO:"); //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); Parcel out = Parcel.obtain(); - mStats.writeToParcel(out, 0); + updateExternalStats("get-stats"); + synchronized (mStats) { + mStats.writeToParcel(out, 0); + } byte[] data = out.marshall(); out.recycle(); return data; @@ -187,7 +242,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub //Slog.i("foo", "SENDING BATTERY INFO:"); //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM)); Parcel out = Parcel.obtain(); - mStats.writeToParcel(out, 0); + updateExternalStats("get-stats"); + synchronized (mStats) { + mStats.writeToParcel(out, 0); + } byte[] data = out.marshall(); out.recycle(); try { @@ -198,6 +256,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + public boolean isCharging() { + synchronized (mStats) { + return mStats.isCharging(); + } + } + public long computeBatteryTimeRemaining() { synchronized (mStats) { long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime()); @@ -247,6 +311,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + public void noteAlarmStart(String name, int uid) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteAlarmStartLocked(name, uid); + } + } + + public void noteAlarmFinish(String name, int uid) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteAlarmFinishLocked(name, uid); + } + } + public void noteStartWakelock(int uid, int pid, String name, String historyName, int type, boolean unimportantForLogging) { enforceCallingPermission(); @@ -481,6 +559,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + @Override + public void noteWifiRadioPowerState(int powerState, long tsNanos) { + enforceCallingPermission(); + + // There was a change in WiFi power state. + // Collect data now for the past activity. + mHandler.scheduleSync("wifi-data"); + synchronized (mStats) { + mStats.noteWifiRadioPowerState(powerState, tsNanos); + } + } + public void noteWifiRunning(WorkSource ws) { enforceCallingPermission(); synchronized (mStats) { @@ -523,56 +613,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - public void noteBluetoothOn() { - enforceCallingPermission(); - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, - BluetoothProfile.HEADSET); - } - synchronized (mStats) { - if (mBluetoothHeadset != null) { - mStats.noteBluetoothOnLocked(); - mStats.setBtHeadset(mBluetoothHeadset); - } else { - mBluetoothPendingStats = true; - } - } - } - - private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = - new BluetoothProfile.ServiceListener() { - public void onServiceConnected(int profile, BluetoothProfile proxy) { - mBluetoothHeadset = (BluetoothHeadset) proxy; - synchronized (mStats) { - if (mBluetoothPendingStats) { - mStats.noteBluetoothOnLocked(); - mStats.setBtHeadset(mBluetoothHeadset); - mBluetoothPendingStats = false; - } - } - } - - public void onServiceDisconnected(int profile) { - mBluetoothHeadset = null; - } - }; - - public void noteBluetoothOff() { - enforceCallingPermission(); - synchronized (mStats) { - mBluetoothPendingStats = false; - mStats.noteBluetoothOffLocked(); - } - } - - public void noteBluetoothState(int bluetoothState) { - enforceCallingPermission(); - synchronized (mStats) { - mStats.noteBluetoothStateLocked(bluetoothState); - } - } - public void noteFullWifiLockAcquired(int uid) { enforceCallingPermission(); synchronized (mStats) { @@ -664,6 +704,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + @Override public void noteWifiMulticastDisabledFromSource(WorkSource ws) { enforceCallingPermission(); synchronized (mStats) { @@ -672,10 +713,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub } @Override - public void noteNetworkInterfaceType(String iface, int type) { + public void noteNetworkInterfaceType(String iface, int networkType) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteNetworkInterfaceTypeLocked(iface, type); + mStats.noteNetworkInterfaceTypeLocked(iface, networkType); } } @@ -687,6 +728,28 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + @Override + public void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteDeviceIdleModeLocked(enabled, fromActive, fromMotion); + } + } + + public void notePackageInstalled(String pkgName, int versionCode) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.notePackageInstalledLocked(pkgName, versionCode); + } + } + + public void notePackageUninstalled(String pkgName) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.notePackageUninstalledLocked(pkgName); + } + } + public boolean isOnBattery() { return mStats.isOnBattery(); } @@ -694,7 +757,22 @@ public final class BatteryStatsService extends IBatteryStats.Stub public void setBatteryState(int status, int health, int plugType, int level, int temp, int volt) { enforceCallingPermission(); - mStats.setBatteryState(status, health, plugType, level, temp, volt); + synchronized (mStats) { + final boolean onBattery = plugType == BatteryStatsImpl.BATTERY_PLUGGED_NONE; + if (mStats.isOnBattery() == onBattery) { + // The battery state has not changed, so we don't need to sync external + // stats immediately. + mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt); + return; + } + } + + // Sync external stats first as the battery has changed states. If we don't sync + // immediately here, we may not collect the relevant data later. + updateExternalStats("battery-state"); + synchronized (mStats) { + mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt); + } } public long getAwakeTimeBattery() { @@ -751,15 +829,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub private void dumpHelp(PrintWriter pw) { pw.println("Battery stats (batterystats) dump options:"); - pw.println(" [--checkin] [--history] [--history-start] [--unplugged] [--charged] [-c]"); - pw.println(" [--reset] [--write] [-h] [<package.name>]"); + pw.println(" [--checkin] [--history] [--history-start] [--charged] [-c]"); + pw.println(" [--daily] [--reset] [--write] [--new-daily] [--read-daily] [-h] [<package.name>]"); pw.println(" --checkin: format output for a checkin report."); pw.println(" --history: show only history data."); pw.println(" --history-start <num>: show only history data starting at given time offset."); - pw.println(" --unplugged: only output data since last unplugged."); pw.println(" --charged: only output data since last charged."); + pw.println(" --daily: only output full daily data."); pw.println(" --reset: reset the stats, clearing all current data."); pw.println(" --write: force write current collected stats to disk."); + pw.println(" --new-daily: immediately create and write new daily stats record."); + pw.println(" --read-daily: read-load last written daily stats."); pw.println(" <package.name>: optional name of package to filter output by."); pw.println(" -h: print this help text."); pw.println("Battery stats (batterystats) commands:"); @@ -767,7 +847,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub pw.println(" Enable or disable a running option. Option state is not saved across boots."); pw.println(" Options are:"); pw.println(" full-history: include additional detailed events in battery history:"); - pw.println(" wake_lock_in and proc events"); + pw.println(" wake_lock_in, alarms and proc events"); pw.println(" no-auto-reset: don't automatically reset stats when unplugged"); } @@ -794,6 +874,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub return i; } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -832,22 +913,36 @@ public final class BatteryStatsService extends IBatteryStats.Stub } else if ("-c".equals(arg)) { useCheckinFormat = true; flags |= BatteryStats.DUMP_INCLUDE_HISTORY; - } else if ("--unplugged".equals(arg)) { - flags |= BatteryStats.DUMP_UNPLUGGED_ONLY; } else if ("--charged".equals(arg)) { flags |= BatteryStats.DUMP_CHARGED_ONLY; + } else if ("--daily".equals(arg)) { + flags |= BatteryStats.DUMP_DAILY_ONLY; } else if ("--reset".equals(arg)) { synchronized (mStats) { mStats.resetAllStatsCmdLocked(); pw.println("Battery stats reset."); noOutput = true; } + updateExternalStats("dump"); } else if ("--write".equals(arg)) { + updateExternalStats("dump"); synchronized (mStats) { mStats.writeSyncLocked(); pw.println("Battery stats written."); noOutput = true; } + } else if ("--new-daily".equals(arg)) { + synchronized (mStats) { + mStats.recordDailyStatsLocked(); + pw.println("New daily stats written."); + noOutput = true; + } + } else if ("--read-daily".equals(arg)) { + synchronized (mStats) { + mStats.readDailyStatsLocked(); + pw.println("Last daily stats read."); + noOutput = true; + } } else if ("--enable".equals(arg) || "enable".equals(arg)) { i = doEnableOrDisable(pw, i, args, true); if (i < 0) { @@ -887,19 +982,28 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (noOutput) { return; } - if (BatteryStatsHelper.checkWifiOnly(mContext)) { - flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY; + + long ident = Binder.clearCallingIdentity(); + try { + if (BatteryStatsHelper.checkWifiOnly(mContext)) { + flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY; + } + // Fetch data from external sources and update the BatteryStatsImpl object with them. + updateExternalStats("dump"); + } finally { + Binder.restoreCallingIdentity(ident); } + if (reqUid >= 0) { // By default, if the caller is only interested in a specific package, then // we only dump the aggregated data since charged. - if ((flags&(BatteryStats.DUMP_HISTORY_ONLY|BatteryStats.DUMP_UNPLUGGED_ONLY - |BatteryStats.DUMP_CHARGED_ONLY)) == 0) { + if ((flags&(BatteryStats.DUMP_HISTORY_ONLY|BatteryStats.DUMP_CHARGED_ONLY)) == 0) { flags |= BatteryStats.DUMP_CHARGED_ONLY; // Also if they are doing -c, we don't want history. flags &= ~BatteryStats.DUMP_INCLUDE_HISTORY; } } + if (useCheckinFormat) { List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0); if (isRealCheckin) { @@ -914,7 +1018,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub in.unmarshall(raw, 0, raw.length); in.setDataPosition(0); BatteryStatsImpl checkinStats = new BatteryStatsImpl( - null, mStats.mHandler); + null, mStats.mHandler, null); checkinStats.readSummaryFromParcel(in); in.recycle(); checkinStats.dumpCheckinLocked(mContext, pw, apps, flags, @@ -944,4 +1048,113 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } } + + // Objects for extracting data from external sources. + private final Object mExternalStatsLock = new Object(); + + @GuardedBy("mExternalStatsLock") + private IWifiManager mWifiManager; + + // WiFi keeps an accumulated total of stats, unlike Bluetooth. + // Keep the last WiFi stats so we can compute a delta. + @GuardedBy("mExternalStatsLock") + private WifiActivityEnergyInfo mLastInfo = new WifiActivityEnergyInfo(0, 0, 0, 0, 0, 0); + + @GuardedBy("mExternalStatsLock") + private WifiActivityEnergyInfo pullWifiEnergyInfoLocked() { + if (mWifiManager == null) { + mWifiManager = IWifiManager.Stub.asInterface( + ServiceManager.getService(Context.WIFI_SERVICE)); + if (mWifiManager == null) { + return null; + } + } + + try { + // We read the data even if we are not on battery. This is so that we keep the + // correct delta from when we should start reading (aka when we are on battery). + WifiActivityEnergyInfo info = mWifiManager.reportActivityInfo(); + if (info != null && info.isValid()) { + // We will modify the last info object to be the delta, and store the new + // WifiActivityEnergyInfo object as our last one. + final WifiActivityEnergyInfo result = mLastInfo; + result.mTimestamp = info.getTimeStamp(); + result.mStackState = info.getStackState(); + result.mControllerTxTimeMs = + info.mControllerTxTimeMs - mLastInfo.mControllerTxTimeMs; + result.mControllerRxTimeMs = + info.mControllerRxTimeMs - mLastInfo.mControllerRxTimeMs; + result.mControllerEnergyUsed = + info.mControllerEnergyUsed - mLastInfo.mControllerEnergyUsed; + + // WiFi calculates the idle time as a difference from the on time and the various + // Rx + Tx times. There seems to be some missing time there because this sometimes + // becomes negative. Just cap it at 0 and move on. + result.mControllerIdleTimeMs = + Math.max(0, info.mControllerIdleTimeMs - mLastInfo.mControllerIdleTimeMs); + + if (result.mControllerTxTimeMs < 0 || + result.mControllerRxTimeMs < 0) { + // The stats were reset by the WiFi system (which is why our delta is negative). + // Returns the unaltered stats. + result.mControllerEnergyUsed = info.mControllerEnergyUsed; + result.mControllerRxTimeMs = info.mControllerRxTimeMs; + result.mControllerTxTimeMs = info.mControllerTxTimeMs; + result.mControllerIdleTimeMs = info.mControllerIdleTimeMs; + + Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + result); + } + mLastInfo = info; + return result; + } + } catch (RemoteException e) { + // Nothing to report, WiFi is dead. + } + return null; + } + + @GuardedBy("mExternalStatsLock") + private BluetoothActivityEnergyInfo pullBluetoothEnergyInfoLocked() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo( + BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED); + if (info != null && info.isValid()) { + return info; + } + } + return null; + } + + /** + * Fetches data from external sources (WiFi controller, bluetooth chipset) and updates + * batterystats with that information. + * + * We first grab a lock specific to this method, then once all the data has been collected, + * we grab the mStats lock and update the data. + */ + void updateExternalStats(String reason) { + synchronized (mExternalStatsLock) { + if (mContext == null) { + // We haven't started yet (which means the BatteryStatsImpl object has + // no power profile. Don't consume data we can't compute yet. + return; + } + + final WifiActivityEnergyInfo wifiEnergyInfo = pullWifiEnergyInfoLocked(); + final BluetoothActivityEnergyInfo bluetoothEnergyInfo = pullBluetoothEnergyInfoLocked(); + synchronized (mStats) { + if (mStats.mRecordAllHistory) { + final long elapsedRealtime = SystemClock.elapsedRealtime(); + final long uptime = SystemClock.uptimeMillis(); + mStats.addHistoryEventLocked(elapsedRealtime, uptime, + BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, reason, 0); + } + mStats.updateKernelWakelocksLocked(); + mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime()); + mStats.updateWifiStateLocked(wifiEnergyInfo); + mStats.updateBluetoothStateLocked(bluetoothEnergyInfo); + } + } + } } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 9b7d0b2..a91a7ca 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -18,7 +18,9 @@ package com.android.server.am; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import android.app.ActivityManager; import android.app.AppGlobals; @@ -27,7 +29,6 @@ import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; @@ -40,9 +41,10 @@ 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 static com.android.server.am.ActivityManagerDebugConfig.*; + /** * BROADCASTS * @@ -50,11 +52,9 @@ import android.util.Slog; * foreground priority, and one for normal (background-priority) broadcasts. */ 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; + private static final String TAG = "BroadcastQueue"; + private static final String TAG_MU = TAG + POSTFIX_MU; + private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST; static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50; static final int MAX_BROADCAST_SUMMARY_HISTORY @@ -97,14 +97,27 @@ public final class BroadcastQueue { final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<BroadcastRecord>(); /** - * Historical data of past broadcasts, for debugging. + * Historical data of past broadcasts, for debugging. This is a ring buffer + * whose last element is at mHistoryNext. */ final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + int mHistoryNext = 0; /** - * Summary of historical data of past broadcasts, for debugging. + * Summary of historical data of past broadcasts, for debugging. This is a + * ring buffer whose last element is at mSummaryHistoryNext. */ final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY]; + int mSummaryHistoryNext = 0; + + /** + * Various milestone timestamps of entries in the mBroadcastSummaryHistory ring + * buffer, also tracked via the mSummaryHistoryNext index. These are all in wall + * clock time, not elapsed. + */ + final long[] mSummaryHistoryEnqueueTime = new long[MAX_BROADCAST_SUMMARY_HISTORY]; + final long[] mSummaryHistoryDispatchTime = new long[MAX_BROADCAST_SUMMARY_HISTORY]; + final long[] mSummaryHistoryFinishTime = new long[MAX_BROADCAST_SUMMARY_HISTORY]; /** * Set when we current have a BROADCAST_INTENT_MSG in flight. @@ -145,7 +158,7 @@ public final class BroadcastQueue { switch (msg.what) { case BROADCAST_INTENT_MSG: { if (DEBUG_BROADCAST) Slog.v( - TAG, "Received BROADCAST_INTENT_MSG"); + TAG_BROADCAST, "Received BROADCAST_INTENT_MSG"); processNextBroadcast(true); } break; case BROADCAST_TIMEOUT_MSG: { @@ -187,16 +200,18 @@ public final class BroadcastQueue { public void enqueueParallelBroadcastLocked(BroadcastRecord r) { mParallelBroadcasts.add(r); + r.enqueueClockTime = System.currentTimeMillis(); } public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { mOrderedBroadcasts.add(r); + r.enqueueClockTime = System.currentTimeMillis(); } public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) { for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { if (r.intent.filterEquals(mParallelBroadcasts.get(i).intent)) { - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "***** DROPPING PARALLEL [" + mQueueName + "]: " + r.intent); mParallelBroadcasts.set(i, r); @@ -209,7 +224,7 @@ public final class BroadcastQueue { public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) { for (int i=mOrderedBroadcasts.size()-1; i>0; i--) { if (r.intent.filterEquals(mOrderedBroadcasts.get(i).intent)) { - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "***** DROPPING ORDERED [" + mQueueName + "]: " + r.intent); mOrderedBroadcasts.set(i, r); @@ -221,7 +236,7 @@ public final class BroadcastQueue { private final void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) throws RemoteException { - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Process cur broadcast " + r + " for app " + app); if (app.thread == null) { throw new RemoteException(); @@ -238,7 +253,7 @@ public final class BroadcastQueue { boolean started = false; try { - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Delivering to component " + r.curComponent + ": " + r); mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); @@ -246,12 +261,12 @@ public final class BroadcastQueue { mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId, app.repProcState); - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Process cur broadcast " + r + " DELIVERED for app " + app); started = true; } finally { if (!started) { - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Process cur broadcast " + r + ": NOT STARTED!"); r.receiver = null; r.curApp = null; @@ -305,23 +320,22 @@ public final class BroadcastQueue { r.resultExtras, r.resultAbort, false); reschedule = true; } - - r = mPendingBroadcast; - if (r != null && r.curApp == app) { - if (DEBUG_BROADCAST) Slog.v(TAG, + if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) { + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "[" + mQueueName + "] skip & discard pending app " + r); + r = mPendingBroadcast; + } + + if (r != null) { logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, false); - reschedule = true; - } - if (reschedule) { scheduleBroadcastsLocked(); } } public void scheduleBroadcastsLocked() { - if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts [" + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts [" + mQueueName + "]: current=" + mBroadcastsScheduled); @@ -391,8 +405,7 @@ public final class BroadcastQueue { // 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()); + Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString()); r.state = BroadcastRecord.WAITING_SERVICES; return false; } @@ -412,7 +425,7 @@ public final class BroadcastQueue { 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"); + Slog.i(TAG, "Resuming delayed broadcast"); br.curComponent = null; br.state = BroadcastRecord.IDLE; processNextBroadcast(false); @@ -475,7 +488,7 @@ public final class BroadcastQueue { int mode = mService.mAppOpsService.noteOperation(r.appOp, filter.receiverList.uid, filter.packageName); if (mode != AppOpsManager.MODE_ALLOWED) { - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "App op " + r.appOp + " not allowed for broadcast to uid " + filter.receiverList.uid + " pkg " + filter.packageName); skip = true; @@ -513,14 +526,11 @@ public final class BroadcastQueue { } } try { - if (DEBUG_BROADCAST_LIGHT) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Delivering to " + filter - + " (seq=" + seq + "): " + r); - } + if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST, + "Delivering to " + filter + " : " + r); performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, - new Intent(r.intent), r.resultCode, r.resultData, - r.resultExtras, r.ordered, r.initialSticky, r.userId); + new Intent(r.intent), r.resultCode, r.resultData, + r.resultExtras, r.ordered, r.initialSticky, r.userId); if (ordered) { r.state = BroadcastRecord.CALL_DONE_RECEIVE; } @@ -542,7 +552,7 @@ public final class BroadcastQueue { synchronized(mService) { BroadcastRecord r; - if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast [" + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast [" + mQueueName + "]: " + mParallelBroadcasts.size() + " broadcasts, " + mOrderedBroadcasts.size() + " ordered broadcasts"); @@ -559,17 +569,17 @@ public final class BroadcastQueue { r.dispatchTime = SystemClock.uptimeMillis(); r.dispatchClockTime = System.currentTimeMillis(); final int N = r.receivers.size(); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast [" + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast [" + mQueueName + "] " + r); for (int i=0; i<N; i++) { Object target = r.receivers.get(i); - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Delivering non-ordered on [" + mQueueName + "] to registered " + target + ": " + r); deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); } addBroadcastToHistoryLocked(r); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast [" + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast [" + mQueueName + "] " + r); } @@ -579,11 +589,9 @@ public final class BroadcastQueue { // broadcast, then do nothing at this point. Just in case, we // check that the process we're waiting for still exists. if (mPendingBroadcast != null) { - if (DEBUG_BROADCAST_LIGHT) { - Slog.v(TAG, "processNextBroadcast [" - + mQueueName + "]: waiting for " - + mPendingBroadcast.curApp); - } + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, + "processNextBroadcast [" + mQueueName + "]: waiting for " + + mPendingBroadcast.curApp); boolean isDead; synchronized (mService.mPidsSelfLocked) { @@ -649,7 +657,7 @@ public final class BroadcastQueue { } if (r.state != BroadcastRecord.IDLE) { - if (DEBUG_BROADCAST) Slog.d(TAG, + if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST, "processNextBroadcast(" + mQueueName + ") called when not idle (state=" + r.state + ")"); @@ -662,12 +670,9 @@ public final class BroadcastQueue { // result if requested... if (r.resultTo != null) { try { - if (DEBUG_BROADCAST) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Finishing broadcast [" - + mQueueName + "] " + r.intent.getAction() - + " seq=" + seq + " app=" + r.callerApp); - } + if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST, + "Finishing broadcast [" + mQueueName + "] " + + r.intent.getAction() + " app=" + r.callerApp); performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, false, false, r.userId); @@ -682,11 +687,11 @@ public final class BroadcastQueue { } } - if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG"); + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG"); cancelBroadcastTimeoutLocked(); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast " - + r); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, + "Finished with ordered broadcast " + r); // ... and on to the next... addBroadcastToHistoryLocked(r); @@ -706,12 +711,12 @@ public final class BroadcastQueue { if (recIdx == 0) { r.dispatchTime = r.receiverTime; r.dispatchClockTime = System.currentTimeMillis(); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast [" + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast [" + mQueueName + "] " + r); } if (! mPendingBroadcastTimeoutMessage) { long timeoutTime = r.receiverTime + mTimeoutPeriod; - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Submitting BROADCAST_TIMEOUT_MSG [" + mQueueName + "] for " + r + " at " + timeoutTime); setBroadcastTimeoutLocked(timeoutTime); @@ -722,7 +727,7 @@ public final class BroadcastQueue { // Simple case: this is a registered receiver who gets // a direct call. BroadcastFilter filter = (BroadcastFilter)nextReceiver; - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Delivering ordered [" + mQueueName + "] to registered " + filter + ": " + r); @@ -730,7 +735,7 @@ public final class BroadcastQueue { if (r.receiver == null || !r.ordered) { // The receiver has already finished, so schedule to // process the next one. - if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing [" + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing [" + mQueueName + "]: ordered=" + r.ordered + " receiver=" + r.receiver); r.state = BroadcastRecord.IDLE; @@ -775,7 +780,8 @@ public final class BroadcastQueue { try { perm = AppGlobals.getPackageManager(). checkPermission(r.requiredPermission, - info.activityInfo.applicationInfo.packageName); + info.activityInfo.applicationInfo.packageName, + UserHandle.getUserId(info.activityInfo.applicationInfo.uid)); } catch (RemoteException e) { perm = PackageManager.PERMISSION_DENIED; } @@ -793,7 +799,7 @@ public final class BroadcastQueue { 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, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "App op " + r.appOp + " not allowed for broadcast to uid " + info.activityInfo.applicationInfo.uid + " pkg " + info.activityInfo.packageName); @@ -842,19 +848,18 @@ public final class BroadcastQueue { + info.activityInfo.packageName, e); } if (!isAvailable) { - if (DEBUG_BROADCAST) { - Slog.v(TAG, "Skipping delivery to " + info.activityInfo.packageName - + " / " + info.activityInfo.applicationInfo.uid - + " : package no longer available"); - } + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, + "Skipping delivery to " + info.activityInfo.packageName + " / " + + info.activityInfo.applicationInfo.uid + + " : package no longer available"); skip = true; } } if (skip) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "Skipping delivery of ordered [" - + mQueueName + "] " + r + " for whatever reason"); + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, + "Skipping delivery of ordered [" + mQueueName + "] " + + r + " for whatever reason"); r.receiver = null; r.curFilter = null; r.state = BroadcastRecord.IDLE; @@ -922,7 +927,7 @@ public final class BroadcastQueue { } // Not running -- get it started, to be executed when the app comes up. - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Need to start app [" + mQueueName + "] " + targetProcess + " for broadcast " + r); if ((r.curApp=mService.startProcessLocked(targetProcess, @@ -997,7 +1002,7 @@ public final class BroadcastQueue { // broadcast timeout message after each receiver finishes. Instead, we set up // an initial timeout then kick it down the road a little further as needed // when it expires. - if (DEBUG_BROADCAST) Slog.v(TAG, + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Premature timeout [" + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for " + timeoutTime); @@ -1012,7 +1017,7 @@ public final class BroadcastQueue { // 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 + Slog.i(TAG, "Waited long enough for: " + (br.curComponent != null ? br.curComponent.flattenToShortString() : "(null)")); br.curComponent = null; br.state = BroadcastRecord.IDLE; @@ -1070,18 +1075,28 @@ public final class BroadcastQueue { } } + private final int ringAdvance(int x, final int increment, final int ringSize) { + x += increment; + if (x < 0) return (ringSize - 1); + else if (x >= ringSize) return 0; + else return x; + } + private final void addBroadcastToHistoryLocked(BroadcastRecord r) { if (r.callingUid < 0) { // This was from a registerReceiver() call; ignore it. return; } - System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1, - MAX_BROADCAST_HISTORY-1); r.finishTime = SystemClock.uptimeMillis(); - mBroadcastHistory[0] = r; - System.arraycopy(mBroadcastSummaryHistory, 0, mBroadcastSummaryHistory, 1, - MAX_BROADCAST_SUMMARY_HISTORY-1); - mBroadcastSummaryHistory[0] = r.intent; + + mBroadcastHistory[mHistoryNext] = r; + mHistoryNext = ringAdvance(mHistoryNext, 1, MAX_BROADCAST_HISTORY); + + mBroadcastSummaryHistory[mSummaryHistoryNext] = r.intent; + mSummaryHistoryEnqueueTime[mSummaryHistoryNext] = r.enqueueClockTime; + mSummaryHistoryDispatchTime[mSummaryHistoryNext] = r.dispatchClockTime; + mSummaryHistoryFinishTime[mSummaryHistoryNext] = System.currentTimeMillis(); + mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY); } final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { @@ -1168,11 +1183,20 @@ public final class BroadcastQueue { int i; boolean printed = false; - for (i=0; i<MAX_BROADCAST_HISTORY; i++) { - BroadcastRecord r = mBroadcastHistory[i]; + + i = -1; + int lastIndex = mHistoryNext; + int ringIndex = lastIndex; + do { + // increasing index = more recent entry, and we want to print the most + // recent first and work backwards, so we roll through the ring backwards. + ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY); + BroadcastRecord r = mBroadcastHistory[ringIndex]; if (r == null) { - break; + continue; } + + i++; // genuine record of some sort even if we're filtering it out if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) { continue; } @@ -1200,17 +1224,33 @@ public final class BroadcastQueue { pw.print(" extras: "); pw.println(bundle.toString()); } } - } + } while (ringIndex != lastIndex); if (dumpPackage == null) { + lastIndex = ringIndex = mSummaryHistoryNext; if (dumpAll) { - i = 0; printed = false; + i = -1; + } else { + // roll over the 'i' full dumps that have already been issued + for (int j = i; + j > 0 && ringIndex != lastIndex;) { + ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY); + BroadcastRecord r = mBroadcastHistory[ringIndex]; + if (r == null) { + continue; + } + j--; + } } - for (; i<MAX_BROADCAST_SUMMARY_HISTORY; i++) { - Intent intent = mBroadcastSummaryHistory[i]; + // done skipping; dump the remainder of the ring. 'i' is still the ordinal within + // the overall broadcast history. + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + do { + ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY); + Intent intent = mBroadcastSummaryHistory[ringIndex]; if (intent == null) { - break; + continue; } if (!printed) { if (needSep) { @@ -1224,13 +1264,17 @@ public final class BroadcastQueue { pw.println(" ..."); break; } + i++; pw.print(" #"); pw.print(i); pw.print(": "); pw.println(intent.toShortString(false, true, true, false)); + pw.print(" enq="); pw.print(sdf.format(new Date(mSummaryHistoryEnqueueTime[ringIndex]))); + pw.print(" disp="); pw.print(sdf.format(new Date(mSummaryHistoryDispatchTime[ringIndex]))); + pw.print(" fin="); pw.println(sdf.format(new Date(mSummaryHistoryFinishTime[ringIndex]))); Bundle bundle = intent.getExtras(); if (bundle != null) { pw.print(" extras: "); pw.println(bundle.toString()); } - } + } while (ringIndex != lastIndex); } return needSep; diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index b2cfd7a..9a4d7a0 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -52,6 +52,7 @@ final class BroadcastRecord extends Binder { final int appOp; // an app op that is associated with this broadcast final List receivers; // contains BroadcastFilter and ResolveInfo IIntentReceiver resultTo; // who receives final result if non-null + long enqueueClockTime; // the clock time the broadcast was enqueued long dispatchTime; // when dispatch started on this set of receivers long dispatchClockTime; // the clock time the dispatch started long receiverTime; // when current receiver started for timeouts. @@ -102,7 +103,9 @@ final class BroadcastRecord extends Binder { pw.print(prefix); pw.print("requiredPermission="); pw.print(requiredPermission); pw.print(" appOp="); pw.println(appOp); } - pw.print(prefix); pw.print("dispatchClockTime="); + pw.print(prefix); pw.print("enqueueClockTime="); + pw.print(new Date(enqueueClockTime)); + pw.print(" dispatchClockTime="); pw.println(new Date(dispatchClockTime)); pw.print(prefix); pw.print("dispatchTime="); TimeUtils.formatDuration(dispatchTime, now, pw); diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java index ec500c2..0fe9231 100644 --- a/services/core/java/com/android/server/am/CompatModePackages.java +++ b/services/core/java/com/android/server/am/CompatModePackages.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static com.android.server.am.ActivityManagerDebugConfig.*; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -43,8 +45,8 @@ import android.util.Slog; import android.util.Xml; public final class CompatModePackages { - private final String TAG = ActivityManagerService.TAG; - private final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION; + private static final String TAG = TAG_WITH_CLASS_NAME ? "CompatModePackages" : TAG_AM; + private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private final ActivityManagerService mService; private final AtomicFile mFile; @@ -332,7 +334,7 @@ public final class CompatModePackages { } try { if (app.thread != null) { - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc " + if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc " + app.processName + " new compat " + ci); app.thread.updatePackageCompatibilityInfo(packageName, ci); } diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index d1682b8..9dd07a9 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -21,9 +21,6 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.util.Log; - import java.util.HashMap; import java.util.Map; diff --git a/services/core/java/com/android/server/am/DumpHeapProvider.java b/services/core/java/com/android/server/am/DumpHeapProvider.java new file mode 100644 index 0000000..a8b639e --- /dev/null +++ b/services/core/java/com/android/server/am/DumpHeapProvider.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 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.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.os.ParcelFileDescriptor; + +import java.io.File; +import java.io.FileNotFoundException; + +public class DumpHeapProvider extends ContentProvider { + static final Object sLock = new Object(); + static File sHeapDumpJavaFile; + + static public File getJavaFile() { + synchronized (sLock) { + return sHeapDumpJavaFile; + } + } + + @Override + public boolean onCreate() { + synchronized (sLock) { + File dataDir = Environment.getDataDirectory(); + File systemDir = new File(dataDir, "system"); + File heapdumpDir = new File(systemDir, "heapdump"); + heapdumpDir.mkdir(); + sHeapDumpJavaFile = new File(heapdumpDir, "javaheap.bin"); + } + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return "application/octet-stream"; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + synchronized (sLock) { + String path = uri.getEncodedPath(); + final String tag = Uri.decode(path); + if (tag.equals("/java")) { + return ParcelFileDescriptor.open(sHeapDumpJavaFile, + ParcelFileDescriptor.MODE_READ_ONLY); + } else { + throw new FileNotFoundException("Invalid path for " + uri); + } + } + } +} diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index c376744..9a645df 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -95,3 +95,11 @@ option java_package com.android.server.am # Home Stack brought to front or rear 30044 am_home_stack_moved (User|1|5),(To Front|1|5),(Top Stack Id|1|5),(Focused Stack Id|1|5),(Reason|3) + +# Running pre boot receiver +30045 am_pre_boot (User|1|5),(Package|3) + +# Report collection of global memory state +30046 am_meminfo (CachedKb|2|2),(FreeKb|2|2),(ZramKb|2|2),(KernelKb|2|2),(NativeKb|2|2) +# Report collection of memory used by a process +30047 am_pss (Pid|1|5),(UID|1|5),(Process Name|3),(PssKb|2|2),(UssKb|2|2) diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java index b3777ed..afde322 100644 --- a/services/core/java/com/android/server/am/LockTaskNotify.java +++ b/services/core/java/com/android/server/am/LockTaskNotify.java @@ -16,6 +16,7 @@ package com.android.server.am; +import android.app.ActivityManager; import android.content.Context; import android.os.Handler; import android.os.Message; @@ -44,15 +45,20 @@ public class LockTaskNotify { mHandler = new H(); } - public void showToast(boolean isLocked) { - mHandler.obtainMessage(H.SHOW_TOAST, isLocked ? 1 : 0, 0 /* Not used */).sendToTarget(); + public void showToast(int lockTaskModeState) { + mHandler.obtainMessage(H.SHOW_TOAST, lockTaskModeState, 0 /* Not used */).sendToTarget(); } - public void handleShowToast(boolean isLocked) { - String text = mContext.getString(isLocked - ? R.string.lock_to_app_toast_locked : R.string.lock_to_app_toast); - if (!isLocked && mAccessibilityManager.isEnabled()) { - text = mContext.getString(R.string.lock_to_app_toast_accessible); + public void handleShowToast(int lockTaskModeState) { + String text = null; + if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) { + text = mContext.getString(R.string.lock_to_app_toast_locked); + } else if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED) { + text = mContext.getString(mAccessibilityManager.isEnabled() + ? R.string.lock_to_app_toast_accessible : R.string.lock_to_app_toast); + } + if (text == null) { + return; } if (mLastToast != null) { mLastToast.cancel(); @@ -83,7 +89,7 @@ public class LockTaskNotify { public void handleMessage(Message msg) { switch(msg.what) { case SHOW_TOAST: - handleShowToast(msg.arg1 != 0); + handleShowToast(msg.arg1); break; } } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index ffaa03d..531de46 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -16,6 +16,9 @@ package com.android.server.am; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + import android.app.ActivityManager; import android.app.IActivityContainer; import android.content.IIntentSender; @@ -26,13 +29,18 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.os.TransactionTooLargeException; import android.os.UserHandle; import android.util.Slog; +import com.android.server.am.ActivityStackSupervisor.ActivityContainer; + import java.io.PrintWriter; import java.lang.ref.WeakReference; final class PendingIntentRecord extends IIntentSender.Stub { + private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentRecord" : TAG_AM; + final ActivityManagerService owner; final Key key; final int uid; @@ -43,7 +51,7 @@ final class PendingIntentRecord extends IIntentSender.Stub { String stringName; String lastTagPrefix; String lastTag; - + final static class Key { final int type; final String packageName; @@ -159,7 +167,7 @@ final class PendingIntentRecord extends IIntentSender.Stub { public int hashCode() { return hashCode; } - + public String toString() { return "Key{" + typeName() + " pkg=" + packageName + " intent=" @@ -167,7 +175,7 @@ final class PendingIntentRecord extends IIntentSender.Stub { ? requestIntent.toShortString(false, true, false, false) : "<null>") + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}"; } - + String typeName() { switch (type) { case ActivityManager.INTENT_SENDER_ACTIVITY: @@ -182,7 +190,7 @@ final class PendingIntentRecord extends IIntentSender.Stub { return Integer.toString(type); } } - + PendingIntentRecord(ActivityManagerService _owner, Key _k, int _u) { owner = _owner; key = _k; @@ -190,39 +198,53 @@ final class PendingIntentRecord extends IIntentSender.Stub { ref = new WeakReference<PendingIntentRecord>(this); } - public int send(int code, Intent intent, String resolvedType, - IIntentReceiver finishedReceiver, String requiredPermission) { + public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, + String requiredPermission) throws TransactionTooLargeException { return sendInner(code, intent, resolvedType, finishedReceiver, requiredPermission, null, null, 0, 0, 0, null, null); } - - int sendInner(int code, Intent intent, String resolvedType, - IIntentReceiver finishedReceiver, String requiredPermission, - IBinder resultTo, String resultWho, int requestCode, - int flagsMask, int flagsValues, Bundle options, IActivityContainer container) { + + int sendInner(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, + String requiredPermission, IBinder resultTo, String resultWho, int requestCode, + int flagsMask, int flagsValues, Bundle options, IActivityContainer container) + throws TransactionTooLargeException { synchronized(owner) { + final ActivityContainer activityContainer = (ActivityContainer)container; + if (activityContainer != null && activityContainer.mParentActivity != null && + activityContainer.mParentActivity.state + != ActivityStack.ActivityState.RESUMED) { + // Cannot start a child activity if the parent is not resumed. + return ActivityManager.START_CANCELED; + } if (!canceled) { sent = true; if ((key.flags&PendingIntent.FLAG_ONE_SHOT) != 0) { owner.cancelIntentSenderLocked(this, true); canceled = true; } + Intent finalIntent = key.requestIntent != null ? new Intent(key.requestIntent) : new Intent(); - if (intent != null) { - int changes = finalIntent.fillIn(intent, key.flags); - if ((changes&Intent.FILL_IN_DATA) == 0) { + + final boolean immutable = (key.flags & PendingIntent.FLAG_IMMUTABLE) != 0; + if (!immutable) { + if (intent != null) { + int changes = finalIntent.fillIn(intent, key.flags); + if ((changes & Intent.FILL_IN_DATA) == 0) { + resolvedType = key.requestResolvedType; + } + } else { resolvedType = key.requestResolvedType; } + flagsMask &= ~Intent.IMMUTABLE_FLAGS; + flagsValues &= flagsMask; + finalIntent.setFlags((finalIntent.getFlags() & ~flagsMask) | flagsValues); } else { resolvedType = key.requestResolvedType; } - flagsMask &= ~Intent.IMMUTABLE_FLAGS; - flagsValues &= flagsMask; - finalIntent.setFlags((finalIntent.getFlags()&~flagsMask) | flagsValues); - + final long origId = Binder.clearCallingIdentity(); - + boolean sendFinish = finishedReceiver != null; int userId = key.userId; if (userId == UserHandle.USER_CURRENT) { @@ -257,13 +279,14 @@ final class PendingIntentRecord extends IIntentSender.Stub { options, userId, container, null); } } catch (RuntimeException e) { - Slog.w(ActivityManagerService.TAG, - "Unable to send startActivity intent", e); + Slog.w(TAG, "Unable to send startActivity intent", e); } break; case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT: - key.activity.task.stack.sendActivityResultLocked(-1, key.activity, - key.who, key.requestCode, code, finalIntent); + if (key.activity.task.stack != null) { + key.activity.task.stack.sendActivityResultLocked(-1, key.activity, + key.who, key.requestCode, code, finalIntent); + } break; case ActivityManager.INTENT_SENDER_BROADCAST: try { @@ -277,21 +300,18 @@ final class PendingIntentRecord extends IIntentSender.Stub { sendFinish = false; } } catch (RuntimeException e) { - Slog.w(ActivityManagerService.TAG, - "Unable to send startActivity intent", e); + Slog.w(TAG, "Unable to send startActivity intent", e); } break; case ActivityManager.INTENT_SENDER_SERVICE: try { - owner.startServiceInPackage(uid, - finalIntent, resolvedType, userId); + owner.startServiceInPackage(uid, finalIntent, resolvedType, userId); } catch (RuntimeException e) { - Slog.w(ActivityManagerService.TAG, - "Unable to send startService intent", e); + Slog.w(TAG, "Unable to send startService intent", e); } break; } - + if (sendFinish) { try { finishedReceiver.performReceive(new Intent(finalIntent), 0, diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index c380160..cdfcd0c 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -16,6 +16,9 @@ package com.android.server.am; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; @@ -38,6 +41,8 @@ import android.view.Display; * Activity manager code dealing with processes. */ final class ProcessList { + private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; + // 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; @@ -363,6 +368,12 @@ final class ProcessList { case ActivityManager.PROCESS_STATE_TOP: procState = "T "; break; + case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE: + procState = "FS"; + break; + case ActivityManager.PROCESS_STATE_TOP_SLEEPING: + procState = "TS"; + break; case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: procState = "IF"; break; @@ -470,6 +481,8 @@ final class ProcessList { 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_FOREGROUND_SERVICE + PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP @@ -487,6 +500,8 @@ final class ProcessList { 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_FOREGROUND_SERVICE + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING 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 @@ -504,6 +519,8 @@ final class ProcessList { 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_FOREGROUND_SERVICE + PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING 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 @@ -521,6 +538,8 @@ final class ProcessList { PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP @@ -538,6 +557,8 @@ final class ProcessList { PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP + PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE + PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP @@ -633,8 +654,7 @@ final class ProcessList { LocalSocketAddress.Namespace.RESERVED)); sLmkdOutputStream = sLmkdSocket.getOutputStream(); } catch (IOException ex) { - Slog.w(ActivityManagerService.TAG, - "lowmemorykiller daemon socket open failed"); + Slog.w(TAG, "lowmemorykiller daemon socket open failed"); sLmkdSocket = null; return false; } @@ -659,8 +679,7 @@ final class ProcessList { sLmkdOutputStream.write(buf.array(), 0, buf.position()); return; } catch (IOException ex) { - Slog.w(ActivityManagerService.TAG, - "Error writing to lowmemorykiller socket"); + Slog.w(TAG, "Error writing to lowmemorykiller socket"); try { sLmkdSocket.close(); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index a6c616a..29e14f8 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -16,7 +16,12 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + import android.util.ArraySet; +import android.util.DebugUtils; import android.util.EventLog; import android.util.Slog; import com.android.internal.app.ProcessStats; @@ -48,6 +53,8 @@ import java.util.ArrayList; * is currently running. */ final class ProcessRecord { + private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessRecord" : TAG_AM; + private final BatteryStatsImpl mBatteryStats; // 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 @@ -83,10 +90,10 @@ final class ProcessRecord { int curSchedGroup; // Currently desired scheduling class int setSchedGroup; // Last set to background scheduling class int trimMemoryLevel; // Last selected memory trimming level - 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 + int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state + int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state + int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker + int pssProcState = PROCESS_STATE_NONEXISTENT; // 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 setIsForeground; // Running foreground UI when last set? @@ -248,8 +255,9 @@ final class ProcessRecord { 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(" lastPss="); DebugUtils.printSizeValue(pw, lastPss*1024); + pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss*1024); + pw.println(); pw.print(prefix); pw.print("cached="); pw.print(cached); pw.print(" empty="); pw.println(empty); if (serviceb) { @@ -418,7 +426,7 @@ final class ProcessRecord { tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList); origBase.makeInactive(); } - baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid, + baseProcessTracker = tracker.getProcessStateLocked(info.packageName, uid, info.versionCode, processName); baseProcessTracker.makeActive(); for (int i=0; i<pkgList.size(); i++) { @@ -426,7 +434,7 @@ final class ProcessRecord { if (holder.state != null && holder.state != origBase) { holder.state.makeInactive(); } - holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid, + holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), uid, info.versionCode, processName); if (holder.state != baseProcessTracker) { holder.state.makeActive(); @@ -470,7 +478,7 @@ final class ProcessRecord { } return false; } - + public void stopFreezingAllLocked() { int i = activities.size(); while (i > 0) { @@ -478,7 +486,7 @@ final class ProcessRecord { activities.get(i).stopFreezingScreenLocked(true); } } - + public void unlinkDeathRecipient() { if (deathRecipient != null && thread != null) { thread.asBinder().unlinkToDeath(deathRecipient, 0); @@ -522,8 +530,7 @@ final class ProcessRecord { void kill(String reason, boolean noisy) { if (!killedByAm) { if (noisy) { - Slog.i(ActivityManagerService.TAG, "Killing " + toShortString() + " (adj " + setAdj - + "): " + reason); + Slog.i(TAG, "Killing " + toShortString() + " (adj " + setAdj + "): " + reason); } EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason); Process.killProcessQuiet(pid); @@ -617,7 +624,7 @@ final class ProcessRecord { versionCode); if (baseProcessTracker != null) { holder.state = tracker.getProcessStateLocked( - pkg, info.uid, versionCode, processName); + pkg, uid, versionCode, processName); pkgList.put(pkg, holder); if (holder.state != baseProcessTracker) { holder.state.makeActive(); @@ -664,7 +671,7 @@ final class ProcessRecord { } pkgList.clear(); ProcessStats.ProcessState ps = tracker.getProcessStateLocked( - info.packageName, info.uid, info.versionCode, processName); + info.packageName, uid, info.versionCode, processName); ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder( info.versionCode); holder.state = ps; diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index 55aec65..9634dff 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -445,14 +445,14 @@ public final class ProcessStatsService extends IProcessStats.Stub { mAm.mContext.enforceCallingOrSelfPermission( android.Manifest.permission.PACKAGE_USAGE_STATS, null); Parcel current = Parcel.obtain(); + synchronized (mAm) { + long now = SystemClock.uptimeMillis(); + mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); + mProcessStats.mTimePeriodEndUptime = now; + mProcessStats.writeToParcel(current, now, 0); + } mWriteLock.lock(); try { - synchronized (mAm) { - long now = SystemClock.uptimeMillis(); - mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); - mProcessStats.mTimePeriodEndUptime = now; - mProcessStats.writeToParcel(current, now, 0); - } if (historic != null) { ArrayList<String> files = getCommittedFiles(0, false, true); if (files != null) { @@ -476,18 +476,18 @@ public final class ProcessStatsService extends IProcessStats.Stub { public ParcelFileDescriptor getStatsOverTime(long minTime) { mAm.mContext.enforceCallingOrSelfPermission( android.Manifest.permission.PACKAGE_USAGE_STATS, null); + Parcel current = Parcel.obtain(); + long curTime; + synchronized (mAm) { + long now = SystemClock.uptimeMillis(); + mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); + mProcessStats.mTimePeriodEndUptime = now; + mProcessStats.writeToParcel(current, now, 0); + curTime = mProcessStats.mTimePeriodEndRealtime + - mProcessStats.mTimePeriodStartRealtime; + } mWriteLock.lock(); try { - Parcel current = Parcel.obtain(); - long curTime; - synchronized (mAm) { - long now = SystemClock.uptimeMillis(); - mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); - mProcessStats.mTimePeriodEndUptime = now; - mProcessStats.writeToParcel(current, now, 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); diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java new file mode 100644 index 0000000..3a20ded --- /dev/null +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2014 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 com.android.server.am.ActivityManagerDebugConfig.*; +import static com.android.server.am.TaskRecord.INVALID_TASK_ID; + +import android.app.ActivityManager; +import android.app.AppGlobals; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; + +/** + * Class for managing the recent tasks list. + */ +class RecentTasks extends ArrayList<TaskRecord> { + private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM; + private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; + private static final String TAG_TASKS = TAG + POSTFIX_TASKS; + + // Maximum number recent bitmaps to keep in memory. + private static final int MAX_RECENT_BITMAPS = 3; + + // Activity manager service. + private final ActivityManagerService mService; + + // Mainly to avoid object recreation on multiple calls. + private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>(); + private final HashMap<ComponentName, ActivityInfo> tmpAvailActCache = new HashMap<>(); + private final HashMap<String, ApplicationInfo> tmpAvailAppCache = new HashMap<>(); + private final ActivityInfo tmpActivityInfo = new ActivityInfo(); + private final ApplicationInfo tmpAppInfo = new ApplicationInfo(); + + RecentTasks(ActivityManagerService service) { + mService = service; + } + + TaskRecord taskForIdLocked(int id) { + final int recentsCount = size(); + for (int i = 0; i < recentsCount; i++) { + TaskRecord tr = get(i); + if (tr.taskId == id) { + return tr; + } + } + return null; + } + + /** Remove recent tasks for a user. */ + void removeTasksForUserLocked(int userId) { + if(userId <= 0) { + Slog.i(TAG, "Can't remove recent task on user " + userId); + return; + } + + for (int i = size() - 1; i >= 0; --i) { + TaskRecord tr = get(i); + if (tr.userId == userId) { + if(DEBUG_TASKS) Slog.i(TAG_TASKS, + "remove RecentTask " + tr + " when finishing user" + userId); + remove(i); + tr.removedFromRecents(); + } + } + + // Remove tasks from persistent storage. + mService.notifyTaskPersisterLocked(null, true); + } + + /** + * Update the recent tasks lists: make sure tasks should still be here (their + * applications / activities still exist), update their availability, fix-up ordering + * of affiliations. + */ + void cleanupLocked(int userId) { + int recentsCount = size(); + if (recentsCount == 0) { + // Happens when called from the packagemanager broadcast before boot, + // or just any empty list. + return; + } + + final IPackageManager pm = AppGlobals.getPackageManager(); + final int[] users = (userId == UserHandle.USER_ALL) + ? mService.getUsersLocked() : new int[] { userId }; + for (int userIdx = 0; userIdx < users.length; userIdx++) { + final int user = users[userIdx]; + recentsCount = size() - 1; + for (int i = recentsCount; i >= 0; i--) { + TaskRecord task = get(i); + if (task.userId != user) { + // Only look at tasks for the user ID of interest. + continue; + } + if (task.autoRemoveRecents && task.getTopActivity() == null) { + // This situation is broken, and we should just get rid of it now. + remove(i); + task.removedFromRecents(); + Slog.w(TAG, "Removing auto-remove without activity: " + task); + continue; + } + // Check whether this activity is currently available. + if (task.realActivity != null) { + ActivityInfo ai = tmpAvailActCache.get(task.realActivity); + if (ai == null) { + try { + ai = pm.getActivityInfo(task.realActivity, + PackageManager.GET_UNINSTALLED_PACKAGES + | PackageManager.GET_DISABLED_COMPONENTS, user); + } catch (RemoteException e) { + // Will never happen. + continue; + } + if (ai == null) { + ai = tmpActivityInfo; + } + tmpAvailActCache.put(task.realActivity, ai); + } + if (ai == tmpActivityInfo) { + // This could be either because the activity no longer exists, or the + // app is temporarily gone. For the former we want to remove the recents + // entry; for the latter we want to mark it as unavailable. + ApplicationInfo app = tmpAvailAppCache.get(task.realActivity.getPackageName()); + if (app == null) { + try { + app = pm.getApplicationInfo(task.realActivity.getPackageName(), + PackageManager.GET_UNINSTALLED_PACKAGES + | PackageManager.GET_DISABLED_COMPONENTS, user); + } catch (RemoteException e) { + // Will never happen. + continue; + } + if (app == null) { + app = tmpAppInfo; + } + tmpAvailAppCache.put(task.realActivity.getPackageName(), app); + } + if (app == tmpAppInfo || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { + // Doesn't exist any more! Good-bye. + remove(i); + task.removedFromRecents(); + Slog.w(TAG, "Removing no longer valid recent: " + task); + continue; + } else { + // Otherwise just not available for now. + if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS, + "Making recent unavailable: " + task); + task.isAvailable = false; + } + } else { + if (!ai.enabled || !ai.applicationInfo.enabled + || (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { + if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS, + "Making recent unavailable: " + task + + " (enabled=" + ai.enabled + "/" + ai.applicationInfo.enabled + + " flags=" + Integer.toHexString(ai.applicationInfo.flags) + + ")"); + task.isAvailable = false; + } else { + if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS, + "Making recent available: " + task); + task.isAvailable = true; + } + } + } + } + } + + // Verify the affiliate chain for each task. + int i = 0; + recentsCount = size(); + while (i < recentsCount) { + i = processNextAffiliateChainLocked(i); + } + // recent tasks are now in sorted, affiliated order. + } + + private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) { + int recentsCount = size(); + TaskRecord top = task; + int topIndex = taskIndex; + while (top.mNextAffiliate != null && topIndex > 0) { + top = top.mNextAffiliate; + topIndex--; + } + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at " + + topIndex + " from intial " + taskIndex); + // Find the end of the chain, doing a sanity check along the way. + boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId; + int endIndex = topIndex; + TaskRecord prev = top; + while (endIndex < recentsCount) { + TaskRecord cur = get(endIndex); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @" + + endIndex + " " + cur); + if (cur == top) { + // Verify start of the chain. + if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": first task has next affiliate: " + prev); + sane = false; + break; + } + } else { + // Verify middle of the chain's next points back to the one before. + if (cur.mNextAffiliate != prev + || cur.mNextAffiliateTaskId != prev.taskId) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": middle task " + cur + " @" + endIndex + + " has bad next affiliate " + + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId + + ", expected " + prev); + sane = false; + break; + } + } + if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) { + // Chain ends here. + if (cur.mPrevAffiliate != null) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": last task " + cur + " has previous affiliate " + + cur.mPrevAffiliate); + sane = false; + } + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: end of chain @" + endIndex); + break; + } else { + // Verify middle of the chain's prev points to a valid item. + if (cur.mPrevAffiliate == null) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": task " + cur + " has previous affiliate " + + cur.mPrevAffiliate + " but should be id " + + cur.mPrevAffiliate); + sane = false; + break; + } + } + if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": task " + cur + " has affiliated id " + + cur.mAffiliatedTaskId + " but should be " + + task.mAffiliatedTaskId); + sane = false; + break; + } + prev = cur; + endIndex++; + if (endIndex >= recentsCount) { + Slog.wtf(TAG, "Bad chain ran off index " + endIndex + + ": last task " + prev); + sane = false; + break; + } + } + if (sane) { + if (endIndex < taskIndex) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": did not extend to task " + task + " @" + taskIndex); + sane = false; + } + } + if (sane) { + // All looks good, we can just move all of the affiliated tasks + // to the top. + for (int i=topIndex; i<=endIndex; i++) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task + + " from " + i + " to " + (i-topIndex)); + TaskRecord cur = remove(i); + add(i - topIndex, cur); + } + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex + + " to " + endIndex); + return true; + } + + // Whoops, couldn't do it. + return false; + } + + final void addLocked(TaskRecord task) { + final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId + || task.mNextAffiliateTaskId != INVALID_TASK_ID + || task.mPrevAffiliateTaskId != INVALID_TASK_ID; + + int recentsCount = size(); + // Quick case: never add voice sessions. + if (task.voiceSession != null) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "addRecent: not adding voice interaction " + task); + return; + } + // Another quick case: check if the top-most recent task is the same. + if (!isAffiliated && recentsCount > 0 && get(0) == task) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task); + return; + } + // Another quick case: check if this is part of a set of affiliated + // tasks that are at the top. + if (isAffiliated && recentsCount > 0 && task.inRecents + && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0) + + " at top when adding " + task); + return; + } + + boolean needAffiliationFix = false; + + // Slightly less quick case: the task is already in recents, so all we need + // to do is move it. + if (task.inRecents) { + int taskIndex = indexOf(task); + if (taskIndex >= 0) { + if (!isAffiliated) { + // Simple case: this is not an affiliated task, so we just move it to the front. + remove(taskIndex); + add(0, task); + mService.notifyTaskPersisterLocked(task, false); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task + + " from " + taskIndex); + return; + } else { + // More complicated: need to keep all affiliated tasks together. + if (moveAffiliatedTasksToFront(task, taskIndex)) { + // All went well. + return; + } + + // Uh oh... something bad in the affiliation chain, try to rebuild + // everything and then go through our general path of adding a new task. + needAffiliationFix = true; + } + } else { + Slog.wtf(TAG, "Task with inRecent not in recents: " + task); + needAffiliationFix = true; + } + } + + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task); + trimForTaskLocked(task, true); + + recentsCount = size(); + final int maxRecents = ActivityManager.getMaxRecentTasksStatic(); + while (recentsCount >= maxRecents) { + final TaskRecord tr = remove(recentsCount - 1); + tr.removedFromRecents(); + recentsCount--; + } + task.inRecents = true; + if (!isAffiliated || needAffiliationFix) { + // If this is a simple non-affiliated task, or we had some failure trying to + // handle it as part of an affilated task, then just place it at the top. + add(0, task); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task); + } else if (isAffiliated) { + // If this is a new affiliated task, then move all of the affiliated tasks + // to the front and insert this new one. + TaskRecord other = task.mNextAffiliate; + if (other == null) { + other = task.mPrevAffiliate; + } + if (other != null) { + int otherIndex = indexOf(other); + if (otherIndex >= 0) { + // Insert new task at appropriate location. + int taskIndex; + if (other == task.mNextAffiliate) { + // We found the index of our next affiliation, which is who is + // before us in the list, so add after that point. + taskIndex = otherIndex+1; + } else { + // We found the index of our previous affiliation, which is who is + // after us in the list, so add at their position. + taskIndex = otherIndex; + } + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "addRecent: new affiliated task added at " + taskIndex + ": " + task); + add(taskIndex, task); + + // Now move everything to the front. + if (moveAffiliatedTasksToFront(task, taskIndex)) { + // All went well. + return; + } + + // Uh oh... something bad in the affiliation chain, try to rebuild + // everything and then go through our general path of adding a new task. + needAffiliationFix = true; + } else { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "addRecent: couldn't find other affiliation " + other); + needAffiliationFix = true; + } + } else { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, + "addRecent: adding affiliated task without next/prev:" + task); + needAffiliationFix = true; + } + } + + if (needAffiliationFix) { + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations"); + cleanupLocked(task.userId); + } + } + + /** + * If needed, remove oldest existing entries in recents that are for the same kind + * of task as the given one. + */ + int trimForTaskLocked(TaskRecord task, boolean doTrim) { + int recentsCount = size(); + final Intent intent = task.intent; + final boolean document = intent != null && intent.isDocument(); + + int maxRecents = task.maxRecents - 1; + for (int i = 0; i < recentsCount; i++) { + final TaskRecord tr = get(i); + if (task != tr) { + if (task.userId != tr.userId) { + continue; + } + if (i > MAX_RECENT_BITMAPS) { + tr.freeLastThumbnail(); + } + final Intent trIntent = tr.intent; + if ((task.affinity == null || !task.affinity.equals(tr.affinity)) && + (intent == null || !intent.filterEquals(trIntent))) { + continue; + } + final boolean trIsDocument = trIntent != null && trIntent.isDocument(); + if (document && trIsDocument) { + // These are the same document activity (not necessarily the same doc). + if (maxRecents > 0) { + --maxRecents; + continue; + } + // Hit the maximum number of documents for this task. Fall through + // and remove this document from recents. + } else if (document || trIsDocument) { + // Only one of these is a document. Not the droid we're looking for. + continue; + } + } + + if (!doTrim) { + // If the caller is not actually asking for a trim, just tell them we reached + // a point where the trim would happen. + return i; + } + + // Either task and tr are the same or, their affinities match or their intents match + // and neither of them is a document, or they are documents using the same activity + // and their maxRecents has been reached. + tr.disposeThumbnail(); + remove(i); + if (task != tr) { + tr.removedFromRecents(); + } + i--; + recentsCount--; + if (task.intent == null) { + // If the new recent task we are adding is not fully + // specified, then replace it with the existing recent task. + task = tr; + } + mService.notifyTaskPersisterLocked(tr, false); + } + + return -1; + } + + // Sort by taskId + private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() { + @Override + public int compare(TaskRecord lhs, TaskRecord rhs) { + return rhs.taskId - lhs.taskId; + } + }; + + // Extract the affiliates of the chain containing recent at index start. + private int processNextAffiliateChainLocked(int start) { + final TaskRecord startTask = get(start); + final int affiliateId = startTask.mAffiliatedTaskId; + + // Quick identification of isolated tasks. I.e. those not launched behind. + if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null && + startTask.mNextAffiliate == null) { + // There is still a slim chance that there are other tasks that point to this task + // and that the chain is so messed up that this task no longer points to them but + // the gain of this optimization outweighs the risk. + startTask.inRecents = true; + return start + 1; + } + + // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents. + mTmpRecents.clear(); + for (int i = size() - 1; i >= start; --i) { + final TaskRecord task = get(i); + if (task.mAffiliatedTaskId == affiliateId) { + remove(i); + mTmpRecents.add(task); + } + } + + // Sort them all by taskId. That is the order they were create in and that order will + // always be correct. + Collections.sort(mTmpRecents, sTaskRecordComparator); + + // Go through and fix up the linked list. + // The first one is the end of the chain and has no next. + final TaskRecord first = mTmpRecents.get(0); + first.inRecents = true; + if (first.mNextAffiliate != null) { + Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate); + first.setNextAffiliate(null); + mService.notifyTaskPersisterLocked(first, false); + } + // Everything in the middle is doubly linked from next to prev. + final int tmpSize = mTmpRecents.size(); + for (int i = 0; i < tmpSize - 1; ++i) { + final TaskRecord next = mTmpRecents.get(i); + final TaskRecord prev = mTmpRecents.get(i + 1); + if (next.mPrevAffiliate != prev) { + Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate + + " setting prev=" + prev); + next.setPrevAffiliate(prev); + mService.notifyTaskPersisterLocked(next, false); + } + if (prev.mNextAffiliate != next) { + Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate + + " setting next=" + next); + prev.setNextAffiliate(next); + mService.notifyTaskPersisterLocked(prev, false); + } + prev.inRecents = true; + } + // The last one is the beginning of the list and has no prev. + final TaskRecord last = mTmpRecents.get(tmpSize - 1); + if (last.mPrevAffiliate != null) { + Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate); + last.setPrevAffiliate(null); + mService.notifyTaskPersisterLocked(last, false); + } + + // Insert the group back into mRecentTasks at start. + addAll(start, mTmpRecents); + mTmpRecents.clear(); + + // Let the caller know where we left off. + return start + tmpSize; + } + +} diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 1cb1bb8..f7d241e 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -47,10 +47,15 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; + /** * A running application service. */ final class ServiceRecord extends Binder { + private static final String TAG = TAG_WITH_CLASS_NAME ? "ServiceRecord" : TAG_AM; + // Maximum number of delivery attempts before giving up. static final int MAX_DELIVERY_COUNT = 3; @@ -458,7 +463,7 @@ final class ServiceRecord extends Binder { appInfo.packageName, null)); PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0, runningIntent, PendingIntent.FLAG_UPDATE_CURRENT); - localForegroundNoti.color = ams.mContext.getResources().getColor( + localForegroundNoti.color = ams.mContext.getColor( com.android.internal .R.color.system_notification_accent_color); localForegroundNoti.setLatestEventInfo(ctx, @@ -487,8 +492,7 @@ final class ServiceRecord extends Binder { appUid, appPid, null, localForegroundId, localForegroundNoti, outId, userId); } catch (RuntimeException e) { - Slog.w(ActivityManagerService.TAG, - "Error showing notification for service", e); + Slog.w(TAG, "Error showing notification for service", e); // If it gave us a garbage notification, it doesn't // get to be foreground. ams.setServiceForeground(name, ServiceRecord.this, @@ -517,8 +521,7 @@ final class ServiceRecord extends Binder { inm.cancelNotificationWithTag(localPackageName, null, localForegroundId, userId); } catch (RuntimeException e) { - Slog.w(ActivityManagerService.TAG, - "Error canceling notification for service", e); + Slog.w(TAG, "Error canceling notification for service", e); } catch (RemoteException e) { } } diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index 24c723f..318cd45 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -99,6 +99,7 @@ public class TaskPersister { private final ActivityManagerService mService; private final ActivityStackSupervisor mStackSupervisor; + private final RecentTasks mRecentTasks; /** Value determines write delay mode as follows: * < 0 We are Flushing. No delays between writes until the image queue is drained and all @@ -140,7 +141,8 @@ public class TaskPersister { // tasks. private long mExpiredTasksCleanupTime = Long.MAX_VALUE; - TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) { + TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor, + RecentTasks recentTasks) { sTasksDir = new File(systemDir, TASKS_DIRNAME); if (!sTasksDir.exists()) { if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir); @@ -161,12 +163,14 @@ public class TaskPersister { mStackSupervisor = stackSupervisor; mService = stackSupervisor.mService; - + mRecentTasks = recentTasks; mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread"); } void startPersisting() { - mLazyTaskWriterThread.start(); + if (!mLazyTaskWriterThread.isAlive()) { + mLazyTaskWriterThread.start(); + } } private void removeThumbnails(TaskRecord task) { @@ -725,10 +729,9 @@ public class TaskPersister { // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just // adding to the back of the list. int spaceLeft = - ActivityManager.getMaxRecentTasksStatic() - - mService.mRecentTasks.size(); + ActivityManager.getMaxRecentTasksStatic() - mRecentTasks.size(); if (spaceLeft >= tasks.size()) { - mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks); + mRecentTasks.addAll(mRecentTasks.size(), tasks); for (int k = tasks.size() - 1; k >= 0; k--) { // Persist new tasks. wakeup(tasks.get(k), false); @@ -941,10 +944,9 @@ public class TaskPersister { if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files."); persistentTaskIds.clear(); synchronized (mService) { - final ArrayList<TaskRecord> tasks = mService.mRecentTasks; - if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks); - for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { - final TaskRecord task = tasks.get(taskNdx); + if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + mRecentTasks); + for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mRecentTasks.get(taskNdx); if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" + task.isPersistable); if ((task.isPersistable || task.inRecents) diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 60f8a48..f3b4516 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -16,7 +16,13 @@ package com.android.server.am; -import static com.android.server.am.ActivityManagerService.TAG; +import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; +import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED; +import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; +import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE; @@ -54,6 +60,10 @@ import java.io.PrintWriter; import java.util.ArrayList; final class TaskRecord { + private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM; + private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; + private static final String TAG_TASKS = TAG + POSTFIX_TASKS; + static final String ATTR_TASKID = "task_id"; private static final String TAG_INTENT = "intent"; private static final String TAG_AFFINITYINTENT = "affinity_intent"; @@ -79,6 +89,7 @@ final class TaskRecord { private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color"; private static final String ATTR_CALLING_UID = "calling_uid"; private static final String ATTR_CALLING_PACKAGE = "calling_package"; + private static final String ATTR_RESIZEABLE = "resizeable"; private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail"; @@ -109,10 +120,26 @@ final class TaskRecord { String stringName; // caching of toString() result. int userId; // user for which this task was created - int creatorUid; // The app uid that originally created the task int numFullscreen; // Number of fullscreen activities. + boolean mResizeable; // Activities in the task resizeable. Based on the resizable setting of + // the root activity. + int mLockTaskMode; // Which tasklock mode to launch this task in. One of + // ActivityManager.LOCK_TASK_LAUNCH_MODE_* + /** Can't be put in lockTask mode. */ + final static int LOCK_TASK_AUTH_DONT_LOCK = 0; + /** Can enter lockTask with user approval if not already in lockTask. */ + final static int LOCK_TASK_AUTH_PINNABLE = 1; + /** Starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing lockTask task. */ + final static int LOCK_TASK_AUTH_LAUNCHABLE = 2; + /** Enters LOCK_TASK_MODE_LOCKED via startLockTask(), enters LOCK_TASK_MODE_PINNED from + * Overview. Can start over existing lockTask task. */ + final static int LOCK_TASK_AUTH_WHITELISTED = 3; + int mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE; + + int mLockTaskUid = -1; // The uid of the application that called startLockTask(). + // This represents the last resolved activity values for this task // NOTE: This value needs to be persisted with each task TaskDescription lastTaskDescription = new TaskDescription(); @@ -176,7 +203,9 @@ final class TaskRecord { voiceSession = _voiceSession; voiceInteractor = _voiceInteractor; isAvailable = true; - mActivities = new ArrayList<ActivityRecord>(); + mActivities = new ArrayList<>(); + mCallingUid = info.applicationInfo.uid; + mCallingPackage = info.packageName; setIntent(_intent, info); } @@ -191,13 +220,13 @@ final class TaskRecord { voiceSession = null; voiceInteractor = null; isAvailable = true; - mActivities = new ArrayList<ActivityRecord>(); + mActivities = new ArrayList<>(); + mCallingUid = info.applicationInfo.uid; + mCallingPackage = info.packageName; setIntent(_intent, info); taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE; isPersistable = true; - mCallingUid = info.applicationInfo.uid; - mCallingPackage = info.packageName; // Clamp to [1, max]. maxRecents = Math.min(Math.max(info.maxRecents, 1), ActivityManager.getMaxAppRecentsLimitStatic()); @@ -206,19 +235,17 @@ final class TaskRecord { mTaskToReturnTo = HOME_ACTIVITY_TYPE; userId = UserHandle.getUserId(info.applicationInfo.uid); lastTaskDescription = _taskDescription; - mCallingUid = info.applicationInfo.uid; - mCallingPackage = info.packageName; } - TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent, - String _affinity, String _rootAffinity, ComponentName _realActivity, - ComponentName _origActivity, boolean _rootWasReset, boolean _autoRemoveRecents, - boolean _askedCompatMode, int _taskType, int _userId, int _effectiveUid, - String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime, - long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity, - TaskDescription _lastTaskDescription, int taskAffiliation, - int prevTaskId, int nextTaskId, int taskAffiliationColor, int callingUid, - String callingPackage) { + private TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, + Intent _affinityIntent, String _affinity, String _rootAffinity, + ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, + boolean _autoRemoveRecents, boolean _askedCompatMode, int _taskType, int _userId, + int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities, + long _firstActiveTime, long _lastActiveTime, long lastTimeMoved, + boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription, + int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor, + int callingUid, String callingPackage, boolean resizeable) { mService = service; mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION; @@ -227,7 +254,7 @@ final class TaskRecord { intent = _intent; affinityIntent = _affinityIntent; affinity = _affinity; - rootAffinity = _affinity; + rootAffinity = _rootAffinity; voiceSession = null; voiceInteractor = null; realActivity = _realActivity; @@ -253,6 +280,7 @@ final class TaskRecord { mNextAffiliateTaskId = nextTaskId; mCallingUid = callingUid; mCallingPackage = callingPackage; + mResizeable = resizeable; } void touchActiveTime() { @@ -268,9 +296,9 @@ final class TaskRecord { /** Sets the original intent, and the calling uid and package. */ void setIntent(ActivityRecord r) { - setIntent(r.intent, r.info); mCallingUid = r.launchedFromUid; mCallingPackage = r.launchedFromPackage; + setIntent(r.intent, r.info); } /** Sets the original intent, _without_ updating the calling uid or package. */ @@ -303,8 +331,7 @@ final class TaskRecord { _intent.setSourceBounds(null); } } - if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG, - "Setting Intent of " + this + " to " + _intent); + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Setting Intent of " + this + " to " + _intent); intent = _intent; realActivity = _intent != null ? _intent.getComponent() : null; origActivity = null; @@ -316,7 +343,7 @@ final class TaskRecord { targetIntent.setComponent(targetComponent); targetIntent.setSelector(null); targetIntent.setSourceBounds(null); - if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG, + if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Setting Intent of " + this + " to target " + targetIntent); intent = targetIntent; realActivity = targetComponent; @@ -339,8 +366,8 @@ final class TaskRecord { if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) { // If the activity itself has requested auto-remove, then just always do it. autoRemoveRecents = true; - } else if ((intentFlags & (Intent.FLAG_ACTIVITY_NEW_DOCUMENT - | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)) == Intent.FLAG_ACTIVITY_NEW_DOCUMENT) { + } else if ((intentFlags & (FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS)) + == FLAG_ACTIVITY_NEW_DOCUMENT) { // If the caller has not asked for the document to be retained, then we may // want to turn on auto-remove, depending on whether the target has set its // own document launch mode. @@ -352,13 +379,14 @@ final class TaskRecord { } else { autoRemoveRecents = false; } + mResizeable = info.resizeable; + mLockTaskMode = info.lockTaskLaunchMode; + setLockTaskAuth(); } void setTaskToReturnTo(int taskToReturnTo) { - if (IGNORE_RETURN_TO_RECENTS && taskToReturnTo == RECENTS_ACTIVITY_TYPE) { - taskToReturnTo = HOME_ACTIVITY_TYPE; - } - mTaskToReturnTo = taskToReturnTo; + mTaskToReturnTo = (IGNORE_RETURN_TO_RECENTS && taskToReturnTo == RECENTS_ACTIVITY_TYPE) + ? HOME_ACTIVITY_TYPE : taskToReturnTo; } int getTaskToReturnTo() { @@ -492,10 +520,12 @@ final class TaskRecord { } ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { - for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) { - ActivityRecord r = mActivities.get(activityNdx); - if (!r.finishing && r != notTop && stack.okToShowLocked(r)) { - return r; + if (stack != null) { + for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord r = mActivities.get(activityNdx); + if (!r.finishing && r != notTop && stack.okToShowLocked(r)) { + return r; + } } } return null; @@ -610,8 +640,8 @@ final class TaskRecord { mActivities.remove(activityNdx); --activityNdx; --numActivities; - } else if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear", - false)) { + } else if (stack.finishActivityLocked( + r, Activity.RESULT_CANCELED, null, "clear-task-index", false)) { --activityNdx; --numActivities; } @@ -658,8 +688,8 @@ final class TaskRecord { if (opts != null) { ret.updateOptionsLocked(opts); } - if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear", - false)) { + if (stack != null && stack.finishActivityLocked( + r, Activity.RESULT_CANCELED, null, "clear-task-stack", false)) { --activityNdx; --numActivities; } @@ -671,8 +701,10 @@ final class TaskRecord { 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); + if (stack != null) { + stack.finishActivityLocked( + ret, Activity.RESULT_CANCELED, null, "clear-task-top", false); + } return null; } } @@ -702,6 +734,53 @@ final class TaskRecord { performClearTaskAtIndexLocked(0); } + private boolean isPrivileged() { + final ProcessRecord proc = mService.mProcessNames.get(mCallingPackage, mCallingUid); + if (proc != null) { + return (proc.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; + } + return false; + } + + void setLockTaskAuth() { + switch (mLockTaskMode) { + case LOCK_TASK_LAUNCH_MODE_DEFAULT: + mLockTaskAuth = isLockTaskWhitelistedLocked() ? + LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE; + break; + + case LOCK_TASK_LAUNCH_MODE_NEVER: + mLockTaskAuth = isPrivileged() ? + LOCK_TASK_AUTH_DONT_LOCK : LOCK_TASK_AUTH_PINNABLE; + break; + + case LOCK_TASK_LAUNCH_MODE_ALWAYS: + mLockTaskAuth = isPrivileged() ? + LOCK_TASK_AUTH_LAUNCHABLE: LOCK_TASK_AUTH_PINNABLE; + break; + + case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED: + mLockTaskAuth = isLockTaskWhitelistedLocked() ? + LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE; + break; + } + } + + boolean isLockTaskWhitelistedLocked() { + if (mCallingPackage == null) { + return false; + } + String[] packages = mService.mLockTaskPackages.get(userId); + if (packages == null) { + return false; + } + for (int i = packages.length - 1; i >= 0; --i) { + if (mCallingPackage.equals(packages[i])) { + return true; + } + } + return false; + } boolean isHomeTask() { return taskType == HOME_ACTIVITY_TYPE; } @@ -807,7 +886,7 @@ final class TaskRecord { } void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { - if (ActivityManagerService.DEBUG_RECENTS) Slog.i(TAG, "Saving task=" + this); + if (DEBUG_RECENTS) Slog.i(TAG_RECENTS, "Saving task=" + this); out.attribute(null, ATTR_TASKID, String.valueOf(taskId)); if (realActivity != null) { @@ -850,6 +929,7 @@ final class TaskRecord { out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId)); out.attribute(null, ATTR_CALLING_UID, String.valueOf(mCallingUid)); out.attribute(null, ATTR_CALLING_PACKAGE, mCallingPackage == null ? "" : mCallingPackage); + out.attribute(null, ATTR_RESIZEABLE, String.valueOf(mResizeable)); if (affinityIntent != null) { out.startTag(null, TAG_AFFINITYINTENT); @@ -866,7 +946,8 @@ final class TaskRecord { for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = activities.get(activityNdx); if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() || - ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) && + ((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT + | FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) && activityNdx > 0) { // Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET). break; @@ -911,6 +992,7 @@ final class TaskRecord { int nextTaskId = INVALID_TASK_ID; int callingUid = -1; String callingPackage = ""; + boolean resizeable = false; for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) { final String attrName = in.getAttributeName(attrNdx); @@ -964,6 +1046,8 @@ final class TaskRecord { callingUid = Integer.valueOf(attrValue); } else if (ATTR_CALLING_PACKAGE.equals(attrName)) { callingPackage = attrValue; + } else if (ATTR_RESIZEABLE.equals(attrName)) { + resizeable = Boolean.valueOf(attrValue); } else { Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName); } @@ -993,13 +1077,11 @@ final class TaskRecord { } } } - if (!hasRootAffinity) { rootAffinity = affinity; } else if ("@".equals(rootAffinity)) { rootAffinity = null; } - if (effectiveUid <= 0) { Intent checkIntent = intent != null ? intent : affinityIntent; effectiveUid = 0; @@ -1025,13 +1107,13 @@ final class TaskRecord { autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription, activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, - callingUid, callingPackage); + callingUid, callingPackage, resizeable); for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) { activities.get(activityNdx).task = task; } - if (ActivityManagerService.DEBUG_RECENTS) Slog.d(TAG, "Restored task=" + task); + if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Restored task=" + task); return task; } @@ -1121,6 +1203,7 @@ final class TaskRecord { pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription); } pw.print(prefix); pw.print("hasBeenVisible="); pw.print(hasBeenVisible); + pw.print(" mResizeable="); pw.print(mResizeable); pw.print(" firstActiveTime="); pw.print(lastActiveTime); pw.print(" lastActiveTime="); pw.print(lastActiveTime); pw.print(" (inactive for "); diff --git a/services/core/java/com/android/server/am/UriPermission.java b/services/core/java/com/android/server/am/UriPermission.java index 650a837..6e371c1 100644 --- a/services/core/java/com/android/server/am/UriPermission.java +++ b/services/core/java/com/android/server/am/UriPermission.java @@ -19,7 +19,6 @@ package com.android.server.am; import android.content.Intent; import android.os.UserHandle; import android.util.ArraySet; -import android.util.Log; import android.util.Slog; import com.android.server.am.ActivityManagerService.GrantUri; diff --git a/services/core/java/com/android/server/am/UriPermissionOwner.java b/services/core/java/com/android/server/am/UriPermissionOwner.java index ae83940..28344df 100644 --- a/services/core/java/com/android/server/am/UriPermissionOwner.java +++ b/services/core/java/com/android/server/am/UriPermissionOwner.java @@ -17,7 +17,6 @@ package com.android.server.am; import android.content.Intent; -import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 36263ec..5a66f4a 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -19,6 +19,8 @@ package com.android.server.am; import android.app.AlertDialog; import android.content.Context; import android.content.res.Resources; +import android.os.Handler; +import android.os.Message; import android.view.LayoutInflater; import android.view.View; import android.view.ViewTreeObserver; @@ -26,6 +28,7 @@ import android.view.WindowManager; import android.widget.TextView; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; /** * Dialog to show when a user switch it about to happen. The intent is to snapshot the screen @@ -37,8 +40,14 @@ final class UserSwitchingDialog extends AlertDialog implements ViewTreeObserver.OnWindowShownListener { private static final String TAG = "ActivityManagerUserSwitchingDialog"; + // Time to wait for the onWindowShown() callback before continuing the user switch + private static final int WINDOW_SHOWN_TIMEOUT_MS = 3000; + private final ActivityManagerService mService; private final int mUserId; + private static final int MSG_START_USER = 1; + @GuardedBy("this") + private boolean mStartedUser; public UserSwitchingDialog(ActivityManagerService service, Context context, int userId, String userName, boolean aboveSystem) { @@ -73,15 +82,40 @@ final class UserSwitchingDialog extends AlertDialog if (decorView != null) { decorView.getViewTreeObserver().addOnWindowShownListener(this); } + // Add a timeout as a safeguard, in case a race in screen on/off causes the window + // callback to never come. + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_USER), + WINDOW_SHOWN_TIMEOUT_MS); } @Override public void onWindowShown() { // Slog.v(TAG, "onWindowShown called"); - mService.startUserInForeground(mUserId, this); - final View decorView = getWindow().getDecorView(); - if (decorView != null) { - decorView.getViewTreeObserver().removeOnWindowShownListener(this); + startUser(); + } + + void startUser() { + synchronized (this) { + if (!mStartedUser) { + mService.startUserInForeground(mUserId, this); + mStartedUser = true; + final View decorView = getWindow().getDecorView(); + if (decorView != null) { + decorView.getViewTreeObserver().removeOnWindowShownListener(this); + } + mHandler.removeMessages(MSG_START_USER); + } } } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_USER: + startUser(); + break; + } + } + }; } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java new file mode 100644 index 0000000..6b56279 --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -0,0 +1,5960 @@ +/* + * 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.audio; + +import static android.Manifest.permission.REMOTE_AUDIO_PLAYBACK; +import static android.media.AudioManager.RINGER_MODE_NORMAL; +import static android.media.AudioManager.RINGER_MODE_SILENT; +import static android.media.AudioManager.RINGER_MODE_VIBRATE; + +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.AppOpsManager; +import android.app.KeyguardManager; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +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.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.database.ContentObserver; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiPlaybackClient; +import android.hardware.hdmi.HdmiTvClient; +import android.hardware.usb.UsbManager; +import android.media.AudioAttributes; +import android.media.AudioDevicePort; +import android.media.AudioSystem; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioManagerInternal; +import android.media.AudioPort; +import android.media.AudioRoutesInfo; +import android.media.IAudioFocusDispatcher; +import android.media.IAudioRoutesObserver; +import android.media.IAudioService; +import android.media.IRemoteControlDisplay; +import android.media.IRingtonePlayer; +import android.media.IVolumeController; +import android.media.MediaPlayer; +import android.media.SoundPool; +import android.media.VolumePolicy; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.audiopolicy.AudioPolicy; +import android.media.audiopolicy.AudioPolicyConfig; +import android.media.audiopolicy.IAudioPolicyCallback; +import android.os.Binder; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.Vibrator; +import android.provider.Settings; +import android.provider.Settings.System; +import android.telecom.TelecomManager; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.util.MathUtils; +import android.util.Slog; +import android.util.SparseIntArray; +import android.view.KeyEvent; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.util.XmlUtils; +import com.android.server.EventLogTags; +import com.android.server.LocalServices; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * The implementation of the volume manager service. + * <p> + * This implementation focuses on delivering a responsive UI. Most methods are + * asynchronous to external calls. For example, the task of setting a volume + * will update our internal state, but in a separate thread will set the system + * volume and later persist to the database. Similarly, setting the ringer mode + * will update the state and broadcast a change and in a separate thread later + * persist the ringer mode. + * + * @hide + */ +public class AudioService extends IAudioService.Stub { + + private static final String TAG = "AudioService"; + + /** Debug audio mode */ + protected static final boolean DEBUG_MODE = Log.isLoggable(TAG + ".MOD", Log.DEBUG); + + /** Debug audio policy feature */ + protected static final boolean DEBUG_AP = Log.isLoggable(TAG + ".AP", Log.DEBUG); + + /** Debug volumes */ + protected static final boolean DEBUG_VOL = Log.isLoggable(TAG + ".VOL", Log.DEBUG); + + /** debug calls to devices APIs */ + protected static final boolean DEBUG_DEVICES = Log.isLoggable(TAG + ".DEVICES", Log.DEBUG); + + /** How long to delay before persisting a change in volume/ringer mode. */ + private static final int PERSIST_DELAY = 500; + + /** How long to delay after a volume down event before unmuting a stream */ + private static final int UNMUTE_STREAM_DELAY = 350; + + /** + * Only used in the result from {@link #checkForRingerModeChange(int, int, int)} + */ + private static final int FLAG_ADJUST_VOLUME = 1; + + private final Context mContext; + private final ContentResolver mContentResolver; + private final AppOpsManager mAppOps; + + // the platform type affects volume and silent mode behavior + private final int mPlatformType; + + private boolean isPlatformVoice() { + return mPlatformType == AudioSystem.PLATFORM_VOICE; + } + + private boolean isPlatformTelevision() { + return mPlatformType == AudioSystem.PLATFORM_TELEVISION; + } + + /** The controller for the volume UI. */ + private final VolumeController mVolumeController = new VolumeController(); + private final ControllerService mControllerService = new ControllerService(); + + // sendMsg() flags + /** If the msg is already queued, replace it with this one. */ + private static final int SENDMSG_REPLACE = 0; + /** If the msg is already queued, ignore this one and leave the old. */ + private static final int SENDMSG_NOOP = 1; + /** If the msg is already queued, queue this one and leave the old. */ + private static final int SENDMSG_QUEUE = 2; + + // AudioHandler messages + private static final int MSG_SET_DEVICE_VOLUME = 0; + private static final int MSG_PERSIST_VOLUME = 1; + private static final int MSG_PERSIST_RINGER_MODE = 3; + private static final int MSG_MEDIA_SERVER_DIED = 4; + private static final int MSG_PLAY_SOUND_EFFECT = 5; + private static final int MSG_BTA2DP_DOCK_TIMEOUT = 6; + private static final int MSG_LOAD_SOUND_EFFECTS = 7; + private static final int MSG_SET_FORCE_USE = 8; + private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; + private static final int MSG_SET_ALL_VOLUMES = 10; + private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 11; + private static final int MSG_REPORT_NEW_ROUTES = 12; + private static final int MSG_SET_FORCE_BT_A2DP_USE = 13; + private static final int MSG_CHECK_MUSIC_ACTIVE = 14; + private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16; + private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17; + private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18; + private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19; + private static final int MSG_UNLOAD_SOUND_EFFECTS = 20; + private static final int MSG_SYSTEM_READY = 21; + private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 22; + private static final int MSG_PERSIST_MICROPHONE_MUTE = 23; + private static final int MSG_UNMUTE_STREAM = 24; + // start of messages handled under wakelock + // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), + // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) + private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100; + private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101; + private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102; + // end of messages handled under wakelock + + private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; + // Timeout for connection to bluetooth headset service + private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000; + + /** @see AudioSystemThread */ + private AudioSystemThread mAudioSystemThread; + /** @see AudioHandler */ + private AudioHandler mAudioHandler; + /** @see VolumeStreamState */ + private VolumeStreamState[] mStreamStates; + private SettingsObserver mSettingsObserver; + + private int mMode = AudioSystem.MODE_NORMAL; + // protects mRingerMode + private final Object mSettingsLock = new Object(); + + private SoundPool mSoundPool; + private final Object mSoundEffectsLock = new Object(); + private static final int NUM_SOUNDPOOL_CHANNELS = 4; + + /* Sound effect file names */ + private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/"; + private static final List<String> SOUND_EFFECT_FILES = new ArrayList<String>(); + + /* Sound effect file name mapping sound effect id (AudioManager.FX_xxx) to + * file index in SOUND_EFFECT_FILES[] (first column) and indicating if effect + * uses soundpool (second column) */ + private final int[][] SOUND_EFFECT_FILES_MAP = new int[AudioManager.NUM_SOUND_EFFECTS][2]; + + /** Maximum volume index values for audio streams */ + private static int[] MAX_STREAM_VOLUME = new int[] { + 5, // STREAM_VOICE_CALL + 7, // STREAM_SYSTEM + 7, // STREAM_RING + 15, // STREAM_MUSIC + 7, // STREAM_ALARM + 7, // STREAM_NOTIFICATION + 15, // STREAM_BLUETOOTH_SCO + 7, // STREAM_SYSTEM_ENFORCED + 15, // STREAM_DTMF + 15 // STREAM_TTS + }; + + /** Minimum volume index values for audio streams */ + private static int[] MIN_STREAM_VOLUME = new int[] { + 1, // STREAM_VOICE_CALL + 0, // STREAM_SYSTEM + 0, // STREAM_RING + 0, // STREAM_MUSIC + 0, // STREAM_ALARM + 0, // STREAM_NOTIFICATION + 1, // STREAM_BLUETOOTH_SCO + 0, // STREAM_SYSTEM_ENFORCED + 0, // STREAM_DTMF + 0 // STREAM_TTS + }; + + /* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings + * of another stream: This avoids multiplying the volume settings for hidden + * stream types that follow other stream behavior for volume settings + * NOTE: do not create loops in aliases! + * Some streams alias to different streams according to device category (phone or tablet) or + * use case (in call vs off call...). See updateStreamVolumeAlias() for more details. + * mStreamVolumeAlias contains STREAM_VOLUME_ALIAS_VOICE aliases for a voice capable device + * (phone), STREAM_VOLUME_ALIAS_TELEVISION for a television or set-top box and + * STREAM_VOLUME_ALIAS_DEFAULT for other devices (e.g. tablets).*/ + private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] { + AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL + AudioSystem.STREAM_RING, // STREAM_SYSTEM + AudioSystem.STREAM_RING, // STREAM_RING + AudioSystem.STREAM_MUSIC, // STREAM_MUSIC + AudioSystem.STREAM_ALARM, // STREAM_ALARM + AudioSystem.STREAM_RING, // STREAM_NOTIFICATION + AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO + AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED + AudioSystem.STREAM_RING, // STREAM_DTMF + AudioSystem.STREAM_MUSIC // STREAM_TTS + }; + private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] { + AudioSystem.STREAM_MUSIC, // STREAM_VOICE_CALL + AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM + AudioSystem.STREAM_MUSIC, // STREAM_RING + AudioSystem.STREAM_MUSIC, // STREAM_MUSIC + AudioSystem.STREAM_MUSIC, // STREAM_ALARM + AudioSystem.STREAM_MUSIC, // STREAM_NOTIFICATION + AudioSystem.STREAM_MUSIC, // STREAM_BLUETOOTH_SCO + AudioSystem.STREAM_MUSIC, // STREAM_SYSTEM_ENFORCED + AudioSystem.STREAM_MUSIC, // STREAM_DTMF + AudioSystem.STREAM_MUSIC // STREAM_TTS + }; + private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] { + AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALL + AudioSystem.STREAM_RING, // STREAM_SYSTEM + AudioSystem.STREAM_RING, // STREAM_RING + AudioSystem.STREAM_MUSIC, // STREAM_MUSIC + AudioSystem.STREAM_ALARM, // STREAM_ALARM + AudioSystem.STREAM_RING, // STREAM_NOTIFICATION + AudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO + AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCED + AudioSystem.STREAM_RING, // STREAM_DTMF + AudioSystem.STREAM_MUSIC // STREAM_TTS + }; + private int[] mStreamVolumeAlias; + + /** + * Map AudioSystem.STREAM_* constants to app ops. This should be used + * after mapping through mStreamVolumeAlias. + */ + private static final int[] STREAM_VOLUME_OPS = new int[] { + AppOpsManager.OP_AUDIO_VOICE_VOLUME, // STREAM_VOICE_CALL + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM + AppOpsManager.OP_AUDIO_RING_VOLUME, // STREAM_RING + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_MUSIC + AppOpsManager.OP_AUDIO_ALARM_VOLUME, // STREAM_ALARM + AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME, // STREAM_NOTIFICATION + AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME, // STREAM_BLUETOOTH_SCO + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_SYSTEM_ENFORCED + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_DTMF + AppOpsManager.OP_AUDIO_MEDIA_VOLUME, // STREAM_TTS + }; + + private final boolean mUseFixedVolume; + + private final AudioSystem.ErrorCallback mAudioSystemCallback = new AudioSystem.ErrorCallback() { + public void onError(int error) { + switch (error) { + case AudioSystem.AUDIO_STATUS_SERVER_DIED: + sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, + SENDMSG_NOOP, 0, 0, null, 0); + break; + default: + break; + } + } + }; + + /** + * Current ringer mode from one of {@link AudioManager#RINGER_MODE_NORMAL}, + * {@link AudioManager#RINGER_MODE_SILENT}, or + * {@link AudioManager#RINGER_MODE_VIBRATE}. + */ + // protected by mSettingsLock + private int mRingerMode; // internal ringer mode, affects muting of underlying streams + private int mRingerModeExternal = -1; // reported ringer mode to outside clients (AudioManager) + + /** @see System#MODE_RINGER_STREAMS_AFFECTED */ + private int mRingerModeAffectedStreams = 0; + + // Streams currently muted by ringer mode + private int mRingerModeMutedStreams; + + /** Streams that can be muted. Do not resolve to aliases when checking. + * @see System#MUTE_STREAMS_AFFECTED */ + private int mMuteAffectedStreams; + + /** + * NOTE: setVibrateSetting(), getVibrateSetting(), shouldVibrate() are deprecated. + * mVibrateSetting is just maintained during deprecation period but vibration policy is + * now only controlled by mHasVibrator and mRingerMode + */ + private int mVibrateSetting; + + // Is there a vibrator + private final boolean mHasVibrator; + + // Broadcast receiver for device connections intent broadcasts + private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver(); + + // Devices currently connected + // Use makeDeviceListKey() to make a unique key for this list. + private class DeviceListSpec { + int mDeviceType; + String mDeviceName; + String mDeviceAddress; + + public DeviceListSpec(int deviceType, String deviceName, String deviceAddress) { + mDeviceType = deviceType; + mDeviceName = deviceName; + mDeviceAddress = deviceAddress; + } + + public String toString() { + return "[type:0x" + Integer.toHexString(mDeviceType) + " name:" + mDeviceName + + " address:" + mDeviceAddress + "]"; + } + } + + // Generate a unique key for the mConnectedDevices List by composing the device "type" + // and the "address" associated with a specific instance of that device type + private String makeDeviceListKey(int device, String deviceAddress) { + return "0x" + Integer.toHexString(device) + ":" + deviceAddress; + } + + private final ArrayMap<String, DeviceListSpec> mConnectedDevices = new ArrayMap<>(); + + // Forced device usage for communications + private int mForcedUseForComm; + + // List of binder death handlers for setMode() client processes. + // The last process to have called setMode() is at the top of the list. + private final ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>(); + + // List of clients having issued a SCO start request + private final ArrayList <ScoClient> mScoClients = new ArrayList <ScoClient>(); + + // BluetoothHeadset API to control SCO connection + private BluetoothHeadset mBluetoothHeadset; + + // Bluetooth headset device + private BluetoothDevice mBluetoothHeadsetDevice; + + // Indicate if SCO audio connection is currently active and if the initiator is + // audio service (internal) or bluetooth headset (external) + private int mScoAudioState; + // SCO audio state is not active + private static final int SCO_STATE_INACTIVE = 0; + // SCO audio activation request waiting for headset service to connect + private static final int SCO_STATE_ACTIVATE_REQ = 1; + // SCO audio state is active or starting due to a request from AudioManager API + private static final int SCO_STATE_ACTIVE_INTERNAL = 3; + // SCO audio deactivation request waiting for headset service to connect + private static final int SCO_STATE_DEACTIVATE_REQ = 5; + + // SCO audio state is active due to an action in BT handsfree (either voice recognition or + // in call audio) + private static final int SCO_STATE_ACTIVE_EXTERNAL = 2; + // Deactivation request for all SCO connections (initiated by audio mode change) + // waiting for headset service to connect + private static final int SCO_STATE_DEACTIVATE_EXT_REQ = 4; + + // Indicates the mode used for SCO audio connection. The mode is virtual call if the request + // originated from an app targeting an API version before JB MR2 and raw audio after that. + private int mScoAudioMode; + // SCO audio mode is undefined + private static final int SCO_MODE_UNDEFINED = -1; + // SCO audio mode is virtual voice call (BluetoothHeadset.startScoUsingVirtualVoiceCall()) + private static final int SCO_MODE_VIRTUAL_CALL = 0; + // SCO audio mode is raw audio (BluetoothHeadset.connectAudio()) + private static final int SCO_MODE_RAW = 1; + // SCO audio mode is Voice Recognition (BluetoothHeadset.startVoiceRecognition()) + private static final int SCO_MODE_VR = 2; + + private static final int SCO_MODE_MAX = 2; + + // Current connection state indicated by bluetooth headset + private int mScoConnectionState; + + // true if boot sequence has been completed + private boolean mSystemReady; + // listener for SoundPool sample load completion indication + private SoundPoolCallback mSoundPoolCallBack; + // thread for SoundPool listener + private SoundPoolListenerThread mSoundPoolListenerThread; + // message looper for SoundPool listener + private Looper mSoundPoolLooper = null; + // volume applied to sound played with playSoundEffect() + private static int sSoundEffectVolumeDb; + // previous volume adjustment direction received by checkForRingerModeChange() + private int mPrevVolDirection = AudioManager.ADJUST_SAME; + // Keyguard manager proxy + private KeyguardManager mKeyguardManager; + // mVolumeControlStream is set by VolumePanel to temporarily force the stream type which volume + // is controlled by Vol keys. + private int mVolumeControlStream = -1; + private final Object mForceControlStreamLock = new Object(); + // VolumePanel is currently the only client of forceVolumeControlStream() and runs in system + // server process so in theory it is not necessary to monitor the client death. + // However it is good to be ready for future evolutions. + private ForceControlStreamClient mForceControlStreamClient = null; + // Used to play ringtones outside system_server + private volatile IRingtonePlayer mRingtonePlayer; + + private int mDeviceOrientation = Configuration.ORIENTATION_UNDEFINED; + private int mDeviceRotation = Surface.ROTATION_0; + + // Request to override default use of A2DP for media. + private boolean mBluetoothA2dpEnabled; + private final Object mBluetoothA2dpEnabledLock = new Object(); + + // Monitoring of audio routes. Protected by mCurAudioRoutes. + final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); + final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers + = new RemoteCallbackList<IAudioRoutesObserver>(); + + // Devices for which the volume is fixed and VolumePanel slider should be disabled + int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI | + AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_HDMI_ARC | + AudioSystem.DEVICE_OUT_SPDIF | + AudioSystem.DEVICE_OUT_AUX_LINE; + int mFullVolumeDevices = 0; + + // TODO merge orientation and rotation + private final boolean mMonitorOrientation; + private final boolean mMonitorRotation; + + private boolean mDockAudioMediaEnabled = true; + + private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + + // Used when safe volume warning message display is requested by setStreamVolume(). In this + // case, the new requested volume, stream type and device are stored in mPendingVolumeCommand + // and used later when/if disableSafeMediaVolume() is called. + private StreamVolumeCommand mPendingVolumeCommand; + + private PowerManager.WakeLock mAudioEventWakeLock; + + private final MediaFocusControl mMediaFocusControl; + + // Reference to BluetoothA2dp to query for AbsoluteVolume. + private BluetoothA2dp mA2dp; + // lock always taken synchronized on mConnectedDevices + private final Object mA2dpAvrcpLock = new Object(); + // If absolute volume is supported in AVRCP device + private boolean mAvrcpAbsVolSupported = false; + + private AudioOrientationEventListener mOrientationListener; + + private static Long mLastDeviceConnectMsgTime = new Long(0); + + private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate; + private VolumePolicy mVolumePolicy = VolumePolicy.DEFAULT; + private long mLoweredFromNormalToVibrateTime; + + // Intent "extra" data keys. + public static final String CONNECT_INTENT_KEY_PORT_NAME = "portName"; + public static final String CONNECT_INTENT_KEY_STATE = "state"; + public static final String CONNECT_INTENT_KEY_ADDRESS = "address"; + public static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback"; + public static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture"; + public static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI"; + public static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class"; + + // Defines the format for the connection "address" for ALSA devices + public static String makeAlsaAddressString(int card, int device) { + return "card=" + card + ";device=" + device + ";"; + } + + private final String DEVICE_NAME_A2DP = "a2dp-device"; + + /////////////////////////////////////////////////////////////////////////// + // Construction + /////////////////////////////////////////////////////////////////////////// + + /** @hide */ + public AudioService(Context context) { + mContext = context; + mContentResolver = context.getContentResolver(); + mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); + + mPlatformType = AudioSystem.getPlatformType(context); + + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent"); + + Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + mHasVibrator = vibrator == null ? false : vibrator.hasVibrator(); + + // Initialize volume + int maxVolume = SystemProperties.getInt("ro.config.vc_call_vol_steps", + MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]); + if (maxVolume != MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL]) { + MAX_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = maxVolume; + AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] = (maxVolume * 3) / 4; + } + maxVolume = SystemProperties.getInt("ro.config.media_vol_steps", + MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]); + if (maxVolume != MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC]) { + MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxVolume; + AudioSystem.DEFAULT_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = (maxVolume * 3) / 4; + } + + sSoundEffectVolumeDb = context.getResources().getInteger( + com.android.internal.R.integer.config_soundEffectVolumeDb); + + mForcedUseForComm = AudioSystem.FORCE_NONE; + + createAudioSystemThread(); + + AudioSystem.setErrorCallback(mAudioSystemCallback); + + boolean cameraSoundForced = readCameraSoundForced(); + mCameraSoundForced = new Boolean(cameraSoundForced); + sendMsg(mAudioHandler, + MSG_SET_FORCE_USE, + SENDMSG_QUEUE, + AudioSystem.FOR_SYSTEM, + cameraSoundForced ? + AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, + null, + 0); + + mSafeMediaVolumeState = new Integer(Settings.Global.getInt(mContentResolver, + Settings.Global.AUDIO_SAFE_VOLUME_STATE, + SAFE_MEDIA_VOLUME_NOT_CONFIGURED)); + // The default safe volume index read here will be replaced by the actual value when + // the mcc is read by onConfigureSafeVolume() + mSafeMediaVolumeIndex = mContext.getResources().getInteger( + com.android.internal.R.integer.config_safe_media_volume_index) * 10; + + mUseFixedVolume = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_useFixedVolume); + + // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[] + // array initialized by updateStreamVolumeAlias() + updateStreamVolumeAlias(false /*updateVolumes*/, TAG); + readPersistedSettings(); + mSettingsObserver = new SettingsObserver(); + createStreamStates(); + + mMediaFocusControl = new MediaFocusControl(mAudioHandler.getLooper(), + mContext, mVolumeController, this); + + readAndSetLowRamDevice(); + + // Call setRingerModeInt() to apply correct mute + // state on streams affected by ringer mode. + mRingerModeMutedStreams = 0; + setRingerModeInt(getRingerModeInternal(), false); + + // Register for device connection intent broadcasts. + IntentFilter intentFilter = + new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); + intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + intentFilter.addAction(Intent.ACTION_DOCK_EVENT); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(Intent.ACTION_USER_SWITCHED); + intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + + intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + // TODO merge orientation and rotation + mMonitorOrientation = SystemProperties.getBoolean("ro.audio.monitorOrientation", false); + if (mMonitorOrientation) { + Log.v(TAG, "monitoring device orientation"); + // initialize orientation in AudioSystem + setOrientationForAudioSystem(); + } + mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false); + if (mMonitorRotation) { + mDeviceRotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay().getRotation(); + Log.v(TAG, "monitoring device rotation, initial=" + mDeviceRotation); + + mOrientationListener = new AudioOrientationEventListener(mContext); + mOrientationListener.enable(); + + // initialize rotation in AudioSystem + setRotationForAudioSystem(); + } + + context.registerReceiver(mReceiver, intentFilter); + + LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal()); + } + + public void systemReady() { + sendMsg(mAudioHandler, MSG_SYSTEM_READY, SENDMSG_QUEUE, + 0, 0, null, 0); + } + + public void onSystemReady() { + mSystemReady = true; + sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, + 0, 0, null, 0); + + mKeyguardManager = + (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + mScoConnectionState = AudioManager.SCO_AUDIO_STATE_ERROR; + resetBluetoothSco(); + getBluetoothHeadset(); + //FIXME: this is to maintain compatibility with deprecated intent + // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + sendStickyBroadcastToAll(newIntent); + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.A2DP); + } + + mHdmiManager = + (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE); + if (mHdmiManager != null) { + synchronized (mHdmiManager) { + mHdmiTvClient = mHdmiManager.getTvClient(); + if (mHdmiTvClient != null) { + mFixedVolumeDevices &= ~AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER; + } + mHdmiPlaybackClient = mHdmiManager.getPlaybackClient(); + mHdmiCecSink = false; + } + } + + sendMsg(mAudioHandler, + MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED, + SENDMSG_REPLACE, + 0, + 0, + TAG, + SAFE_VOLUME_CONFIGURE_TIMEOUT_MS); + + StreamOverride.init(mContext); + mControllerService.init(); + } + + private void createAudioSystemThread() { + mAudioSystemThread = new AudioSystemThread(); + mAudioSystemThread.start(); + waitForAudioHandlerCreation(); + } + + /** Waits for the volume handler to be created by the other thread. */ + private void waitForAudioHandlerCreation() { + synchronized(this) { + while (mAudioHandler == null) { + try { + // Wait for mAudioHandler to be set by the other thread + wait(); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting on volume handler."); + } + } + } + } + + private void checkAllAliasStreamVolumes() { + synchronized (VolumeStreamState.class) { + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = 0; streamType < numStreamTypes; streamType++) { + if (streamType != mStreamVolumeAlias[streamType]) { + mStreamStates[streamType]. + setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], + TAG); + } + // apply stream volume + if (!mStreamStates[streamType].mIsMuted) { + mStreamStates[streamType].applyAllVolumes(); + } + } + } + } + + private void checkAllFixedVolumeDevices() + { + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = 0; streamType < numStreamTypes; streamType++) { + mStreamStates[streamType].checkFixedVolumeDevices(); + } + } + + private void checkAllFixedVolumeDevices(int streamType) { + mStreamStates[streamType].checkFixedVolumeDevices(); + } + + private void checkMuteAffectedStreams() { + // any stream with a min level > 0 is not muteable by definition + for (int i = 0; i < mStreamStates.length; i++) { + final VolumeStreamState vss = mStreamStates[i]; + if (vss.mIndexMin > 0) { + mMuteAffectedStreams &= ~(1 << vss.mStreamType); + } + } + } + + private void createStreamStates() { + int numStreamTypes = AudioSystem.getNumStreamTypes(); + VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes]; + + for (int i = 0; i < numStreamTypes; i++) { + streams[i] = new VolumeStreamState(System.VOLUME_SETTINGS[mStreamVolumeAlias[i]], i); + } + + checkAllFixedVolumeDevices(); + checkAllAliasStreamVolumes(); + checkMuteAffectedStreams(); + } + + private void dumpStreamStates(PrintWriter pw) { + pw.println("\nStream volumes (device: index)"); + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int i = 0; i < numStreamTypes; i++) { + pw.println("- " + AudioSystem.STREAM_NAMES[i] + ":"); + mStreamStates[i].dump(pw); + pw.println(""); + } + pw.print("\n- mute affected streams = 0x"); + pw.println(Integer.toHexString(mMuteAffectedStreams)); + } + + private void updateStreamVolumeAlias(boolean updateVolumes, String caller) { + int dtmfStreamAlias; + + switch (mPlatformType) { + case AudioSystem.PLATFORM_VOICE: + mStreamVolumeAlias = STREAM_VOLUME_ALIAS_VOICE; + dtmfStreamAlias = AudioSystem.STREAM_RING; + break; + case AudioSystem.PLATFORM_TELEVISION: + mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION; + dtmfStreamAlias = AudioSystem.STREAM_MUSIC; + break; + default: + mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT; + dtmfStreamAlias = AudioSystem.STREAM_MUSIC; + } + + if (isPlatformTelevision()) { + mRingerModeAffectedStreams = 0; + } else { + if (isInCommunication()) { + dtmfStreamAlias = AudioSystem.STREAM_VOICE_CALL; + mRingerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF); + } else { + mRingerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF); + } + } + + mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias; + if (updateVolumes) { + mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias], + caller); + // apply stream mute states according to new value of mRingerModeAffectedStreams + setRingerModeInt(getRingerModeInternal(), false); + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + mStreamStates[AudioSystem.STREAM_DTMF], 0); + } + } + + private void readDockAudioSettings(ContentResolver cr) + { + mDockAudioMediaEnabled = Settings.Global.getInt( + cr, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED, 0) == 1; + + sendMsg(mAudioHandler, + MSG_SET_FORCE_USE, + SENDMSG_QUEUE, + AudioSystem.FOR_DOCK, + mDockAudioMediaEnabled ? + AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE, + null, + 0); + } + + private void readPersistedSettings() { + final ContentResolver cr = mContentResolver; + + int ringerModeFromSettings = + Settings.Global.getInt( + cr, Settings.Global.MODE_RINGER, AudioManager.RINGER_MODE_NORMAL); + int ringerMode = ringerModeFromSettings; + // sanity check in case the settings are restored from a device with incompatible + // ringer modes + if (!isValidRingerMode(ringerMode)) { + ringerMode = AudioManager.RINGER_MODE_NORMAL; + } + if ((ringerMode == AudioManager.RINGER_MODE_VIBRATE) && !mHasVibrator) { + ringerMode = AudioManager.RINGER_MODE_SILENT; + } + if (ringerMode != ringerModeFromSettings) { + Settings.Global.putInt(cr, Settings.Global.MODE_RINGER, ringerMode); + } + if (mUseFixedVolume || isPlatformTelevision()) { + ringerMode = AudioManager.RINGER_MODE_NORMAL; + } + synchronized(mSettingsLock) { + mRingerMode = ringerMode; + if (mRingerModeExternal == -1) { + mRingerModeExternal = mRingerMode; + } + + // System.VIBRATE_ON is not used any more but defaults for mVibrateSetting + // are still needed while setVibrateSetting() and getVibrateSetting() are being + // deprecated. + mVibrateSetting = AudioSystem.getValueForVibrateSetting(0, + AudioManager.VIBRATE_TYPE_NOTIFICATION, + mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT + : AudioManager.VIBRATE_SETTING_OFF); + mVibrateSetting = AudioSystem.getValueForVibrateSetting(mVibrateSetting, + AudioManager.VIBRATE_TYPE_RINGER, + mHasVibrator ? AudioManager.VIBRATE_SETTING_ONLY_SILENT + : AudioManager.VIBRATE_SETTING_OFF); + + updateRingerModeAffectedStreams(); + readDockAudioSettings(cr); + } + + mMuteAffectedStreams = System.getIntForUser(cr, + System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED, + UserHandle.USER_CURRENT); + + boolean masterMute = System.getIntForUser(cr, System.VOLUME_MASTER_MUTE, + 0, UserHandle.USER_CURRENT) == 1; + if (mUseFixedVolume) { + masterMute = false; + AudioSystem.setMasterVolume(1.0f); + } + AudioSystem.setMasterMute(masterMute); + broadcastMasterMuteStatus(masterMute); + + boolean microphoneMute = + System.getIntForUser(cr, System.MICROPHONE_MUTE, 0, UserHandle.USER_CURRENT) == 1; + AudioSystem.muteMicrophone(microphoneMute); + + // Each stream will read its own persisted settings + + // Broadcast the sticky intents + broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, mRingerModeExternal); + broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, mRingerMode); + + // Broadcast vibrate settings + broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); + broadcastVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION); + + // Load settings for the volume controller + mVolumeController.loadSettings(cr); + } + + private int rescaleIndex(int index, int srcStream, int dstStream) { + return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex(); + } + + private class AudioOrientationEventListener + extends OrientationEventListener { + public AudioOrientationEventListener(Context context) { + super(context); + } + + @Override + public void onOrientationChanged(int orientation) { + //Even though we're responding to phone orientation events, + //use display rotation so audio stays in sync with video/dialogs + int newRotation = ((WindowManager) mContext.getSystemService( + Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation(); + if (newRotation != mDeviceRotation) { + mDeviceRotation = newRotation; + setRotationForAudioSystem(); + } + } + } + + /////////////////////////////////////////////////////////////////////////// + // IPC methods + /////////////////////////////////////////////////////////////////////////// + /** @see AudioManager#adjustVolume(int, int) */ + public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, + String callingPackage, String caller) { + adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, + caller, Binder.getCallingUid()); + } + + private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, + String callingPackage, String caller, int uid) { + if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType + + ", flags=" + flags + ", caller=" + caller); + int streamType; + boolean isMute = isMuteAdjust(direction); + if (mVolumeControlStream != -1) { + streamType = mVolumeControlStream; + } else { + streamType = getActiveStreamType(suggestedStreamType); + } + ensureValidStreamType(streamType); + final int resolvedStream = mStreamVolumeAlias[streamType]; + + // Play sounds on STREAM_RING only. + if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && + resolvedStream != AudioSystem.STREAM_RING) { + flags &= ~AudioManager.FLAG_PLAY_SOUND; + } + + // For notifications/ring, show the ui before making any adjustments + // Don't suppress mute/unmute requests + if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) { + direction = 0; + flags &= ~AudioManager.FLAG_PLAY_SOUND; + flags &= ~AudioManager.FLAG_VIBRATE; + if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment"); + } + + adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid); + } + + /** @see AudioManager#adjustStreamVolume(int, int, int) */ + public void adjustStreamVolume(int streamType, int direction, int flags, + String callingPackage) { + adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, + Binder.getCallingUid()); + } + + private void adjustStreamVolume(int streamType, int direction, int flags, + String callingPackage, String caller, int uid) { + if (mUseFixedVolume) { + return; + } + if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction + + ", flags=" + flags + ", caller=" + caller); + + ensureValidDirection(direction); + ensureValidStreamType(streamType); + + boolean isMuteAdjust = isMuteAdjust(direction); + + if (isMuteAdjust && !isStreamAffectedByMute(streamType)) { + return; + } + + // use stream type alias here so that streams with same alias have the same behavior, + // including with regard to silent mode control (e.g the use of STREAM_RING below and in + // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION) + int streamTypeAlias = mStreamVolumeAlias[streamType]; + + VolumeStreamState streamState = mStreamStates[streamTypeAlias]; + + final int device = getDeviceForStream(streamTypeAlias); + + int aliasIndex = streamState.getIndex(device); + boolean adjustVolume = true; + int step; + + // skip a2dp absolute volume control request when the device + // is not an a2dp device + if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 && + (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { + return; + } + + if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage) + != AppOpsManager.MODE_ALLOWED) { + return; + } + + // reset any pending volume command + synchronized (mSafeMediaVolumeState) { + mPendingVolumeCommand = null; + } + + flags &= ~AudioManager.FLAG_FIXED_VOLUME; + if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && + ((device & mFixedVolumeDevices) != 0)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; + + // Always toggle between max safe volume and 0 for fixed volume devices where safe + // volume is enforced, and max and 0 for the others. + // This is simulated by stepping by the full allowed volume range + if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && + (device & mSafeMediaVolumeDevices) != 0) { + step = mSafeMediaVolumeIndex; + } else { + step = streamState.getMaxIndex(); + } + if (aliasIndex != 0) { + aliasIndex = step; + } + } else { + // convert one UI step (+/-1) into a number of internal units on the stream alias + step = rescaleIndex(10, streamType, streamTypeAlias); + } + + // If either the client forces allowing ringer modes for this adjustment, + // or the stream type is one that is affected by ringer modes + if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || + (streamTypeAlias == getUiSoundsStreamType())) { + int ringerMode = getRingerModeInternal(); + // do not vibrate if already in vibrate mode + if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + flags &= ~AudioManager.FLAG_VIBRATE; + } + // Check if the ringer mode handles this adjustment. If it does we don't + // need to adjust the volume further. + final int result = checkForRingerModeChange(aliasIndex, direction, step, streamState.mIsMuted); + adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0; + // If suppressing a volume adjustment in silent mode, display the UI hint + if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) { + flags |= AudioManager.FLAG_SHOW_SILENT_HINT; + } + // If suppressing a volume down adjustment in vibrate mode, display the UI hint + if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) { + flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT; + } + } + + int oldIndex = mStreamStates[streamType].getIndex(device); + + if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) { + mAudioHandler.removeMessages(MSG_UNMUTE_STREAM); + + // Check if volume update should be send to AVRCP + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && + (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { + synchronized (mA2dpAvrcpLock) { + if (mA2dp != null && mAvrcpAbsVolSupported) { + mA2dp.adjustAvrcpAbsoluteVolume(direction); + } + } + } + + if (isMuteAdjust) { + boolean state; + if (direction == AudioManager.ADJUST_TOGGLE_MUTE) { + state = !streamState.mIsMuted; + } else { + state = direction == AudioManager.ADJUST_MUTE; + } + if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { + setSystemAudioMute(state); + } + for (int stream = 0; stream < mStreamStates.length; stream++) { + if (streamTypeAlias == mStreamVolumeAlias[stream]) { + mStreamStates[stream].mute(state); + } + } + } else if ((direction == AudioManager.ADJUST_RAISE) && + !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { + Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex); + mVolumeController.postDisplaySafeVolumeWarning(flags); + } else if (streamState.adjustIndex(direction * step, device, caller) + || streamState.mIsMuted) { + // Post message to set system volume (it in turn will post a + // message to persist). + if (streamState.mIsMuted) { + // Unmute the stream if it was previously muted + if (direction == AudioManager.ADJUST_RAISE) { + // unmute immediately for volume up + streamState.mute(false); + } else if (direction == AudioManager.ADJUST_LOWER) { + if (mPlatformType == AudioSystem.PLATFORM_TELEVISION) { + sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE, + streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY); + } + } + } + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + + // Check if volume update should be sent to Hdmi system audio. + int newIndex = mStreamStates[streamType].getIndex(device); + if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { + setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags); + } + if (mHdmiManager != null) { + synchronized (mHdmiManager) { + // mHdmiCecSink true => mHdmiPlaybackClient != null + if (mHdmiCecSink && + streamTypeAlias == AudioSystem.STREAM_MUSIC && + oldIndex != newIndex) { + synchronized (mHdmiPlaybackClient) { + int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN : + KeyEvent.KEYCODE_VOLUME_UP; + mHdmiPlaybackClient.sendKeyEvent(keyCode, true); + mHdmiPlaybackClient.sendKeyEvent(keyCode, false); + } + } + } + } + } + int index = mStreamStates[streamType].getIndex(device); + sendVolumeUpdate(streamType, oldIndex, index, flags); + } + + // Called after a delay when volume down is pressed while muted + private void onUnmuteStream(int stream, int flags) { + VolumeStreamState streamState = mStreamStates[stream]; + streamState.mute(false); + + final int device = getDeviceForStream(stream); + final int index = mStreamStates[stream].getIndex(device); + sendVolumeUpdate(stream, index, index, flags); + } + + private void setSystemAudioVolume(int oldVolume, int newVolume, int maxVolume, int flags) { + if (mHdmiManager == null + || mHdmiTvClient == null + || oldVolume == newVolume + || (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) != 0) return; + + // Sets the audio volume of AVR when we are in system audio mode. The new volume info + // is tranformed to HDMI-CEC commands and passed through CEC bus. + synchronized (mHdmiManager) { + if (!mHdmiSystemAudioSupported) return; + synchronized (mHdmiTvClient) { + final long token = Binder.clearCallingIdentity(); + try { + mHdmiTvClient.setSystemAudioVolume(oldVolume, newVolume, maxVolume); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + } + + // StreamVolumeCommand contains the information needed to defer the process of + // setStreamVolume() in case the user has to acknowledge the safe volume warning message. + class StreamVolumeCommand { + public final int mStreamType; + public final int mIndex; + public final int mFlags; + public final int mDevice; + + StreamVolumeCommand(int streamType, int index, int flags, int device) { + mStreamType = streamType; + mIndex = index; + mFlags = flags; + mDevice = device; + } + + @Override + public String toString() { + return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=") + .append(mIndex).append(",flags=").append(mFlags).append(",device=") + .append(mDevice).append('}').toString(); + } + }; + + private void onSetStreamVolume(int streamType, int index, int flags, int device, + String caller) { + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false, caller); + // setting volume on ui sounds stream type also controls silent mode + if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || + (mStreamVolumeAlias[streamType] == getUiSoundsStreamType())) { + int newRingerMode; + if (index == 0) { + newRingerMode = mHasVibrator ? AudioManager.RINGER_MODE_VIBRATE + : mVolumePolicy.volumeDownToEnterSilent ? AudioManager.RINGER_MODE_SILENT + : AudioManager.RINGER_MODE_NORMAL; + } else { + newRingerMode = AudioManager.RINGER_MODE_NORMAL; + } + setRingerMode(newRingerMode, TAG + ".onSetStreamVolume", false /*external*/); + } + } + + /** @see AudioManager#setStreamVolume(int, int, int) */ + public void setStreamVolume(int streamType, int index, int flags, String callingPackage) { + setStreamVolume(streamType, index, flags, callingPackage, callingPackage, + Binder.getCallingUid()); + } + + private void setStreamVolume(int streamType, int index, int flags, String callingPackage, + String caller, int uid) { + if (mUseFixedVolume) { + return; + } + + ensureValidStreamType(streamType); + int streamTypeAlias = mStreamVolumeAlias[streamType]; + VolumeStreamState streamState = mStreamStates[streamTypeAlias]; + + final int device = getDeviceForStream(streamType); + int oldIndex; + + // skip a2dp absolute volume control request when the device + // is not an a2dp device + if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 && + (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { + return; + } + + if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage) + != AppOpsManager.MODE_ALLOWED) { + return; + } + + synchronized (mSafeMediaVolumeState) { + // reset any pending volume command + mPendingVolumeCommand = null; + + oldIndex = streamState.getIndex(device); + + index = rescaleIndex(index * 10, streamType, streamTypeAlias); + + if (streamTypeAlias == AudioSystem.STREAM_MUSIC && + (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { + synchronized (mA2dpAvrcpLock) { + if (mA2dp != null && mAvrcpAbsVolSupported) { + mA2dp.setAvrcpAbsoluteVolume(index / 10); + } + } + } + + if (streamTypeAlias == AudioSystem.STREAM_MUSIC) { + setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags); + } + + flags &= ~AudioManager.FLAG_FIXED_VOLUME; + if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) && + ((device & mFixedVolumeDevices) != 0)) { + flags |= AudioManager.FLAG_FIXED_VOLUME; + + // volume is either 0 or max allowed for fixed volume devices + if (index != 0) { + if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && + (device & mSafeMediaVolumeDevices) != 0) { + index = mSafeMediaVolumeIndex; + } else { + index = streamState.getMaxIndex(); + } + } + } + + if (!checkSafeMediaVolume(streamTypeAlias, index, device)) { + mVolumeController.postDisplaySafeVolumeWarning(flags); + mPendingVolumeCommand = new StreamVolumeCommand( + streamType, index, flags, device); + } else { + onSetStreamVolume(streamType, index, flags, device, caller); + index = mStreamStates[streamType].getIndex(device); + } + } + sendVolumeUpdate(streamType, oldIndex, index, flags); + } + + /** @see AudioManager#forceVolumeControlStream(int) */ + public void forceVolumeControlStream(int streamType, IBinder cb) { + synchronized(mForceControlStreamLock) { + mVolumeControlStream = streamType; + if (mVolumeControlStream == -1) { + if (mForceControlStreamClient != null) { + mForceControlStreamClient.release(); + mForceControlStreamClient = null; + } + } else { + mForceControlStreamClient = new ForceControlStreamClient(cb); + } + } + } + + private class ForceControlStreamClient implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + + ForceControlStreamClient(IBinder cb) { + if (cb != null) { + try { + cb.linkToDeath(this, 0); + } catch (RemoteException e) { + // Client has died! + Log.w(TAG, "ForceControlStreamClient() could not link to "+cb+" binder death"); + cb = null; + } + } + mCb = cb; + } + + public void binderDied() { + synchronized(mForceControlStreamLock) { + Log.w(TAG, "SCO client died"); + if (mForceControlStreamClient != this) { + Log.w(TAG, "unregistered control stream client died"); + } else { + mForceControlStreamClient = null; + mVolumeControlStream = -1; + } + } + } + + public void release() { + if (mCb != null) { + mCb.unlinkToDeath(this, 0); + mCb = null; + } + } + } + + private void sendBroadcastToAll(Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void sendStickyBroadcastToAll(Intent intent) { + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // UI update and Broadcast Intent + private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) { + if (!isPlatformVoice() && (streamType == AudioSystem.STREAM_RING)) { + streamType = AudioSystem.STREAM_NOTIFICATION; + } else { + streamType = mStreamVolumeAlias[streamType]; + } + + if (streamType == AudioSystem.STREAM_MUSIC) { + flags = updateFlagsForSystemAudio(flags); + } + mVolumeController.postVolumeChanged(streamType, flags); + } + + // If Hdmi-CEC system audio mode is on, we show volume bar only when TV + // receives volume notification from Audio Receiver. + private int updateFlagsForSystemAudio(int flags) { + if (mHdmiTvClient != null) { + synchronized (mHdmiTvClient) { + if (mHdmiSystemAudioSupported && + ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) { + flags &= ~AudioManager.FLAG_SHOW_UI; + } + } + } + return flags; + } + + // UI update and Broadcast Intent + private void sendMasterMuteUpdate(boolean muted, int flags) { + mVolumeController.postMasterMuteChanged(updateFlagsForSystemAudio(flags)); + broadcastMasterMuteStatus(muted); + } + + private void broadcastMasterMuteStatus(boolean muted) { + Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_REPLACE_PENDING); + sendStickyBroadcastToAll(intent); + } + + /** + * Sets the stream state's index, and posts a message to set system volume. + * This will not call out to the UI. Assumes a valid stream type. + * + * @param streamType Type of the stream + * @param index Desired volume index of the stream + * @param device the device whose volume must be changed + * @param force If true, set the volume even if the desired volume is same + * as the current volume. + */ + private void setStreamVolumeInt(int streamType, + int index, + int device, + boolean force, + String caller) { + VolumeStreamState streamState = mStreamStates[streamType]; + + if (streamState.setIndex(index, device, caller) || force) { + // Post message to set system volume (it in turn will post a message + // to persist). + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + } + + private void setSystemAudioMute(boolean state) { + if (mHdmiManager == null || mHdmiTvClient == null) return; + synchronized (mHdmiManager) { + if (!mHdmiSystemAudioSupported) return; + synchronized (mHdmiTvClient) { + final long token = Binder.clearCallingIdentity(); + try { + mHdmiTvClient.setSystemAudioMute(state); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + } + + /** get stream mute state. */ + public boolean isStreamMute(int streamType) { + if (streamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { + streamType = getActiveStreamType(streamType); + } + synchronized (VolumeStreamState.class) { + return mStreamStates[streamType].mIsMuted; + } + } + + private class RmtSbmxFullVolDeathHandler implements IBinder.DeathRecipient { + private IBinder mICallback; // To be notified of client's death + + RmtSbmxFullVolDeathHandler(IBinder cb) { + mICallback = cb; + try { + cb.linkToDeath(this, 0/*flags*/); + } catch (RemoteException e) { + Log.e(TAG, "can't link to death", e); + } + } + + boolean isHandlerFor(IBinder cb) { + return mICallback.equals(cb); + } + + void forget() { + try { + mICallback.unlinkToDeath(this, 0/*flags*/); + } catch (NoSuchElementException e) { + Log.e(TAG, "error unlinking to death", e); + } + } + + public void binderDied() { + Log.w(TAG, "Recorder with remote submix at full volume died " + mICallback); + forceRemoteSubmixFullVolume(false, mICallback); + } + } + + /** + * call must be synchronized on mRmtSbmxFullVolDeathHandlers + * @return true if there is a registered death handler, false otherwise */ + private boolean discardRmtSbmxFullVolDeathHandlerFor(IBinder cb) { + Iterator<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator(); + while (it.hasNext()) { + final RmtSbmxFullVolDeathHandler handler = it.next(); + if (handler.isHandlerFor(cb)) { + handler.forget(); + mRmtSbmxFullVolDeathHandlers.remove(handler); + return true; + } + } + return false; + } + + /** call synchronized on mRmtSbmxFullVolDeathHandlers */ + private boolean hasRmtSbmxFullVolDeathHandlerFor(IBinder cb) { + Iterator<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator(); + while (it.hasNext()) { + if (it.next().isHandlerFor(cb)) { + return true; + } + } + return false; + } + + private int mRmtSbmxFullVolRefCount = 0; + private ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers = + new ArrayList<RmtSbmxFullVolDeathHandler>(); + + public void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb) { + if (cb == null) { + return; + } + if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( + android.Manifest.permission.CAPTURE_AUDIO_OUTPUT))) { + Log.w(TAG, "Trying to call forceRemoteSubmixFullVolume() without CAPTURE_AUDIO_OUTPUT"); + return; + } + synchronized(mRmtSbmxFullVolDeathHandlers) { + boolean applyRequired = false; + if (startForcing) { + if (!hasRmtSbmxFullVolDeathHandlerFor(cb)) { + mRmtSbmxFullVolDeathHandlers.add(new RmtSbmxFullVolDeathHandler(cb)); + if (mRmtSbmxFullVolRefCount == 0) { + mFullVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; + mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; + applyRequired = true; + } + mRmtSbmxFullVolRefCount++; + } + } else { + if (discardRmtSbmxFullVolDeathHandlerFor(cb) && (mRmtSbmxFullVolRefCount > 0)) { + mRmtSbmxFullVolRefCount--; + if (mRmtSbmxFullVolRefCount == 0) { + mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; + mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; + applyRequired = true; + } + } + } + if (applyRequired) { + // Assumes only STREAM_MUSIC going through DEVICE_OUT_REMOTE_SUBMIX + checkAllFixedVolumeDevices(AudioSystem.STREAM_MUSIC); + mStreamStates[AudioSystem.STREAM_MUSIC].applyAllVolumes(); + } + } + } + + private void setMasterMuteInternal(boolean mute, int flags, String callingPackage, int uid) { + if (mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage) + != AppOpsManager.MODE_ALLOWED) { + return; + } + if (mute != AudioSystem.getMasterMute()) { + setSystemAudioMute(mute); + AudioSystem.setMasterMute(mute); + // Post a persist master volume msg + sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, mute ? 1 + : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY); + sendMasterMuteUpdate(mute, flags); + + Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, mute); + sendBroadcastToAll(intent); + } + } + + /** get master mute state. */ + public boolean isMasterMute() { + return AudioSystem.getMasterMute(); + } + + public void setMasterMute(boolean mute, int flags, String callingPackage) { + setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid()); + } + + /** @see AudioManager#getStreamVolume(int) */ + public int getStreamVolume(int streamType) { + ensureValidStreamType(streamType); + int device = getDeviceForStream(streamType); + synchronized (VolumeStreamState.class) { + int index = mStreamStates[streamType].getIndex(device); + + // by convention getStreamVolume() returns 0 when a stream is muted. + if (mStreamStates[streamType].mIsMuted) { + index = 0; + } + if (index != 0 && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + (device & mFixedVolumeDevices) != 0) { + index = mStreamStates[streamType].getMaxIndex(); + } + return (index + 5) / 10; + } + } + + /** @see AudioManager#getStreamMaxVolume(int) */ + public int getStreamMaxVolume(int streamType) { + ensureValidStreamType(streamType); + return (mStreamStates[streamType].getMaxIndex() + 5) / 10; + } + + /** @see AudioManager#getStreamMinVolume(int) */ + public int getStreamMinVolume(int streamType) { + ensureValidStreamType(streamType); + return (mStreamStates[streamType].getMinIndex() + 5) / 10; + } + + /** Get last audible volume before stream was muted. */ + public int getLastAudibleStreamVolume(int streamType) { + ensureValidStreamType(streamType); + int device = getDeviceForStream(streamType); + return (mStreamStates[streamType].getIndex(device) + 5) / 10; + } + + /** @see AudioManager#getUiSoundsStreamType() */ + public int getUiSoundsStreamType() { + return mStreamVolumeAlias[AudioSystem.STREAM_SYSTEM]; + } + + /** @see AudioManager#setMicrophoneMute(boolean) */ + public void setMicrophoneMute(boolean on, String callingPackage) { + if (mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, Binder.getCallingUid(), + callingPackage) != AppOpsManager.MODE_ALLOWED) { + return; + } + if (!checkAudioSettingsPermission("setMicrophoneMute()")) { + return; + } + + AudioSystem.muteMicrophone(on); + // Post a persist microphone msg. + sendMsg(mAudioHandler, MSG_PERSIST_MICROPHONE_MUTE, SENDMSG_REPLACE, on ? 1 + : 0, UserHandle.getCallingUserId(), null, PERSIST_DELAY); + } + + @Override + public int getRingerModeExternal() { + synchronized(mSettingsLock) { + return mRingerModeExternal; + } + } + + @Override + public int getRingerModeInternal() { + synchronized(mSettingsLock) { + return mRingerMode; + } + } + + private void ensureValidRingerMode(int ringerMode) { + if (!isValidRingerMode(ringerMode)) { + throw new IllegalArgumentException("Bad ringer mode " + ringerMode); + } + } + + /** @see AudioManager#isValidRingerMode(int) */ + public boolean isValidRingerMode(int ringerMode) { + return ringerMode >= 0 && ringerMode <= AudioManager.RINGER_MODE_MAX; + } + + public void setRingerModeExternal(int ringerMode, String caller) { + setRingerMode(ringerMode, caller, true /*external*/); + } + + public void setRingerModeInternal(int ringerMode, String caller) { + enforceVolumeController("setRingerModeInternal"); + setRingerMode(ringerMode, caller, false /*external*/); + } + + private void setRingerMode(int ringerMode, String caller, boolean external) { + if (mUseFixedVolume || isPlatformTelevision()) { + return; + } + if (caller == null || caller.length() == 0) { + throw new IllegalArgumentException("Bad caller: " + caller); + } + ensureValidRingerMode(ringerMode); + if ((ringerMode == AudioManager.RINGER_MODE_VIBRATE) && !mHasVibrator) { + ringerMode = AudioManager.RINGER_MODE_SILENT; + } + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mSettingsLock) { + final int ringerModeInternal = getRingerModeInternal(); + final int ringerModeExternal = getRingerModeExternal(); + if (external) { + setRingerModeExt(ringerMode); + if (mRingerModeDelegate != null) { + ringerMode = mRingerModeDelegate.onSetRingerModeExternal(ringerModeExternal, + ringerMode, caller, ringerModeInternal, mVolumePolicy); + } + if (ringerMode != ringerModeInternal) { + setRingerModeInt(ringerMode, true /*persist*/); + } + } else /*internal*/ { + if (ringerMode != ringerModeInternal) { + setRingerModeInt(ringerMode, true /*persist*/); + } + if (mRingerModeDelegate != null) { + ringerMode = mRingerModeDelegate.onSetRingerModeInternal(ringerModeInternal, + ringerMode, caller, ringerModeExternal, mVolumePolicy); + } + setRingerModeExt(ringerMode); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void setRingerModeExt(int ringerMode) { + synchronized(mSettingsLock) { + if (ringerMode == mRingerModeExternal) return; + mRingerModeExternal = ringerMode; + } + // Send sticky broadcast + broadcastRingerMode(AudioManager.RINGER_MODE_CHANGED_ACTION, ringerMode); + } + + private void setRingerModeInt(int ringerMode, boolean persist) { + final boolean change; + synchronized(mSettingsLock) { + change = mRingerMode != ringerMode; + mRingerMode = ringerMode; + } + + // Mute stream if not previously muted by ringer mode and ringer mode + // is not RINGER_MODE_NORMAL and stream is affected by ringer mode. + // Unmute stream if previously muted by ringer mode and ringer mode + // is RINGER_MODE_NORMAL or stream is not affected by ringer mode. + int numStreamTypes = AudioSystem.getNumStreamTypes(); + final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE + || ringerMode == AudioManager.RINGER_MODE_SILENT; + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + final boolean isMuted = isStreamMutedByRingerMode(streamType); + final boolean shouldMute = ringerModeMute && isStreamAffectedByRingerMode(streamType); + if (isMuted == shouldMute) continue; + if (!shouldMute) { + // unmute + // ring and notifications volume should never be 0 when not silenced + // on voice capable devices or devices that support vibration + if ((isPlatformVoice() || mHasVibrator) && + mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) { + synchronized (VolumeStreamState.class) { + final VolumeStreamState vss = mStreamStates[streamType]; + for (int i = 0; i < vss.mIndexMap.size(); i++) { + int device = vss.mIndexMap.keyAt(i); + int value = vss.mIndexMap.valueAt(i); + if (value == 0) { + vss.setIndex(10, device, TAG); + } + } + // Persist volume for stream ring when it is changed here + final int device = getDeviceForStream(streamType); + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_QUEUE, + device, + 0, + mStreamStates[streamType], + PERSIST_DELAY); + } + } + mStreamStates[streamType].mute(false); + mRingerModeMutedStreams &= ~(1 << streamType); + } else { + // mute + mStreamStates[streamType].mute(true); + mRingerModeMutedStreams |= (1 << streamType); + } + } + + // Post a persist ringer mode msg + if (persist) { + sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE, + SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY); + } + if (change) { + // Send sticky broadcast + broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, ringerMode); + } + } + + /** @see AudioManager#shouldVibrate(int) */ + public boolean shouldVibrate(int vibrateType) { + if (!mHasVibrator) return false; + + switch (getVibrateSetting(vibrateType)) { + + case AudioManager.VIBRATE_SETTING_ON: + return getRingerModeExternal() != AudioManager.RINGER_MODE_SILENT; + + case AudioManager.VIBRATE_SETTING_ONLY_SILENT: + return getRingerModeExternal() == AudioManager.RINGER_MODE_VIBRATE; + + case AudioManager.VIBRATE_SETTING_OFF: + // return false, even for incoming calls + return false; + + default: + return false; + } + } + + /** @see AudioManager#getVibrateSetting(int) */ + public int getVibrateSetting(int vibrateType) { + if (!mHasVibrator) return AudioManager.VIBRATE_SETTING_OFF; + return (mVibrateSetting >> (vibrateType * 2)) & 3; + } + + /** @see AudioManager#setVibrateSetting(int, int) */ + public void setVibrateSetting(int vibrateType, int vibrateSetting) { + + if (!mHasVibrator) return; + + mVibrateSetting = AudioSystem.getValueForVibrateSetting(mVibrateSetting, vibrateType, + vibrateSetting); + + // Broadcast change + broadcastVibrateSetting(vibrateType); + + } + + private class SetModeDeathHandler implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + private int mPid; + private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client + + SetModeDeathHandler(IBinder cb, int pid) { + mCb = cb; + mPid = pid; + } + + public void binderDied() { + int newModeOwnerPid = 0; + synchronized(mSetModeDeathHandlers) { + Log.w(TAG, "setMode() client died"); + int index = mSetModeDeathHandlers.indexOf(this); + if (index < 0) { + Log.w(TAG, "unregistered setMode() client died"); + } else { + newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG); + } + } + // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all + // SCO connections not started by the application changing the mode + if (newModeOwnerPid != 0) { + final long ident = Binder.clearCallingIdentity(); + disconnectBluetoothSco(newModeOwnerPid); + Binder.restoreCallingIdentity(ident); + } + } + + public int getPid() { + return mPid; + } + + public void setMode(int mode) { + mMode = mode; + } + + public int getMode() { + return mMode; + } + + public IBinder getBinder() { + return mCb; + } + } + + /** @see AudioManager#setMode(int) */ + public void setMode(int mode, IBinder cb, String callingPackage) { + if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); } + if (!checkAudioSettingsPermission("setMode()")) { + return; + } + + if ( (mode == AudioSystem.MODE_IN_CALL) && + (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + != PackageManager.PERMISSION_GRANTED)) { + Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + return; + } + + if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) { + return; + } + + int newModeOwnerPid = 0; + synchronized(mSetModeDeathHandlers) { + if (mode == AudioSystem.MODE_CURRENT) { + mode = mMode; + } + newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage); + } + // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all + // SCO connections not started by the application changing the mode + if (newModeOwnerPid != 0) { + disconnectBluetoothSco(newModeOwnerPid); + } + } + + // must be called synchronized on mSetModeDeathHandlers + // setModeInt() returns a valid PID if the audio mode was successfully set to + // any mode other than NORMAL. + private int setModeInt(int mode, IBinder cb, int pid, String caller) { + if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller=" + + caller + ")"); } + int newModeOwnerPid = 0; + if (cb == null) { + Log.e(TAG, "setModeInt() called with null binder"); + return newModeOwnerPid; + } + + SetModeDeathHandler hdlr = null; + Iterator iter = mSetModeDeathHandlers.iterator(); + while (iter.hasNext()) { + SetModeDeathHandler h = (SetModeDeathHandler)iter.next(); + if (h.getPid() == pid) { + hdlr = h; + // Remove from client list so that it is re-inserted at top of list + iter.remove(); + hdlr.getBinder().unlinkToDeath(hdlr, 0); + break; + } + } + int status = AudioSystem.AUDIO_STATUS_OK; + do { + if (mode == AudioSystem.MODE_NORMAL) { + // get new mode from client at top the list if any + if (!mSetModeDeathHandlers.isEmpty()) { + hdlr = mSetModeDeathHandlers.get(0); + cb = hdlr.getBinder(); + mode = hdlr.getMode(); + if (DEBUG_MODE) { + Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid=" + + hdlr.mPid); + } + } + } else { + if (hdlr == null) { + hdlr = new SetModeDeathHandler(cb, pid); + } + // Register for client death notification + try { + cb.linkToDeath(hdlr, 0); + } catch (RemoteException e) { + // Client has died! + Log.w(TAG, "setMode() could not link to "+cb+" binder death"); + } + + // Last client to call setMode() is always at top of client list + // as required by SetModeDeathHandler.binderDied() + mSetModeDeathHandlers.add(0, hdlr); + hdlr.setMode(mode); + } + + if (mode != mMode) { + status = AudioSystem.setPhoneState(mode); + if (status == AudioSystem.AUDIO_STATUS_OK) { + if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + mode); } + mMode = mode; + } else { + if (hdlr != null) { + mSetModeDeathHandlers.remove(hdlr); + cb.unlinkToDeath(hdlr, 0); + } + // force reading new top of mSetModeDeathHandlers stack + if (DEBUG_MODE) { Log.w(TAG, " mode set to MODE_NORMAL after phoneState pb"); } + mode = AudioSystem.MODE_NORMAL; + } + } else { + status = AudioSystem.AUDIO_STATUS_OK; + } + } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty()); + + if (status == AudioSystem.AUDIO_STATUS_OK) { + if (mode != AudioSystem.MODE_NORMAL) { + if (mSetModeDeathHandlers.isEmpty()) { + Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack"); + } else { + newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); + } + } + int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); + int device = getDeviceForStream(streamType); + int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device); + setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller); + + updateStreamVolumeAlias(true /*updateVolumes*/, caller); + } + return newModeOwnerPid; + } + + /** @see AudioManager#getMode() */ + public int getMode() { + return mMode; + } + + //========================================================================================== + // Sound Effects + //========================================================================================== + + private static final String TAG_AUDIO_ASSETS = "audio_assets"; + private static final String ATTR_VERSION = "version"; + private static final String TAG_GROUP = "group"; + private static final String ATTR_GROUP_NAME = "name"; + private static final String TAG_ASSET = "asset"; + private static final String ATTR_ASSET_ID = "id"; + private static final String ATTR_ASSET_FILE = "file"; + + private static final String ASSET_FILE_VERSION = "1.0"; + private static final String GROUP_TOUCH_SOUNDS = "touch_sounds"; + + private static final int SOUND_EFFECTS_LOAD_TIMEOUT_MS = 5000; + + class LoadSoundEffectReply { + public int mStatus = 1; + }; + + private void loadTouchSoundAssetDefaults() { + SOUND_EFFECT_FILES.add("Effect_Tick.ogg"); + for (int i = 0; i < AudioManager.NUM_SOUND_EFFECTS; i++) { + SOUND_EFFECT_FILES_MAP[i][0] = 0; + SOUND_EFFECT_FILES_MAP[i][1] = -1; + } + } + + private void loadTouchSoundAssets() { + XmlResourceParser parser = null; + + // only load assets once. + if (!SOUND_EFFECT_FILES.isEmpty()) { + return; + } + + loadTouchSoundAssetDefaults(); + + try { + parser = mContext.getResources().getXml(com.android.internal.R.xml.audio_assets); + + XmlUtils.beginDocument(parser, TAG_AUDIO_ASSETS); + String version = parser.getAttributeValue(null, ATTR_VERSION); + boolean inTouchSoundsGroup = false; + + if (ASSET_FILE_VERSION.equals(version)) { + while (true) { + XmlUtils.nextElement(parser); + String element = parser.getName(); + if (element == null) { + break; + } + if (element.equals(TAG_GROUP)) { + String name = parser.getAttributeValue(null, ATTR_GROUP_NAME); + if (GROUP_TOUCH_SOUNDS.equals(name)) { + inTouchSoundsGroup = true; + break; + } + } + } + while (inTouchSoundsGroup) { + XmlUtils.nextElement(parser); + String element = parser.getName(); + if (element == null) { + break; + } + if (element.equals(TAG_ASSET)) { + String id = parser.getAttributeValue(null, ATTR_ASSET_ID); + String file = parser.getAttributeValue(null, ATTR_ASSET_FILE); + int fx; + + try { + Field field = AudioManager.class.getField(id); + fx = field.getInt(null); + } catch (Exception e) { + Log.w(TAG, "Invalid touch sound ID: "+id); + continue; + } + + int i = SOUND_EFFECT_FILES.indexOf(file); + if (i == -1) { + i = SOUND_EFFECT_FILES.size(); + SOUND_EFFECT_FILES.add(file); + } + SOUND_EFFECT_FILES_MAP[fx][0] = i; + } else { + break; + } + } + } + } catch (Resources.NotFoundException e) { + Log.w(TAG, "audio assets file not found", e); + } catch (XmlPullParserException e) { + Log.w(TAG, "XML parser exception reading touch sound assets", e); + } catch (IOException e) { + Log.w(TAG, "I/O exception reading touch sound assets", e); + } finally { + if (parser != null) { + parser.close(); + } + } + } + + /** @see AudioManager#playSoundEffect(int) */ + public void playSoundEffect(int effectType) { + playSoundEffectVolume(effectType, -1.0f); + } + + /** @see AudioManager#playSoundEffect(int, float) */ + public void playSoundEffectVolume(int effectType, float volume) { + if (effectType >= AudioManager.NUM_SOUND_EFFECTS || effectType < 0) { + Log.w(TAG, "AudioService effectType value " + effectType + " out of range"); + return; + } + + sendMsg(mAudioHandler, MSG_PLAY_SOUND_EFFECT, SENDMSG_QUEUE, + effectType, (int) (volume * 1000), null, 0); + } + + /** + * Loads samples into the soundpool. + * This method must be called at first when sound effects are enabled + */ + public boolean loadSoundEffects() { + int attempts = 3; + LoadSoundEffectReply reply = new LoadSoundEffectReply(); + + synchronized (reply) { + sendMsg(mAudioHandler, MSG_LOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, reply, 0); + while ((reply.mStatus == 1) && (attempts-- > 0)) { + try { + reply.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS); + } catch (InterruptedException e) { + Log.w(TAG, "loadSoundEffects Interrupted while waiting sound pool loaded."); + } + } + } + return (reply.mStatus == 0); + } + + /** + * Unloads samples from the sound pool. + * This method can be called to free some memory when + * sound effects are disabled. + */ + public void unloadSoundEffects() { + sendMsg(mAudioHandler, MSG_UNLOAD_SOUND_EFFECTS, SENDMSG_QUEUE, 0, 0, null, 0); + } + + class SoundPoolListenerThread extends Thread { + public SoundPoolListenerThread() { + super("SoundPoolListenerThread"); + } + + @Override + public void run() { + + Looper.prepare(); + mSoundPoolLooper = Looper.myLooper(); + + synchronized (mSoundEffectsLock) { + if (mSoundPool != null) { + mSoundPoolCallBack = new SoundPoolCallback(); + mSoundPool.setOnLoadCompleteListener(mSoundPoolCallBack); + } + mSoundEffectsLock.notify(); + } + Looper.loop(); + } + } + + private final class SoundPoolCallback implements + android.media.SoundPool.OnLoadCompleteListener { + + int mStatus = 1; // 1 means neither error nor last sample loaded yet + List<Integer> mSamples = new ArrayList<Integer>(); + + public int status() { + return mStatus; + } + + public void setSamples(int[] samples) { + for (int i = 0; i < samples.length; i++) { + // do not wait ack for samples rejected upfront by SoundPool + if (samples[i] > 0) { + mSamples.add(samples[i]); + } + } + } + + public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { + synchronized (mSoundEffectsLock) { + int i = mSamples.indexOf(sampleId); + if (i >= 0) { + mSamples.remove(i); + } + if ((status != 0) || mSamples. isEmpty()) { + mStatus = status; + mSoundEffectsLock.notify(); + } + } + } + } + + /** @see AudioManager#reloadAudioSettings() */ + public void reloadAudioSettings() { + readAudioSettings(false /*userSwitch*/); + } + + private void readAudioSettings(boolean userSwitch) { + // restore ringer mode, ringer mode affected streams, mute affected streams and vibrate settings + readPersistedSettings(); + + // restore volume settings + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = 0; streamType < numStreamTypes; streamType++) { + VolumeStreamState streamState = mStreamStates[streamType]; + + if (userSwitch && mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) { + continue; + } + + streamState.readSettings(); + synchronized (VolumeStreamState.class) { + // unmute stream that was muted but is not affect by mute anymore + if (streamState.mIsMuted && ((!isStreamAffectedByMute(streamType) && + !isStreamMutedByRingerMode(streamType)) || mUseFixedVolume)) { + streamState.mIsMuted = false; + } + } + } + + // apply new ringer mode before checking volume for alias streams so that streams + // muted by ringer mode have the correct volume + setRingerModeInt(getRingerModeInternal(), false); + + checkAllFixedVolumeDevices(); + checkAllAliasStreamVolumes(); + checkMuteAffectedStreams(); + + synchronized (mSafeMediaVolumeState) { + mMusicActiveMs = MathUtils.constrain(Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT), + 0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX); + if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) { + enforceSafeMediaVolume(TAG); + } + } + } + + /** @see AudioManager#setSpeakerphoneOn(boolean) */ + public void setSpeakerphoneOn(boolean on){ + if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { + return; + } + + if (on) { + if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, null, 0); + } + mForcedUseForComm = AudioSystem.FORCE_SPEAKER; + } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER){ + mForcedUseForComm = AudioSystem.FORCE_NONE; + } + + sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0); + } + + /** @see AudioManager#isSpeakerphoneOn() */ + public boolean isSpeakerphoneOn() { + return (mForcedUseForComm == AudioSystem.FORCE_SPEAKER); + } + + /** @see AudioManager#setBluetoothScoOn(boolean) */ + public void setBluetoothScoOn(boolean on){ + if (!checkAudioSettingsPermission("setBluetoothScoOn()")) { + return; + } + + if (on) { + mForcedUseForComm = AudioSystem.FORCE_BT_SCO; + } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { + mForcedUseForComm = AudioSystem.FORCE_NONE; + } + + sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0); + sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, + AudioSystem.FOR_RECORD, mForcedUseForComm, null, 0); + } + + /** @see AudioManager#isBluetoothScoOn() */ + public boolean isBluetoothScoOn() { + return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO); + } + + /** @see AudioManager#setBluetoothA2dpOn(boolean) */ + public void setBluetoothA2dpOn(boolean on) { + synchronized (mBluetoothA2dpEnabledLock) { + mBluetoothA2dpEnabled = on; + sendMsg(mAudioHandler, MSG_SET_FORCE_BT_A2DP_USE, SENDMSG_QUEUE, + AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP, + null, 0); + } + } + + /** @see AudioManager#isBluetoothA2dpOn() */ + public boolean isBluetoothA2dpOn() { + synchronized (mBluetoothA2dpEnabledLock) { + return mBluetoothA2dpEnabled; + } + } + + /** @see AudioManager#startBluetoothSco() */ + public void startBluetoothSco(IBinder cb, int targetSdkVersion) { + int scoAudioMode = + (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ? + SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED; + startBluetoothScoInt(cb, scoAudioMode); + } + + /** @see AudioManager#startBluetoothScoVirtualCall() */ + public void startBluetoothScoVirtualCall(IBinder cb) { + startBluetoothScoInt(cb, SCO_MODE_VIRTUAL_CALL); + } + + void startBluetoothScoInt(IBinder cb, int scoAudioMode){ + if (!checkAudioSettingsPermission("startBluetoothSco()") || + !mSystemReady) { + return; + } + ScoClient client = getScoClient(cb, true); + // The calling identity must be cleared before calling ScoClient.incCount(). + // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + // The caller identity must be cleared after getScoClient() because it is needed if a new + // client is created. + final long ident = Binder.clearCallingIdentity(); + client.incCount(scoAudioMode); + Binder.restoreCallingIdentity(ident); + } + + /** @see AudioManager#stopBluetoothSco() */ + public void stopBluetoothSco(IBinder cb){ + if (!checkAudioSettingsPermission("stopBluetoothSco()") || + !mSystemReady) { + return; + } + ScoClient client = getScoClient(cb, false); + // The calling identity must be cleared before calling ScoClient.decCount(). + // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + final long ident = Binder.clearCallingIdentity(); + if (client != null) { + client.decCount(); + } + Binder.restoreCallingIdentity(ident); + } + + + private class ScoClient implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + private int mCreatorPid; + private int mStartcount; // number of SCO connections started by this client + + ScoClient(IBinder cb) { + mCb = cb; + mCreatorPid = Binder.getCallingPid(); + mStartcount = 0; + } + + public void binderDied() { + synchronized(mScoClients) { + Log.w(TAG, "SCO client died"); + int index = mScoClients.indexOf(this); + if (index < 0) { + Log.w(TAG, "unregistered SCO client died"); + } else { + clearCount(true); + mScoClients.remove(this); + } + } + } + + public void incCount(int scoAudioMode) { + synchronized(mScoClients) { + requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); + if (mStartcount == 0) { + try { + mCb.linkToDeath(this, 0); + } catch (RemoteException e) { + // client has already died! + Log.w(TAG, "ScoClient incCount() could not link to "+mCb+" binder death"); + } + } + mStartcount++; + } + } + + public void decCount() { + synchronized(mScoClients) { + if (mStartcount == 0) { + Log.w(TAG, "ScoClient.decCount() already 0"); + } else { + mStartcount--; + if (mStartcount == 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "decCount() going to 0 but not registered to binder"); + } + } + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + } + + public void clearCount(boolean stopSco) { + synchronized(mScoClients) { + if (mStartcount != 0) { + try { + mCb.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Log.w(TAG, "clearCount() mStartcount: "+mStartcount+" != 0 but not registered to binder"); + } + } + mStartcount = 0; + if (stopSco) { + requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0); + } + } + } + + public int getCount() { + return mStartcount; + } + + public IBinder getBinder() { + return mCb; + } + + public int getPid() { + return mCreatorPid; + } + + public int totalCount() { + synchronized(mScoClients) { + int count = 0; + int size = mScoClients.size(); + for (int i = 0; i < size; i++) { + count += mScoClients.get(i).getCount(); + } + return count; + } + } + + private void requestScoState(int state, int scoAudioMode) { + checkScoAudioState(); + if (totalCount() == 0) { + if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + // Make sure that the state transitions to CONNECTING even if we cannot initiate + // the connection. + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); + // Accept SCO audio activation only in NORMAL audio mode or if the mode is + // currently controlled by the same client process. + synchronized(mSetModeDeathHandlers) { + if ((mSetModeDeathHandlers.isEmpty() || + mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) && + (mScoAudioState == SCO_STATE_INACTIVE || + mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) { + if (mScoAudioState == SCO_STATE_INACTIVE) { + mScoAudioMode = scoAudioMode; + if (scoAudioMode == SCO_MODE_UNDEFINED) { + if (mBluetoothHeadsetDevice != null) { + mScoAudioMode = new Integer(Settings.Global.getInt( + mContentResolver, + "bluetooth_sco_channel_"+ + mBluetoothHeadsetDevice.getAddress(), + SCO_MODE_VIRTUAL_CALL)); + if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + } + } else { + mScoAudioMode = SCO_MODE_RAW; + } + } + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) { + boolean status = false; + if (mScoAudioMode == SCO_MODE_RAW) { + status = mBluetoothHeadset.connectAudio(); + } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { + status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( + mBluetoothHeadsetDevice); + } else if (mScoAudioMode == SCO_MODE_VR) { + status = mBluetoothHeadset.startVoiceRecognition( + mBluetoothHeadsetDevice); + } + + if (status) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } else { + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } else if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + } + } else { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); + } + } else { + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } + } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED && + (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || + mScoAudioState == SCO_STATE_ACTIVATE_REQ)) { + if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) { + boolean status = false; + if (mScoAudioMode == SCO_MODE_RAW) { + status = mBluetoothHeadset.disconnectAudio(); + } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { + status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( + mBluetoothHeadsetDevice); + } else if (mScoAudioMode == SCO_MODE_VR) { + status = mBluetoothHeadset.stopVoiceRecognition( + mBluetoothHeadsetDevice); + } + + if (!status) { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } else if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_DEACTIVATE_REQ; + } + } else { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } + } + } + } + + private void checkScoAudioState() { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && + mScoAudioState == SCO_STATE_INACTIVE && + mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + } + + private ScoClient getScoClient(IBinder cb, boolean create) { + synchronized(mScoClients) { + ScoClient client = null; + int size = mScoClients.size(); + for (int i = 0; i < size; i++) { + client = mScoClients.get(i); + if (client.getBinder() == cb) + return client; + } + if (create) { + client = new ScoClient(cb); + mScoClients.add(client); + } + return client; + } + } + + public void clearAllScoClients(int exceptPid, boolean stopSco) { + synchronized(mScoClients) { + ScoClient savedClient = null; + int size = mScoClients.size(); + for (int i = 0; i < size; i++) { + ScoClient cl = mScoClients.get(i); + if (cl.getPid() != exceptPid) { + cl.clearCount(stopSco); + } else { + savedClient = cl; + } + } + mScoClients.clear(); + if (savedClient != null) { + mScoClients.add(savedClient); + } + } + } + + private boolean getBluetoothHeadset() { + boolean result = false; + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + result = adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, + BluetoothProfile.HEADSET); + } + // If we could not get a bluetooth headset proxy, send a failure message + // without delay to reset the SCO audio state and clear SCO clients. + // If we could get a proxy, send a delayed failure message that will reset our state + // in case we don't receive onServiceConnected(). + sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, + SENDMSG_REPLACE, 0, 0, null, result ? BT_HEADSET_CNCT_TIMEOUT_MS : 0); + return result; + } + + private void disconnectBluetoothSco(int exceptPid) { + synchronized(mScoClients) { + checkScoAudioState(); + if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL || + mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) { + if (mBluetoothHeadsetDevice != null) { + if (mBluetoothHeadset != null) { + if (!mBluetoothHeadset.stopVoiceRecognition( + mBluetoothHeadsetDevice)) { + sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, + SENDMSG_REPLACE, 0, 0, null, 0); + } + } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL && + getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_DEACTIVATE_EXT_REQ; + } + } + } else { + clearAllScoClients(exceptPid, true); + } + } + } + + private void resetBluetoothSco() { + synchronized(mScoClients) { + clearAllScoClients(0, false); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } + + private void broadcastScoConnectionState(int state) { + sendMsg(mAudioHandler, MSG_BROADCAST_BT_CONNECTION_STATE, + SENDMSG_QUEUE, state, 0, null, 0); + } + + private void onBroadcastScoConnectionState(int state) { + if (state != mScoConnectionState) { + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, state); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, + mScoConnectionState); + sendStickyBroadcastToAll(newIntent); + mScoConnectionState = state; + } + } + + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + BluetoothDevice btDevice; + List<BluetoothDevice> deviceList; + switch(profile) { + case BluetoothProfile.A2DP: + synchronized (mConnectedDevices) { + synchronized (mA2dpAvrcpLock) { + mA2dp = (BluetoothA2dp) proxy; + deviceList = mA2dp.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + int state = mA2dp.getConnectionState(btDevice); + int delay = checkSendBecomingNoisyIntent( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0); + queueMsgUnderWakeLock(mAudioHandler, + MSG_SET_A2DP_SINK_CONNECTION_STATE, + state, + 0, + btDevice, + delay); + } + } + } + break; + + case BluetoothProfile.A2DP_SINK: + deviceList = proxy.getConnectedDevices(); + if (deviceList.size() > 0) { + btDevice = deviceList.get(0); + synchronized (mConnectedDevices) { + int state = proxy.getConnectionState(btDevice); + queueMsgUnderWakeLock(mAudioHandler, + MSG_SET_A2DP_SRC_CONNECTION_STATE, + state, + 0, + btDevice, + 0 /* delay */); + } + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + // Discard timeout message + mAudioHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED); + mBluetoothHeadset = (BluetoothHeadset) proxy; + deviceList = mBluetoothHeadset.getConnectedDevices(); + if (deviceList.size() > 0) { + mBluetoothHeadsetDevice = deviceList.get(0); + } else { + mBluetoothHeadsetDevice = null; + } + // Refresh SCO audio state + checkScoAudioState(); + // Continue pending action if any + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ || + mScoAudioState == SCO_STATE_DEACTIVATE_REQ || + mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) { + boolean status = false; + if (mBluetoothHeadsetDevice != null) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVATE_REQ: + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + if (mScoAudioMode == SCO_MODE_RAW) { + status = mBluetoothHeadset.connectAudio(); + } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { + status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( + mBluetoothHeadsetDevice); + } else if (mScoAudioMode == SCO_MODE_VR) { + status = mBluetoothHeadset.startVoiceRecognition( + mBluetoothHeadsetDevice); + } + break; + case SCO_STATE_DEACTIVATE_REQ: + if (mScoAudioMode == SCO_MODE_RAW) { + status = mBluetoothHeadset.disconnectAudio(); + } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { + status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( + mBluetoothHeadsetDevice); + } else if (mScoAudioMode == SCO_MODE_VR) { + status = mBluetoothHeadset.stopVoiceRecognition( + mBluetoothHeadsetDevice); + } + break; + case SCO_STATE_DEACTIVATE_EXT_REQ: + status = mBluetoothHeadset.stopVoiceRecognition( + mBluetoothHeadsetDevice); + } + } + if (!status) { + sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, + SENDMSG_REPLACE, 0, 0, null, 0); + } + } + } + break; + + default: + break; + } + } + public void onServiceDisconnected(int profile) { + ArraySet<String> toRemove = null; + switch (profile) { + case BluetoothProfile.A2DP: + synchronized (mConnectedDevices) { + synchronized (mA2dpAvrcpLock) { + // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices + for (int i = 0; i < mConnectedDevices.size(); i++) { + DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); + if (deviceSpec.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) { + toRemove = toRemove != null ? toRemove : new ArraySet<String>(); + toRemove.add(deviceSpec.mDeviceAddress); + } + } + if (toRemove != null) { + for (int i = 0; i < toRemove.size(); i++) { + makeA2dpDeviceUnavailableNow(toRemove.valueAt(i)); + } + } + } + } + break; + + case BluetoothProfile.A2DP_SINK: + synchronized (mConnectedDevices) { + // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices + for(int i = 0; i < mConnectedDevices.size(); i++) { + DeviceListSpec deviceSpec = mConnectedDevices.valueAt(i); + if (deviceSpec.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) { + toRemove = toRemove != null ? toRemove : new ArraySet<String>(); + toRemove.add(deviceSpec.mDeviceAddress); + } + } + if (toRemove != null) { + for (int i = 0; i < toRemove.size(); i++) { + makeA2dpSrcUnavailable(toRemove.valueAt(i)); + } + } + } + break; + + case BluetoothProfile.HEADSET: + synchronized (mScoClients) { + mBluetoothHeadset = null; + } + break; + + default: + break; + } + } + }; + + private void onCheckMusicActive(String caller) { + synchronized (mSafeMediaVolumeState) { + if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) { + int device = getDeviceForStream(AudioSystem.STREAM_MUSIC); + + if ((device & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); + int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device); + if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && + (index > mSafeMediaVolumeIndex)) { + // Approximate cumulative active music time + mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS; + if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) { + setSafeMediaVolumeEnabled(true, caller); + mMusicActiveMs = 0; + } + saveMusicActiveMs(); + } + } + } + } + } + + private void saveMusicActiveMs() { + mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget(); + } + + private void onConfigureSafeVolume(boolean force, String caller) { + synchronized (mSafeMediaVolumeState) { + int mcc = mContext.getResources().getConfiguration().mcc; + if ((mMcc != mcc) || ((mMcc == 0) && force)) { + mSafeMediaVolumeIndex = mContext.getResources().getInteger( + com.android.internal.R.integer.config_safe_media_volume_index) * 10; + boolean safeMediaVolumeEnabled = + SystemProperties.getBoolean("audio.safemedia.force", false) + || mContext.getResources().getBoolean( + com.android.internal.R.bool.config_safe_media_volume_enabled); + + // The persisted state is either "disabled" or "active": this is the state applied + // next time we boot and cannot be "inactive" + int persistedState; + if (safeMediaVolumeEnabled) { + persistedState = SAFE_MEDIA_VOLUME_ACTIVE; + // The state can already be "inactive" here if the user has forced it before + // the 30 seconds timeout for forced configuration. In this case we don't reset + // it to "active". + if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) { + if (mMusicActiveMs == 0) { + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; + enforceSafeMediaVolume(caller); + } else { + // We have existing playback time recorded, already confirmed. + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE; + } + } + } else { + persistedState = SAFE_MEDIA_VOLUME_DISABLED; + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED; + } + mMcc = mcc; + sendMsg(mAudioHandler, + MSG_PERSIST_SAFE_VOLUME_STATE, + SENDMSG_QUEUE, + persistedState, + 0, + null, + 0); + } + } + } + + /////////////////////////////////////////////////////////////////////////// + // Internal methods + /////////////////////////////////////////////////////////////////////////// + + /** + * Checks if the adjustment should change ringer mode instead of just + * adjusting volume. If so, this will set the proper ringer mode and volume + * indices on the stream states. + */ + private int checkForRingerModeChange(int oldIndex, int direction, int step, boolean isMuted) { + final boolean isTv = mPlatformType == AudioSystem.PLATFORM_TELEVISION; + int result = FLAG_ADJUST_VOLUME; + int ringerMode = getRingerModeInternal(); + + switch (ringerMode) { + case RINGER_MODE_NORMAL: + if (direction == AudioManager.ADJUST_LOWER) { + if (mHasVibrator) { + // "step" is the delta in internal index units corresponding to a + // change of 1 in UI index units. + // Because of rounding when rescaling from one stream index range to its alias + // index range, we cannot simply test oldIndex == step: + // (step <= oldIndex < 2 * step) is equivalent to: (old UI index == 1) + if (step <= oldIndex && oldIndex < 2 * step) { + ringerMode = RINGER_MODE_VIBRATE; + mLoweredFromNormalToVibrateTime = SystemClock.uptimeMillis(); + } + } else { + // (oldIndex < step) is equivalent to (old UI index == 0) + if ((oldIndex < step) + && mVolumePolicy.volumeDownToEnterSilent + && mPrevVolDirection != AudioManager.ADJUST_LOWER) { + ringerMode = RINGER_MODE_SILENT; + } + } + } else if (isTv && (direction == AudioManager.ADJUST_TOGGLE_MUTE + || direction == AudioManager.ADJUST_MUTE)) { + if (mHasVibrator) { + ringerMode = RINGER_MODE_VIBRATE; + } else { + ringerMode = RINGER_MODE_SILENT; + } + // Setting the ringer mode will toggle mute + result &= ~FLAG_ADJUST_VOLUME; + } + break; + case RINGER_MODE_VIBRATE: + if (!mHasVibrator) { + Log.e(TAG, "checkForRingerModeChange() current ringer mode is vibrate" + + "but no vibrator is present"); + break; + } + if ((direction == AudioManager.ADJUST_LOWER)) { + // This is the case we were muted with the volume turned up + if (isTv && oldIndex >= 2 * step && isMuted) { + ringerMode = RINGER_MODE_NORMAL; + } else if (mPrevVolDirection != AudioManager.ADJUST_LOWER) { + if (mVolumePolicy.volumeDownToEnterSilent) { + final long diff = SystemClock.uptimeMillis() + - mLoweredFromNormalToVibrateTime; + if (diff > mVolumePolicy.vibrateToSilentDebounce) { + ringerMode = RINGER_MODE_SILENT; + } + } else { + result |= AudioManager.FLAG_SHOW_VIBRATE_HINT; + } + } + } else if (direction == AudioManager.ADJUST_RAISE + || direction == AudioManager.ADJUST_TOGGLE_MUTE + || direction == AudioManager.ADJUST_UNMUTE) { + ringerMode = RINGER_MODE_NORMAL; + } + result &= ~FLAG_ADJUST_VOLUME; + break; + case RINGER_MODE_SILENT: + if (isTv && direction == AudioManager.ADJUST_LOWER && oldIndex >= 2 * step && isMuted) { + // This is the case we were muted with the volume turned up + ringerMode = RINGER_MODE_NORMAL; + } else if (direction == AudioManager.ADJUST_RAISE + || direction == AudioManager.ADJUST_TOGGLE_MUTE + || direction == AudioManager.ADJUST_UNMUTE) { + if (!mVolumePolicy.volumeUpToExitSilent) { + result |= AudioManager.FLAG_SHOW_SILENT_HINT; + } else { + if (mHasVibrator && direction == AudioManager.ADJUST_RAISE) { + ringerMode = RINGER_MODE_VIBRATE; + } else { + // If we don't have a vibrator or they were toggling mute + // go straight back to normal. + ringerMode = RINGER_MODE_NORMAL; + } + } + } + result &= ~FLAG_ADJUST_VOLUME; + break; + default: + Log.e(TAG, "checkForRingerModeChange() wrong ringer mode: "+ringerMode); + break; + } + + setRingerMode(ringerMode, TAG + ".checkForRingerModeChange", false /*external*/); + + mPrevVolDirection = direction; + + return result; + } + + @Override + public boolean isStreamAffectedByRingerMode(int streamType) { + return (mRingerModeAffectedStreams & (1 << streamType)) != 0; + } + + private boolean isStreamMutedByRingerMode(int streamType) { + return (mRingerModeMutedStreams & (1 << streamType)) != 0; + } + + boolean updateRingerModeAffectedStreams() { + int ringerModeAffectedStreams; + // make sure settings for ringer mode are consistent with device type: non voice capable + // devices (tablets) include media stream in silent mode whereas phones don't. + ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver, + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + ((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)| + (1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)), + UserHandle.USER_CURRENT); + + // ringtone, notification and system streams are always affected by ringer mode + ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_RING)| + (1 << AudioSystem.STREAM_NOTIFICATION)| + (1 << AudioSystem.STREAM_SYSTEM); + + switch (mPlatformType) { + case AudioSystem.PLATFORM_TELEVISION: + ringerModeAffectedStreams = 0; + break; + default: + ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_MUSIC); + break; + } + + synchronized (mCameraSoundForced) { + if (mCameraSoundForced) { + ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } else { + ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } + } + if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) { + ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF); + } else { + ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_DTMF); + } + + if (ringerModeAffectedStreams != mRingerModeAffectedStreams) { + Settings.System.putIntForUser(mContentResolver, + Settings.System.MODE_RINGER_STREAMS_AFFECTED, + ringerModeAffectedStreams, + UserHandle.USER_CURRENT); + mRingerModeAffectedStreams = ringerModeAffectedStreams; + return true; + } + return false; + } + + @Override + public boolean isStreamAffectedByMute(int streamType) { + return (mMuteAffectedStreams & (1 << streamType)) != 0; + } + + private void ensureValidDirection(int direction) { + switch (direction) { + case AudioManager.ADJUST_LOWER: + case AudioManager.ADJUST_RAISE: + case AudioManager.ADJUST_SAME: + case AudioManager.ADJUST_MUTE: + case AudioManager.ADJUST_UNMUTE: + case AudioManager.ADJUST_TOGGLE_MUTE: + break; + default: + throw new IllegalArgumentException("Bad direction " + direction); + } + } + + private void ensureValidStreamType(int streamType) { + if (streamType < 0 || streamType >= mStreamStates.length) { + throw new IllegalArgumentException("Bad stream type " + streamType); + } + } + + private boolean isMuteAdjust(int adjust) { + return adjust == AudioManager.ADJUST_MUTE || adjust == AudioManager.ADJUST_UNMUTE + || adjust == AudioManager.ADJUST_TOGGLE_MUTE; + } + + private boolean isInCommunication() { + boolean IsInCall = false; + + TelecomManager telecomManager = + (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); + + final long ident = Binder.clearCallingIdentity(); + IsInCall = telecomManager.isInCall(); + Binder.restoreCallingIdentity(ident); + + return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION); + } + + /** + * For code clarity for getActiveStreamType(int) + * @param delay_ms max time since last STREAM_MUSIC activity to consider + * @return true if STREAM_MUSIC is active in streams handled by AudioFlinger now or + * in the last "delay_ms" ms. + */ + private boolean isAfMusicActiveRecently(int delay_ms) { + return AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, delay_ms) + || AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, delay_ms); + } + + private int getActiveStreamType(int suggestedStreamType) { + switch (mPlatformType) { + case AudioSystem.PLATFORM_VOICE: + if (isInCommunication()) { + if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) + == AudioSystem.FORCE_BT_SCO) { + // Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO..."); + return AudioSystem.STREAM_BLUETOOTH_SCO; + } else { + // Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL..."); + return AudioSystem.STREAM_VOICE_CALL; + } + } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { + if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); + return AudioSystem.STREAM_MUSIC; + } else { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default"); + return AudioSystem.STREAM_RING; + } + } else if (isAfMusicActiveRecently(0)) { + if (DEBUG_VOL) + Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active"); + return AudioSystem.STREAM_MUSIC; + } + break; + case AudioSystem.PLATFORM_TELEVISION: + if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { + // TV always defaults to STREAM_MUSIC + return AudioSystem.STREAM_MUSIC; + } + break; + default: + if (isInCommunication()) { + if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) + == AudioSystem.FORCE_BT_SCO) { + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO"); + return AudioSystem.STREAM_BLUETOOTH_SCO; + } else { + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL"); + return AudioSystem.STREAM_VOICE_CALL; + } + } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION, + StreamOverride.sDelayMs) || + AudioSystem.isStreamActive(AudioSystem.STREAM_RING, + StreamOverride.sDelayMs)) { + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION"); + return AudioSystem.STREAM_NOTIFICATION; + } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) { + if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) { + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC"); + return AudioSystem.STREAM_MUSIC; + } else { + if (DEBUG_VOL) Log.v(TAG, + "getActiveStreamType: using STREAM_NOTIFICATION as default"); + return AudioSystem.STREAM_NOTIFICATION; + } + } + break; + } + if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Returning suggested type " + + suggestedStreamType); + return suggestedStreamType; + } + + private void broadcastRingerMode(String action, int ringerMode) { + // Send sticky broadcast + Intent broadcast = new Intent(action); + broadcast.putExtra(AudioManager.EXTRA_RINGER_MODE, ringerMode); + broadcast.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_REPLACE_PENDING); + sendStickyBroadcastToAll(broadcast); + } + + private void broadcastVibrateSetting(int vibrateType) { + // Send broadcast + if (ActivityManagerNative.isSystemReady()) { + Intent broadcast = new Intent(AudioManager.VIBRATE_SETTING_CHANGED_ACTION); + broadcast.putExtra(AudioManager.EXTRA_VIBRATE_TYPE, vibrateType); + broadcast.putExtra(AudioManager.EXTRA_VIBRATE_SETTING, getVibrateSetting(vibrateType)); + sendBroadcastToAll(broadcast); + } + } + + // Message helper methods + /** + * Queue a message on the given handler's message queue, after acquiring the service wake lock. + * Note that the wake lock needs to be released after the message has been handled. + */ + private void queueMsgUnderWakeLock(Handler handler, int msg, + int arg1, int arg2, Object obj, int delay) { + final long ident = Binder.clearCallingIdentity(); + // Always acquire the wake lock as AudioService because it is released by the + // message handler. + mAudioEventWakeLock.acquire(); + Binder.restoreCallingIdentity(ident); + sendMsg(handler, msg, SENDMSG_QUEUE, arg1, arg2, obj, delay); + } + + private static void sendMsg(Handler handler, int msg, + int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { + + if (existingMsgPolicy == SENDMSG_REPLACE) { + handler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { + return; + } + synchronized (mLastDeviceConnectMsgTime) { + long time = SystemClock.uptimeMillis() + delay; + handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time); + if (msg == MSG_SET_WIRED_DEVICE_CONNECTION_STATE || + msg == MSG_SET_A2DP_SRC_CONNECTION_STATE || + msg == MSG_SET_A2DP_SINK_CONNECTION_STATE) { + mLastDeviceConnectMsgTime = time; + } + } + } + + boolean checkAudioSettingsPermission(String method) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + String msg = "Audio Settings Permission Denial: " + method + " from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid(); + Log.w(TAG, msg); + return false; + } + + private int getDeviceForStream(int stream) { + int device = getDevicesForStream(stream); + if ((device & (device - 1)) != 0) { + // Multiple device selection is either: + // - speaker + one other device: give priority to speaker in this case. + // - one A2DP device + another device: happens with duplicated output. In this case + // retain the device on the A2DP output as the other must not correspond to an active + // selection if not the speaker. + // - HDMI-CEC system audio mode only output: give priority to available item in order. + if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) { + device = AudioSystem.DEVICE_OUT_SPEAKER; + } else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) { + device = AudioSystem.DEVICE_OUT_HDMI_ARC; + } else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) { + device = AudioSystem.DEVICE_OUT_SPDIF; + } else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) { + device = AudioSystem.DEVICE_OUT_AUX_LINE; + } else { + device &= AudioSystem.DEVICE_OUT_ALL_A2DP; + } + } + return device; + } + + private int getDevicesForStream(int stream) { + return getDevicesForStream(stream, true /*checkOthers*/); + } + + private int getDevicesForStream(int stream, boolean checkOthers) { + ensureValidStreamType(stream); + synchronized (VolumeStreamState.class) { + return mStreamStates[stream].observeDevicesForStream_syncVSS(checkOthers); + } + } + + private void observeDevicesForStreams(int skipStream) { + synchronized (VolumeStreamState.class) { + for (int stream = 0; stream < mStreamStates.length; stream++) { + if (stream != skipStream) { + mStreamStates[stream].observeDevicesForStream_syncVSS(false /*checkOthers*/); + } + } + } + } + + /* + * A class just for packaging up a set of connection parameters. + */ + private class WiredDeviceConnectionState { + public final int mType; + public final int mState; + public final String mAddress; + public final String mName; + public final String mCaller; + + public WiredDeviceConnectionState(int type, int state, String address, String name, + String caller) { + mType = type; + mState = state; + mAddress = address; + mName = name; + mCaller = caller; + } + } + + public void setWiredDeviceConnectionState(int type, int state, String address, String name, + String caller) { + synchronized (mConnectedDevices) { + if (DEBUG_DEVICES) { + Slog.i(TAG, "setWiredDeviceConnectionState(" + state + " nm: " + name + " addr:" + + address + ")"); + } + int delay = checkSendBecomingNoisyIntent(type, state); + queueMsgUnderWakeLock(mAudioHandler, + MSG_SET_WIRED_DEVICE_CONNECTION_STATE, + 0, + 0, + new WiredDeviceConnectionState(type, state, address, name, caller), + delay); + } + } + + public int setBluetoothA2dpDeviceConnectionState(BluetoothDevice device, int state, int profile) + { + int delay; + if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { + throw new IllegalArgumentException("invalid profile " + profile); + } + synchronized (mConnectedDevices) { + if (profile == BluetoothProfile.A2DP) { + delay = checkSendBecomingNoisyIntent(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0); + } else { + delay = 0; + } + queueMsgUnderWakeLock(mAudioHandler, + (profile == BluetoothProfile.A2DP ? + MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE), + state, + 0, + device, + delay); + } + return delay; + } + + /////////////////////////////////////////////////////////////////////////// + // Inner classes + /////////////////////////////////////////////////////////////////////////// + + // NOTE: Locking order for synchronized objects related to volume or ringer mode management: + // 1 mScoclient OR mSafeMediaVolumeState + // 2 mSetModeDeathHandlers + // 3 mSettingsLock + // 4 VolumeStreamState.class + // 5 mCameraSoundForced + public class VolumeStreamState { + private final int mStreamType; + private final int mIndexMin; + private final int mIndexMax; + + private boolean mIsMuted; + private String mVolumeIndexSettingName; + private int mObservedDevices; + + private final SparseIntArray mIndexMap = new SparseIntArray(8); + private final Intent mVolumeChanged; + private final Intent mStreamDevicesChanged; + + private VolumeStreamState(String settingName, int streamType) { + + mVolumeIndexSettingName = settingName; + + mStreamType = streamType; + mIndexMin = MIN_STREAM_VOLUME[streamType] * 10; + mIndexMax = MAX_STREAM_VOLUME[streamType] * 10; + AudioSystem.initStreamVolume(streamType, mIndexMin / 10, mIndexMax / 10); + + readSettings(); + mVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION); + mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); + mStreamDevicesChanged = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION); + mStreamDevicesChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); + } + + public int observeDevicesForStream_syncVSS(boolean checkOthers) { + final int devices = AudioSystem.getDevicesForStream(mStreamType); + if (devices == mObservedDevices) { + return devices; + } + final int prevDevices = mObservedDevices; + mObservedDevices = devices; + if (checkOthers) { + // one stream's devices have changed, check the others + observeDevicesForStreams(mStreamType); + } + // log base stream changes to the event log + if (mStreamVolumeAlias[mStreamType] == mStreamType) { + EventLogTags.writeStreamDevicesChanged(mStreamType, prevDevices, devices); + } + sendBroadcastToAll(mStreamDevicesChanged + .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, prevDevices) + .putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, devices)); + return devices; + } + + public String getSettingNameForDevice(int device) { + String name = mVolumeIndexSettingName; + String suffix = AudioSystem.getOutputDeviceName(device); + if (suffix.isEmpty()) { + return name; + } + return name + "_" + suffix; + } + + public void readSettings() { + synchronized (VolumeStreamState.class) { + // force maximum volume on all streams if fixed volume property is set + if (mUseFixedVolume) { + mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); + return; + } + // do not read system stream volume from settings: this stream is always aliased + // to another stream type and its volume is never persisted. Values in settings can + // only be stale values + if ((mStreamType == AudioSystem.STREAM_SYSTEM) || + (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) { + int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType]; + synchronized (mCameraSoundForced) { + if (mCameraSoundForced) { + index = mIndexMax; + } + } + mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index); + return; + } + + int remainingDevices = AudioSystem.DEVICE_OUT_ALL; + + for (int i = 0; remainingDevices != 0; i++) { + int device = (1 << i); + if ((device & remainingDevices) == 0) { + continue; + } + remainingDevices &= ~device; + + // retrieve current volume for device + String name = getSettingNameForDevice(device); + // if no volume stored for current stream and device, use default volume if default + // device, continue otherwise + int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT) ? + AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType] : -1; + int index = Settings.System.getIntForUser( + mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); + if (index == -1) { + continue; + } + + mIndexMap.put(device, getValidIndex(10 * index)); + } + } + } + + // must be called while synchronized VolumeStreamState.class + public void applyDeviceVolume_syncVSS(int device) { + int index; + if (mIsMuted) { + index = 0; + } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) + || ((device & mFullVolumeDevices) != 0)) { + index = (mIndexMax + 5)/10; + } else { + index = (getIndex(device) + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, device); + } + + public void applyAllVolumes() { + synchronized (VolumeStreamState.class) { + // apply default volume first: by convention this will reset all + // devices volumes in audio policy manager to the supplied value + int index; + if (mIsMuted) { + index = 0; + } else { + index = (getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, AudioSystem.DEVICE_OUT_DEFAULT); + // then apply device specific volumes + for (int i = 0; i < mIndexMap.size(); i++) { + int device = mIndexMap.keyAt(i); + if (device != AudioSystem.DEVICE_OUT_DEFAULT) { + if (mIsMuted) { + index = 0; + } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + mAvrcpAbsVolSupported) + || ((device & mFullVolumeDevices) != 0)) + { + index = (mIndexMax + 5)/10; + } else { + index = (mIndexMap.valueAt(i) + 5)/10; + } + AudioSystem.setStreamVolumeIndex(mStreamType, index, device); + } + } + } + } + + public boolean adjustIndex(int deltaIndex, int device, String caller) { + return setIndex(getIndex(device) + deltaIndex, device, caller); + } + + public boolean setIndex(int index, int device, String caller) { + boolean changed = false; + int oldIndex; + synchronized (VolumeStreamState.class) { + oldIndex = getIndex(device); + index = getValidIndex(index); + synchronized (mCameraSoundForced) { + if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) { + index = mIndexMax; + } + } + mIndexMap.put(device, index); + + changed = oldIndex != index; + if (changed) { + // Apply change to all streams using this one as alias + // if changing volume of current device, also change volume of current + // device on aliased stream + boolean currentDevice = (device == getDeviceForStream(mStreamType)); + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + if (streamType != mStreamType && + mStreamVolumeAlias[streamType] == mStreamType) { + int scaledIndex = rescaleIndex(index, mStreamType, streamType); + mStreamStates[streamType].setIndex(scaledIndex, device, caller); + if (currentDevice) { + mStreamStates[streamType].setIndex(scaledIndex, + getDeviceForStream(streamType), caller); + } + } + } + } + } + if (changed) { + oldIndex = (oldIndex + 5) / 10; + index = (index + 5) / 10; + // log base stream changes to the event log + if (mStreamVolumeAlias[mStreamType] == mStreamType) { + if (caller == null) { + Log.w(TAG, "No caller for volume_changed event", new Throwable()); + } + EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10, + caller); + } + // fire changed intents for all streams + mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index); + mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex); + sendBroadcastToAll(mVolumeChanged); + } + return changed; + } + + public int getIndex(int device) { + synchronized (VolumeStreamState.class) { + int index = mIndexMap.get(device, -1); + if (index == -1) { + // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT + index = mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT); + } + return index; + } + } + + public int getMaxIndex() { + return mIndexMax; + } + + public int getMinIndex() { + return mIndexMin; + } + + public void setAllIndexes(VolumeStreamState srcStream, String caller) { + synchronized (VolumeStreamState.class) { + int srcStreamType = srcStream.getStreamType(); + // apply default device volume from source stream to all devices first in case + // some devices are present in this stream state but not in source stream state + int index = srcStream.getIndex(AudioSystem.DEVICE_OUT_DEFAULT); + index = rescaleIndex(index, srcStreamType, mStreamType); + for (int i = 0; i < mIndexMap.size(); i++) { + mIndexMap.put(mIndexMap.keyAt(i), index); + } + // Now apply actual volume for devices in source stream state + SparseIntArray srcMap = srcStream.mIndexMap; + for (int i = 0; i < srcMap.size(); i++) { + int device = srcMap.keyAt(i); + index = srcMap.valueAt(i); + index = rescaleIndex(index, srcStreamType, mStreamType); + + setIndex(index, device, caller); + } + } + } + + public void setAllIndexesToMax() { + synchronized (VolumeStreamState.class) { + for (int i = 0; i < mIndexMap.size(); i++) { + mIndexMap.put(mIndexMap.keyAt(i), mIndexMax); + } + } + } + + public void mute(boolean state) { + boolean changed = false; + synchronized (VolumeStreamState.class) { + if (state != mIsMuted) { + changed = true; + mIsMuted = state; + + // Set the new mute volume. This propagates the values to + // the audio system, otherwise the volume won't be changed + // at the lower level. + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + this, 0); + } + } + if (changed) { + // Stream mute changed, fire the intent. + Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); + intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state); + sendBroadcastToAll(intent); + } + } + + public int getStreamType() { + return mStreamType; + } + + public void checkFixedVolumeDevices() { + synchronized (VolumeStreamState.class) { + // ignore settings for fixed volume devices: volume should always be at max or 0 + if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC) { + for (int i = 0; i < mIndexMap.size(); i++) { + int device = mIndexMap.keyAt(i); + int index = mIndexMap.valueAt(i); + if (((device & mFullVolumeDevices) != 0) + || (((device & mFixedVolumeDevices) != 0) && index != 0)) { + mIndexMap.put(device, mIndexMax); + } + applyDeviceVolume_syncVSS(device); + } + } + } + } + + private int getValidIndex(int index) { + if (index < mIndexMin) { + return mIndexMin; + } else if (mUseFixedVolume || index > mIndexMax) { + return mIndexMax; + } + + return index; + } + + private void dump(PrintWriter pw) { + pw.print(" Muted: "); + pw.println(mIsMuted); + pw.print(" Min: "); + pw.println((mIndexMin + 5) / 10); + pw.print(" Max: "); + pw.println((mIndexMax + 5) / 10); + pw.print(" Current: "); + for (int i = 0; i < mIndexMap.size(); i++) { + if (i > 0) { + pw.print(", "); + } + final int device = mIndexMap.keyAt(i); + pw.print(Integer.toHexString(device)); + final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default" + : AudioSystem.getOutputDeviceName(device); + if (!deviceName.isEmpty()) { + pw.print(" ("); + pw.print(deviceName); + pw.print(")"); + } + pw.print(": "); + final int index = (mIndexMap.valueAt(i) + 5) / 10; + pw.print(index); + } + pw.println(); + pw.print(" Devices: "); + final int devices = getDevicesForStream(mStreamType); + int device, i = 0, n = 0; + // iterate all devices from 1 to DEVICE_OUT_DEFAULT exclusive + // (the default device is not returned by getDevicesForStream) + while ((device = 1 << i) != AudioSystem.DEVICE_OUT_DEFAULT) { + if ((devices & device) != 0) { + if (n++ > 0) { + pw.print(", "); + } + pw.print(AudioSystem.getOutputDeviceName(device)); + } + i++; + } + } + } + + /** Thread that handles native AudioSystem control. */ + private class AudioSystemThread extends Thread { + AudioSystemThread() { + super("AudioService"); + } + + @Override + public void run() { + // Set this thread up so the handler will work on it + Looper.prepare(); + + synchronized(AudioService.this) { + mAudioHandler = new AudioHandler(); + + // Notify that the handler has been created + AudioService.this.notify(); + } + + // Listen for volume change requests that are set by VolumePanel + Looper.loop(); + } + } + + /** Handles internal volume messages in separate volume thread. */ + private class AudioHandler extends Handler { + + private void setDeviceVolume(VolumeStreamState streamState, int device) { + + synchronized (VolumeStreamState.class) { + // Apply volume + streamState.applyDeviceVolume_syncVSS(device); + + // Apply change to all streams using this one as alias + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + if (streamType != streamState.mStreamType && + mStreamVolumeAlias[streamType] == streamState.mStreamType) { + // Make sure volume is also maxed out on A2DP device for aliased stream + // that may have a different device selected + int streamDevice = getDeviceForStream(streamType); + if ((device != streamDevice) && mAvrcpAbsVolSupported && + ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { + mStreamStates[streamType].applyDeviceVolume_syncVSS(device); + } + mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice); + } + } + } + // Post a persist volume msg + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + PERSIST_DELAY); + + } + + private void setAllVolumes(VolumeStreamState streamState) { + + // Apply volume + streamState.applyAllVolumes(); + + // Apply change to all streams using this one as alias + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + if (streamType != streamState.mStreamType && + mStreamVolumeAlias[streamType] == streamState.mStreamType) { + mStreamStates[streamType].applyAllVolumes(); + } + } + } + + private void persistVolume(VolumeStreamState streamState, int device) { + if (mUseFixedVolume) { + return; + } + if (isPlatformTelevision() && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) { + return; + } + System.putIntForUser(mContentResolver, + streamState.getSettingNameForDevice(device), + (streamState.getIndex(device) + 5)/ 10, + UserHandle.USER_CURRENT); + } + + private void persistRingerMode(int ringerMode) { + if (mUseFixedVolume) { + return; + } + Settings.Global.putInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode); + } + + private boolean onLoadSoundEffects() { + int status; + + synchronized (mSoundEffectsLock) { + if (!mSystemReady) { + Log.w(TAG, "onLoadSoundEffects() called before boot complete"); + return false; + } + + if (mSoundPool != null) { + return true; + } + + loadTouchSoundAssets(); + + mSoundPool = new SoundPool.Builder() + .setMaxStreams(NUM_SOUNDPOOL_CHANNELS) + .setAudioAttributes(new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build()) + .build(); + mSoundPoolCallBack = null; + mSoundPoolListenerThread = new SoundPoolListenerThread(); + mSoundPoolListenerThread.start(); + int attempts = 3; + while ((mSoundPoolCallBack == null) && (attempts-- > 0)) { + try { + // Wait for mSoundPoolCallBack to be set by the other thread + mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS); + } catch (InterruptedException e) { + Log.w(TAG, "Interrupted while waiting sound pool listener thread."); + } + } + + if (mSoundPoolCallBack == null) { + Log.w(TAG, "onLoadSoundEffects() SoundPool listener or thread creation error"); + if (mSoundPoolLooper != null) { + mSoundPoolLooper.quit(); + mSoundPoolLooper = null; + } + mSoundPoolListenerThread = null; + mSoundPool.release(); + mSoundPool = null; + return false; + } + /* + * poolId table: The value -1 in this table indicates that corresponding + * file (same index in SOUND_EFFECT_FILES[] has not been loaded. + * Once loaded, the value in poolId is the sample ID and the same + * sample can be reused for another effect using the same file. + */ + int[] poolId = new int[SOUND_EFFECT_FILES.size()]; + for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) { + poolId[fileIdx] = -1; + } + /* + * Effects whose value in SOUND_EFFECT_FILES_MAP[effect][1] is -1 must be loaded. + * If load succeeds, value in SOUND_EFFECT_FILES_MAP[effect][1] is > 0: + * this indicates we have a valid sample loaded for this effect. + */ + + int numSamples = 0; + for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) { + // Do not load sample if this effect uses the MediaPlayer + if (SOUND_EFFECT_FILES_MAP[effect][1] == 0) { + continue; + } + if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == -1) { + String filePath = Environment.getRootDirectory() + + SOUND_EFFECTS_PATH + + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effect][0]); + int sampleId = mSoundPool.load(filePath, 0); + if (sampleId <= 0) { + Log.w(TAG, "Soundpool could not load file: "+filePath); + } else { + SOUND_EFFECT_FILES_MAP[effect][1] = sampleId; + poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = sampleId; + numSamples++; + } + } else { + SOUND_EFFECT_FILES_MAP[effect][1] = + poolId[SOUND_EFFECT_FILES_MAP[effect][0]]; + } + } + // wait for all samples to be loaded + if (numSamples > 0) { + mSoundPoolCallBack.setSamples(poolId); + + attempts = 3; + status = 1; + while ((status == 1) && (attempts-- > 0)) { + try { + mSoundEffectsLock.wait(SOUND_EFFECTS_LOAD_TIMEOUT_MS); + status = mSoundPoolCallBack.status(); + } catch (InterruptedException e) { + Log.w(TAG, "Interrupted while waiting sound pool callback."); + } + } + } else { + status = -1; + } + + if (mSoundPoolLooper != null) { + mSoundPoolLooper.quit(); + mSoundPoolLooper = null; + } + mSoundPoolListenerThread = null; + if (status != 0) { + Log.w(TAG, + "onLoadSoundEffects(), Error "+status+ " while loading samples"); + for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) { + if (SOUND_EFFECT_FILES_MAP[effect][1] > 0) { + SOUND_EFFECT_FILES_MAP[effect][1] = -1; + } + } + + mSoundPool.release(); + mSoundPool = null; + } + } + return (status == 0); + } + + /** + * Unloads samples from the sound pool. + * This method can be called to free some memory when + * sound effects are disabled. + */ + private void onUnloadSoundEffects() { + synchronized (mSoundEffectsLock) { + if (mSoundPool == null) { + return; + } + + int[] poolId = new int[SOUND_EFFECT_FILES.size()]; + for (int fileIdx = 0; fileIdx < SOUND_EFFECT_FILES.size(); fileIdx++) { + poolId[fileIdx] = 0; + } + + for (int effect = 0; effect < AudioManager.NUM_SOUND_EFFECTS; effect++) { + if (SOUND_EFFECT_FILES_MAP[effect][1] <= 0) { + continue; + } + if (poolId[SOUND_EFFECT_FILES_MAP[effect][0]] == 0) { + mSoundPool.unload(SOUND_EFFECT_FILES_MAP[effect][1]); + SOUND_EFFECT_FILES_MAP[effect][1] = -1; + poolId[SOUND_EFFECT_FILES_MAP[effect][0]] = -1; + } + } + mSoundPool.release(); + mSoundPool = null; + } + } + + private void onPlaySoundEffect(int effectType, int volume) { + synchronized (mSoundEffectsLock) { + + onLoadSoundEffects(); + + if (mSoundPool == null) { + return; + } + float volFloat; + // use default if volume is not specified by caller + if (volume < 0) { + volFloat = (float)Math.pow(10, (float)sSoundEffectVolumeDb/20); + } else { + volFloat = volume / 1000.0f; + } + + if (SOUND_EFFECT_FILES_MAP[effectType][1] > 0) { + mSoundPool.play(SOUND_EFFECT_FILES_MAP[effectType][1], + volFloat, volFloat, 0, 0, 1.0f); + } else { + MediaPlayer mediaPlayer = new MediaPlayer(); + try { + String filePath = Environment.getRootDirectory() + SOUND_EFFECTS_PATH + + SOUND_EFFECT_FILES.get(SOUND_EFFECT_FILES_MAP[effectType][0]); + mediaPlayer.setDataSource(filePath); + mediaPlayer.setAudioStreamType(AudioSystem.STREAM_SYSTEM); + mediaPlayer.prepare(); + mediaPlayer.setVolume(volFloat); + mediaPlayer.setOnCompletionListener(new OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + cleanupPlayer(mp); + } + }); + mediaPlayer.setOnErrorListener(new OnErrorListener() { + public boolean onError(MediaPlayer mp, int what, int extra) { + cleanupPlayer(mp); + return true; + } + }); + mediaPlayer.start(); + } catch (IOException ex) { + Log.w(TAG, "MediaPlayer IOException: "+ex); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "MediaPlayer IllegalArgumentException: "+ex); + } catch (IllegalStateException ex) { + Log.w(TAG, "MediaPlayer IllegalStateException: "+ex); + } + } + } + } + + private void cleanupPlayer(MediaPlayer mp) { + if (mp != null) { + try { + mp.stop(); + mp.release(); + } catch (IllegalStateException ex) { + Log.w(TAG, "MediaPlayer IllegalStateException: "+ex); + } + } + } + + private void setForceUse(int usage, int config) { + synchronized (mConnectedDevices) { + setForceUseInt_SyncDevices(usage, config); + } + } + + private void onPersistSafeVolumeState(int state) { + Settings.Global.putInt(mContentResolver, + Settings.Global.AUDIO_SAFE_VOLUME_STATE, + state); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + + case MSG_SET_DEVICE_VOLUME: + setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1); + break; + + case MSG_SET_ALL_VOLUMES: + setAllVolumes((VolumeStreamState) msg.obj); + break; + + case MSG_PERSIST_VOLUME: + persistVolume((VolumeStreamState) msg.obj, msg.arg1); + break; + + case MSG_PERSIST_MASTER_VOLUME_MUTE: + if (mUseFixedVolume) { + return; + } + Settings.System.putIntForUser(mContentResolver, + Settings.System.VOLUME_MASTER_MUTE, + msg.arg1, + msg.arg2); + break; + + case MSG_PERSIST_RINGER_MODE: + // note that the value persisted is the current ringer mode, not the + // value of ringer mode as of the time the request was made to persist + persistRingerMode(getRingerModeInternal()); + break; + + case MSG_MEDIA_SERVER_DIED: + if (!mSystemReady || + (AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK)) { + Log.e(TAG, "Media server died."); + sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0, + null, 500); + break; + } + Log.e(TAG, "Media server started."); + + // indicate to audio HAL that we start the reconfiguration phase after a media + // server crash + // Note that we only execute this when the media server + // process restarts after a crash, not the first time it is started. + AudioSystem.setParameters("restarting=true"); + + readAndSetLowRamDevice(); + + // Restore device connection states + synchronized (mConnectedDevices) { + for (int i = 0; i < mConnectedDevices.size(); i++) { + DeviceListSpec spec = mConnectedDevices.valueAt(i); + AudioSystem.setDeviceConnectionState( + spec.mDeviceType, + AudioSystem.DEVICE_STATE_AVAILABLE, + spec.mDeviceAddress, + spec.mDeviceName); + } + } + // Restore call state + AudioSystem.setPhoneState(mMode); + + // Restore forced usage for communcations and record + AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm); + AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm); + AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, mCameraSoundForced ? + AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE); + + // Restore stream volumes + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + VolumeStreamState streamState = mStreamStates[streamType]; + AudioSystem.initStreamVolume(streamType, 0, (streamState.mIndexMax + 5) / 10); + + streamState.applyAllVolumes(); + } + + // Restore ringer mode + setRingerModeInt(getRingerModeInternal(), false); + + // Reset device orientation (if monitored for this device) + if (mMonitorOrientation) { + setOrientationForAudioSystem(); + } + if (mMonitorRotation) { + setRotationForAudioSystem(); + } + + synchronized (mBluetoothA2dpEnabledLock) { + AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? + AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP); + } + + synchronized (mSettingsLock) { + AudioSystem.setForceUse(AudioSystem.FOR_DOCK, + mDockAudioMediaEnabled ? + AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE); + } + if (mHdmiManager != null) { + synchronized (mHdmiManager) { + if (mHdmiTvClient != null) { + setHdmiSystemAudioSupported(mHdmiSystemAudioSupported); + } + } + } + + synchronized (mAudioPolicies) { + for(AudioPolicyProxy policy : mAudioPolicies.values()) { + policy.connectMixes(); + } + } + + // indicate the end of reconfiguration phase to audio HAL + AudioSystem.setParameters("restarting=false"); + break; + + case MSG_UNLOAD_SOUND_EFFECTS: + onUnloadSoundEffects(); + break; + + case MSG_LOAD_SOUND_EFFECTS: + //FIXME: onLoadSoundEffects() should be executed in a separate thread as it + // can take several dozens of milliseconds to complete + boolean loaded = onLoadSoundEffects(); + if (msg.obj != null) { + LoadSoundEffectReply reply = (LoadSoundEffectReply)msg.obj; + synchronized (reply) { + reply.mStatus = loaded ? 0 : -1; + reply.notify(); + } + } + break; + + case MSG_PLAY_SOUND_EFFECT: + onPlaySoundEffect(msg.arg1, msg.arg2); + break; + + case MSG_BTA2DP_DOCK_TIMEOUT: + // msg.obj == address of BTA2DP device + synchronized (mConnectedDevices) { + makeA2dpDeviceUnavailableNow( (String) msg.obj ); + } + break; + + case MSG_SET_FORCE_USE: + case MSG_SET_FORCE_BT_A2DP_USE: + setForceUse(msg.arg1, msg.arg2); + break; + + case MSG_BT_HEADSET_CNCT_FAILED: + resetBluetoothSco(); + break; + + case MSG_SET_WIRED_DEVICE_CONNECTION_STATE: + { WiredDeviceConnectionState connectState = + (WiredDeviceConnectionState)msg.obj; + onSetWiredDeviceConnectionState(connectState.mType, connectState.mState, + connectState.mAddress, connectState.mName, connectState.mCaller); + mAudioEventWakeLock.release(); + } + break; + + case MSG_SET_A2DP_SRC_CONNECTION_STATE: + onSetA2dpSourceConnectionState((BluetoothDevice)msg.obj, msg.arg1); + mAudioEventWakeLock.release(); + break; + + case MSG_SET_A2DP_SINK_CONNECTION_STATE: + onSetA2dpSinkConnectionState((BluetoothDevice)msg.obj, msg.arg1); + mAudioEventWakeLock.release(); + break; + + case MSG_REPORT_NEW_ROUTES: { + int N = mRoutesObservers.beginBroadcast(); + if (N > 0) { + AudioRoutesInfo routes; + synchronized (mCurAudioRoutes) { + routes = new AudioRoutesInfo(mCurAudioRoutes); + } + while (N > 0) { + N--; + IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(N); + try { + obs.dispatchAudioRoutesChanged(routes); + } catch (RemoteException e) { + } + } + } + mRoutesObservers.finishBroadcast(); + observeDevicesForStreams(-1); + break; + } + + case MSG_CHECK_MUSIC_ACTIVE: + onCheckMusicActive((String) msg.obj); + break; + + case MSG_BROADCAST_AUDIO_BECOMING_NOISY: + onSendBecomingNoisyIntent(); + break; + + case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED: + case MSG_CONFIGURE_SAFE_MEDIA_VOLUME: + onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED), + (String) msg.obj); + break; + case MSG_PERSIST_SAFE_VOLUME_STATE: + onPersistSafeVolumeState(msg.arg1); + break; + + case MSG_BROADCAST_BT_CONNECTION_STATE: + onBroadcastScoConnectionState(msg.arg1); + break; + + case MSG_SYSTEM_READY: + onSystemReady(); + break; + + case MSG_PERSIST_MUSIC_ACTIVE_MS: + final int musicActiveMs = msg.arg1; + Settings.Secure.putIntForUser(mContentResolver, + Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs, + UserHandle.USER_CURRENT); + break; + case MSG_PERSIST_MICROPHONE_MUTE: + Settings.System.putIntForUser(mContentResolver, + Settings.System.MICROPHONE_MUTE, + msg.arg1, + msg.arg2); + break; + case MSG_UNMUTE_STREAM: + onUnmuteStream(msg.arg1, msg.arg2); + break; + } + } + } + + private class SettingsObserver extends ContentObserver { + + SettingsObserver() { + super(new Handler()); + mContentResolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.MODE_RINGER_STREAMS_AFFECTED), false, this); + mContentResolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + // FIXME This synchronized is not necessary if mSettingsLock only protects mRingerMode. + // However there appear to be some missing locks around mRingerModeMutedStreams + // and mRingerModeAffectedStreams, so will leave this synchronized for now. + // mRingerModeMutedStreams and mMuteAffectedStreams are safe (only accessed once). + synchronized (mSettingsLock) { + if (updateRingerModeAffectedStreams()) { + /* + * Ensure all stream types that should be affected by ringer mode + * are in the proper state. + */ + setRingerModeInt(getRingerModeInternal(), false); + } + readDockAudioSettings(mContentResolver); + } + } + } + + // must be called synchronized on mConnectedDevices + private void makeA2dpDeviceAvailable(String address) { + // enable A2DP before notifying A2DP connection to avoid unecessary processing in + // audio policy manager + VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0); + setBluetoothA2dpOnInt(true); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, address, DEVICE_NAME_A2DP); + // Reset A2DP suspend state each time a new sink is connected + AudioSystem.setParameters("A2dpSuspended=false"); + mConnectedDevices.put( + makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), + new DeviceListSpec(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, DEVICE_NAME_A2DP, + address)); + } + + private void onSendBecomingNoisyIntent() { + sendBroadcastToAll(new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + } + + // must be called synchronized on mConnectedDevices + private void makeA2dpDeviceUnavailableNow(String address) { + synchronized (mA2dpAvrcpLock) { + mAvrcpAbsVolSupported = false; + } + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, DEVICE_NAME_A2DP); + mConnectedDevices.remove( + makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); + synchronized (mCurAudioRoutes) { + // Remove A2DP routes as well + if (mCurAudioRoutes.bluetoothName != null) { + mCurAudioRoutes.bluetoothName = null; + sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, + SENDMSG_NOOP, 0, 0, null, 0); + } + } + } + + // must be called synchronized on mConnectedDevices + private void makeA2dpDeviceUnavailableLater(String address) { + // prevent any activity on the A2DP audio output to avoid unwanted + // reconnection of the sink. + AudioSystem.setParameters("A2dpSuspended=true"); + // the device will be made unavailable later, so consider it disconnected right away + mConnectedDevices.remove( + makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); + // send the delayed message to make the device unavailable later + Message msg = mAudioHandler.obtainMessage(MSG_BTA2DP_DOCK_TIMEOUT, address); + mAudioHandler.sendMessageDelayed(msg, BTA2DP_DOCK_TIMEOUT_MILLIS); + + } + + // must be called synchronized on mConnectedDevices + private void makeA2dpSrcAvailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, address, DEVICE_NAME_A2DP); + mConnectedDevices.put( + makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), + new DeviceListSpec(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, DEVICE_NAME_A2DP, + address)); + } + + // must be called synchronized on mConnectedDevices + private void makeA2dpSrcUnavailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, address, DEVICE_NAME_A2DP); + mConnectedDevices.remove( + makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); + } + + // must be called synchronized on mConnectedDevices + private void cancelA2dpDeviceTimeout() { + mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT); + } + + // must be called synchronized on mConnectedDevices + private boolean hasScheduledA2dpDockTimeout() { + return mAudioHandler.hasMessages(MSG_BTA2DP_DOCK_TIMEOUT); + } + + private void onSetA2dpSinkConnectionState(BluetoothDevice btDevice, int state) + { + if (DEBUG_VOL) { + Log.d(TAG, "onSetA2dpSinkConnectionState btDevice="+btDevice+"state="+state); + } + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + + synchronized (mConnectedDevices) { + String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + btDevice.getAddress()); + DeviceListSpec deviceSpec = mConnectedDevices.get(key); + boolean isConnected = deviceSpec != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + if (state == BluetoothProfile.STATE_DISCONNECTED) { + // introduction of a delay for transient disconnections of docks when + // power is rapidly turned off/on, this message will be canceled if + // we reconnect the dock under a preset delay + makeA2dpDeviceUnavailableLater(address); + // the next time isConnected is evaluated, it will be false for the dock + } + } else { + makeA2dpDeviceUnavailableNow(address); + } + synchronized (mCurAudioRoutes) { + if (mCurAudioRoutes.bluetoothName != null) { + mCurAudioRoutes.bluetoothName = null; + sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, + SENDMSG_NOOP, 0, 0, null, 0); + } + } + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + if (btDevice.isBluetoothDock()) { + // this could be a reconnection after a transient disconnection + cancelA2dpDeviceTimeout(); + mDockAddress = address; + } else { + // this could be a connection of another A2DP device before the timeout of + // a dock: cancel the dock timeout, and make the dock unavailable now + if(hasScheduledA2dpDockTimeout()) { + cancelA2dpDeviceTimeout(); + makeA2dpDeviceUnavailableNow(mDockAddress); + } + } + makeA2dpDeviceAvailable(address); + synchronized (mCurAudioRoutes) { + String name = btDevice.getAliasName(); + if (!TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { + mCurAudioRoutes.bluetoothName = name; + sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, + SENDMSG_NOOP, 0, 0, null, 0); + } + } + } + } + } + + private void onSetA2dpSourceConnectionState(BluetoothDevice btDevice, int state) + { + if (DEBUG_VOL) { + Log.d(TAG, "onSetA2dpSourceConnectionState btDevice="+btDevice+" state="+state); + } + if (btDevice == null) { + return; + } + String address = btDevice.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + + synchronized (mConnectedDevices) { + String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); + DeviceListSpec deviceSpec = mConnectedDevices.get(key); + boolean isConnected = deviceSpec != null; + + if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { + makeA2dpSrcUnavailable(address); + } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { + makeA2dpSrcAvailable(address); + } + } + } + + public void avrcpSupportsAbsoluteVolume(String address, boolean support) { + // address is not used for now, but may be used when multiple a2dp devices are supported + synchronized (mA2dpAvrcpLock) { + mAvrcpAbsVolSupported = support; + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, + mStreamStates[AudioSystem.STREAM_MUSIC], 0); + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, + mStreamStates[AudioSystem.STREAM_RING], 0); + } + } + + private boolean handleDeviceConnection(boolean connect, int device, String address, + String deviceName) { + if (DEBUG_DEVICES) { + Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:" + Integer.toHexString(device) + + " address:" + address + " name:" + deviceName + ")"); + } + synchronized (mConnectedDevices) { + String deviceKey = makeDeviceListKey(device, address); + if (DEBUG_DEVICES) { + Slog.i(TAG, "deviceKey:" + deviceKey); + } + DeviceListSpec deviceSpec = mConnectedDevices.get(deviceKey); + boolean isConnected = deviceSpec != null; + if (DEBUG_DEVICES) { + Slog.i(TAG, "deviceSpec:" + deviceSpec + " is(already)Connected:" + isConnected); + } + if (connect && !isConnected) { + AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, + address, deviceName); + mConnectedDevices.put(deviceKey, new DeviceListSpec(device, deviceName, address)); + return true; + } else if (!connect && isConnected) { + AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE, + address, deviceName); + mConnectedDevices.remove(deviceKey); + return true; + } + } + return false; + } + + // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only + // sent if none of these devices is connected. + // Access synchronized on mConnectedDevices + int mBecomingNoisyIntentDevices = + AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE | + AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI | + AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET | + AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE; + + // must be called before removing the device from mConnectedDevices + // Called synchronized on mConnectedDevices + private int checkSendBecomingNoisyIntent(int device, int state) { + int delay = 0; + if ((state == 0) && ((device & mBecomingNoisyIntentDevices) != 0)) { + int devices = 0; + for (int i = 0; i < mConnectedDevices.size(); i++) { + int dev = mConnectedDevices.valueAt(i).mDeviceType; + if (((dev & AudioSystem.DEVICE_BIT_IN) == 0) + && ((dev & mBecomingNoisyIntentDevices) != 0)) { + devices |= dev; + } + } + if (devices == device) { + sendMsg(mAudioHandler, + MSG_BROADCAST_AUDIO_BECOMING_NOISY, + SENDMSG_REPLACE, + 0, + 0, + null, + 0); + delay = 1000; + } + } + + if (mAudioHandler.hasMessages(MSG_SET_A2DP_SRC_CONNECTION_STATE) || + mAudioHandler.hasMessages(MSG_SET_A2DP_SINK_CONNECTION_STATE) || + mAudioHandler.hasMessages(MSG_SET_WIRED_DEVICE_CONNECTION_STATE)) { + synchronized (mLastDeviceConnectMsgTime) { + long time = SystemClock.uptimeMillis(); + if (mLastDeviceConnectMsgTime > time) { + delay = (int)(mLastDeviceConnectMsgTime - time) + 30; + } + } + } + return delay; + } + + private void sendDeviceConnectionIntent(int device, int state, String address, + String deviceName) { + if (DEBUG_DEVICES) { + Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device) + + " state:0x" + Integer.toHexString(state) + " address:" + address + + " name:" + deviceName + ");"); + } + Intent intent = new Intent(); + + intent.putExtra(CONNECT_INTENT_KEY_STATE, state); + intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address); + intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName); + + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + int connType = 0; + + if (device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) { + connType = AudioRoutesInfo.MAIN_HEADSET; + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 1); + } else if (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE || + device == AudioSystem.DEVICE_OUT_LINE) { + /*do apps care about line-out vs headphones?*/ + connType = AudioRoutesInfo.MAIN_HEADPHONES; + intent.setAction(Intent.ACTION_HEADSET_PLUG); + intent.putExtra("microphone", 0); + } else if (device == AudioSystem.DEVICE_OUT_HDMI || + device == AudioSystem.DEVICE_OUT_HDMI_ARC) { + connType = AudioRoutesInfo.MAIN_HDMI; + configureHdmiPlugIntent(intent, state); + } else if (device == AudioSystem.DEVICE_OUT_USB_DEVICE) { + connType = AudioRoutesInfo.MAIN_USB; + } + + synchronized (mCurAudioRoutes) { + if (connType != 0) { + int newConn = mCurAudioRoutes.mainType; + if (state != 0) { + newConn |= connType; + } else { + newConn &= ~connType; + } + if (newConn != mCurAudioRoutes.mainType) { + mCurAudioRoutes.mainType = newConn; + sendMsg(mAudioHandler, MSG_REPORT_NEW_ROUTES, + SENDMSG_NOOP, 0, 0, null, 0); + } + } + } + + final long ident = Binder.clearCallingIdentity(); + try { + ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void onSetWiredDeviceConnectionState(int device, int state, String address, + String deviceName, String caller) { + if (DEBUG_DEVICES) { + Slog.i(TAG, "onSetWiredDeviceConnectionState(dev:" + Integer.toHexString(device) + + " state:" + Integer.toHexString(state) + + " address:" + address + + " deviceName:" + deviceName + + " caller: " + caller + ");"); + } + + synchronized (mConnectedDevices) { + if ((state == 0) && ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || + (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) || + (device == AudioSystem.DEVICE_OUT_LINE))) { + setBluetoothA2dpOnInt(true); + } + boolean isUsb = ((device & ~AudioSystem.DEVICE_OUT_ALL_USB) == 0) || + (((device & AudioSystem.DEVICE_BIT_IN) != 0) && + ((device & ~AudioSystem.DEVICE_IN_ALL_USB) == 0)); + handleDeviceConnection(state == 1, device, address, deviceName); + if (state != 0) { + if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) || + (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE) || + (device == AudioSystem.DEVICE_OUT_LINE)) { + setBluetoothA2dpOnInt(false); + } + if ((device & mSafeMediaVolumeDevices) != 0) { + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + // Television devices without CEC service apply software volume on HDMI output + if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) { + mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI; + checkAllFixedVolumeDevices(); + if (mHdmiManager != null) { + synchronized (mHdmiManager) { + if (mHdmiPlaybackClient != null) { + mHdmiCecSink = false; + mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback); + } + } + } + } + } else { + if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) { + if (mHdmiManager != null) { + synchronized (mHdmiManager) { + mHdmiCecSink = false; + } + } + } + } + if (!isUsb && device != AudioSystem.DEVICE_IN_WIRED_HEADSET) { + sendDeviceConnectionIntent(device, state, address, deviceName); + } + } + } + + private void configureHdmiPlugIntent(Intent intent, int state) { + intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG); + intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state); + if (state == 1) { + ArrayList<AudioPort> ports = new ArrayList<AudioPort>(); + int[] portGeneration = new int[1]; + int status = AudioSystem.listAudioPorts(ports, portGeneration); + if (status == AudioManager.SUCCESS) { + for (AudioPort port : ports) { + if (port instanceof AudioDevicePort) { + final AudioDevicePort devicePort = (AudioDevicePort) port; + if (devicePort.type() == AudioManager.DEVICE_OUT_HDMI || + devicePort.type() == AudioManager.DEVICE_OUT_HDMI_ARC) { + // format the list of supported encodings + int[] formats = devicePort.formats(); + if (formats.length > 0) { + ArrayList<Integer> encodingList = new ArrayList(1); + for (int format : formats) { + // a format in the list can be 0, skip it + if (format != AudioFormat.ENCODING_INVALID) { + encodingList.add(format); + } + } + int[] encodingArray = new int[encodingList.size()]; + for (int i = 0 ; i < encodingArray.length ; i++) { + encodingArray[i] = encodingList.get(i); + } + intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray); + } + // find the maximum supported number of channels + int maxChannels = 0; + for (int mask : devicePort.channelMasks()) { + int channelCount = AudioFormat.channelCountFromOutChannelMask(mask); + if (channelCount > maxChannels) { + maxChannels = channelCount; + } + } + intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels); + } + } + } + } + } + } + + /* cache of the address of the last dock the device was connected to */ + private String mDockAddress; + + /** + * Receiver for misc intent broadcasts the Phone app cares about. + */ + private class AudioServiceBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + int outDevice; + int inDevice; + int state; + + if (action.equals(Intent.ACTION_DOCK_EVENT)) { + int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + int config; + switch (dockState) { + case Intent.EXTRA_DOCK_STATE_DESK: + config = AudioSystem.FORCE_BT_DESK_DOCK; + break; + case Intent.EXTRA_DOCK_STATE_CAR: + config = AudioSystem.FORCE_BT_CAR_DOCK; + break; + case Intent.EXTRA_DOCK_STATE_LE_DESK: + config = AudioSystem.FORCE_ANALOG_DOCK; + break; + case Intent.EXTRA_DOCK_STATE_HE_DESK: + config = AudioSystem.FORCE_DIGITAL_DOCK; + break; + case Intent.EXTRA_DOCK_STATE_UNDOCKED: + default: + config = AudioSystem.FORCE_NONE; + } + // Low end docks have a menu to enable or disable audio + // (see mDockAudioMediaEnabled) + if (!((dockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + ((dockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) && + (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK)))) { + AudioSystem.setForceUse(AudioSystem.FOR_DOCK, config); + } + mDockState = dockState; + } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { + state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, + BluetoothProfile.STATE_DISCONNECTED); + outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO; + inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; + String address = null; + + BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (btDevice == null) { + return; + } + + address = btDevice.getAddress(); + BluetoothClass btClass = btDevice.getBluetoothClass(); + if (btClass != null) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: + case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: + outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET; + break; + case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: + outDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_CARKIT; + break; + } + } + + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + address = ""; + } + + boolean connected = (state == BluetoothProfile.STATE_CONNECTED); + boolean success = + handleDeviceConnection(connected, outDevice, address, "Bluetooth Headset") && + handleDeviceConnection(connected, inDevice, address, "Bluetooth Headset"); + if (success) { + synchronized (mScoClients) { + if (connected) { + mBluetoothHeadsetDevice = btDevice; + } else { + mBluetoothHeadsetDevice = null; + resetBluetoothSco(); + } + } + } + } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { + boolean broadcast = false; + int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; + synchronized (mScoClients) { + int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); + // broadcast intent if the connection was initated by AudioService + if (!mScoClients.isEmpty() && + (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || + mScoAudioState == SCO_STATE_ACTIVATE_REQ || + mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) { + broadcast = true; + } + switch (btState) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && + mScoAudioState != SCO_STATE_DEACTIVATE_REQ && + mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + mScoAudioState = SCO_STATE_INACTIVE; + clearAllScoClients(0, false); + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && + mScoAudioState != SCO_STATE_DEACTIVATE_REQ && + mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + default: + // do not broadcast CONNECTING or invalid state + broadcast = false; + break; + } + } + if (broadcast) { + broadcastScoConnectionState(scoAudioState); + //FIXME: this is to maintain compatibility with deprecated intent + // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. + Intent newIntent = new Intent(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); + newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); + sendStickyBroadcastToAll(newIntent); + } + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + if (mMonitorRotation) { + mOrientationListener.onOrientationChanged(0); //argument is ignored anyway + mOrientationListener.enable(); + } + AudioSystem.setParameters("screen_state=on"); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + if (mMonitorRotation) { + //reduce wakeups (save current) by only listening when display is on + mOrientationListener.disable(); + } + AudioSystem.setParameters("screen_state=off"); + } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { + handleConfigurationChanged(context); + } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { + // attempt to stop music playback for background user + sendMsg(mAudioHandler, + MSG_BROADCAST_AUDIO_BECOMING_NOISY, + SENDMSG_REPLACE, + 0, + 0, + null, + 0); + // the current audio focus owner is no longer valid + mMediaFocusControl.discardAudioFocusOwner(); + + // load volume settings for new user + readAudioSettings(true /*userSwitch*/); + // preserve STREAM_MUSIC volume from one user to the next. + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + mStreamStates[AudioSystem.STREAM_MUSIC], 0); + } + } + } // end class AudioServiceBroadcastReceiver + + //========================================================================================== + // RemoteControlDisplay / RemoteControlClient / Remote info + //========================================================================================== + public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h, + ComponentName listenerComp) { + return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp); + } + + public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { + return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h); + } + + public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { + mMediaFocusControl.unregisterRemoteControlDisplay(rcd); + } + + public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { + mMediaFocusControl.remoteControlDisplayUsesBitmapSize(rcd, w, h); + } + + public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, + boolean wantsSync) { + mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync); + } + + @Override + public void setRemoteStreamVolume(int index) { + enforceVolumeController("set the remote stream volume"); + mMediaFocusControl.setRemoteStreamVolume(index); + } + + //========================================================================================== + // Audio Focus + //========================================================================================== + public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb, + IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, + IAudioPolicyCallback pcb) { + // permission checks + if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) { + if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) { + if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE)) { + Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception()); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + } else { + // only a registered audio policy can be used to lock focus + synchronized (mAudioPolicies) { + if (!mAudioPolicies.containsKey(pcb.asBinder())) { + Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus"); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + } + } + } + + return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd, + clientId, callingPackageName, flags); + } + + public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa) { + return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa); + } + + public void unregisterAudioFocusClient(String clientId) { + mMediaFocusControl.unregisterAudioFocusClient(clientId); + } + + public int getCurrentAudioFocus() { + return mMediaFocusControl.getCurrentAudioFocus(); + } + + private boolean readCameraSoundForced() { + return SystemProperties.getBoolean("audio.camerasound.force", false) || + mContext.getResources().getBoolean( + com.android.internal.R.bool.config_camera_sound_forced); + } + + //========================================================================================== + // Device orientation + //========================================================================================== + /** + * Handles device configuration changes that may map to a change in the orientation + * or orientation. + * Monitoring orientation and rotation is optional, and is defined by the definition and value + * of the "ro.audio.monitorOrientation" and "ro.audio.monitorRotation" system properties. + */ + private void handleConfigurationChanged(Context context) { + try { + // reading new orientation "safely" (i.e. under try catch) in case anything + // goes wrong when obtaining resources and configuration + Configuration config = context.getResources().getConfiguration(); + // TODO merge rotation and orientation + if (mMonitorOrientation) { + int newOrientation = config.orientation; + if (newOrientation != mDeviceOrientation) { + mDeviceOrientation = newOrientation; + setOrientationForAudioSystem(); + } + } + sendMsg(mAudioHandler, + MSG_CONFIGURE_SAFE_MEDIA_VOLUME, + SENDMSG_REPLACE, + 0, + 0, + TAG, + 0); + + boolean cameraSoundForced = readCameraSoundForced(); + synchronized (mSettingsLock) { + boolean cameraSoundForcedChanged = false; + synchronized (mCameraSoundForced) { + if (cameraSoundForced != mCameraSoundForced) { + mCameraSoundForced = cameraSoundForced; + cameraSoundForcedChanged = true; + } + } + if (cameraSoundForcedChanged) { + if (!isPlatformTelevision()) { + VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED]; + if (cameraSoundForced) { + s.setAllIndexesToMax(); + mRingerModeAffectedStreams &= + ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } else { + s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG); + mRingerModeAffectedStreams |= + (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } + // take new state into account for streams muted by ringer mode + setRingerModeInt(getRingerModeInternal(), false); + } + + sendMsg(mAudioHandler, + MSG_SET_FORCE_USE, + SENDMSG_QUEUE, + AudioSystem.FOR_SYSTEM, + cameraSoundForced ? + AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE, + null, + 0); + + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED], 0); + } + } + mVolumeController.setLayoutDirection(config.getLayoutDirection()); + } catch (Exception e) { + Log.e(TAG, "Error handling configuration change: ", e); + } + } + + private void setOrientationForAudioSystem() { + switch (mDeviceOrientation) { + case Configuration.ORIENTATION_LANDSCAPE: + //Log.i(TAG, "orientation is landscape"); + AudioSystem.setParameters("orientation=landscape"); + break; + case Configuration.ORIENTATION_PORTRAIT: + //Log.i(TAG, "orientation is portrait"); + AudioSystem.setParameters("orientation=portrait"); + break; + case Configuration.ORIENTATION_SQUARE: + //Log.i(TAG, "orientation is square"); + AudioSystem.setParameters("orientation=square"); + break; + case Configuration.ORIENTATION_UNDEFINED: + //Log.i(TAG, "orientation is undefined"); + AudioSystem.setParameters("orientation=undefined"); + break; + default: + Log.e(TAG, "Unknown orientation"); + } + } + + private void setRotationForAudioSystem() { + switch (mDeviceRotation) { + case Surface.ROTATION_0: + AudioSystem.setParameters("rotation=0"); + break; + case Surface.ROTATION_90: + AudioSystem.setParameters("rotation=90"); + break; + case Surface.ROTATION_180: + AudioSystem.setParameters("rotation=180"); + break; + case Surface.ROTATION_270: + AudioSystem.setParameters("rotation=270"); + break; + default: + Log.e(TAG, "Unknown device rotation"); + } + } + + + // Handles request to override default use of A2DP for media. + // Must be called synchronized on mConnectedDevices + public void setBluetoothA2dpOnInt(boolean on) { + synchronized (mBluetoothA2dpEnabledLock) { + mBluetoothA2dpEnabled = on; + mAudioHandler.removeMessages(MSG_SET_FORCE_BT_A2DP_USE); + setForceUseInt_SyncDevices(AudioSystem.FOR_MEDIA, + mBluetoothA2dpEnabled ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP); + } + } + + // Must be called synchronized on mConnectedDevices + private void setForceUseInt_SyncDevices(int usage, int config) { + switch (usage) { + case AudioSystem.FOR_MEDIA: + if (config == AudioSystem.FORCE_NO_BT_A2DP) { + mBecomingNoisyIntentDevices &= ~AudioSystem.DEVICE_OUT_ALL_A2DP; + } else { // config == AudioSystem.FORCE_NONE + mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ALL_A2DP; + } + break; + case AudioSystem.FOR_DOCK: + if (config == AudioSystem.FORCE_ANALOG_DOCK) { + mBecomingNoisyIntentDevices |= AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET; + } else { // config == AudioSystem.FORCE_NONE + mBecomingNoisyIntentDevices &= ~AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET; + } + break; + default: + // usage doesn't affect the broadcast of ACTION_AUDIO_BECOMING_NOISY + } + AudioSystem.setForceUse(usage, config); + } + + @Override + public void setRingtonePlayer(IRingtonePlayer player) { + mContext.enforceCallingOrSelfPermission(REMOTE_AUDIO_PLAYBACK, null); + mRingtonePlayer = player; + } + + @Override + public IRingtonePlayer getRingtonePlayer() { + return mRingtonePlayer; + } + + @Override + public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { + synchronized (mCurAudioRoutes) { + AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes); + mRoutesObservers.register(observer); + return routes; + } + } + + + //========================================================================================== + // Safe media volume management. + // MUSIC stream volume level is limited when headphones are connected according to safety + // regulation. When the user attempts to raise the volume above the limit, a warning is + // displayed and the user has to acknowlegde before the volume is actually changed. + // The volume index corresponding to the limit is stored in config_safe_media_volume_index + // property. Platforms with a different limit must set this property accordingly in their + // overlay. + //========================================================================================== + + // mSafeMediaVolumeState indicates whether the media volume is limited over headphones. + // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected + // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or + // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it + // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume() + // (when user opts out). + private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0; + private static final int SAFE_MEDIA_VOLUME_DISABLED = 1; + private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed + private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed + private Integer mSafeMediaVolumeState; + + private int mMcc = 0; + // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property + private int mSafeMediaVolumeIndex; + // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced, + private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET | + AudioSystem.DEVICE_OUT_WIRED_HEADPHONE; + // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled. + // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled + // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS. + private int mMusicActiveMs; + private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours + private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval + private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed + + private void setSafeMediaVolumeEnabled(boolean on, String caller) { + synchronized (mSafeMediaVolumeState) { + if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) && + (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) { + if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) { + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE; + enforceSafeMediaVolume(caller); + } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) { + mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE; + mMusicActiveMs = 1; // nonzero = confirmed + saveMusicActiveMs(); + sendMsg(mAudioHandler, + MSG_CHECK_MUSIC_ACTIVE, + SENDMSG_REPLACE, + 0, + 0, + caller, + MUSIC_ACTIVE_POLL_PERIOD_MS); + } + } + } + } + + private void enforceSafeMediaVolume(String caller) { + VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; + int devices = mSafeMediaVolumeDevices; + int i = 0; + + while (devices != 0) { + int device = 1 << i++; + if ((device & devices) == 0) { + continue; + } + int index = streamState.getIndex(device); + if (index > mSafeMediaVolumeIndex) { + streamState.setIndex(mSafeMediaVolumeIndex, device, caller); + sendMsg(mAudioHandler, + MSG_SET_DEVICE_VOLUME, + SENDMSG_QUEUE, + device, + 0, + streamState, + 0); + } + devices &= ~device; + } + } + + private boolean checkSafeMediaVolume(int streamType, int index, int device) { + synchronized (mSafeMediaVolumeState) { + if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) && + (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) && + ((device & mSafeMediaVolumeDevices) != 0) && + (index > mSafeMediaVolumeIndex)) { + return false; + } + return true; + } + } + + @Override + public void disableSafeMediaVolume(String callingPackage) { + enforceVolumeController("disable the safe media volume"); + synchronized (mSafeMediaVolumeState) { + setSafeMediaVolumeEnabled(false, callingPackage); + if (mPendingVolumeCommand != null) { + onSetStreamVolume(mPendingVolumeCommand.mStreamType, + mPendingVolumeCommand.mIndex, + mPendingVolumeCommand.mFlags, + mPendingVolumeCommand.mDevice, + callingPackage); + mPendingVolumeCommand = null; + } + } + } + + //========================================================================================== + // Hdmi Cec system audio mode. + // If Hdmi Cec's system audio mode is on, audio service should send the volume change + // to HdmiControlService so that the audio receiver can handle it. + //========================================================================================== + + private class MyDisplayStatusCallback implements HdmiPlaybackClient.DisplayStatusCallback { + public void onComplete(int status) { + if (mHdmiManager != null) { + synchronized (mHdmiManager) { + mHdmiCecSink = (status != HdmiControlManager.POWER_STATUS_UNKNOWN); + // Television devices without CEC service apply software volume on HDMI output + if (isPlatformTelevision() && !mHdmiCecSink) { + mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI; + } + checkAllFixedVolumeDevices(); + } + } + } + }; + + // If HDMI-CEC system audio is supported + private boolean mHdmiSystemAudioSupported = false; + // Set only when device is tv. + private HdmiTvClient mHdmiTvClient; + // true if the device has system feature PackageManager.FEATURE_LEANBACK. + // cached HdmiControlManager interface + private HdmiControlManager mHdmiManager; + // Set only when device is a set-top box. + private HdmiPlaybackClient mHdmiPlaybackClient; + // true if we are a set-top box, an HDMI sink is connected and it supports CEC. + private boolean mHdmiCecSink; + + private MyDisplayStatusCallback mHdmiDisplayStatusCallback = new MyDisplayStatusCallback(); + + @Override + public int setHdmiSystemAudioSupported(boolean on) { + int device = AudioSystem.DEVICE_NONE; + if (mHdmiManager != null) { + synchronized (mHdmiManager) { + if (mHdmiTvClient == null) { + Log.w(TAG, "Only Hdmi-Cec enabled TV device supports system audio mode."); + return device; + } + + synchronized (mHdmiTvClient) { + if (mHdmiSystemAudioSupported != on) { + mHdmiSystemAudioSupported = on; + AudioSystem.setForceUse(AudioSystem.FOR_HDMI_SYSTEM_AUDIO, + on ? AudioSystem.FORCE_HDMI_SYSTEM_AUDIO_ENFORCED : + AudioSystem.FORCE_NONE); + } + device = getDevicesForStream(AudioSystem.STREAM_MUSIC); + } + } + } + return device; + } + + @Override + public boolean isHdmiSystemAudioSupported() { + return mHdmiSystemAudioSupported; + } + + //========================================================================================== + // Accessibility: taking touch exploration into account for selecting the default + // stream override timeout when adjusting volume + //========================================================================================== + private static class StreamOverride + implements AccessibilityManager.TouchExplorationStateChangeListener { + + // AudioService.getActiveStreamType() will return: + // - STREAM_NOTIFICATION on tablets during this period after a notification stopped + // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt + // stopped + private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 5000; + private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000; + + static int sDelayMs; + + static void init(Context ctxt) { + AccessibilityManager accessibilityManager = + (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE); + updateDefaultStreamOverrideDelay( + accessibilityManager.isTouchExplorationEnabled()); + accessibilityManager.addTouchExplorationStateChangeListener( + new StreamOverride()); + } + + @Override + public void onTouchExplorationStateChanged(boolean enabled) { + updateDefaultStreamOverrideDelay(enabled); + } + + private static void updateDefaultStreamOverrideDelay(boolean touchExploreEnabled) { + if (touchExploreEnabled) { + sDelayMs = TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS; + } else { + sDelayMs = DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS; + } + if (DEBUG_VOL) Log.d(TAG, "Touch exploration enabled=" + touchExploreEnabled + + " stream override delay is now " + sDelayMs + " ms"); + } + } + + //========================================================================================== + // Camera shutter sound policy. + // config_camera_sound_forced configuration option in config.xml defines if the camera shutter + // sound is forced (sound even if the device is in silent mode) or not. This option is false by + // default and can be overridden by country specific overlay in values-mccXXX/config.xml. + //========================================================================================== + + // cached value of com.android.internal.R.bool.config_camera_sound_forced + private Boolean mCameraSoundForced; + + // called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound + public boolean isCameraSoundForced() { + synchronized (mCameraSoundForced) { + return mCameraSoundForced; + } + } + + private static final String[] RINGER_MODE_NAMES = new String[] { + "SILENT", + "VIBRATE", + "NORMAL" + }; + + private void dumpRingerMode(PrintWriter pw) { + pw.println("\nRinger mode: "); + pw.println("- mode (internal) = " + RINGER_MODE_NAMES[mRingerMode]); + pw.println("- mode (external) = " + RINGER_MODE_NAMES[mRingerModeExternal]); + pw.print("- ringer mode affected streams = 0x"); + pw.println(Integer.toHexString(mRingerModeAffectedStreams)); + pw.print("- ringer mode muted streams = 0x"); + pw.println(Integer.toHexString(mRingerModeMutedStreams)); + pw.print("- delegate = "); pw.println(mRingerModeDelegate); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + + mMediaFocusControl.dump(pw); + dumpStreamStates(pw); + dumpRingerMode(pw); + pw.println("\nAudio routes:"); + pw.print(" mMainType=0x"); pw.println(Integer.toHexString(mCurAudioRoutes.mainType)); + pw.print(" mBluetoothName="); pw.println(mCurAudioRoutes.bluetoothName); + + pw.println("\nOther state:"); + pw.print(" mVolumeController="); pw.println(mVolumeController); + pw.print(" mSafeMediaVolumeState="); + pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState)); + pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex); + pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand); + pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs); + pw.print(" mMcc="); pw.println(mMcc); + pw.print(" mCameraSoundForced="); pw.println(mCameraSoundForced); + pw.print(" mHasVibrator="); pw.println(mHasVibrator); + pw.print(" mControllerService="); pw.println(mControllerService); + pw.print(" mVolumePolicy="); pw.println(mVolumePolicy); + + dumpAudioPolicies(pw); + } + + private static String safeMediaVolumeStateToString(Integer state) { + switch(state) { + case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED"; + case SAFE_MEDIA_VOLUME_DISABLED: return "SAFE_MEDIA_VOLUME_DISABLED"; + case SAFE_MEDIA_VOLUME_INACTIVE: return "SAFE_MEDIA_VOLUME_INACTIVE"; + case SAFE_MEDIA_VOLUME_ACTIVE: return "SAFE_MEDIA_VOLUME_ACTIVE"; + } + return null; + } + + // Inform AudioFlinger of our device's low RAM attribute + private static void readAndSetLowRamDevice() + { + int status = AudioSystem.setLowRamDevice(ActivityManager.isLowRamDeviceStatic()); + if (status != 0) { + Log.w(TAG, "AudioFlinger informed of device's low RAM attribute; status " + status); + } + } + + private void enforceVolumeController(String action) { + if (mControllerService.mUid != 0 && Binder.getCallingUid() == mControllerService.mUid) { + return; + } + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + "Only SystemUI can " + action); + } + + @Override + public void setVolumeController(final IVolumeController controller) { + enforceVolumeController("set the volume controller"); + + // return early if things are not actually changing + if (mVolumeController.isSameBinder(controller)) { + return; + } + + // dismiss the old volume controller + mVolumeController.postDismiss(); + if (controller != null) { + // we are about to register a new controller, listen for its death + try { + controller.asBinder().linkToDeath(new DeathRecipient() { + @Override + public void binderDied() { + if (mVolumeController.isSameBinder(controller)) { + Log.w(TAG, "Current remote volume controller died, unregistering"); + setVolumeController(null); + } + } + }, 0); + } catch (RemoteException e) { + // noop + } + } + mVolumeController.setController(controller); + if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController); + } + + @Override + public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) { + enforceVolumeController("notify about volume controller visibility"); + + // return early if the controller is not current + if (!mVolumeController.isSameBinder(controller)) { + return; + } + + mVolumeController.setVisible(visible); + if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible); + } + + @Override + public void setVolumePolicy(VolumePolicy policy) { + enforceVolumeController("set volume policy"); + if (policy != null && !policy.equals(mVolumePolicy)) { + mVolumePolicy = policy; + if (DEBUG_VOL) Log.d(TAG, "Volume policy changed: " + mVolumePolicy); + } + } + + public static class VolumeController { + private static final String TAG = "VolumeController"; + + private IVolumeController mController; + private boolean mVisible; + private long mNextLongPress; + private int mLongPressTimeout; + + public void setController(IVolumeController controller) { + mController = controller; + mVisible = false; + } + + public void loadSettings(ContentResolver cr) { + mLongPressTimeout = Settings.Secure.getIntForUser(cr, + Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT); + } + + public boolean suppressAdjustment(int resolvedStream, int flags, boolean isMute) { + if (isMute) { + return false; + } + boolean suppress = false; + if (resolvedStream == AudioSystem.STREAM_RING && mController != null) { + final long now = SystemClock.uptimeMillis(); + if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) { + // ui will become visible + if (mNextLongPress < now) { + mNextLongPress = now + mLongPressTimeout; + } + suppress = true; + } else if (mNextLongPress > 0) { // in a long-press + if (now > mNextLongPress) { + // long press triggered, no more suppression + mNextLongPress = 0; + } else { + // keep suppressing until the long press triggers + suppress = true; + } + } + } + return suppress; + } + + public void setVisible(boolean visible) { + mVisible = visible; + } + + public boolean isSameBinder(IVolumeController controller) { + return Objects.equals(asBinder(), binder(controller)); + } + + public IBinder asBinder() { + return binder(mController); + } + + private static IBinder binder(IVolumeController controller) { + return controller == null ? null : controller.asBinder(); + } + + @Override + public String toString() { + return "VolumeController(" + asBinder() + ",mVisible=" + mVisible + ")"; + } + + public void postDisplaySafeVolumeWarning(int flags) { + if (mController == null) + return; + try { + mController.displaySafeVolumeWarning(flags); + } catch (RemoteException e) { + Log.w(TAG, "Error calling displaySafeVolumeWarning", e); + } + } + + public void postVolumeChanged(int streamType, int flags) { + if (mController == null) + return; + try { + mController.volumeChanged(streamType, flags); + } catch (RemoteException e) { + Log.w(TAG, "Error calling volumeChanged", e); + } + } + + public void postMasterMuteChanged(int flags) { + if (mController == null) + return; + try { + mController.masterMuteChanged(flags); + } catch (RemoteException e) { + Log.w(TAG, "Error calling masterMuteChanged", e); + } + } + + public void setLayoutDirection(int layoutDirection) { + if (mController == null) + return; + try { + mController.setLayoutDirection(layoutDirection); + } catch (RemoteException e) { + Log.w(TAG, "Error calling setLayoutDirection", e); + } + } + + public void postDismiss() { + if (mController == null) + return; + try { + mController.dismiss(); + } catch (RemoteException e) { + Log.w(TAG, "Error calling dismiss", e); + } + } + } + + /** + * Interface for system components to get some extra functionality through + * LocalServices. + */ + final class AudioServiceInternal extends AudioManagerInternal { + @Override + public void setRingerModeDelegate(RingerModeDelegate delegate) { + mRingerModeDelegate = delegate; + if (mRingerModeDelegate != null) { + setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate"); + } + } + + @Override + public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags, + String callingPackage, int uid) { + // direction and stream type swap here because the public + // adjustSuggested has a different order than the other methods. + adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage, + callingPackage, uid); + } + + @Override + public void adjustStreamVolumeForUid(int streamType, int direction, int flags, + String callingPackage, int uid) { + adjustStreamVolume(streamType, direction, flags, callingPackage, + callingPackage, uid); + } + + @Override + public void setStreamVolumeForUid(int streamType, int direction, int flags, + String callingPackage, int uid) { + setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid); + } + + @Override + public int getRingerModeInternal() { + return AudioService.this.getRingerModeInternal(); + } + + @Override + public void setRingerModeInternal(int ringerMode, String caller) { + AudioService.this.setRingerModeInternal(ringerMode, caller); + } + + @Override + public int getVolumeControllerUid() { + return mControllerService.mUid; + } + } + + //========================================================================================== + // Audio policy management + //========================================================================================== + public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb, + boolean hasFocusListener) { + if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder() + + " with config:" + policyConfig); + String regId = null; + // error handling + boolean hasPermissionForPolicy = + (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + if (!hasPermissionForPolicy) { + Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid " + + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING"); + return null; + } + + synchronized (mAudioPolicies) { + try { + if (mAudioPolicies.containsKey(pcb.asBinder())) { + Slog.e(TAG, "Cannot re-register policy"); + return null; + } + AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener); + pcb.asBinder().linkToDeath(app, 0/*flags*/); + regId = app.getRegistrationId(); + mAudioPolicies.put(pcb.asBinder(), app); + } catch (RemoteException e) { + // audio policy owner has already died! + Slog.w(TAG, "Audio policy registration failed, could not link to " + pcb + + " binder death", e); + return null; + } + } + return regId; + } + + public void unregisterAudioPolicyAsync(IAudioPolicyCallback pcb) { + if (DEBUG_AP) Log.d(TAG, "unregisterAudioPolicyAsync for " + pcb.asBinder()); + synchronized (mAudioPolicies) { + AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder()); + if (app == null) { + Slog.w(TAG, "Trying to unregister unknown audio policy for pid " + + Binder.getCallingPid() + " / uid " + Binder.getCallingUid()); + return; + } else { + pcb.asBinder().unlinkToDeath(app, 0/*flags*/); + } + app.release(); + } + // TODO implement clearing mix attribute matching info in native audio policy + } + + public int setFocusPropertiesForPolicy(int duckingBehavior, IAudioPolicyCallback pcb) { + if (DEBUG_AP) Log.d(TAG, "setFocusPropertiesForPolicy() duck behavior=" + duckingBehavior + + " policy " + pcb.asBinder()); + // error handling + boolean hasPermissionForPolicy = + (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + if (!hasPermissionForPolicy) { + Slog.w(TAG, "Cannot change audio policy ducking handling for pid " + + + Binder.getCallingPid() + " / uid " + + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING"); + return AudioManager.ERROR; + } + + synchronized (mAudioPolicies) { + if (!mAudioPolicies.containsKey(pcb.asBinder())) { + Slog.e(TAG, "Cannot change audio policy focus properties, unregistered policy"); + return AudioManager.ERROR; + } + final AudioPolicyProxy app = mAudioPolicies.get(pcb.asBinder()); + if (duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) { + // is there already one policy managing ducking? + for(AudioPolicyProxy policy : mAudioPolicies.values()) { + if (policy.mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) { + Slog.e(TAG, "Cannot change audio policy ducking behavior, already handled"); + return AudioManager.ERROR; + } + } + } + app.mFocusDuckBehavior = duckingBehavior; + mMediaFocusControl.setDuckingInExtPolicyAvailable( + duckingBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY); + } + return AudioManager.SUCCESS; + } + + private void dumpAudioPolicies(PrintWriter pw) { + pw.println("\nAudio policies:"); + synchronized (mAudioPolicies) { + for(AudioPolicyProxy policy : mAudioPolicies.values()) { + pw.println(policy.toLogFriendlyString()); + } + } + } + + //====================== + // Audio policy proxy + //====================== + /** + * This internal class inherits from AudioPolicyConfig, each instance contains all the + * mixes of an AudioPolicy and their configurations. + */ + public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient { + private static final String TAG = "AudioPolicyProxy"; + AudioPolicyConfig mConfig; + IAudioPolicyCallback mPolicyToken; + boolean mHasFocusListener; + /** + * Audio focus ducking behavior for an audio policy. + * This variable reflects the value that was successfully set in + * {@link AudioService#setFocusPropertiesForPolicy(int, IAudioPolicyCallback)}. This + * implies that a value of FOCUS_POLICY_DUCKING_IN_POLICY means the corresponding policy + * is handling ducking for audio focus. + */ + int mFocusDuckBehavior = AudioPolicy.FOCUS_POLICY_DUCKING_DEFAULT; + + AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token, + boolean hasFocusListener) { + super(config); + setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++)); + mPolicyToken = token; + mHasFocusListener = hasFocusListener; + if (mHasFocusListener) { + mMediaFocusControl.addFocusFollower(mPolicyToken); + } + connectMixes(); + } + + public void binderDied() { + synchronized (mAudioPolicies) { + Log.i(TAG, "audio policy " + mPolicyToken + " died"); + release(); + mAudioPolicies.remove(mPolicyToken.asBinder()); + } + } + + String getRegistrationId() { + return getRegistration(); + } + + void release() { + if (mFocusDuckBehavior == AudioPolicy.FOCUS_POLICY_DUCKING_IN_POLICY) { + mMediaFocusControl.setDuckingInExtPolicyAvailable(false); + } + if (mHasFocusListener) { + mMediaFocusControl.removeFocusFollower(mPolicyToken); + } + AudioSystem.registerPolicyMixes(mMixes, false); + } + + void connectMixes() { + AudioSystem.registerPolicyMixes(mMixes, true); + } + }; + + private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies = + new HashMap<IBinder, AudioPolicyProxy>(); + private int mAudioPolicyCounter = 0; // always accessed synchronized on mAudioPolicies + + private class ControllerService extends ContentObserver { + private int mUid; + private ComponentName mComponent; + + public ControllerService() { + super(null); + } + + @Override + public String toString() { + return String.format("{mUid=%d,mComponent=%s}", mUid, mComponent); + } + + public void init() { + onChange(true); + mContentResolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT), false, this); + } + + @Override + public void onChange(boolean selfChange) { + mUid = 0; + mComponent = null; + final String setting = Settings.Secure.getString(mContentResolver, + Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT); + if (setting == null) return; + try { + mComponent = ComponentName.unflattenFromString(setting); + if (mComponent == null) return; + mUid = mContext.getPackageManager() + .getApplicationInfo(mComponent.getPackageName(), 0).uid; + } catch (Exception e) { + Log.w(TAG, "Error loading controller service", e); + } + if (DEBUG_VOL) Log.d(TAG, "Reloaded controller service: " + this); + } + } +} diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java new file mode 100644 index 0000000..49be879 --- /dev/null +++ b/services/core/java/com/android/server/audio/FocusRequester.java @@ -0,0 +1,333 @@ +/* + * 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.audio; + +import android.annotation.NonNull; +import android.media.AudioAttributes; +import android.media.AudioFocusInfo; +import android.media.AudioManager; +import android.media.IAudioFocusDispatcher; +import android.os.IBinder; +import android.util.Log; + +import com.android.server.audio.MediaFocusControl.AudioFocusDeathHandler; + +import java.io.PrintWriter; + +/** + * @hide + * Class to handle all the information about a user of audio focus. The lifecycle of each + * instance is managed by android.media.MediaFocusControl, from its addition to the audio focus + * stack to its release. + */ +public class FocusRequester { + + // on purpose not using this classe's name, as it will only be used from MediaFocusControl + private static final String TAG = "MediaFocusControl"; + private static final boolean DEBUG = false; + + private AudioFocusDeathHandler mDeathHandler; + private final IAudioFocusDispatcher mFocusDispatcher; // may be null + private final IBinder mSourceRef; + private final String mClientId; + private final String mPackageName; + private final int mCallingUid; + private final MediaFocusControl mFocusController; // never null + /** + * the audio focus gain request that caused the addition of this object in the focus stack. + */ + private final int mFocusGainRequest; + /** + * the flags associated with the gain request that qualify the type of grant (e.g. accepting + * delay vs grant must be immediate) + */ + private final int mGrantFlags; + /** + * the audio focus loss received my mFocusDispatcher, is AudioManager.AUDIOFOCUS_NONE if + * it never lost focus. + */ + private int mFocusLossReceived; + /** + * the audio attributes associated with the focus request + */ + private final AudioAttributes mAttributes; + + /** + * Class constructor + * @param aa + * @param focusRequest + * @param grantFlags + * @param afl + * @param source + * @param id + * @param hdlr + * @param pn + * @param uid + * @param ctlr cannot be null + */ + FocusRequester(AudioAttributes aa, int focusRequest, int grantFlags, + IAudioFocusDispatcher afl, IBinder source, String id, AudioFocusDeathHandler hdlr, + String pn, int uid, @NonNull MediaFocusControl ctlr) { + mAttributes = aa; + mFocusDispatcher = afl; + mSourceRef = source; + mClientId = id; + mDeathHandler = hdlr; + mPackageName = pn; + mCallingUid = uid; + mFocusGainRequest = focusRequest; + mGrantFlags = grantFlags; + mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; + mFocusController = ctlr; + } + + + boolean hasSameClient(String otherClient) { + try { + return mClientId.compareTo(otherClient) == 0; + } catch (NullPointerException e) { + return false; + } + } + + boolean isLockedFocusOwner() { + return ((mGrantFlags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0); + } + + boolean hasSameBinder(IBinder ib) { + return (mSourceRef != null) && mSourceRef.equals(ib); + } + + boolean hasSamePackage(String pack) { + try { + return mPackageName.compareTo(pack) == 0; + } catch (NullPointerException e) { + return false; + } + } + + boolean hasSameUid(int uid) { + return mCallingUid == uid; + } + + String getClientId() { + return mClientId; + } + + int getGainRequest() { + return mFocusGainRequest; + } + + int getGrantFlags() { + return mGrantFlags; + } + + AudioAttributes getAudioAttributes() { + return mAttributes; + } + + + private static String focusChangeToString(int focus) { + switch(focus) { + case AudioManager.AUDIOFOCUS_NONE: + return "none"; + case AudioManager.AUDIOFOCUS_GAIN: + return "GAIN"; + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: + return "GAIN_TRANSIENT"; + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: + return "GAIN_TRANSIENT_MAY_DUCK"; + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: + return "GAIN_TRANSIENT_EXCLUSIVE"; + case AudioManager.AUDIOFOCUS_LOSS: + return "LOSS"; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + return "LOSS_TRANSIENT"; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + return "LOSS_TRANSIENT_CAN_DUCK"; + default: + return "[invalid focus change" + focus + "]"; + } + } + + private String focusGainToString() { + return focusChangeToString(mFocusGainRequest); + } + + private String focusLossToString() { + return focusChangeToString(mFocusLossReceived); + } + + private static String flagsToString(int flags) { + String msg = new String(); + if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) != 0) { + msg += "DELAY_OK"; + } + if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) != 0) { + if (!msg.isEmpty()) { msg += "|"; } + msg += "LOCK"; + } + if ((flags & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) != 0) { + if (!msg.isEmpty()) { msg += "|"; } + msg += "PAUSES_ON_DUCKABLE_LOSS"; + } + return msg; + } + + void dump(PrintWriter pw) { + pw.println(" source:" + mSourceRef + + " -- pack: " + mPackageName + + " -- client: " + mClientId + + " -- gain: " + focusGainToString() + + " -- flags: " + flagsToString(mGrantFlags) + + " -- loss: " + focusLossToString() + + " -- uid: " + mCallingUid + + " -- attr: " + mAttributes); + } + + + void release() { + try { + if (mSourceRef != null && mDeathHandler != null) { + mSourceRef.unlinkToDeath(mDeathHandler, 0); + mDeathHandler = null; + } + } catch (java.util.NoSuchElementException e) { + Log.e(TAG, "FocusRequester.release() hit ", e); + } + } + + @Override + protected void finalize() throws Throwable { + release(); + super.finalize(); + } + + /** + * For a given audio focus gain request, return the audio focus loss type that will result + * from it, taking into account any previous focus loss. + * @param gainRequest + * @return the audio focus loss type that matches the gain request + */ + private int focusLossForGainRequest(int gainRequest) { + switch(gainRequest) { + case AudioManager.AUDIOFOCUS_GAIN: + switch(mFocusLossReceived) { + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + case AudioManager.AUDIOFOCUS_LOSS: + case AudioManager.AUDIOFOCUS_NONE: + return AudioManager.AUDIOFOCUS_LOSS; + } + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: + switch(mFocusLossReceived) { + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + case AudioManager.AUDIOFOCUS_NONE: + return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; + case AudioManager.AUDIOFOCUS_LOSS: + return AudioManager.AUDIOFOCUS_LOSS; + } + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: + switch(mFocusLossReceived) { + case AudioManager.AUDIOFOCUS_NONE: + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; + case AudioManager.AUDIOFOCUS_LOSS: + return AudioManager.AUDIOFOCUS_LOSS; + } + default: + Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest); + return AudioManager.AUDIOFOCUS_NONE; + } + } + + /** + * Called synchronized on MediaFocusControl.mAudioFocusLock + */ + void handleExternalFocusGain(int focusGain) { + int focusLoss = focusLossForGainRequest(focusGain); + handleFocusLoss(focusLoss); + } + + /** + * Called synchronized on MediaFocusControl.mAudioFocusLock + */ + void handleFocusGain(int focusGain) { + try { + mFocusLossReceived = AudioManager.AUDIOFOCUS_NONE; + mFocusController.notifyExtPolicyFocusGrant_syncAf(toAudioFocusInfo(), + AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + if (mFocusDispatcher != null) { + if (DEBUG) { + Log.v(TAG, "dispatching " + focusChangeToString(focusGain) + " to " + + mClientId); + } + mFocusDispatcher.dispatchAudioFocusChange(focusGain, mClientId); + } + } catch (android.os.RemoteException e) { + Log.e(TAG, "Failure to signal gain of audio focus due to: ", e); + } + } + + /** + * Called synchronized on MediaFocusControl.mAudioFocusLock + */ + void handleFocusLoss(int focusLoss) { + try { + if (focusLoss != mFocusLossReceived) { + mFocusLossReceived = focusLoss; + // before dispatching a focus loss, check if the following conditions are met: + // 1/ the framework is not supposed to notify the focus loser on a DUCK loss + // 2/ it is a DUCK loss + // 3/ the focus loser isn't flagged as pausing in a DUCK loss + // if they are, do not notify the focus loser + if (!mFocusController.mustNotifyFocusOwnerOnDuck() + && mFocusLossReceived == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK + && (mGrantFlags + & AudioManager.AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS) == 0) { + if (DEBUG) { + Log.v(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived) + + " to " + mClientId + ", to be handled externally"); + } + mFocusController.notifyExtPolicyFocusLoss_syncAf( + toAudioFocusInfo(), false /* wasDispatched */); + return; + } + if (mFocusDispatcher != null) { + if (DEBUG) { + Log.v(TAG, "dispatching " + focusChangeToString(mFocusLossReceived) + " to " + + mClientId); + } + mFocusController.notifyExtPolicyFocusLoss_syncAf( + toAudioFocusInfo(), true /* wasDispatched */); + mFocusDispatcher.dispatchAudioFocusChange(mFocusLossReceived, mClientId); + } + } + } catch (android.os.RemoteException e) { + Log.e(TAG, "Failure to signal loss of audio focus due to:", e); + } + } + + AudioFocusInfo toAudioFocusInfo() { + return new AudioFocusInfo(mAttributes, mClientId, mPackageName, + mFocusGainRequest, mFocusLossReceived, mGrantFlags); + } +} diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java new file mode 100644 index 0000000..4ccb5ad --- /dev/null +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -0,0 +1,2228 @@ +/* + * 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.audio; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.app.PendingIntent.OnFinished; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.media.AudioAttributes; +import android.media.AudioFocusInfo; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.IAudioFocusDispatcher; +import android.media.IRemoteControlClient; +import android.media.IRemoteControlDisplay; +import android.media.IRemoteVolumeObserver; +import android.media.RemoteControlClient; +import android.media.audiopolicy.IAudioPolicyCallback; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.speech.RecognizerIntent; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.util.Slog; +import android.view.KeyEvent; + +import com.android.server.audio.PlayerRecord.RemotePlaybackState; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.Stack; +import java.text.DateFormat; + +/** + * @hide + * + */ +public class MediaFocusControl implements OnFinished { + + private static final String TAG = "MediaFocusControl"; + + /** Debug remote control client/display feature */ + protected static final boolean DEBUG_RC = false; + /** Debug volumes */ + protected static final boolean DEBUG_VOL = false; + + /** Used to alter media button redirection when the phone is ringing. */ + private boolean mIsRinging = false; + + private final PowerManager.WakeLock mMediaEventWakeLock; + private final MediaEventHandler mEventHandler; + private final Context mContext; + private final ContentResolver mContentResolver; + private final AudioService.VolumeController mVolumeController; + private final AppOpsManager mAppOps; + private final KeyguardManager mKeyguardManager; + private final AudioService mAudioService; + private final NotificationListenerObserver mNotifListenerObserver; + + protected MediaFocusControl(Looper looper, Context cntxt, + AudioService.VolumeController volumeCtrl, AudioService as) { + mEventHandler = new MediaEventHandler(looper); + mContext = cntxt; + mContentResolver = mContext.getContentResolver(); + mVolumeController = volumeCtrl; + mAudioService = as; + + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); + int maxMusicLevel = as.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + mMainRemote = new RemotePlaybackState(-1, maxMusicLevel, maxMusicLevel); + + // Register for phone state monitoring + TelephonyManager tmgr = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + + mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); + mKeyguardManager = + (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + mNotifListenerObserver = new NotificationListenerObserver(); + + mHasRemotePlayback = false; + mMainRemoteIsActive = false; + + PlayerRecord.setMediaFocusControl(this); + + postReevaluateRemote(); + } + + protected void dump(PrintWriter pw) { + pw.println("\nMediaFocusControl dump time: " + + DateFormat.getTimeInstance().format(new Date())); + dumpFocusStack(pw); + dumpRCStack(pw); + dumpRCCStack(pw); + dumpRCDList(pw); + } + + //========================================================================================== + // Management of RemoteControlDisplay registration permissions + //========================================================================================== + private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI = + Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + + private class NotificationListenerObserver extends ContentObserver { + + NotificationListenerObserver() { + super(mEventHandler); + mContentResolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) { + return; + } + if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); } + postReevaluateRemoteControlDisplays(); + } + } + + private final static int RCD_REG_FAILURE = 0; + private final static int RCD_REG_SUCCESS_PERMISSION = 1; + private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2; + + /** + * Checks a caller's authorization to register an IRemoteControlDisplay. + * Authorization is granted if one of the following is true: + * <ul> + * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li> + * <li>the caller's listener is one of the enabled notification listeners</li> + * </ul> + * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay + * registration. + */ + private int checkRcdRegistrationAuthorization(ComponentName listenerComp) { + // MEDIA_CONTENT_CONTROL permission check + if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MEDIA_CONTENT_CONTROL)) { + if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");} + return RCD_REG_SUCCESS_PERMISSION; + } + + // ENABLED_NOTIFICATION_LISTENERS settings check + if (listenerComp != null) { + // this call is coming from an app, can't use its identity to read secure settings + final long ident = Binder.clearCallingIdentity(); + try { + final int currentUser = ActivityManager.getCurrentUser(); + final String enabledNotifListeners = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + if (enabledNotifListeners != null) { + final String[] components = enabledNotifListeners.split(":"); + for (int i=0; i<components.length; i++) { + final ComponentName component = + ComponentName.unflattenFromString(components[i]); + if (component != null) { + if (listenerComp.equals(component)) { + if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component + + " is authorized notification listener"); } + return RCD_REG_SUCCESS_ENABLED_NOTIF; + } + } + } + } + if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp + + " is not in list of ENABLED_NOTIFICATION_LISTENERS"); } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + return RCD_REG_FAILURE; + } + + protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h, + ComponentName listenerComp) { + int reg = checkRcdRegistrationAuthorization(listenerComp); + if (reg != RCD_REG_FAILURE) { + registerRemoteControlDisplay_int(rcd, w, h, listenerComp); + return true; + } else { + Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() + + ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL + + " or be an enabled NotificationListenerService for registerRemoteController"); + return false; + } + } + + protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { + int reg = checkRcdRegistrationAuthorization(null); + if (reg != RCD_REG_FAILURE) { + registerRemoteControlDisplay_int(rcd, w, h, null); + return true; + } else { + Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() + + ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL + + " to register IRemoteControlDisplay"); + return false; + } + } + + private void postReevaluateRemoteControlDisplays() { + sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0); + } + + private void onReevaluateRemoteControlDisplays() { + if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); } + // read which components are enabled notification listeners + final int currentUser = ActivityManager.getCurrentUser(); + final String enabledNotifListeners = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); } + synchronized(mAudioFocusLock) { + synchronized(mPRStack) { + // check whether the "enable" status of each RCD with a notification listener + // has changed + final String[] enabledComponents; + if (enabledNotifListeners == null) { + enabledComponents = null; + } else { + enabledComponents = enabledNotifListeners.split(":"); + } + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = + displayIterator.next(); + if (di.mClientNotifListComp != null) { + boolean wasEnabled = di.mEnabled; + di.mEnabled = isComponentInStringArray(di.mClientNotifListComp, + enabledComponents); + if (wasEnabled != di.mEnabled){ + try { + // tell the RCD whether it's enabled + di.mRcDisplay.setEnabled(di.mEnabled); + // tell the RCCs about the change for this RCD + enableRemoteControlDisplayForClient_syncRcStack( + di.mRcDisplay, di.mEnabled); + // when enabling, refresh the information on the display + if (di.mEnabled) { + sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, + di.mArtworkExpectedWidth /*arg1*/, + di.mArtworkExpectedHeight/*arg2*/, + di.mRcDisplay /*obj*/, 0/*delay*/); + } + } catch (RemoteException e) { + Log.e(TAG, "Error en/disabling RCD: ", e); + } + } + } + } + } + } + } + + /** + * @param comp a non-null ComponentName + * @param enabledArray may be null + * @return + */ + private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) { + if (enabledArray == null || enabledArray.length == 0) { + if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); } + return false; + } + final String compString = comp.flattenToString(); + for (int i=0; i<enabledArray.length; i++) { + if (compString.equals(enabledArray[i])) { + if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); } + return true; + } + } + if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); } + return false; + } + + //========================================================================================== + // Internal event handling + //========================================================================================== + + // event handler messages + private static final int MSG_RCDISPLAY_CLEAR = 1; + private static final int MSG_RCDISPLAY_UPDATE = 2; + private static final int MSG_REEVALUATE_REMOTE = 3; + private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4; + private static final int MSG_RCC_NEW_VOLUME_OBS = 5; + private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6; + private static final int MSG_RCC_SEEK_REQUEST = 7; + private static final int MSG_RCC_UPDATE_METADATA = 8; + private static final int MSG_RCDISPLAY_INIT_INFO = 9; + private static final int MSG_REEVALUATE_RCD = 10; + private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11; + + // sendMsg() flags + /** If the msg is already queued, replace it with this one. */ + private static final int SENDMSG_REPLACE = 0; + /** If the msg is already queued, ignore this one and leave the old. */ + private static final int SENDMSG_NOOP = 1; + /** If the msg is already queued, queue this one and leave the old. */ + private static final int SENDMSG_QUEUE = 2; + + private static void sendMsg(Handler handler, int msg, + int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { + + if (existingMsgPolicy == SENDMSG_REPLACE) { + handler.removeMessages(msg); + } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { + return; + } + + handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay); + } + + private class MediaEventHandler extends Handler { + MediaEventHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_RCDISPLAY_CLEAR: + onRcDisplayClear(); + break; + + case MSG_RCDISPLAY_UPDATE: + // msg.obj is guaranteed to be non null + onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1); + break; + + case MSG_REEVALUATE_REMOTE: + onReevaluateRemote(); + break; + + case MSG_RCC_NEW_VOLUME_OBS: + onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, + (IRemoteVolumeObserver)msg.obj /* rvo */); + break; + + case MSG_RCDISPLAY_INIT_INFO: + // msg.obj is guaranteed to be non null + onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/, + msg.arg1/*w*/, msg.arg2/*h*/); + break; + + case MSG_REEVALUATE_RCD: + onReevaluateRemoteControlDisplays(); + break; + + case MSG_UNREGISTER_MEDIABUTTONINTENT: + unregisterMediaButtonIntent( (PendingIntent) msg.obj ); + break; + } + } + } + + + //========================================================================================== + // AudioFocus + //========================================================================================== + + private final static Object mAudioFocusLock = new Object(); + + private final static Object mRingingLock = new Object(); + + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + if (state == TelephonyManager.CALL_STATE_RINGING) { + //Log.v(TAG, " CALL_STATE_RINGING"); + synchronized(mRingingLock) { + mIsRinging = true; + } + } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK) + || (state == TelephonyManager.CALL_STATE_IDLE)) { + synchronized(mRingingLock) { + mIsRinging = false; + } + } + } + }; + + /** + * Discard the current audio focus owner. + * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign + * focus), remove it from the stack, and clear the remote control display. + */ + protected void discardAudioFocusOwner() { + synchronized(mAudioFocusLock) { + if (!mFocusStack.empty()) { + // notify the current focus owner it lost focus after removing it from stack + final FocusRequester exFocusOwner = mFocusStack.pop(); + exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS); + exFocusOwner.release(); + } + } + } + + /** + * Called synchronized on mAudioFocusLock + */ + private void notifyTopOfAudioFocusStack() { + // notify the top of the stack it gained focus + if (!mFocusStack.empty()) { + if (canReassignAudioFocus()) { + mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); + } + } + } + + /** + * Focus is requested, propagate the associated loss throughout the stack. + * @param focusGain the new focus gain that will later be added at the top of the stack + */ + private void propagateFocusLossFromGain_syncAf(int focusGain) { + // going through the audio focus stack to signal new focus, traversing order doesn't + // matter as all entries respond to the same external focus gain + Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + stackIterator.next().handleExternalFocusGain(focusGain); + } + } + + private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>(); + + /** + * Helper function: + * Display in the log the current entries in the audio focus stack + */ + private void dumpFocusStack(PrintWriter pw) { + pw.println("\nAudio Focus stack entries (last is top of stack):"); + synchronized(mAudioFocusLock) { + Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + stackIterator.next().dump(pw); + } + } + pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n"); + } + + /** + * Helper function: + * Called synchronized on mAudioFocusLock + * Remove a focus listener from the focus stack. + * @param clientToRemove the focus listener + * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding + * focus, notify the next item in the stack it gained focus. + */ + private void removeFocusStackEntry(String clientToRemove, boolean signal, + boolean notifyFocusFollowers) { + // is the current top of the focus stack abandoning focus? (because of request, not death) + if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove)) + { + //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); + FocusRequester fr = mFocusStack.pop(); + fr.release(); + if (notifyFocusFollowers) { + final AudioFocusInfo afi = fr.toAudioFocusInfo(); + afi.clearLossReceived(); + notifyExtPolicyFocusLoss_syncAf(afi, false); + } + if (signal) { + // notify the new top of the stack it gained focus + notifyTopOfAudioFocusStack(); + } + } else { + // focus is abandoned by a client that's not at the top of the stack, + // no need to update focus. + // (using an iterator on the stack so we can safely remove an entry after having + // evaluated it, traversal order doesn't matter here) + Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + FocusRequester fr = stackIterator.next(); + if(fr.hasSameClient(clientToRemove)) { + Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + + clientToRemove); + stackIterator.remove(); + fr.release(); + } + } + } + } + + /** + * Helper function: + * Called synchronized on mAudioFocusLock + * Remove focus listeners from the focus stack for a particular client when it has died. + */ + private void removeFocusStackEntryForClient(IBinder cb) { + // is the owner of the audio focus part of the client to remove? + boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() && + mFocusStack.peek().hasSameBinder(cb); + // (using an iterator on the stack so we can safely remove an entry after having + // evaluated it, traversal order doesn't matter here) + Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); + while(stackIterator.hasNext()) { + FocusRequester fr = stackIterator.next(); + if(fr.hasSameBinder(cb)) { + Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb); + stackIterator.remove(); + // the client just died, no need to unlink to its death + } + } + if (isTopOfStackForClientToRemove) { + // we removed an entry at the top of the stack: + // notify the new top of the stack it gained focus. + notifyTopOfAudioFocusStack(); + } + } + + /** + * Helper function: + * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. + * The implementation guarantees that a state where focus cannot be immediately reassigned + * implies that an "locked" focus owner is at the top of the focus stack. + * Modifications to the implementation that break this assumption will cause focus requests to + * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag. + */ + private boolean canReassignAudioFocus() { + // focus requests are rejected during a phone call or when the phone is ringing + // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus + if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) { + return false; + } + return true; + } + + private boolean isLockedFocusOwner(FocusRequester fr) { + return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner()); + } + + /** + * Helper function + * Pre-conditions: focus stack is not empty, there is one or more locked focus owner + * at the top of the focus stack + * Push the focus requester onto the audio focus stack at the first position immediately + * following the locked focus owners. + * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or + * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED} + */ + private int pushBelowLockedFocusOwners(FocusRequester nfr) { + int lastLockedFocusOwnerIndex = mFocusStack.size(); + for (int index = mFocusStack.size()-1; index >= 0; index--) { + if (isLockedFocusOwner(mFocusStack.elementAt(index))) { + lastLockedFocusOwnerIndex = index; + } + } + if (lastLockedFocusOwnerIndex == mFocusStack.size()) { + // this should not happen, but handle it and log an error + Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()", + new Exception()); + // no exclusive owner, push at top of stack, focus is granted, propagate change + propagateFocusLossFromGain_syncAf(nfr.getGainRequest()); + mFocusStack.push(nfr); + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } else { + mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex); + return AudioManager.AUDIOFOCUS_REQUEST_DELAYED; + } + } + + /** + * Inner class to monitor audio focus client deaths, and remove them from the audio focus + * stack if necessary. + */ + protected class AudioFocusDeathHandler implements IBinder.DeathRecipient { + private IBinder mCb; // To be notified of client's death + + AudioFocusDeathHandler(IBinder cb) { + mCb = cb; + } + + public void binderDied() { + synchronized(mAudioFocusLock) { + Log.w(TAG, " AudioFocus audio focus client died"); + removeFocusStackEntryForClient(mCb); + } + } + + public IBinder getBinder() { + return mCb; + } + } + + /** + * Indicates whether to notify an audio focus owner when it loses focus + * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck. + * This variable being false indicates an AudioPolicy has been registered and has signaled + * it will handle audio ducking. + */ + private boolean mNotifyFocusOwnerOnDuck = true; + + protected void setDuckingInExtPolicyAvailable(boolean available) { + mNotifyFocusOwnerOnDuck = !available; + } + + boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; } + + private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>(); + + void addFocusFollower(IAudioPolicyCallback ff) { + if (ff == null) { + return; + } + synchronized(mAudioFocusLock) { + boolean found = false; + for (IAudioPolicyCallback pcb : mFocusFollowers) { + if (pcb.asBinder().equals(ff.asBinder())) { + found = true; + break; + } + } + if (found) { + return; + } else { + mFocusFollowers.add(ff); + notifyExtPolicyCurrentFocusAsync(ff); + } + } + } + + void removeFocusFollower(IAudioPolicyCallback ff) { + if (ff == null) { + return; + } + synchronized(mAudioFocusLock) { + for (IAudioPolicyCallback pcb : mFocusFollowers) { + if (pcb.asBinder().equals(ff.asBinder())) { + mFocusFollowers.remove(pcb); + break; + } + } + } + } + + /** + * @param pcb non null + */ + void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) { + final IAudioPolicyCallback pcb2 = pcb; + final Thread thread = new Thread() { + @Override + public void run() { + synchronized(mAudioFocusLock) { + if (mFocusStack.isEmpty()) { + return; + } + try { + pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(), + // top of focus stack always has focus + AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + } catch (RemoteException e) { + Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback " + + pcb2.asBinder(), e); + } + } + } + }; + thread.start(); + } + + /** + * Called synchronized on mAudioFocusLock + */ + void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) { + for (IAudioPolicyCallback pcb : mFocusFollowers) { + try { + // oneway + pcb.notifyAudioFocusGrant(afi, requestResult); + } catch (RemoteException e) { + Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback " + + pcb.asBinder(), e); + } + } + } + + /** + * Called synchronized on mAudioFocusLock + */ + void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) { + for (IAudioPolicyCallback pcb : mFocusFollowers) { + try { + // oneway + pcb.notifyAudioFocusLoss(afi, wasDispatched); + } catch (RemoteException e) { + Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback " + + pcb.asBinder(), e); + } + } + } + + protected int getCurrentAudioFocus() { + synchronized(mAudioFocusLock) { + if (mFocusStack.empty()) { + return AudioManager.AUDIOFOCUS_NONE; + } else { + return mFocusStack.peek().getGainRequest(); + } + } + } + + /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */ + protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb, + IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) { + Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId + " req=" + focusChangeHint + + "flags=0x" + Integer.toHexString(flags)); + // we need a valid binder callback for clients + if (!cb.pingBinder()) { + Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting."); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), + callingPackageName) != AppOpsManager.MODE_ALLOWED) { + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + synchronized(mAudioFocusLock) { + boolean focusGrantDelayed = false; + if (!canReassignAudioFocus()) { + if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } else { + // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be + // granted right now, so the requester will be inserted in the focus stack + // to receive focus later + focusGrantDelayed = true; + } + } + + // handle the potential premature death of the new holder of the focus + // (premature death == death before abandoning focus) + // Register for client death notification + AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); + try { + cb.linkToDeath(afdh, 0); + } catch (RemoteException e) { + // client has already died! + Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); + return AudioManager.AUDIOFOCUS_REQUEST_FAILED; + } + + if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) { + // if focus is already owned by this client and the reason for acquiring the focus + // hasn't changed, don't do anything + final FocusRequester fr = mFocusStack.peek(); + if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) { + // unlink death handler so it can be gc'ed. + // linkToDeath() creates a JNI global reference preventing collection. + cb.unlinkToDeath(afdh, 0); + notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(), + AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + // the reason for the audio focus request has changed: remove the current top of + // stack and respond as if we had a new focus owner + if (!focusGrantDelayed) { + mFocusStack.pop(); + // the entry that was "popped" is the same that was "peeked" above + fr.release(); + } + } + + // focus requester might already be somewhere below in the stack, remove it + removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/); + + final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb, + clientId, afdh, callingPackageName, Binder.getCallingUid(), this); + if (focusGrantDelayed) { + // focusGrantDelayed being true implies we can't reassign focus right now + // which implies the focus stack is not empty. + final int requestResult = pushBelowLockedFocusOwners(nfr); + if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) { + notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult); + } + return requestResult; + } else { + // propagate the focus change through the stack + if (!mFocusStack.empty()) { + propagateFocusLossFromGain_syncAf(focusChangeHint); + } + + // push focus requester at the top of the audio focus stack + mFocusStack.push(nfr); + } + notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), + AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + + }//synchronized(mAudioFocusLock) + + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + + /** + * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) + * */ + protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) { + // AudioAttributes are currently ignored, to be used for zones + Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId); + try { + // this will take care of notifying the new focus owner if needed + synchronized(mAudioFocusLock) { + removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/); + } + } catch (java.util.ConcurrentModificationException cme) { + // Catching this exception here is temporary. It is here just to prevent + // a crash seen when the "Silent" notification is played. This is believed to be fixed + // but this try catch block is left just to be safe. + Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme); + cme.printStackTrace(); + } + + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; + } + + + protected void unregisterAudioFocusClient(String clientId) { + synchronized(mAudioFocusLock) { + removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/); + } + } + + + //========================================================================================== + // RemoteControl + //========================================================================================== + /** + * No-op if the key code for keyEvent is not a valid media key + * (see {@link #isValidMediaKeyEvent(KeyEvent)}) + * @param keyEvent the key event to send + */ + protected void dispatchMediaKeyEvent(KeyEvent keyEvent) { + filterMediaKeyEvent(keyEvent, false /*needWakeLock*/); + } + + /** + * No-op if the key code for keyEvent is not a valid media key + * (see {@link #isValidMediaKeyEvent(KeyEvent)}) + * @param keyEvent the key event to send + */ + protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { + filterMediaKeyEvent(keyEvent, true /*needWakeLock*/); + } + + private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + // sanity check on the incoming key event + if (!isValidMediaKeyEvent(keyEvent)) { + Log.e(TAG, "not dispatching invalid media key event " + keyEvent); + return; + } + // event filtering for telephony + synchronized(mRingingLock) { + synchronized(mPRStack) { + if ((mMediaReceiverForCalls != null) && + (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) { + dispatchMediaKeyEventForCalls(keyEvent, needWakeLock); + return; + } + } + } + // event filtering based on voice-based interactions + if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) { + filterVoiceInputKeyEvent(keyEvent, needWakeLock); + } else { + dispatchMediaKeyEvent(keyEvent, needWakeLock); + } + } + + /** + * Handles the dispatching of the media button events to the telephony package. + * Precondition: mMediaReceiverForCalls != null + * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) { + Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + keyIntent.setPackage(mMediaReceiverForCalls.getPackageName()); + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); + } + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * Handles the dispatching of the media button events to one of the registered listeners, + * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system. + * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + } + Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); + keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + synchronized(mPRStack) { + if (!mPRStack.empty()) { + // send the intent that was registered by the client + try { + mPRStack.peek().getMediaButtonIntent().send(mContext, + needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/, + keyIntent, this, mEventHandler); + } catch (CanceledException e) { + Log.e(TAG, "Error sending pending intent " + mPRStack.peek()); + e.printStackTrace(); + } + } else { + // legacy behavior when nobody registered their media button event receiver + // through AudioManager + if (needWakeLock) { + keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); + } + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, + null, mKeyEventDone, + mEventHandler, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + } + + /** + * The different actions performed in response to a voice button key event. + */ + private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1; + private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2; + private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3; + + private final Object mVoiceEventLock = new Object(); + private boolean mVoiceButtonDown; + private boolean mVoiceButtonHandled; + + /** + * Filter key events that may be used for voice-based interactions + * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported + * media buttons that can be used to trigger voice-based interactions. + * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event + * is dispatched. + */ + private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { + if (DEBUG_RC) { + Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock); + } + + int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS; + int keyAction = keyEvent.getAction(); + synchronized (mVoiceEventLock) { + if (keyAction == KeyEvent.ACTION_DOWN) { + if (keyEvent.getRepeatCount() == 0) { + // initial down + mVoiceButtonDown = true; + mVoiceButtonHandled = false; + } else if (mVoiceButtonDown && !mVoiceButtonHandled + && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { + // long-press, start voice-based interactions + mVoiceButtonHandled = true; + voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT; + } + } else if (keyAction == KeyEvent.ACTION_UP) { + if (mVoiceButtonDown) { + // voice button up + mVoiceButtonDown = false; + if (!mVoiceButtonHandled && !keyEvent.isCanceled()) { + voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS; + } + } + } + }//synchronized (mVoiceEventLock) + + // take action after media button event filtering for voice-based interactions + switch (voiceButtonAction) { + case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS: + if (DEBUG_RC) Log.v(TAG, " ignore key event"); + break; + case VOICEBUTTON_ACTION_START_VOICE_INPUT: + if (DEBUG_RC) Log.v(TAG, " start voice-based interactions"); + // then start the voice-based interactions + startVoiceBasedInteractions(needWakeLock); + break; + case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS: + if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock); + sendSimulatedMediaButtonEvent(keyEvent, needWakeLock); + break; + } + } + + private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) { + // send DOWN event + KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN); + dispatchMediaKeyEvent(keyEvent, needWakeLock); + // send UP event + keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP); + dispatchMediaKeyEvent(keyEvent, needWakeLock); + + } + + private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) { + if (keyEvent == null) { + return false; + } + return KeyEvent.isMediaKey(keyEvent.getKeyCode()); + } + + /** + * Checks whether the given key code is one that can trigger the launch of voice-based + * interactions. + * @param keyCode the key code associated with the key event + * @return true if the key is one of the supported voice-based interaction triggers + */ + private static boolean isValidVoiceInputKeyCode(int keyCode) { + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { + return true; + } else { + return false; + } + } + + /** + * Tell the system to start voice-based interactions / voice commands + */ + private void startVoiceBasedInteractions(boolean needWakeLock) { + Intent voiceIntent = null; + // select which type of search to launch: + // - screen on and device unlocked: action is ACTION_WEB_SEARCH + // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE + // with EXTRA_SECURE set to true if the device is securely locked + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); + if (!isLocked && pm.isScreenOn()) { + voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH); + Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH"); + } else { + voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, + isLocked && mKeyguardManager.isKeyguardSecure()); + Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE"); + } + // start the search activity + if (needWakeLock) { + mMediaEventWakeLock.acquire(); + } + final long identity = Binder.clearCallingIdentity(); + try { + if (voiceIntent != null) { + voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT); + } + } catch (ActivityNotFoundException e) { + Log.w(TAG, "No activity for search: " + e); + } finally { + Binder.restoreCallingIdentity(identity); + if (needWakeLock) { + mMediaEventWakeLock.release(); + } + } + } + + private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number + + // only set when wakelock was acquired, no need to check value when received + private static final String EXTRA_WAKELOCK_ACQUIRED = + "android.media.AudioService.WAKELOCK_ACQUIRED"; + + public void onSendFinished(PendingIntent pendingIntent, Intent intent, + int resultCode, String resultData, Bundle resultExtras) { + if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) { + mMediaEventWakeLock.release(); + } + } + + BroadcastReceiver mKeyEventDone = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + Bundle extras = intent.getExtras(); + if (extras == null) { + return; + } + if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) { + mMediaEventWakeLock.release(); + } + } + }; + + /** + * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack + */ + private final Object mCurrentRcLock = new Object(); + /** + * The one remote control client which will receive a request for display information. + * This object may be null. + * Access protected by mCurrentRcLock. + */ + private IRemoteControlClient mCurrentRcClient = null; + /** + * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant + * if mCurrentRcClient is null + */ + private PendingIntent mCurrentRcClientIntent = null; + + private final static int RC_INFO_NONE = 0; + private final static int RC_INFO_ALL = + RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART | + RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA | + RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA | + RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE; + + /** + * A monotonically increasing generation counter for mCurrentRcClient. + * Only accessed with a lock on mCurrentRcLock. + * No value wrap-around issues as we only act on equal values. + */ + private int mCurrentRcClientGen = 0; + + + /** + * Internal cache for the playback information of the RemoteControlClient whose volume gets to + * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack + * every time we need this info. + */ + private RemotePlaybackState mMainRemote; + /** + * Indicates whether the "main" RemoteControlClient is considered active. + * Use synchronized on mMainRemote. + */ + private boolean mMainRemoteIsActive; + /** + * Indicates whether there is remote playback going on. True even if there is no "active" + * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it + * handles remote playback. + * Use synchronized on mMainRemote. + */ + private boolean mHasRemotePlayback; + + /** + * The stack of remote control event receivers. + * All read and write operations on mPRStack are synchronized. + */ + private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>(); + + /** + * The component the telephony package can register so telephony calls have priority to + * handle media button events + */ + private ComponentName mMediaReceiverForCalls = null; + + /** + * Helper function: + * Display in the log the current entries in the remote control focus stack + */ + private void dumpRCStack(PrintWriter pw) { + pw.println("\nRemote Control stack entries (last is top of stack):"); + synchronized(mPRStack) { + Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); + while(stackIterator.hasNext()) { + stackIterator.next().dump(pw, true); + } + } + } + + /** + * Helper function: + * Display in the log the current entries in the remote control stack, focusing + * on RemoteControlClient data + */ + private void dumpRCCStack(PrintWriter pw) { + pw.println("\nRemote Control Client stack entries (last is top of stack):"); + synchronized(mPRStack) { + Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); + while(stackIterator.hasNext()) { + stackIterator.next().dump(pw, false); + } + synchronized(mCurrentRcLock) { + pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); + } + } + synchronized (mMainRemote) { + pw.println("\nRemote Volume State:"); + pw.println(" has remote: " + mHasRemotePlayback); + pw.println(" is remote active: " + mMainRemoteIsActive); + pw.println(" rccId: " + mMainRemote.mRccId); + pw.println(" volume handling: " + + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ? + "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)")); + pw.println(" volume: " + mMainRemote.mVolume); + pw.println(" volume steps: " + mMainRemote.mVolumeMax); + } + } + + /** + * Helper function: + * Display in the log the current entries in the list of remote control displays + */ + private void dumpRCDList(PrintWriter pw) { + pw.println("\nRemote Control Display list entries:"); + synchronized(mPRStack) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + pw.println(" IRCD: " + di.mRcDisplay + + " -- w:" + di.mArtworkExpectedWidth + + " -- h:" + di.mArtworkExpectedHeight + + " -- wantsPosSync:" + di.mWantsPositionSync + + " -- " + (di.mEnabled ? "enabled" : "disabled")); + } + } + } + + /** + * Helper function: + * Push the new media button receiver "near" the top of the PlayerRecord stack. + * "Near the top" is defined as: + * - at the top if the current PlayerRecord at the top is not playing + * - below the entries at the top of the stack that correspond to the playing PlayerRecord + * otherwise + * Called synchronized on mPRStack + * precondition: mediaIntent != null + * @return true if the top of mPRStack was changed, false otherwise + */ + private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent, + ComponentName target, IBinder token) { + if (mPRStack.empty()) { + mPRStack.push(new PlayerRecord(mediaIntent, target, token)); + return true; + } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) { + // already at top of stack + return false; + } + if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(), + mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) { + return false; + } + PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes + boolean topChanged = false; + PlayerRecord prse = null; + int lastPlayingIndex = mPRStack.size(); + int inStackIndex = -1; + try { + // go through the stack from the top to figure out who's playing, and the position + // of this media button receiver (note that it may not be in the stack) + for (int index = mPRStack.size()-1; index >= 0; index--) { + prse = mPRStack.elementAt(index); + if (prse.isPlaybackActive()) { + lastPlayingIndex = index; + } + if (prse.hasMatchingMediaButtonIntent(mediaIntent)) { + inStackIndex = index; + } + } + + if (inStackIndex == -1) { + // is not in stack + prse = new PlayerRecord(mediaIntent, target, token); + // it's new so it's not playing (no RemoteControlClient to give a playstate), + // therefore it goes after the ones with active playback + mPRStack.add(lastPlayingIndex, prse); + } else { + // is in the stack + if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1 + prse = mPRStack.elementAt(inStackIndex); + // remove it from its old location in the stack + mPRStack.removeElementAt(inStackIndex); + if (prse.isPlaybackActive()) { + // and put it at the top + mPRStack.push(prse); + } else { + // and put it after the ones with active playback + if (inStackIndex > lastPlayingIndex) { + mPRStack.add(lastPlayingIndex, prse); + } else { + mPRStack.add(lastPlayingIndex - 1, prse); + } + } + } + } + + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification or bad index + Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex + + " size=" + mPRStack.size() + + " accessing media button stack", e); + } + + return (topChanged); + } + + /** + * Helper function: + * Remove the remote control receiver from the RC focus stack. + * Called synchronized on mPRStack + * precondition: pi != null + */ + private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) { + try { + for (int index = mPRStack.size()-1; index >= 0; index--) { + final PlayerRecord prse = mPRStack.elementAt(index); + if (prse.hasMatchingMediaButtonIntent(pi)) { + prse.destroy(); + // ok to remove element while traversing the stack since we're leaving the loop + mPRStack.removeElementAt(index); + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + + /** + * Helper function: + * Called synchronized on mPRStack + */ + private boolean isCurrentRcController(PendingIntent pi) { + if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) { + return true; + } + return false; + } + + //========================================================================================== + // Remote control display / client + //========================================================================================== + /** + * Update the remote control displays with the new "focused" client generation + */ + private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, + PendingIntent newMediaIntent, boolean clearing) { + synchronized(mPRStack) { + if (mRcDisplays.size() > 0) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + try { + di.mRcDisplay.setCurrentClientId( + newClientGeneration, newMediaIntent, clearing); + } catch (RemoteException e) { + Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e); + di.release(); + displayIterator.remove(); + } + } + } + } + } + + /** + * Update the remote control clients with the new "focused" client generation + */ + private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) { + // (using an iterator on the stack so we can safely remove an entry if needed, + // traversal order doesn't matter here as we update all entries) + Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); + while(stackIterator.hasNext()) { + PlayerRecord se = stackIterator.next(); + if ((se != null) && (se.getRcc() != null)) { + try { + se.getRcc().setCurrentClientGenerationId(newClientGeneration); + } catch (RemoteException e) { + Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e); + stackIterator.remove(); + se.unlinkToRcClientDeath(); + } + } + } + } + + /** + * Update the displays and clients with the new "focused" client generation and name + * @param newClientGeneration the new generation value matching a client update + * @param newMediaIntent the media button event receiver associated with the client. + * May be null, which implies there is no registered media button event receiver. + * @param clearing true if the new client generation value maps to a remote control update + * where the display should be cleared. + */ + private void setNewRcClient_syncRcsCurrc(int newClientGeneration, + PendingIntent newMediaIntent, boolean clearing) { + // send the new valid client generation ID to all displays + setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing); + // send the new valid client generation ID to all clients + setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration); + } + + /** + * Called when processing MSG_RCDISPLAY_CLEAR event + */ + private void onRcDisplayClear() { + if (DEBUG_RC) Log.i(TAG, "Clear remote control display"); + + synchronized(mPRStack) { + synchronized(mCurrentRcLock) { + mCurrentRcClientGen++; + // synchronously update the displays and clients with the new client generation + setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, + null /*newMediaIntent*/, true /*clearing*/); + } + } + } + + /** + * Called when processing MSG_RCDISPLAY_UPDATE event + */ + private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) { + synchronized(mPRStack) { + synchronized(mCurrentRcLock) { + if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) { + if (DEBUG_RC) Log.i(TAG, "Display/update remote control "); + + mCurrentRcClientGen++; + // synchronously update the displays and clients with + // the new client generation + setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, + prse.getMediaButtonIntent() /*newMediaIntent*/, + false /*clearing*/); + + // tell the current client that it needs to send info + try { + //TODO change name to informationRequestForAllDisplays() + mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: "+e); + mCurrentRcClient = null; + } + } else { + // the remote control display owner has changed between the + // the message to update the display was sent, and the time it + // gets to be processed (now) + } + } + } + } + + /** + * Called when processing MSG_RCDISPLAY_INIT_INFO event + * Causes the current RemoteControlClient to send its info (metadata, playstate...) to + * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE. + */ + private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) { + synchronized(mPRStack) { + synchronized(mCurrentRcLock) { + if (mCurrentRcClient != null) { + if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); } + try { + // synchronously update the new RCD with the current client generation + // and matching PendingIntent + newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent, + false); + + // tell the current RCC that it needs to send info, but only to the new RCD + try { + mCurrentRcClient.informationRequestForDisplay(newRcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Current valid remote client is dead: ", e); + mCurrentRcClient = null; + } + } catch (RemoteException e) { + Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e); + } + } + } + } + } + + /** + * Helper function: + * Called synchronized on mPRStack + */ + private void clearRemoteControlDisplay_syncPrs() { + synchronized(mCurrentRcLock) { + mCurrentRcClient = null; + } + // will cause onRcDisplayClear() to be called in AudioService's handler thread + mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) ); + } + + /** + * Helper function for code readability: only to be called from + * checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for + * this method. + * Preconditions: + * - called synchronized on mPRStack + * - mPRStack.isEmpty() is false + */ + private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) { + PlayerRecord prse = mPRStack.peek(); + int infoFlagsAboutToBeUsed = infoChangedFlags; + // this is where we enforce opt-in for information display on the remote controls + // with the new AudioManager.registerRemoteControlClient() API + if (prse.getRcc() == null) { + //Log.w(TAG, "Can't update remote control display with null remote control client"); + clearRemoteControlDisplay_syncPrs(); + return; + } + synchronized(mCurrentRcLock) { + if (!prse.getRcc().equals(mCurrentRcClient)) { + // new RC client, assume every type of information shall be queried + infoFlagsAboutToBeUsed = RC_INFO_ALL; + } + mCurrentRcClient = prse.getRcc(); + mCurrentRcClientIntent = prse.getMediaButtonIntent(); + } + // will cause onRcDisplayUpdate() to be called in AudioService's handler thread + mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE, + infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) ); + } + + /** + * Helper function: + * Called synchronized on mPRStack + * Check whether the remote control display should be updated, triggers the update if required + * @param infoChangedFlags the flags corresponding to the remote control client information + * that has changed, if applicable (checking for the update conditions might trigger a + * clear, rather than an update event). + */ + private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) { + // determine whether the remote control display should be refreshed + // if the player record stack is empty, there is nothing to display, so clear the RC display + if (mPRStack.isEmpty()) { + clearRemoteControlDisplay_syncPrs(); + return; + } + + // this is where more rules for refresh go + + // refresh conditions were verified: update the remote controls + // ok to call: synchronized on mPRStack, mPRStack is not empty + updateRemoteControlDisplay_syncPrs(infoChangedFlags); + } + + /** + * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) + * precondition: mediaIntent != null + */ + protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, + IBinder token) { + Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); + + synchronized(mPRStack) { + if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) { + // new RC client, assume every type of information shall be queried + checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); + } + } + } + + /** + * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) + * precondition: mediaIntent != null, eventReceiver != null + */ + protected void unregisterMediaButtonIntent(PendingIntent mediaIntent) + { + Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); + + synchronized(mPRStack) { + boolean topOfStackWillChange = isCurrentRcController(mediaIntent); + removeMediaButtonReceiver_syncPrs(mediaIntent); + if (topOfStackWillChange) { + // current RC client will change, assume every type of info needs to be queried + checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); + } + } + } + + protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) { + mEventHandler.sendMessage( + mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0, + mediaIntent)); + } + + /** + * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c) + * precondition: c != null + */ + protected void registerMediaButtonEventReceiverForCalls(ComponentName c) { + if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") + != PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Invalid permissions to register media button receiver for calls"); + return; + } + synchronized(mPRStack) { + mMediaReceiverForCalls = c; + } + } + + /** + * see AudioManager.unregisterMediaButtonEventReceiverForCalls() + */ + protected void unregisterMediaButtonEventReceiverForCalls() { + if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") + != PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Invalid permissions to unregister media button receiver for calls"); + return; + } + synchronized(mPRStack) { + mMediaReceiverForCalls = null; + } + } + + /** + * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) + * @return the unique ID of the PlayerRecord associated with the RemoteControlClient + * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient + * without modifying the RC stack, but while still causing the display to refresh (will + * become blank as a result of this) + */ + protected int registerRemoteControlClient(PendingIntent mediaIntent, + IRemoteControlClient rcClient, String callingPackageName) { + if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + synchronized(mPRStack) { + // store the new display information + try { + for (int index = mPRStack.size()-1; index >= 0; index--) { + final PlayerRecord prse = mPRStack.elementAt(index); + if(prse.hasMatchingMediaButtonIntent(mediaIntent)) { + prse.resetControllerInfoForRcc(rcClient, callingPackageName, + Binder.getCallingUid()); + + if (rcClient == null) { + break; + } + + rccId = prse.getRccId(); + + // there is a new (non-null) client: + // give the new client the displays (if any) + if (mRcDisplays.size() > 0) { + plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc()); + } + break; + } + }//for + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + + // if the eventReceiver is at the top of the stack + // then check for potential refresh of the remote controls + if (isCurrentRcController(mediaIntent)) { + checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); + } + }//synchronized(mPRStack) + return rccId; + } + + /** + * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...) + * rcClient is guaranteed non-null + */ + protected void unregisterRemoteControlClient(PendingIntent mediaIntent, + IRemoteControlClient rcClient) { + if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient); + synchronized(mPRStack) { + boolean topRccChange = false; + try { + for (int index = mPRStack.size()-1; index >= 0; index--) { + final PlayerRecord prse = mPRStack.elementAt(index); + if ((prse.hasMatchingMediaButtonIntent(mediaIntent)) + && rcClient.equals(prse.getRcc())) { + // we found the IRemoteControlClient to unregister + prse.resetControllerInfoForNoRcc(); + topRccChange = (index == mPRStack.size()-1); + // there can only be one matching RCC in the RC stack, we're done + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + if (topRccChange) { + // no more RCC for the RCD, check for potential refresh of the remote controls + checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); + } + } + } + + + /** + * A class to encapsulate all the information about a remote control display. + * After instanciation, init() must always be called before the object is added in the list + * of displays. + * Before being removed from the list of displays, release() must always be called (otherwise + * it will leak death handlers). + */ + private class DisplayInfoForServer implements IBinder.DeathRecipient { + /** may never be null */ + private final IRemoteControlDisplay mRcDisplay; + private final IBinder mRcDisplayBinder; + private int mArtworkExpectedWidth = -1; + private int mArtworkExpectedHeight = -1; + private boolean mWantsPositionSync = false; + private ComponentName mClientNotifListComp; + private boolean mEnabled = true; + + public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) { + if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h); + mRcDisplay = rcd; + mRcDisplayBinder = rcd.asBinder(); + mArtworkExpectedWidth = w; + mArtworkExpectedHeight = h; + } + + public boolean init() { + try { + mRcDisplayBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + // remote control display is DOA, disqualify it + Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder); + return false; + } + return true; + } + + public void release() { + try { + mRcDisplayBinder.unlinkToDeath(this, 0); + } catch (java.util.NoSuchElementException e) { + // not much we can do here, the display should have been unregistered anyway + Log.e(TAG, "Error in DisplaInfoForServer.relase()", e); + } + } + + public void binderDied() { + synchronized(mPRStack) { + Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died"); + // remove the display from the list + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + if (di.mRcDisplay == mRcDisplay) { + if (DEBUG_RC) Log.w(TAG, " RCD removed from list"); + displayIterator.remove(); + return; + } + } + } + } + } + + /** + * The remote control displays. + * Access synchronized on mPRStack + */ + private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1); + + /** + * Plug each registered display into the specified client + * @param rcc, guaranteed non null + */ + private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + try { + rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth, + di.mArtworkExpectedHeight); + if (di.mWantsPositionSync) { + rcc.setWantsSyncForDisplay(di.mRcDisplay, true); + } + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); + } + } + } + + private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd, + boolean enabled) { + // let all the remote control clients know whether the given display is enabled + // (so the remote control stack traversal order doesn't matter). + final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); + while(stackIterator.hasNext()) { + PlayerRecord prse = stackIterator.next(); + if(prse.getRcc() != null) { + try { + prse.getRcc().enableRemoteControlDisplay(rcd, enabled); + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to client: ", e); + } + } + } + } + + /** + * Is the remote control display interface already registered + * @param rcd + * @return true if the IRemoteControlDisplay is already in the list of displays + */ + private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + return true; + } + } + return false; + } + + /** + * Register an IRemoteControlDisplay. + * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient + * at the top of the stack to update the new display with its information. + * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int) + * @param rcd the IRemoteControlDisplay to register. No effect if null. + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param listenerComp the component for the listener interface, may be null if it's not needed + * to verify it belongs to one of the enabled notification listeners + */ + private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h, + ComponentName listenerComp) { + if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")"); + synchronized(mAudioFocusLock) { + synchronized(mPRStack) { + if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) { + return; + } + DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h); + di.mEnabled = true; + di.mClientNotifListComp = listenerComp; + if (!di.init()) { + if (DEBUG_RC) Log.e(TAG, " error registering RCD"); + return; + } + // add RCD to list of displays + mRcDisplays.add(di); + + // let all the remote control clients know there is a new display (so the remote + // control stack traversal order doesn't matter). + Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); + while(stackIterator.hasNext()) { + PlayerRecord prse = stackIterator.next(); + if(prse.getRcc() != null) { + try { + prse.getRcc().plugRemoteControlDisplay(rcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Error connecting RCD to client: ", e); + } + } + } + + // we have a new display, of which all the clients are now aware: have it be + // initialized wih the current gen ID and the current client info, do not + // reset the information for the other (existing) displays + sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, + w /*arg1*/, h /*arg2*/, + rcd /*obj*/, 0/*delay*/); + } + } + } + + /** + * Unregister an IRemoteControlDisplay. + * No effect if the IRemoteControlDisplay hasn't been successfully registered. + * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay) + * @param rcd the IRemoteControlDisplay to unregister. No effect if null. + */ + protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { + if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")"); + synchronized(mPRStack) { + if (rcd == null) { + return; + } + + boolean displayWasPluggedIn = false; + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext() && !displayWasPluggedIn) { + final DisplayInfoForServer di = displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + displayWasPluggedIn = true; + di.release(); + displayIterator.remove(); + } + } + + if (displayWasPluggedIn) { + // disconnect this remote control display from all the clients, so the remote + // control stack traversal order doesn't matter + final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); + while(stackIterator.hasNext()) { + final PlayerRecord prse = stackIterator.next(); + if(prse.getRcc() != null) { + try { + prse.getRcc().unplugRemoteControlDisplay(rcd); + } catch (RemoteException e) { + Log.e(TAG, "Error disconnecting remote control display to client: ", e); + } + } + } + } else { + if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD"); + } + } + } + + /** + * Update the size of the artwork used by an IRemoteControlDisplay. + * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int) + * @param rcd the IRemoteControlDisplay with the new artwork size requirement + * @param w the maximum width of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + * @param h the maximum height of the expected bitmap. Negative or zero values indicate this + * display doesn't need to receive artwork. + */ + protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { + synchronized(mPRStack) { + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + boolean artworkSizeUpdate = false; + while (displayIterator.hasNext() && !artworkSizeUpdate) { + final DisplayInfoForServer di = displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { + di.mArtworkExpectedWidth = w; + di.mArtworkExpectedHeight = h; + artworkSizeUpdate = true; + } + } + } + if (artworkSizeUpdate) { + // RCD is currently plugged in and its artwork size has changed, notify all RCCs, + // stack traversal order doesn't matter + final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); + while(stackIterator.hasNext()) { + final PlayerRecord prse = stackIterator.next(); + if(prse.getRcc() != null) { + try { + prse.getRcc().setBitmapSizeForDisplay(rcd, w, h); + } catch (RemoteException e) { + Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e); + } + } + } + } + } + } + + /** + * Controls whether a remote control display needs periodic checks of the RemoteControlClient + * playback position to verify that the estimated position has not drifted from the actual + * position. By default the check is not performed. + * The IRemoteControlDisplay must have been previously registered for this to have any effect. + * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled + * or disabled. Not null. + * @param wantsSync if true, RemoteControlClient instances which expose their playback position + * to the framework will regularly compare the estimated playback position with the actual + * position, and will update the IRemoteControlDisplay implementation whenever a drift is + * detected. + */ + protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, + boolean wantsSync) { + synchronized(mPRStack) { + boolean rcdRegistered = false; + // store the information about this display + // (display stack traversal order doesn't matter). + final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); + while (displayIterator.hasNext()) { + final DisplayInfoForServer di = displayIterator.next(); + if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { + di.mWantsPositionSync = wantsSync; + rcdRegistered = true; + break; + } + } + if (!rcdRegistered) { + return; + } + // notify all current RemoteControlClients + // (stack traversal order doesn't matter as we notify all RCCs) + final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); + while (stackIterator.hasNext()) { + final PlayerRecord prse = stackIterator.next(); + if (prse.getRcc() != null) { + try { + prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync); + } catch (RemoteException e) { + Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e); + } + } + } + } + } + + // handler for MSG_RCC_NEW_VOLUME_OBS + private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { + synchronized(mPRStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mPRStack.size()-1; index >= 0; index--) { + final PlayerRecord prse = mPRStack.elementAt(index); + if (prse.getRccId() == rccId) { + prse.mRemoteVolumeObs = rvo; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + } + + /** + * Checks if a remote client is active on the supplied stream type. Update the remote stream + * volume state if found and playing + * @param streamType + * @return false if no remote playing is currently playing + */ + protected boolean checkUpdateRemoteStateIfActive(int streamType) { + synchronized(mPRStack) { + // iterating from top of stack as active playback is more likely on entries at the top + try { + for (int index = mPRStack.size()-1; index >= 0; index--) { + final PlayerRecord prse = mPRStack.elementAt(index); + if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) + && isPlaystateActive(prse.mPlaybackState.mState) + && (prse.mPlaybackStream == streamType)) { + if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType + + ", vol =" + prse.mPlaybackVolume); + synchronized (mMainRemote) { + mMainRemote.mRccId = prse.getRccId(); + mMainRemote.mVolume = prse.mPlaybackVolume; + mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax; + mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling; + mMainRemoteIsActive = true; + } + return true; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); + } + } + synchronized (mMainRemote) { + mMainRemoteIsActive = false; + } + return false; + } + + /** + * Returns true if the given playback state is considered "active", i.e. it describes a state + * where playback is happening, or about to + * @param playState the playback state to evaluate + * @return true if active, false otherwise (inactive or unknown) + */ + protected static boolean isPlaystateActive(int playState) { + switch (playState) { + case RemoteControlClient.PLAYSTATE_PLAYING: + case RemoteControlClient.PLAYSTATE_BUFFERING: + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + case RemoteControlClient.PLAYSTATE_REWINDING: + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + return true; + default: + return false; + } + } + + private void sendVolumeUpdateToRemote(int rccId, int direction) { + if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); } + if (direction == 0) { + // only handling discrete events + return; + } + IRemoteVolumeObserver rvo = null; + synchronized (mPRStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mPRStack.size()-1; index >= 0; index--) { + final PlayerRecord prse = mPRStack.elementAt(index); + //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? + if (prse.getRccId() == rccId) { + rvo = prse.mRemoteVolumeObs; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + if (rvo != null) { + try { + rvo.dispatchRemoteVolumeUpdate(direction, -1); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching relative volume update", e); + } + } + } + + protected int getRemoteStreamMaxVolume() { + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return 0; + } + return mMainRemote.mVolumeMax; + } + } + + protected int getRemoteStreamVolume() { + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return 0; + } + return mMainRemote.mVolume; + } + } + + protected void setRemoteStreamVolume(int vol) { + if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); } + int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; + synchronized (mMainRemote) { + if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { + return; + } + rccId = mMainRemote.mRccId; + } + IRemoteVolumeObserver rvo = null; + synchronized (mPRStack) { + // The stack traversal order doesn't matter because there is only one stack entry + // with this RCC ID, but the matching ID is more likely at the top of the stack, so + // start iterating from the top. + try { + for (int index = mPRStack.size()-1; index >= 0; index--) { + final PlayerRecord prse = mPRStack.elementAt(index); + //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? + if (prse.getRccId() == rccId) { + rvo = prse.mRemoteVolumeObs; + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + // not expected to happen, indicates improper concurrent modification + Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); + } + } + if (rvo != null) { + try { + rvo.dispatchRemoteVolumeUpdate(0, vol); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching absolute volume update", e); + } + } + } + + /** + * Call to make AudioService reevaluate whether it's in a mode where remote players should + * have their volume controlled. In this implementation this is only to reset whether + * VolumePanel should display remote volumes + */ + protected void postReevaluateRemote() { + sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0); + } + + private void onReevaluateRemote() { + // TODO This was used to notify VolumePanel if there was remote playback + // in the stack. This is now in MediaSessionService. More code should be + // removed. + } + +} diff --git a/services/core/java/com/android/server/audio/PlayerRecord.java b/services/core/java/com/android/server/audio/PlayerRecord.java new file mode 100644 index 0000000..e98f12e --- /dev/null +++ b/services/core/java/com/android/server/audio/PlayerRecord.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2014 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.audio; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.media.AudioManager; +import android.media.IRemoteControlClient; +import android.media.IRemoteVolumeObserver; +import android.media.RemoteControlClient; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.util.Log; + +import java.io.PrintWriter; + +/** + * @hide + * Class to handle all the information about a media player, encapsulating information + * about its use RemoteControlClient, playback type and volume... The lifecycle of each + * instance is managed by android.media.MediaFocusControl, from its addition to the player stack + * stack to its release. + */ +class PlayerRecord implements DeathRecipient { + + // on purpose not using this classe's name, as it will only be used from MediaFocusControl + private static final String TAG = "MediaFocusControl"; + private static final boolean DEBUG = false; + + /** + * A global counter for RemoteControlClient identifiers + */ + private static int sLastRccId = 0; + + public static MediaFocusControl sController; + + /** + * The target for the ACTION_MEDIA_BUTTON events. + * Always non null. //FIXME verify + */ + final private PendingIntent mMediaIntent; + /** + * The registered media button event receiver. + */ + final private ComponentName mReceiverComponent; + + private int mRccId = -1; + + /** + * A non-null token implies this record tracks a "live" player whose death is being monitored. + */ + private IBinder mToken; + private String mCallingPackageName; + private int mCallingUid; + /** + * Provides access to the information to display on the remote control. + * May be null (when a media button event receiver is registered, + * but no remote control client has been registered) */ + private IRemoteControlClient mRcClient; + private RcClientDeathHandler mRcClientDeathHandler; + /** + * Information only used for non-local playback + */ + //FIXME private? + public int mPlaybackType; + public int mPlaybackVolume; + public int mPlaybackVolumeMax; + public int mPlaybackVolumeHandling; + public int mPlaybackStream; + public RccPlaybackState mPlaybackState; + public IRemoteVolumeObserver mRemoteVolumeObs; + + + protected static class RccPlaybackState { + public int mState; + public long mPositionMs; + public float mSpeed; + + public RccPlaybackState(int state, long positionMs, float speed) { + mState = state; + mPositionMs = positionMs; + mSpeed = speed; + } + + public void reset() { + mState = RemoteControlClient.PLAYSTATE_STOPPED; + mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; + mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; + } + + @Override + public String toString() { + return stateToString() + ", " + posToString() + ", " + mSpeed + "X"; + } + + private String posToString() { + if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) { + return "PLAYBACK_POSITION_INVALID"; + } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { + return "PLAYBACK_POSITION_ALWAYS_UNKNOWN"; + } else { + return (String.valueOf(mPositionMs) + "ms"); + } + } + + private String stateToString() { + switch (mState) { + case RemoteControlClient.PLAYSTATE_NONE: + return "PLAYSTATE_NONE"; + case RemoteControlClient.PLAYSTATE_STOPPED: + return "PLAYSTATE_STOPPED"; + case RemoteControlClient.PLAYSTATE_PAUSED: + return "PLAYSTATE_PAUSED"; + case RemoteControlClient.PLAYSTATE_PLAYING: + return "PLAYSTATE_PLAYING"; + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + return "PLAYSTATE_FAST_FORWARDING"; + case RemoteControlClient.PLAYSTATE_REWINDING: + return "PLAYSTATE_REWINDING"; + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + return "PLAYSTATE_SKIPPING_FORWARDS"; + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + return "PLAYSTATE_SKIPPING_BACKWARDS"; + case RemoteControlClient.PLAYSTATE_BUFFERING: + return "PLAYSTATE_BUFFERING"; + case RemoteControlClient.PLAYSTATE_ERROR: + return "PLAYSTATE_ERROR"; + default: + return "[invalid playstate]"; + } + } + } + + + /** + * Inner class to monitor remote control client deaths, and remove the client for the + * remote control stack if necessary. + */ + private class RcClientDeathHandler implements IBinder.DeathRecipient { + final private IBinder mCb; // To be notified of client's death + //FIXME needed? + final private PendingIntent mMediaIntent; + + RcClientDeathHandler(IBinder cb, PendingIntent pi) { + mCb = cb; + mMediaIntent = pi; + } + + public void binderDied() { + Log.w(TAG, " RemoteControlClient died"); + // remote control client died, make sure the displays don't use it anymore + // by setting its remote control client to null + sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/); + // the dead client was maybe handling remote playback, the controller should reevaluate + sController.postReevaluateRemote(); + } + + public IBinder getBinder() { + return mCb; + } + } + + + protected static class RemotePlaybackState { + int mRccId; + int mVolume; + int mVolumeMax; + int mVolumeHandling; + + protected RemotePlaybackState(int id, int vol, int volMax) { + mRccId = id; + mVolume = vol; + mVolumeMax = volMax; + mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; + } + } + + + void dump(PrintWriter pw, boolean registrationInfo) { + if (registrationInfo) { + pw.println(" pi: " + mMediaIntent + + " -- pack: " + mCallingPackageName + + " -- ercvr: " + mReceiverComponent + + " -- client: " + mRcClient + + " -- uid: " + mCallingUid + + " -- type: " + mPlaybackType + + " state: " + mPlaybackState); + } else { + // emphasis on state + pw.println(" uid: " + mCallingUid + + " -- id: " + mRccId + + " -- type: " + mPlaybackType + + " -- state: " + mPlaybackState + + " -- vol handling: " + mPlaybackVolumeHandling + + " -- vol: " + mPlaybackVolume + + " -- volMax: " + mPlaybackVolumeMax + + " -- volObs: " + mRemoteVolumeObs); + } + } + + + static protected void setMediaFocusControl(MediaFocusControl mfc) { + sController = mfc; + } + + /** precondition: mediaIntent != null */ + protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token) + { + mMediaIntent = mediaIntent; + mReceiverComponent = eventReceiver; + mToken = token; + mCallingUid = -1; + mRcClient = null; + mRccId = ++sLastRccId; + mPlaybackState = new RccPlaybackState( + RemoteControlClient.PLAYSTATE_STOPPED, + RemoteControlClient.PLAYBACK_POSITION_INVALID, + RemoteControlClient.PLAYBACK_SPEED_1X); + + resetPlaybackInfo(); + if (mToken != null) { + try { + mToken.linkToDeath(this, 0); + } catch (RemoteException e) { + sController.unregisterMediaButtonIntentAsync(mMediaIntent); + } + } + } + + //--------------------------------------------- + // Accessors + protected int getRccId() { + return mRccId; + } + + protected IRemoteControlClient getRcc() { + return mRcClient; + } + + protected ComponentName getMediaButtonReceiver() { + return mReceiverComponent; + } + + protected PendingIntent getMediaButtonIntent() { + return mMediaIntent; + } + + protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) { + if (mToken != null) { + return mMediaIntent.equals(pi); + } else { + if (mReceiverComponent != null) { + return mReceiverComponent.equals(pi.getIntent().getComponent()); + } else { + return false; + } + } + } + + protected boolean isPlaybackActive() { + return MediaFocusControl.isPlaystateActive(mPlaybackState.mState); + } + + //--------------------------------------------- + // Modify the records stored in the instance + protected void resetControllerInfoForRcc(IRemoteControlClient rcClient, + String callingPackageName, int uid) { + // already had a remote control client? + if (mRcClientDeathHandler != null) { + // stop monitoring the old client's death + unlinkToRcClientDeath(); + } + // save the new remote control client + mRcClient = rcClient; + mCallingPackageName = callingPackageName; + mCallingUid = uid; + if (rcClient == null) { + // here mcse.mRcClientDeathHandler is null; + resetPlaybackInfo(); + } else { + IBinder b = mRcClient.asBinder(); + RcClientDeathHandler rcdh = + new RcClientDeathHandler(b, mMediaIntent); + try { + b.linkToDeath(rcdh, 0); + } catch (RemoteException e) { + // remote control client is DOA, disqualify it + Log.w(TAG, "registerRemoteControlClient() has a dead client " + b); + mRcClient = null; + } + mRcClientDeathHandler = rcdh; + } + } + + protected void resetControllerInfoForNoRcc() { + // stop monitoring the RCC death + unlinkToRcClientDeath(); + // reset the RCC-related fields + mRcClient = null; + mCallingPackageName = null; + } + + public void resetPlaybackInfo() { + mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL; + mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; + mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; + mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; + mPlaybackStream = AudioManager.STREAM_MUSIC; + mPlaybackState.reset(); + mRemoteVolumeObs = null; + } + + //--------------------------------------------- + public void unlinkToRcClientDeath() { + if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) { + try { + mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0); + mRcClientDeathHandler = null; + } catch (java.util.NoSuchElementException e) { + // not much we can do here + Log.e(TAG, "Error in unlinkToRcClientDeath()", e); + } + } + } + + // FIXME rename to "release"? (as in FocusRequester class) + public void destroy() { + unlinkToRcClientDeath(); + if (mToken != null) { + mToken.unlinkToDeath(this, 0); + mToken = null; + } + } + + @Override + public void binderDied() { + sController.unregisterMediaButtonIntentAsync(mMediaIntent); + } + + @Override + protected void finalize() throws Throwable { + destroy(); // unlink exception handled inside method + super.finalize(); + } +} diff --git a/services/core/java/com/android/server/camera/CameraService.java b/services/core/java/com/android/server/camera/CameraService.java new file mode 100644 index 0000000..f9b17ed --- /dev/null +++ b/services/core/java/com/android/server/camera/CameraService.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015 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.camera; + +import android.content.Context; +import android.hardware.ICameraService; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.server.SystemService; + +/** + * CameraService is the system_server analog to the camera service running in mediaserver. + * + * @hide + */ +public class CameraService extends SystemService { + + /** + * This must match the ICameraService.aidl definition + */ + private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera"; + + // Event arguments to use with the camera service notifySystemEvent call: + public static final int NO_EVENT = 0; // NOOP + public static final int USER_SWITCHED = 1; // User changed, argument is the new user handle + + public CameraService(Context context) { + super(context); + } + + @Override + public void onStart() {} + + @Override + public void onSwitchUser(int userHandle) { + super.onSwitchUser(userHandle); + + /** + * Forward the user switch event to the native camera service running in mediaserver. + */ + IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME); + if (cameraServiceBinder == null) { + return; // Camera service not active, there is no need to evict user clients. + } + ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); + try { + cameraServiceRaw.notifySystemEvent(USER_SWITCHED, userHandle); + } catch (RemoteException e) { + // Do nothing, if camera service is dead, there is no need to evict user clients. + } + } +} diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 3fa21d0..a9eaeee 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -22,16 +22,13 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import java.net.Inet4Address; import android.content.Context; -import android.net.IConnectivityManager; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkAgent; -import android.net.NetworkUtils; import android.net.RouteInfo; import android.os.Handler; import android.os.Message; -import android.os.Messenger; import android.os.INetworkManagementService; import android.os.RemoteException; import android.util.Slog; diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 2d1f939..8a7c902 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -71,7 +71,10 @@ public class NetworkAgentInfo { private static final int UNVALIDATED_SCORE_PENALTY = 40; // Score for explicitly connected network. - private static final int EXPLICITLY_SELECTED_NETWORK_SCORE = 100; + // + // This ensures that a) the explicitly selected network is never trumped by anything else, and + // b) the explicitly selected network is never torn down. + private static final int MAXIMUM_NETWORK_SCORE = 100; // The list of NetworkRequests being satisfied by this Network. public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>(); @@ -123,13 +126,18 @@ public class NetworkAgentInfo { // score. The NetworkScore class would provide a nice place to centralize score constants // so they are not scattered about the transports. - int score = currentScore; + // If this network is explicitly selected and the user has decided to use it even if it's + // unvalidated, give it the maximum score. Also give it the maximum score if it's explicitly + // selected and we're trying to see what its score could be. This ensures that we don't tear + // down an explicitly selected network before the user gets a chance to prefer it when + // a higher-scoring network (e.g., Ethernet) is available. + if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) { + return MAXIMUM_NETWORK_SCORE; + } + int score = currentScore; if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY; if (score < 0) score = 0; - - if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE; - return score; } @@ -156,7 +164,9 @@ public class NetworkAgentInfo { networkCapabilities + "} Score{" + getCurrentScore() + "} " + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + "created{" + created + "} " + - "explicitlySelected{" + networkMisc.explicitlySelected + "} }"; + "explicitlySelected{" + networkMisc.explicitlySelected + "} " + + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + + "}"; } public String name() { diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 76220db..7e20276 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -24,9 +24,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.ProxyInfo; import android.net.TrafficStats; @@ -54,7 +51,6 @@ import android.util.Log; import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import com.android.server.ConnectivityService; import com.android.server.connectivity.NetworkAgentInfo; import java.io.IOException; diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 5ff7022..4c08960 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -40,9 +40,7 @@ import android.os.Binder; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; -import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.Log; @@ -480,7 +478,7 @@ public class Tethering extends BaseNetworkObserver { mTetheredNotification.flags = Notification.FLAG_ONGOING_EVENT; mTetheredNotification.tickerText = title; mTetheredNotification.visibility = Notification.VISIBILITY_PUBLIC; - mTetheredNotification.color = mContext.getResources().getColor( + mTetheredNotification.color = mContext.getColor( com.android.internal.R.color.system_notification_accent_color); mTetheredNotification.setLatestEventInfo(mContext, title, message, pi); mTetheredNotification.category = Notification.CATEGORY_STATUS; diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index d9c96f2..3d478f9 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -76,6 +76,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnInfo; import com.android.internal.net.VpnProfile; import com.android.server.net.BaseNetworkObserver; @@ -731,6 +732,7 @@ public class Vpn { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { mStatusIntent = null; mVpnUsers = null; + mConfig = null; mInterface = null; if (mConnection != null) { mContext.unbindService(mConnection); @@ -816,6 +818,21 @@ public class Vpn { return mConfig.underlyingNetworks; } + /** + * This method should only be called by ConnectivityService. Because it doesn't + * have enough data to fill VpnInfo.primaryUnderlyingIface field. + */ + public synchronized VpnInfo getVpnInfo() { + if (!isRunningLocked()) { + return null; + } + + VpnInfo info = new VpnInfo(); + info.ownerUid = mOwnerUID; + info.vpnIface = mInterface; + return info; + } + public synchronized boolean appliesToUid(int uid) { if (!isRunningLocked()) { return false; @@ -1013,6 +1030,14 @@ public class Vpn { public synchronized LegacyVpnInfo getLegacyVpnInfo() { // Check if the caller is authorized. enforceControlPermission(); + return getLegacyVpnInfoPrivileged(); + } + + /** + * Return the information of the current ongoing legacy VPN. + * Callers are responsible for checking permissions if needed. + */ + public synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() { if (mLegacyVpnRunner == null) return null; final LegacyVpnInfo info = new LegacyVpnInfo(); diff --git a/services/core/java/com/android/server/content/AppIdleMonitor.java b/services/core/java/com/android/server/content/AppIdleMonitor.java new file mode 100644 index 0000000..9598de8 --- /dev/null +++ b/services/core/java/com/android/server/content/AppIdleMonitor.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 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.content; + +import android.app.usage.UsageStatsManagerInternal; +import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.UserHandle; + +import com.android.server.LocalServices; + +/** + * Helper to listen for app idle and charging status changes and restart backed off + * sync operations. + */ +class AppIdleMonitor implements AppIdleStateChangeListener { + + private final SyncManager mSyncManager; + private final UsageStatsManagerInternal mUsageStats; + final BatteryManager mBatteryManager; + /** Is the device currently plugged into power. */ + private boolean mPluggedIn; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onPluggedIn(mBatteryManager.isCharging()); + } + }; + + AppIdleMonitor(SyncManager syncManager, Context context) { + mSyncManager = syncManager; + mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); + mUsageStats.addAppIdleStateChangeListener(this); + mBatteryManager = context.getSystemService(BatteryManager.class); + mPluggedIn = isPowered(); + registerReceivers(context); + } + + private void registerReceivers(Context context) { + // Monitor battery charging state + IntentFilter filter = new IntentFilter(BatteryManager.ACTION_CHARGING); + filter.addAction(BatteryManager.ACTION_DISCHARGING); + context.registerReceiver(mReceiver, filter); + } + + private boolean isPowered() { + return mBatteryManager.isCharging(); + } + + void onPluggedIn(boolean pluggedIn) { + if (mPluggedIn == pluggedIn) { + return; + } + mPluggedIn = pluggedIn; + if (mPluggedIn) { + mSyncManager.onAppNotIdle(null, UserHandle.USER_ALL); + } + } + + boolean isAppIdle(String packageName, int userId) { + return !mPluggedIn && mUsageStats.isAppIdle(packageName, userId); + } + + @Override + public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { + // Don't care if the app is becoming idle + if (idle) return; + mSyncManager.onAppNotIdle(packageName, userId); + } +} diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 165148b..ea461e5 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -25,7 +25,6 @@ import android.content.Context; import android.content.IContentService; import android.content.ISyncStatusObserver; import android.content.PeriodicSync; -import android.content.pm.PackageManager; import android.content.SyncAdapterType; import android.content.SyncInfo; import android.content.SyncRequest; diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 6dcbc42..7cccef2 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -83,6 +83,7 @@ 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.EndPoint; import com.android.server.content.SyncStorageEngine.OnSyncRequestListener; import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -107,7 +108,7 @@ import java.util.Set; * @hide */ public class SyncManager { - private static final String TAG = "SyncManager"; + static final String TAG = "SyncManager"; /** Delay a sync due to local changes this long. In milliseconds */ private static final long LOCAL_SYNC_DELAY; @@ -176,6 +177,7 @@ public class SyncManager { volatile private PowerManager.WakeLock mSyncManagerWakeLock; volatile private boolean mDataConnectionIsConnected = false; volatile private boolean mStorageIsLow = false; + volatile private boolean mDeviceIsIdle = false; private final NotificationManager mNotificationMgr; private AlarmManager mAlarmService = null; @@ -198,6 +200,8 @@ public class SyncManager { protected SyncAdaptersCache mSyncAdapters; + private final AppIdleMonitor mAppIdleMonitor; + private BroadcastReceiver mStorageIntentReceiver = new BroadcastReceiver() { @Override @@ -221,6 +225,20 @@ public class SyncManager { } }; + private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + boolean idle = mPowerManager.isDeviceIdleMode(); + mDeviceIsIdle = idle; + if (idle) { + cancelActiveSync( + SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL, + null /* any sync */); + } else { + sendCheckAlarmsMessage(); + } + } + }; + private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -412,6 +430,8 @@ public class SyncManager { mSyncAlarmIntent = PendingIntent.getBroadcast( mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0); + mAppIdleMonitor = new AppIdleMonitor(this, mContext); + IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); context.registerReceiver(mConnectivityIntentReceiver, intentFilter); @@ -425,6 +445,9 @@ public class SyncManager { intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); context.registerReceiver(mStorageIntentReceiver, intentFilter); + intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); + context.registerReceiver(mDeviceIdleReceiver, intentFilter); + intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); intentFilter.setPriority(100); context.registerReceiver(mShutdownIntentReceiver, intentFilter); @@ -702,6 +725,11 @@ public class SyncManager { } for (AccountAndUser account : accounts) { + // If userId is specified, do not sync accounts of other users + if (userId >= UserHandle.USER_OWNER && account.userId >= UserHandle.USER_OWNER + && userId != account.userId) { + continue; + } // Compile a list of authorities that have sync adapters. // For each authority sync each account that matches a sync adapter. final HashSet<String> syncableAuthorities = new HashSet<String>(); @@ -1146,6 +1174,36 @@ public class SyncManager { } /** + * Clear backoff on operations in the sync queue that match the packageName and userId. + * @param packageName The package that just became active. Can be null to indicate that all + * packages are now considered active due to being plugged in. + * @param userId The user for which the package has become active. Can be USER_ALL if + * the device just plugged in. + */ + void onAppNotIdle(String packageName, int userId) { + synchronized (mSyncQueue) { + // For all sync operations in sync queue, if marked as idle, compare with package name + // and unmark. And clear backoff for the operation. + final Iterator<SyncOperation> operationIterator = + mSyncQueue.getOperations().iterator(); + boolean changed = false; + while (operationIterator.hasNext()) { + final SyncOperation op = operationIterator.next(); + if (op.appIdle + && (packageName == null || getPackageName(op.target).equals(packageName)) + && (userId == UserHandle.USER_ALL || op.target.userId == userId)) { + op.appIdle = false; + clearBackoffSetting(op); + changed = true; + } + } + if (changed) { + sendCheckAlarmsMessage(); + } + } + } + + /** * @hide */ class ActiveSyncContext extends ISyncContext.Stub @@ -1307,6 +1365,7 @@ public class SyncManager { pw.println(); } pw.print("memory low: "); pw.println(mStorageIsLow); + pw.print("device idle: "); pw.println(mDeviceIsIdle); final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts(); @@ -2353,6 +2412,13 @@ public class SyncManager { return Long.MAX_VALUE; } + if (mDeviceIsIdle) { + if (isLoggable) { + Log.v(TAG, "maybeStartNextSync: device idle, skipping"); + } + return Long.MAX_VALUE; + } + // If the accounts aren't known yet then we aren't ready to run. We will be kicked // when the account lookup request does complete. if (mRunningAccounts == INITIAL_ACCOUNTS_ARRAY) { @@ -2416,6 +2482,19 @@ public class SyncManager { } continue; } + String packageName = getPackageName(op.target); + // If app is considered idle, then skip for now and backoff + if (packageName != null + && mAppIdleMonitor.isAppIdle(packageName, op.target.userId)) { + increaseBackoffSetting(op); + op.appIdle = true; + if (isLoggable) { + Log.v(TAG, "Sync backing off idle app " + packageName); + } + continue; + } else { + op.appIdle = false; + } // Add this sync to be run. operations.add(op); } @@ -2979,6 +3058,7 @@ public class SyncManager { // method to be called again if (!mDataConnectionIsConnected) return; if (mStorageIsLow) return; + if (mDeviceIsIdle) return; // When the status bar notification should be raised final long notificationTime = @@ -3114,7 +3194,7 @@ public class SyncManager { new Notification(R.drawable.stat_notify_sync_error, mContext.getString(R.string.contentServiceSync), System.currentTimeMillis()); - notification.color = contextForUser.getResources().getColor( + notification.color = contextForUser.getColor( com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(contextForUser, contextForUser.getString(R.string.contentServiceSyncNotificationTitle), @@ -3162,6 +3242,21 @@ public class SyncManager { } } + String getPackageName(EndPoint endpoint) { + if (endpoint.target_service) { + return endpoint.service.getPackageName(); + } else { + SyncAdapterType syncAdapterType = + SyncAdapterType.newKey(endpoint.provider, endpoint.account.type); + final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; + syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, endpoint.userId); + if (syncAdapterInfo == null) { + return null; + } + return syncAdapterInfo.componentName.getPackageName(); + } + } + private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) { for (ActiveSyncContext sync : mActiveSyncContexts) { if (sync == activeSyncContext) { diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java index 35827cc..10efe81 100644 --- a/services/core/java/com/android/server/content/SyncOperation.java +++ b/services/core/java/com/android/server/content/SyncOperation.java @@ -90,6 +90,9 @@ public class SyncOperation implements Comparable { /** Descriptive string key for this operation */ public String wakeLockName; + /** Whether this sync op was recently skipped due to the app being idle */ + public boolean appIdle; + public SyncOperation(Account account, int userId, int reason, int source, String provider, Bundle extras, long runTimeFromNow, long flexTime, long backoff, long delayUntil, boolean allowParallelSyncs) { diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index 0d5f240..f154c73 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/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.app.backup.BackupManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -670,6 +671,7 @@ public class SyncStorageEngine extends Handler { new Bundle()); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + queueBackup(); } public int getIsSyncable(Account account, int userId, String providerName) { @@ -1035,6 +1037,7 @@ public class SyncStorageEngine extends Handler { } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED); + queueBackup(); } public boolean getMasterSyncAutomatically(int userId) { @@ -2810,4 +2813,12 @@ public class SyncStorageEngine extends Handler { .append(")\n"); } } + + /** + * Let the BackupManager know that account sync settings have changed. This will trigger + * {@link com.android.server.backup.SystemBackupAgent} to run. + */ + public void queueBackup() { + BackupManager.dataChanged("android"); + } } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index d919bf6..93d37f1 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -52,20 +52,9 @@ class AutomaticBrightnessController { // auto-brightness adjustment setting. private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f; - // Light sensor event rate in milliseconds. - private static final int LIGHT_SENSOR_RATE_MILLIS = 1000; - // Period of time in which to consider light samples in milliseconds. private static final int AMBIENT_LIGHT_HORIZON = 10000; - // Stability requirements in milliseconds for accepting a new brightness level. This is used - // for debouncing the light sensor. Different constants are used to debounce the light sensor - // when adapting to brighter or darker environments. This parameter controls how quickly - // brightness changes occur in response to an observed change in light level that exceeds the - // hysteresis threshold. - private static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000; - private static final long DARKENING_LIGHT_DEBOUNCE = 8000; - // Hysteresis constraints for brightening or darkening. // The recent lux must have changed by at least this fraction relative to the // current ambient lux before a change will be considered. @@ -121,6 +110,22 @@ class AutomaticBrightnessController { private final int mScreenBrightnessRangeMaximum; private final float mDozeScaleFactor; + // Light sensor event rate in milliseconds. + private final int mLightSensorRate; + + // Stability requirements in milliseconds for accepting a new brightness level. This is used + // for debouncing the light sensor. Different constants are used to debounce the light sensor + // when adapting to brighter or darker environments. This parameter controls how quickly + // brightness changes occur in response to an observed change in light level that exceeds the + // hysteresis threshold. + private final long mBrighteningLightDebounceConfig; + private final long mDarkeningLightDebounceConfig; + + // If true immediately after the screen is turned on the controller will try to adjust the + // brightness based on the current sensor reads. If false, the controller will collect more data + // and only then decide whether to change brightness. + private final boolean mResetAmbientLuxAfterWarmUpConfig; + // Amount of time to delay auto-brightness after screen on while waiting for // the light sensor to warm-up in milliseconds. // May be 0 if no warm-up is required. @@ -176,7 +181,9 @@ class AutomaticBrightnessController { public AutomaticBrightnessController(Callbacks callbacks, Looper looper, SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime, - int brightnessMin, int brightnessMax, float dozeScaleFactor) { + int brightnessMin, int brightnessMax, float dozeScaleFactor, + int lightSensorRate, long brighteningLightDebounceConfig, + long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig) { mCallbacks = callbacks; mTwilight = LocalServices.getService(TwilightManager.class); mSensorManager = sensorManager; @@ -185,9 +192,13 @@ class AutomaticBrightnessController { mScreenBrightnessRangeMaximum = brightnessMax; mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime; mDozeScaleFactor = dozeScaleFactor; + mLightSensorRate = lightSensorRate; + mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; + mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; + mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; mHandler = new AutomaticBrightnessHandler(looper); - mAmbientLightRingBuffer = new AmbientLightRingBuffer(); + mAmbientLightRingBuffer = new AmbientLightRingBuffer(mLightSensorRate); if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); @@ -226,6 +237,9 @@ class AutomaticBrightnessController { pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); + pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); + pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); + pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); pw.println(); pw.println("Automatic Brightness Controller State:"); @@ -252,13 +266,13 @@ class AutomaticBrightnessController { mLightSensorEnabled = true; mLightSensorEnableTime = SystemClock.uptimeMillis(); mSensorManager.registerListener(mLightSensorListener, mLightSensor, - LIGHT_SENSOR_RATE_MILLIS * 1000, mHandler); + mLightSensorRate * 1000, mHandler); return true; } } else { if (mLightSensorEnabled) { mLightSensorEnabled = false; - mAmbientLuxValid = false; + mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig; mRecentLightSamples = 0; mAmbientLightRingBuffer.clear(); mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX); @@ -347,7 +361,7 @@ class AutomaticBrightnessController { } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } - return earliestValidTime + BRIGHTENING_LIGHT_DEBOUNCE; + return earliestValidTime + mBrighteningLightDebounceConfig; } private long nextAmbientLightDarkeningTransition(long time) { @@ -359,7 +373,7 @@ class AutomaticBrightnessController { } earliestValidTime = mAmbientLightRingBuffer.getTime(i); } - return earliestValidTime + DARKENING_LIGHT_DEBOUNCE; + return earliestValidTime + mDarkeningLightDebounceConfig; } private void updateAmbientLux() { @@ -420,7 +434,7 @@ class AutomaticBrightnessController { // should be enough time to decide whether we should actually transition to the new // weighted ambient lux or not. nextTransitionTime = - nextTransitionTime > time ? nextTransitionTime : time + LIGHT_SENSOR_RATE_MILLIS; + nextTransitionTime > time ? nextTransitionTime : time + mLightSensorRate; if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Scheduling ambient lux update for " + nextTransitionTime + TimeUtils.formatUptime(nextTransitionTime)); @@ -559,8 +573,6 @@ class AutomaticBrightnessController { // Proportional extra capacity of the buffer beyond the expected number of light samples // in the horizon private static final float BUFFER_SLACK = 1.5f; - private static final int DEFAULT_CAPACITY = - (int) Math.ceil(AMBIENT_LIGHT_HORIZON * BUFFER_SLACK / LIGHT_SENSOR_RATE_MILLIS); private float[] mRingLux; private long[] mRingTime; private int mCapacity; @@ -571,8 +583,8 @@ class AutomaticBrightnessController { private int mEnd; private int mCount; - public AmbientLightRingBuffer() { - this(DEFAULT_CAPACITY); + public AmbientLightRingBuffer(long lightSensorRate) { + this((int) Math.ceil(AMBIENT_LIGHT_HORIZON * BUFFER_SLACK / lightSensorRate)); } public AmbientLightRingBuffer(int initialCapacity) { diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 6e61e41..bb4dbc3 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -25,7 +25,6 @@ import java.nio.ByteOrder; import java.nio.FloatBuffer; import android.content.Context; -import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.SurfaceTexture; import android.hardware.display.DisplayManagerInternal; @@ -37,7 +36,6 @@ import android.opengl.EGLDisplay; import android.opengl.EGLSurface; import android.opengl.GLES20; import android.opengl.GLES11Ext; -import android.util.FloatMath; import android.util.Slog; import android.view.DisplayInfo; import android.view.Surface.OutOfResourcesException; @@ -48,7 +46,6 @@ import android.view.SurfaceSession; import libcore.io.Streams; import com.android.server.LocalServices; -import com.android.internal.R; /** * <p> @@ -372,13 +369,13 @@ final class ColorFade { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // Draw the frame. - float one_minus_level = 1 - level; - float cos = FloatMath.cos((float)Math.PI * one_minus_level); - float sign = cos < 0 ? -1 : 1; - float opacity = -FloatMath.pow(one_minus_level, 2) + 1; - float saturation = FloatMath.pow(level, 4); - float scale = (-FloatMath.pow(one_minus_level, 2) + 1) * 0.1f + 0.9f; - float gamma = (0.5f * sign * FloatMath.pow(cos, 2) + 0.5f) * 0.9f + 0.1f; + double one_minus_level = 1 - level; + double cos = Math.cos(Math.PI * one_minus_level); + double sign = cos < 0 ? -1 : 1; + float opacity = (float) -Math.pow(one_minus_level, 2) + 1; + float saturation = (float) Math.pow(level, 4); + float scale = (float) ((-Math.pow(one_minus_level, 2) + 1) * 0.1d + 0.9d); + float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d); drawFaded(opacity, 1.f / gamma, saturation, scale); if (checkGlErrors("drawFrame")) { return false; diff --git a/services/core/java/com/android/server/display/DisplayBlanker.java b/services/core/java/com/android/server/display/DisplayBlanker.java index eb0ae6a..816dc13 100644 --- a/services/core/java/com/android/server/display/DisplayBlanker.java +++ b/services/core/java/com/android/server/display/DisplayBlanker.java @@ -20,5 +20,5 @@ package com.android.server.display; * Interface used to update the actual display state. */ public interface DisplayBlanker { - void requestDisplayState(int state); + void requestDisplayState(int state, int brightness); } diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 61631d4..ee36972 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -47,6 +47,10 @@ abstract class DisplayDevice { // within a transaction from performTraversalInTransactionLocked. private Surface mCurrentSurface; + // DEBUG STATE: Last device info which was written to the log, or null if none. + // Do not use for any other purpose. + DisplayDeviceInfo mDebugLastLoggedDeviceInfo; + public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId) { mDisplayAdapter = displayAdapter; mDisplayToken = displayToken; @@ -118,10 +122,12 @@ abstract class DisplayDevice { /** * Sets the display state, if supported. * + * @param state The new display state. + * @param brightness The new display brightness. * @return A runnable containing work to be deferred until after we have * exited the critical section, or null if none. */ - public Runnable requestDisplayStateLocked(int state) { + public Runnable requestDisplayStateLocked(int state, int brightness) { return null; } diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index d1e73f0..ebf6e4e 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -104,6 +104,16 @@ final class DisplayDeviceInfo { public static final int TOUCH_EXTERNAL = 2; /** + * Diff result: The {@link #state} fields differ. + */ + public static final int DIFF_STATE = 1 << 0; + + /** + * Diff result: Other fields differ. + */ + public static final int DIFF_OTHER = 1 << 1; + + /** * Gets the name of the display device, which may be derived from EDID or * other sources. The name may be localized and displayed to the user. */ @@ -238,26 +248,39 @@ final class DisplayDeviceInfo { } public boolean equals(DisplayDeviceInfo other) { - return other != null - && Objects.equal(name, other.name) - && Objects.equal(uniqueId, other.uniqueId) - && width == other.width - && height == other.height - && refreshRate == other.refreshRate - && Arrays.equals(supportedRefreshRates, other.supportedRefreshRates) - && densityDpi == other.densityDpi - && xDpi == other.xDpi - && yDpi == other.yDpi - && appVsyncOffsetNanos == other.appVsyncOffsetNanos - && presentationDeadlineNanos == other.presentationDeadlineNanos - && flags == other.flags - && touch == other.touch - && rotation == other.rotation - && type == other.type - && Objects.equal(address, other.address) - && state == other.state - && ownerUid == other.ownerUid - && Objects.equal(ownerPackageName, other.ownerPackageName); + return other != null && diff(other) == 0; + } + + /** + * Computes the difference between display device infos. + * Assumes other is not null. + */ + public int diff(DisplayDeviceInfo other) { + int diff = 0; + if (state != other.state) { + diff |= DIFF_STATE; + } + if (!Objects.equal(name, other.name) + || !Objects.equal(uniqueId, other.uniqueId) + || width != other.width + || height != other.height + || refreshRate != other.refreshRate + || !Arrays.equals(supportedRefreshRates, other.supportedRefreshRates) + || densityDpi != other.densityDpi + || xDpi != other.xDpi + || yDpi != other.yDpi + || appVsyncOffsetNanos != other.appVsyncOffsetNanos + || presentationDeadlineNanos != other.presentationDeadlineNanos + || flags != other.flags + || touch != other.touch + || rotation != other.rotation + || type != other.type + || !Objects.equal(address, other.address) + || ownerUid != other.ownerUid + || !Objects.equal(ownerPackageName, other.ownerPackageName)) { + diff |= DIFF_OTHER; + } + return diff; } @Override diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 09dc477..1e87433 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -40,13 +40,14 @@ import android.os.IBinder; import android.os.IBinder.DeathRecipient; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.text.TextUtils; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -180,7 +181,11 @@ public final class DisplayManagerService extends SystemService { // The overall display state, independent of changes that might influence one // display or another in particular. - private int mGlobalDisplayState = Display.STATE_UNKNOWN; + private int mGlobalDisplayState = Display.STATE_ON; + + // The overall display brightness. + // For now, this only applies to the built-in display but we may split it up eventually. + private int mGlobalDisplayBrightness = PowerManager.BRIGHTNESS_DEFAULT; // Set to true when there are pending display changes that have yet to be applied // to the surface flinger state. @@ -227,6 +232,9 @@ public final class DisplayManagerService extends SystemService { mUiHandler = UiThread.getHandler(); mDisplayAdapterListener = new DisplayAdapterListener(); mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); + + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting(); } @Override @@ -323,16 +331,34 @@ public final class DisplayManagerService extends SystemService { } } - private void requestGlobalDisplayStateInternal(int state) { + private void requestGlobalDisplayStateInternal(int state, int brightness) { + if (state == Display.STATE_UNKNOWN) { + state = Display.STATE_ON; + } + if (state == Display.STATE_OFF) { + brightness = PowerManager.BRIGHTNESS_OFF; + } else if (brightness < 0) { + brightness = PowerManager.BRIGHTNESS_DEFAULT; + } else if (brightness > PowerManager.BRIGHTNESS_ON) { + brightness = PowerManager.BRIGHTNESS_ON; + } + synchronized (mTempDisplayStateWorkQueue) { try { // Update the display state within the lock. synchronized (mSyncRoot) { - if (mGlobalDisplayState != state) { - mGlobalDisplayState = state; - updateGlobalDisplayStateLocked(mTempDisplayStateWorkQueue); - scheduleTraversalLocked(false); + if (mGlobalDisplayState == state + && mGlobalDisplayBrightness == brightness) { + return; // no change } + + Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestGlobalDisplayState(" + + Display.stateToString(state) + + ", brightness=" + brightness + ")"); + mGlobalDisplayState = state; + mGlobalDisplayBrightness = brightness; + updateGlobalDisplayStateLocked(mTempDisplayStateWorkQueue); + scheduleTraversalLocked(false); } // Setting the display power state can take hundreds of milliseconds @@ -342,6 +368,7 @@ public final class DisplayManagerService extends SystemService { for (int i = 0; i < mTempDisplayStateWorkQueue.size(); i++) { mTempDisplayStateWorkQueue.get(i).run(); } + Trace.traceEnd(Trace.TRACE_TAG_POWER); } finally { mTempDisplayStateWorkQueue.clear(); } @@ -641,13 +668,14 @@ public final class DisplayManagerService extends SystemService { } private void handleDisplayDeviceAddedLocked(DisplayDevice device) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if (mDisplayDevices.contains(device)) { - Slog.w(TAG, "Attempted to add already added display device: " - + device.getDisplayDeviceInfoLocked()); + Slog.w(TAG, "Attempted to add already added display device: " + info); return; } - Slog.i(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked()); + Slog.i(TAG, "Display device added: " + info); + device.mDebugLastLoggedDeviceInfo = info; mDisplayDevices.add(device); addLogicalDisplayLocked(device); @@ -660,13 +688,20 @@ public final class DisplayManagerService extends SystemService { private void handleDisplayDeviceChanged(DisplayDevice device) { synchronized (mSyncRoot) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if (!mDisplayDevices.contains(device)) { - Slog.w(TAG, "Attempted to change non-existent display device: " - + device.getDisplayDeviceInfoLocked()); + Slog.w(TAG, "Attempted to change non-existent display device: " + info); return; } - Slog.i(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked()); + int diff = device.mDebugLastLoggedDeviceInfo.diff(info); + if (diff == DisplayDeviceInfo.DIFF_STATE) { + Slog.i(TAG, "Display device changed state: \"" + info.name + + "\", " + Display.stateToString(info.state)); + } else if (diff != 0) { + Slog.i(TAG, "Display device changed: " + info); + } + device.mDebugLastLoggedDeviceInfo = info; device.applyPendingDisplayDeviceInfoChangesLocked(); if (updateLogicalDisplaysLocked()) { @@ -681,13 +716,14 @@ public final class DisplayManagerService extends SystemService { } } private void handleDisplayDeviceRemovedLocked(DisplayDevice device) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if (!mDisplayDevices.remove(device)) { - Slog.w(TAG, "Attempted to remove non-existent display device: " - + device.getDisplayDeviceInfoLocked()); + Slog.w(TAG, "Attempted to remove non-existent display device: " + info); return; } - Slog.i(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked()); + Slog.i(TAG, "Display device removed: " + info); + device.mDebugLastLoggedDeviceInfo = info; updateLogicalDisplaysLocked(); scheduleTraversalLocked(false); @@ -709,7 +745,7 @@ public final class DisplayManagerService extends SystemService { // by the display power controller (if known). DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { - return device.requestDisplayStateLocked(mGlobalDisplayState); + return device.requestDisplayStateLocked(mGlobalDisplayState, mGlobalDisplayBrightness); } return null; } @@ -832,6 +868,24 @@ public final class DisplayManagerService extends SystemService { } } + private void setDisplayOffsetsInternal(int displayId, int x, int y) { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display == null) { + return; + } + if (display.getDisplayOffsetXLocked() != x + || display.getDisplayOffsetYLocked() != y) { + if (DEBUG) { + Slog.d(TAG, "Display " + displayId + " burn-in offset set to (" + + x + ", " + y + ")"); + } + display.setDisplayOffsetsLocked(x, y); + scheduleTraversalLocked(false); + } + } + } + private void clearViewportsLocked() { mDefaultViewport.valid = false; mExternalTouchViewport.valid = false; @@ -1445,16 +1499,16 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { DisplayBlanker blanker = new DisplayBlanker() { @Override - public void requestDisplayState(int state) { + public void requestDisplayState(int state, int brightness) { // The order of operations is important for legacy reasons. if (state == Display.STATE_OFF) { - requestGlobalDisplayStateInternal(state); + requestGlobalDisplayStateInternal(state, brightness); } callbacks.onDisplayStateChange(state); if (state != Display.STATE_OFF) { - requestGlobalDisplayStateInternal(state); + requestGlobalDisplayStateInternal(state, brightness); } } }; @@ -1513,5 +1567,10 @@ public final class DisplayManagerService extends SystemService { float requestedRefreshRate, boolean inTraversal) { setDisplayPropertiesInternal(displayId, hasContent, requestedRefreshRate, inTraversal); } + + @Override + public void setDisplayOffsets(int displayId, int x, int y) { + setDisplayOffsetsInternal(displayId, x, y); + } } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 78610ff..f74601e 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -19,7 +19,6 @@ package com.android.server.display; import com.android.internal.app.IBatteryStats; import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; -import com.android.server.lights.LightsManager; import android.animation.Animator; import android.animation.ObjectAnimator; @@ -122,9 +121,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Battery stats. private final IBatteryStats mBatteryStats; - // The lights service. - private final LightsManager mLights; - // The sensor manager. private final SensorManager mSensorManager; @@ -260,7 +256,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mCallbacks = callbacks; mBatteryStats = BatteryStatsService.getService(); - mLights = LocalServices.getService(LightsManager.class); mSensorManager = sensorManager; mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class); mBlanker = blanker; @@ -302,6 +297,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean( com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing); + int lightSensorRate = resources.getInteger( + com.android.internal.R.integer.config_autoBrightnessLightSensorRate); + long brighteningLightDebounce = resources.getInteger( + com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce); + long darkeningLightDebounce = resources.getInteger( + com.android.internal.R.integer.config_autoBrightnessDarkeningLightDebounce); + boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean( + com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); + if (mUseSoftwareAutoBrightnessConfig) { int[] lux = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels); @@ -336,7 +340,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController = new AutomaticBrightnessController(this, handler.getLooper(), sensorManager, screenAutoBrightnessSpline, lightSensorWarmUpTimeConfig, screenBrightnessRangeMinimum, - mScreenBrightnessRangeMaximum, dozeScaleFactor); + mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, + brighteningLightDebounce, darkeningLightDebounce, + autoBrightnessResetAmbientLuxAfterWarmUp); } } @@ -432,7 +438,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Initialize the power state object for the default display. // In the future, we might manage multiple displays independently. mPowerState = new DisplayPowerState(mBlanker, - mLights.getLight(LightsManager.LIGHT_ID_BACKLIGHT), new ColorFade(Display.DEFAULT_DISPLAY)); mColorFadeOnAnimator = ObjectAnimator.ofFloat( diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index fc068af..f53ccc9 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -16,14 +16,10 @@ package com.android.server.display; -import com.android.server.lights.Light; - import android.content.Context; -import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; -import android.os.Trace; import android.util.FloatProperty; import android.util.IntProperty; import android.util.Slog; @@ -57,7 +53,6 @@ final class DisplayPowerState { private final Handler mHandler; private final Choreographer mChoreographer; private final DisplayBlanker mBlanker; - private final Light mBacklight; private final ColorFade mColorFade; private final PhotonicModulator mPhotonicModulator; @@ -73,12 +68,11 @@ final class DisplayPowerState { private Runnable mCleanListener; - public DisplayPowerState(DisplayBlanker blanker, Light backlight, ColorFade electronBeam) { + public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade) { mHandler = new Handler(true /*async*/); mChoreographer = Choreographer.getInstance(); mBlanker = blanker; - mBacklight = backlight; - mColorFade = electronBeam; + mColorFade = colorFade; mPhotonicModulator = new PhotonicModulator(); mPhotonicModulator.start(); @@ -349,6 +343,10 @@ final class DisplayPowerState { private int mActualBacklight = INITIAL_BACKLIGHT; private boolean mChangeInProgress; + public PhotonicModulator() { + super("PhotonicModulator"); + } + public boolean setState(int state, int backlight) { synchronized (mLock) { if (state != mPendingState || backlight != mPendingBacklight) { @@ -412,35 +410,7 @@ final class DisplayPowerState { Slog.d(TAG, "Updating screen state: state=" + Display.stateToString(state) + ", backlight=" + backlight); } - boolean suspending = Display.isSuspendedState(state); - if (stateChanged && !suspending) { - requestDisplayState(state); - } - if (backlightChanged) { - setBrightness(backlight); - } - if (stateChanged && suspending) { - requestDisplayState(state); - } - } - } - - private void requestDisplayState(int state) { - Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestDisplayState(" - + Display.stateToString(state) + ")"); - try { - mBlanker.requestDisplayState(state); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_POWER); - } - } - - private void setBrightness(int backlight) { - Trace.traceBegin(Trace.TRACE_TAG_POWER, "setBrightness(" + backlight + ")"); - try { - mBacklight.setBrightness(backlight); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_POWER); + mBlanker.requestDisplayState(state, backlight); } } } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 5ebe64d..e87f265 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -16,10 +16,15 @@ package com.android.server.display; +import com.android.server.LocalServices; +import com.android.server.lights.Light; +import com.android.server.lights.LightsManager; + import android.content.Context; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.PowerManager; import android.os.SystemProperties; import android.os.Trace; import android.util.Slog; @@ -40,6 +45,7 @@ import java.util.Arrays; */ final class LocalDisplayAdapter extends DisplayAdapter { private static final String TAG = "LocalDisplayAdapter"; + private static final boolean DEBUG = false; private static final String UNIQUE_ID_PREFIX = "local:"; @@ -132,14 +138,17 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final int mBuiltInDisplayId; private final SurfaceControl.PhysicalDisplayInfo mPhys; private final int mDefaultPhysicalDisplayInfo; + private final Light mBacklight; private DisplayDeviceInfo mInfo; private boolean mHavePendingChanges; private int mState = Display.STATE_UNKNOWN; + private int mBrightness = PowerManager.BRIGHTNESS_DEFAULT; private float[] mSupportedRefreshRates; private int[] mRefreshRateConfigIndices; private float mLastRequestedRefreshRate; + public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId, SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) { super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + builtInDisplayId); @@ -148,6 +157,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { physicalDisplayInfos[activeDisplayInfo]); mDefaultPhysicalDisplayInfo = activeDisplayInfo; updateSupportedRefreshRatesLocked(physicalDisplayInfos, mPhys); + + if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) { + LightsManager lights = LocalServices.getService(LightsManager.class); + mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT); + } else { + mBacklight = null; + } } public boolean updatePhysicalDisplayInfoLocked( @@ -225,28 +241,91 @@ final class LocalDisplayAdapter extends DisplayAdapter { } @Override - public Runnable requestDisplayStateLocked(final int state) { - if (mState != state) { + public Runnable requestDisplayStateLocked(final int state, final int brightness) { + // Assume that the brightness is off if the display is being turned off. + assert state != Display.STATE_OFF || brightness == PowerManager.BRIGHTNESS_OFF; + + final boolean stateChanged = (mState != state); + final boolean brightnessChanged = (mBrightness != brightness) && mBacklight != null; + if (stateChanged || brightnessChanged) { final int displayId = mBuiltInDisplayId; final IBinder token = getDisplayTokenLocked(); - final int mode = getPowerModeForState(state); - mState = state; - updateDeviceInfoLocked(); + final int oldState = mState; + + if (stateChanged) { + mState = state; + updateDeviceInfoLocked(); + } - // Defer actually setting the display power mode until we have exited + if (brightnessChanged) { + mBrightness = brightness; + } + + // Defer actually setting the display state until after we have exited // the critical section since it can take hundreds of milliseconds // to complete. return new Runnable() { @Override public void run() { - Trace.traceBegin(Trace.TRACE_TAG_POWER, "requestDisplayState(" - + Display.stateToString(state) + ", id=" + displayId + ")"); + // Exit a suspended state before making any changes. + int currentState = oldState; + if (Display.isSuspendedState(oldState) + || oldState == Display.STATE_UNKNOWN) { + if (!Display.isSuspendedState(state)) { + setDisplayState(state); + currentState = state; + } else if (state == Display.STATE_DOZE_SUSPEND + || oldState == Display.STATE_DOZE_SUSPEND) { + setDisplayState(Display.STATE_DOZE); + currentState = Display.STATE_DOZE; + } else { + return; // old state and new state is off + } + } + + // Apply brightness changes given that we are in a non-suspended state. + if (brightnessChanged) { + setDisplayBrightness(brightness); + } + + // Enter the final desired state, possibly suspended. + if (state != currentState) { + setDisplayState(state); + } + } + + private void setDisplayState(int state) { + if (DEBUG) { + Slog.d(TAG, "setDisplayState(" + + "id=" + displayId + + ", state=" + Display.stateToString(state) + ")"); + } + + Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState(" + + "id=" + displayId + + ", state=" + Display.stateToString(state) + ")"); try { + final int mode = getPowerModeForState(state); SurfaceControl.setDisplayPowerMode(token, mode); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } } + + private void setDisplayBrightness(int brightness) { + if (DEBUG) { + Slog.d(TAG, "setDisplayBrightness(" + + "id=" + displayId + ", brightness=" + brightness + ")"); + } + + Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness(" + + "id=" + displayId + ", brightness=" + brightness + ")"); + try { + mBacklight.setBrightness(brightness); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } }; } return null; @@ -278,6 +357,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId); pw.println("mPhys=" + mPhys); pw.println("mState=" + Display.stateToString(mState)); + pw.println("mBrightness=" + mBrightness); + pw.println("mBacklight=" + mBacklight); } private void updateDeviceInfoLocked() { diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 6c57eec..65dc72f 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -76,6 +76,10 @@ final class LogicalDisplay { // The pending requested refresh rate. 0 if no request is pending. private float mRequestedRefreshRate; + // The display offsets to apply to the display projection. + private int mDisplayOffsetX; + private int mDisplayOffsetY; + // Temporary rectangle used when needed. private final Rect mTempLayerStackRect = new Rect(); private final Rect mTempDisplayRect = new Rect(); @@ -298,7 +302,10 @@ final class LogicalDisplay { // multiplying the fractions by the product of their denominators before // comparing them. int displayRectWidth, displayRectHeight; - if (physWidth * displayInfo.logicalHeight + if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0) { + displayRectWidth = displayInfo.logicalWidth; + displayRectHeight = displayInfo.logicalHeight; + } else if (physWidth * displayInfo.logicalHeight < physHeight * displayInfo.logicalWidth) { // Letter box. displayRectWidth = physWidth; @@ -313,6 +320,10 @@ final class LogicalDisplay { mTempDisplayRect.set(displayRectLeft, displayRectTop, displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight); + mTempDisplayRect.left += mDisplayOffsetX; + mTempDisplayRect.right += mDisplayOffsetX; + mTempDisplayRect.top += mDisplayOffsetY; + mTempDisplayRect.bottom += mDisplayOffsetY; device.setProjectionInTransactionLocked(orientation, mTempLayerStackRect, mTempDisplayRect); } @@ -356,10 +367,34 @@ final class LogicalDisplay { return mRequestedRefreshRate; } + /** + * Gets the burn-in offset in X. + */ + public int getDisplayOffsetXLocked() { + return mDisplayOffsetX; + } + + /** + * Gets the burn-in offset in Y. + */ + public int getDisplayOffsetYLocked() { + return mDisplayOffsetY; + } + + /** + * Sets the burn-in offsets. + */ + public void setDisplayOffsetsLocked(int x, int y) { + mDisplayOffsetX = x; + mDisplayOffsetY = y; + } + public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); pw.println("mLayerStack=" + mLayerStack); pw.println("mHasContent=" + mHasContent); + pw.println("mRequestedRefreshRate=" + mRequestedRefreshRate); + pw.println("mDisplayOffset=(" + mDisplayOffsetX + ", " + mDisplayOffsetY + ")"); pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ? mPrimaryDisplayDevice.getNameLocked() : "null")); pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo); diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 5b6f35b..af9f456 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -355,7 +355,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { if (mWindow != null) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); ipw.increaseIndent(); - DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, 200); + DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, "", 200); } } diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java index 9ca5fda..3f4eab9 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java +++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java @@ -153,7 +153,7 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { } @Override - public void dump(PrintWriter pw) { + public void dump(PrintWriter pw, String prefix) { pw.println("mWindowVisible=" + mWindowVisible); pw.println("mWindowX=" + mWindowX); pw.println("mWindowY=" + mWindowY); diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index f181cd5..6f59b54 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -221,7 +221,7 @@ final class VirtualDisplayAdapter extends DisplayAdapter { } @Override - public Runnable requestDisplayStateLocked(int state) { + public Runnable requestDisplayStateLocked(int state, int brightness) { if (state != mDisplayState) { mDisplayState = state; if (state == Display.STATE_OFF) { diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index c939861..f163555 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -123,7 +123,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { pw.println("mDisplayController:"); final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); ipw.increaseIndent(); - DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200); + DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, "", 200); } } diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java index dbb59b2..31c1eea 100644 --- a/services/core/java/com/android/server/display/WifiDisplayController.java +++ b/services/core/java/com/android/server/display/WifiDisplayController.java @@ -209,7 +209,7 @@ final class WifiDisplayController implements DumpUtils.Dump { } @Override - public void dump(PrintWriter pw) { + public void dump(PrintWriter pw, String prefix) { pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting); pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled); pw.println("mWfdEnabled=" + mWfdEnabled); diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 4521c28..458928f 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -137,10 +137,10 @@ public final class DreamManagerService extends SystemService { DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() { @Override - public void dump(PrintWriter pw) { + public void dump(PrintWriter pw, String prefix) { mController.dump(pw); } - }, pw, 200); + }, pw, "", 200); } private boolean isDreamingInternal() { diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 2941574..28597c1 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -16,30 +16,31 @@ package com.android.server.fingerprint; -import android.app.Service; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.os.Handler; import android.os.IBinder; -import android.os.PowerManager; +import android.os.Looper; +import android.os.MessageQueue; import android.os.RemoteException; -import android.provider.Settings; -import android.service.fingerprint.FingerprintManager; import android.util.ArrayMap; import android.util.Slog; import com.android.server.SystemService; -import android.service.fingerprint.FingerprintUtils; -import android.service.fingerprint.IFingerprintService; -import android.service.fingerprint.IFingerprintServiceReceiver; +import android.hardware.fingerprint.FingerprintUtils; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.IFingerprintService; +import android.hardware.fingerprint.IFingerprintServiceReceiver; + +import static android.Manifest.permission.MANAGE_FINGERPRINT; +import static android.Manifest.permission.USE_FINGERPRINT; -import java.io.PrintWriter; import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map.Entry; -import java.util.Set; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * A service to manage multiple clients that want to access the fingerprint HAL API. @@ -49,17 +50,33 @@ import java.util.Set; * @hide */ public class FingerprintService extends SystemService { - private final String TAG = "FingerprintService"; + private static final String TAG = "FingerprintService"; private static final boolean DEBUG = true; - private ArrayMap<IBinder, ClientData> mClients = new ArrayMap<IBinder, ClientData>(); + private ClientMonitor mAuthClient = null; + private ClientMonitor mEnrollClient = null; + private ClientMonitor mRemoveClient = null; private static final int MSG_NOTIFY = 10; + private static final int ENROLLMENT_TIMEOUT_MS = 60 * 1000; // 1 minute + + // Message types. Used internally to dispatch messages to the correct callback. + // Must agree with the list in fingerprint.h + private static final int FINGERPRINT_ERROR = -1; + private static final int FINGERPRINT_ACQUIRED = 1; + private static final int FINGERPRINT_TEMPLATE_ENROLLING = 3; + private static final int FINGERPRINT_TEMPLATE_REMOVED = 4; + private static final int FINGERPRINT_AUTHENTICATED = 5; + private static final long MS_PER_SEC = 1000; + private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000; + private static final int MAX_FAILED_ATTEMPTS = 5; + Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_NOTIFY: - handleNotify(msg.arg1, msg.arg2, (Integer) msg.obj); + FpHalMsg m = (FpHalMsg) msg.obj; + handleNotify(m.type, m.arg1, m.arg2, m.arg3); break; default: @@ -68,261 +85,530 @@ public class FingerprintService extends SystemService { } }; private Context mContext; + private int mHalDeviceId; + private int mFailedAttempts; + private final Runnable mLockoutReset = new Runnable() { + @Override + public void run() { + resetFailedAttempts(); + } + }; - private static final int STATE_IDLE = 0; - private static final int STATE_LISTENING = 1; - private static final int STATE_ENROLLING = 2; - private static final int STATE_REMOVING = 3; - private static final long MS_PER_SEC = 1000; - public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT"; - public static final String ENROLL_FINGERPRINT = "android.permission.ENROLL_FINGERPRINT"; + public FingerprintService(Context context) { + super(context); + mContext = context; + nativeInit(Looper.getMainLooper().getQueue(), this); + } - private static final class ClientData { - public IFingerprintServiceReceiver receiver; - int state; - int userId; - public TokenWatcher tokenWatcher; - IBinder getToken() { return tokenWatcher.getToken(); } + // TODO: Move these into separate process + // JNI methods to communicate from FingerprintService to HAL + static native int nativeEnroll(byte [] token, int groupId, int timeout); + static native long nativePreEnroll(); + static native int nativeStopEnrollment(); + static native int nativeAuthenticate(long sessionId, int groupId); + static native int nativeStopAuthentication(); + static native int nativeRemove(int fingerId, int groupId); + static native int nativeOpenHal(); + static native int nativeCloseHal(); + static native void nativeInit(MessageQueue queue, FingerprintService service); + static native long nativeGetAuthenticatorId(); + + static final class FpHalMsg { + int type; // Type of the message. One of the constants in fingerprint.h + int arg1; // optional arguments + int arg2; + int arg3; + + FpHalMsg(int type, int arg1, int arg2, int arg3) { + this.type = type; + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + } } - private class TokenWatcher implements IBinder.DeathRecipient { - WeakReference<IBinder> token; + /** + * Called from JNI to communicate messages from fingerprint HAL. + */ + void notify(int type, int arg1, int arg2, int arg3) { + mHandler.obtainMessage(MSG_NOTIFY, new FpHalMsg(type, arg1, arg2, arg3)).sendToTarget(); + } - TokenWatcher(IBinder token) { - this.token = new WeakReference<IBinder>(token); + void handleNotify(int type, int arg1, int arg2, int arg3) { + Slog.v(TAG, "handleNotify(type=" + type + ", arg1=" + arg1 + ", arg2=" + arg2 + ")" + + ", mAuthClients = " + mAuthClient + ", mEnrollClient = " + mEnrollClient); + if (mEnrollClient != null) { + final IBinder token = mEnrollClient.token; + if (dispatchNotify(mEnrollClient, type, arg1, arg2, arg3)) { + stopEnrollment(token, false); + removeClient(mEnrollClient); + } } - - IBinder getToken() { return token.get(); } - public void binderDied() { - mClients.remove(token); - this.token = null; + if (mAuthClient != null) { + final IBinder token = mAuthClient.token; + if (dispatchNotify(mAuthClient, type, arg1, arg2, arg3)) { + stopAuthentication(token, false); + removeClient(mAuthClient); + } } + if (mRemoveClient != null) { + if (dispatchNotify(mRemoveClient, type, arg1, arg2, arg3)) { + removeClient(mRemoveClient); + } + } + } - protected void finalize() throws Throwable { - try { - if (token != null) { - if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token); - mClients.remove(token); + /* + * Dispatch notify events to clients. + * + * @return true if the operation is done, i.e. authentication completed + */ + boolean dispatchNotify(ClientMonitor clientMonitor, int type, int arg1, int arg2, int arg3) { + ContentResolver contentResolver = mContext.getContentResolver(); + boolean operationCompleted = false; + int fpId; + int groupId; + int remaining; + int acquireInfo; + switch (type) { + case FINGERPRINT_ERROR: + fpId = arg1; + operationCompleted = clientMonitor.sendError(fpId); + break; + case FINGERPRINT_ACQUIRED: + acquireInfo = arg1; + operationCompleted = clientMonitor.sendAcquired(acquireInfo); + break; + case FINGERPRINT_AUTHENTICATED: + fpId = arg1; + groupId = arg2; + operationCompleted = clientMonitor.sendAuthenticated(fpId, groupId); + break; + case FINGERPRINT_TEMPLATE_ENROLLING: + fpId = arg1; + groupId = arg2; + remaining = arg3; + operationCompleted = clientMonitor.sendEnrollResult(fpId, groupId, remaining); + if (remaining == 0) { + addTemplateForUser(clientMonitor, contentResolver, fpId); + operationCompleted = true; // enroll completed } - } finally { - super.finalize(); - } + break; + case FINGERPRINT_TEMPLATE_REMOVED: + fpId = arg1; + groupId = arg2; + operationCompleted = clientMonitor.sendRemoved(fpId, groupId); + if (fpId != 0) { + removeTemplateForUser(clientMonitor, contentResolver, fpId); + } + break; } + return operationCompleted; } - public FingerprintService(Context context) { - super(context); - mContext = context; - nativeInit(this); + private void removeClient(ClientMonitor clientMonitor) { + if (clientMonitor == null) return; + clientMonitor.destroy(); + if (clientMonitor == mAuthClient) { + mAuthClient = null; + } else if (clientMonitor == mEnrollClient) { + mEnrollClient = null; + } else if (clientMonitor == mRemoveClient) { + mRemoveClient = null; + } } - // TODO: Move these into separate process - // JNI methods to communicate from FingerprintManagerService to HAL - native int nativeEnroll(int timeout); - native int nativeEnrollCancel(); - native int nativeRemove(int fingerprintId); - native int nativeOpenHal(); - native int nativeCloseHal(); - native void nativeInit(FingerprintService service); - - // JNI methods for communicating from HAL to clients - void notify(int msg, int arg1, int arg2) { - mHandler.obtainMessage(MSG_NOTIFY, msg, arg1, arg2).sendToTarget(); + private boolean inLockoutMode() { + return mFailedAttempts > MAX_FAILED_ATTEMPTS; } - void handleNotify(int msg, int arg1, int arg2) { - Slog.v(TAG, "handleNotify(msg=" + msg + ", arg1=" + arg1 + ", arg2=" + arg2 + ")"); - for (int i = 0; i < mClients.size(); i++) { - ClientData clientData = mClients.valueAt(i); - if (clientData == null || clientData.receiver == null) { - if (DEBUG) Slog.v(TAG, "clientData at " + i + " is invalid!!"); - continue; - } - switch (msg) { - case FingerprintManager.FINGERPRINT_ERROR: { - final int error = arg1; - try { - clientData.receiver.onError(error); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - mClients.remove(mClients.keyAt(i)); - } - } - break; - case FingerprintManager.FINGERPRINT_ACQUIRED: { - final int acquireInfo = arg1; - try { - clientData.receiver.onAcquired(acquireInfo); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - mClients.remove(mClients.keyAt(i)); - } - break; - } - case FingerprintManager.FINGERPRINT_PROCESSED: { - final int fingerId = arg1; - try { - clientData.receiver.onProcessed(fingerId); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - mClients.remove(mClients.keyAt(i)); - } - break; - } - case FingerprintManager.FINGERPRINT_TEMPLATE_ENROLLING: { - final int fingerId = arg1; - final int remaining = arg2; - if (clientData.state == STATE_ENROLLING) { - // Only send enroll updates to clients that are actually enrolling - try { - clientData.receiver.onEnrollResult(fingerId, remaining); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - mClients.remove(mClients.keyAt(i)); - } - // Update the database with new finger id. - // TODO: move to client code (Settings) - if (remaining == 0) { - FingerprintUtils.addFingerprintIdForUser(fingerId, - mContext.getContentResolver(), clientData.userId); - clientData.state = STATE_IDLE; // Nothing left to do - } - } else { - if (DEBUG) Slog.w(TAG, "Client not enrolling"); - break; - } - break; - } - case FingerprintManager.FINGERPRINT_TEMPLATE_REMOVED: { - int fingerId = arg1; - if (fingerId == 0) throw new IllegalStateException("Got illegal id from HAL"); - FingerprintUtils.removeFingerprintIdForUser(fingerId, - mContext.getContentResolver(), clientData.userId); - if (clientData.receiver != null) { - try { - clientData.receiver.onRemoved(fingerId); - } catch (RemoteException e) { - Slog.e(TAG, "can't send message to client. Did it die?", e); - mClients.remove(mClients.keyAt(i)); - } - } - clientData.state = STATE_LISTENING; - } - break; + private void resetFailedAttempts() { + if (DEBUG && inLockoutMode()) { + Slog.v(TAG, "Reset fingerprint lockout"); + } + mFailedAttempts = 0; + } + + private boolean handleFailedAttempt(ClientMonitor clientMonitor) { + mFailedAttempts++; + if (mFailedAttempts > MAX_FAILED_ATTEMPTS) { + // Failing multiple times will continue to push out the lockout time. + mHandler.removeCallbacks(mLockoutReset); + mHandler.postDelayed(mLockoutReset, FAIL_LOCKOUT_TIMEOUT_MS); + if (clientMonitor != null + && !clientMonitor.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { + Slog.w(TAG, "Cannot send lockout message to client"); } + return true; + } + return false; + } + + private void removeTemplateForUser(ClientMonitor clientMonitor, ContentResolver contentResolver, + final int fingerId) { + FingerprintUtils.removeFingerprintIdForUser(fingerId, contentResolver, + clientMonitor.userId); + } + + private void addTemplateForUser(ClientMonitor clientMonitor, ContentResolver contentResolver, + final int fingerId) { + FingerprintUtils.addFingerprintIdForUser(contentResolver, fingerId, + clientMonitor.userId); + } + + void startEnrollment(IBinder token, byte[] cryptoToken, int groupId, + IFingerprintServiceReceiver receiver, int flags) { + stopPendingOperations(); + mEnrollClient = new ClientMonitor(token, receiver, groupId); + final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); + final int result = nativeEnroll(cryptoToken, groupId, timeout); + if (result != 0) { + Slog.w(TAG, "startEnroll failed, result=" + result); } } - void startEnroll(IBinder token, long timeout, int userId) { - ClientData clientData = mClients.get(token); - if (clientData != null) { - if (clientData.userId != userId) throw new IllegalStateException("Bad user"); - clientData.state = STATE_ENROLLING; - nativeEnroll((int) (timeout / MS_PER_SEC)); - } else { - Slog.w(TAG, "enroll(): No listener registered"); + public long startPreEnroll(IBinder token) { + return nativePreEnroll(); + } + + private void stopPendingOperations() { + if (mEnrollClient != null) { + stopEnrollment(mEnrollClient.token, true); + } + if (mAuthClient != null) { + stopAuthentication(mAuthClient.token, true); } + // mRemoveClient is allowed to continue } - void startEnrollCancel(IBinder token, int userId) { - ClientData clientData = mClients.get(token); - if (clientData != null) { - if (clientData.userId != userId) throw new IllegalStateException("Bad user"); - clientData.state = STATE_LISTENING; - nativeEnrollCancel(); - } else { - Slog.w(TAG, "enrollCancel(): No listener registered"); + void stopEnrollment(IBinder token, boolean notify) { + final ClientMonitor client = mEnrollClient; + if (client == null || client.token != token) return; + int result = nativeStopEnrollment(); + if (notify) { + client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED); + } + removeClient(mEnrollClient); + if (result != 0) { + Slog.w(TAG, "startEnrollCancel failed, result=" + result); } } - // Remove all fingerprints for the given user. - void startRemove(IBinder token, int fingerId, int userId) { - ClientData clientData = mClients.get(token); - if (clientData != null) { - if (clientData.userId != userId) throw new IllegalStateException("Bad user"); - clientData.state = STATE_REMOVING; - // The fingerprint id will be removed when we get confirmation from the HAL - int result = nativeRemove(fingerId); - if (result != 0) { - Slog.w(TAG, "Error removing fingerprint with id = " + fingerId); + void startAuthentication(IBinder token, long opId, int groupId, + IFingerprintServiceReceiver receiver, int flags) { + stopPendingOperations(); + mAuthClient = new ClientMonitor(token, receiver, groupId); + if (inLockoutMode()) { + Slog.v(TAG, "In lockout mode; disallowing authentication"); + if (!mAuthClient.sendError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT)) { + Slog.w(TAG, "Cannot send timeout message to client"); } - } else { - Slog.w(TAG, "remove(" + token + "): No listener registered"); + mAuthClient = null; + return; + } + final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); + final int result = nativeAuthenticate(opId, groupId); + if (result != 0) { + Slog.w(TAG, "startAuthentication failed, result=" + result); + } + } + + void stopAuthentication(IBinder token, boolean notify) { + final ClientMonitor client = mAuthClient; + if (client == null || client.token != token) return; + int result = nativeStopAuthentication(); + if (notify) { + client.sendError(FingerprintManager.FINGERPRINT_ERROR_CANCELED); + } + removeClient(mAuthClient); + if (result != 0) { + Slog.w(TAG, "stopAuthentication failed, result=" + result); + } + } + + void startRemove(IBinder token, int fingerId, int userId, + IFingerprintServiceReceiver receiver) { + mRemoveClient = new ClientMonitor(token, receiver, userId); + // The fingerprint template ids will be removed when we get confirmation from the HAL + final int result = nativeRemove(fingerId, userId); + if (result != 0) { + Slog.w(TAG, "startRemove with id = " + fingerId + " failed with result=" + result); } } - void addListener(IBinder token, IFingerprintServiceReceiver receiver, int userId) { - if (DEBUG) Slog.v(TAG, "startListening(" + receiver + ")"); - if (mClients.get(token) == null) { - ClientData clientData = new ClientData(); - clientData.state = STATE_LISTENING; - clientData.receiver = receiver; - clientData.userId = userId; - clientData.tokenWatcher = new TokenWatcher(token); + public List<Fingerprint> getEnrolledFingerprints(int groupId) { + ContentResolver resolver = mContext.getContentResolver(); + int[] ids = FingerprintUtils.getFingerprintIdsForUser(resolver, groupId); + List<Fingerprint> result = new ArrayList<Fingerprint>(); + for (int i = 0; i < ids.length; i++) { + // TODO: persist names in Settings + CharSequence name = "Finger" + ids[i]; + final int group = 0; // TODO + final int fingerId = ids[i]; + final long deviceId = 0; // TODO + Fingerprint item = new Fingerprint(name, 0, ids[i], 0); + result.add(item); + } + return result; + } + + public boolean hasEnrolledFingerprints(int groupId) { + ContentResolver resolver = mContext.getContentResolver(); + return FingerprintUtils.getFingerprintIdsForUser(resolver, groupId).length > 0; + } + + void checkPermission(String permission) { + getContext().enforceCallingOrSelfPermission(permission, + "Must have " + permission + " permission."); + } + + private class ClientMonitor implements IBinder.DeathRecipient { + IBinder token; + WeakReference<IFingerprintServiceReceiver> receiver; + int userId; + + public ClientMonitor(IBinder token, IFingerprintServiceReceiver receiver, int userId) { + this.token = token; + this.receiver = new WeakReference<IFingerprintServiceReceiver>(receiver); + this.userId = userId; try { - token.linkToDeath(clientData.tokenWatcher, 0); - mClients.put(token, clientData); + token.linkToDeath(this, 0); } catch (RemoteException e) { Slog.w(TAG, "caught remote exception in linkToDeath: ", e); } - } else { - if (DEBUG) Slog.v(TAG, "listener already registered for " + token); } - } - void removeListener(IBinder token, int userId) { - if (DEBUG) Slog.v(TAG, "stopListening(" + token + ")"); - ClientData clientData = mClients.get(token); - if (clientData != null) { - token.unlinkToDeath(clientData.tokenWatcher, 0); - mClients.remove(token); - } else { - if (DEBUG) Slog.v(TAG, "listener not registered: " + token); + public void destroy() { + if (token != null) { + token.unlinkToDeath(this, 0); + token = null; + } + receiver = null; + } + + public void binderDied() { + token = null; + removeClient(this); + } + + protected void finalize() throws Throwable { + try { + if (token != null) { + if (DEBUG) Slog.w(TAG, "removing leaked reference: " + token); + removeClient(this); + } + } finally { + super.finalize(); + } + } + + /* + * @return true if we're done. + */ + private boolean sendRemoved(int fingerId, int groupId) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx == null) return true; // client not listening + try { + rx.onRemoved(mHalDeviceId, fingerId, groupId); + return fingerId == 0; + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify Removed:", e); + } + return false; + } + + /* + * @return true if we're done. + */ + private boolean sendEnrollResult(int fpId, int groupId, int remaining) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx == null) return true; // client not listening + FingerprintUtils.vibrateFingerprintSuccess(getContext()); + try { + rx.onEnrollResult(mHalDeviceId, fpId, groupId, remaining); + return remaining == 0; + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify EnrollResult:", e); + return true; + } + } + + /* + * @return true if we're done. + */ + private boolean sendAuthenticated(int fpId, int groupId) { + IFingerprintServiceReceiver rx = receiver.get(); + boolean result = false; + if (rx != null) { + try { + rx.onAuthenticated(mHalDeviceId, fpId, groupId); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify Authenticated:", e); + result = true; // client failed + } + } else { + result = true; // client not listening + } + if (fpId <= 0) { + FingerprintUtils.vibrateFingerprintError(getContext()); + result |= handleFailedAttempt(this); + } else { + FingerprintUtils.vibrateFingerprintSuccess(getContext()); + result |= true; // we have a valid fingerprint + mLockoutReset.run(); + } + return result; + } + + /* + * @return true if we're done. + */ + private boolean sendAcquired(int acquiredInfo) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx == null) return true; // client not listening + try { + rx.onAcquired(mHalDeviceId, acquiredInfo); + return false; // acquisition continues... + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke sendAcquired:", e); + return true; // client failed + } } - mClients.remove(token); - } - void checkPermission(String permisison) { - // TODO + /* + * @return true if we're done. + */ + private boolean sendError(int error) { + IFingerprintServiceReceiver rx = receiver.get(); + if (rx != null) { + try { + rx.onError(mHalDeviceId, error); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke sendError:", e); + } + } + return true; // errors always terminate progress + } } private final class FingerprintServiceWrapper extends IFingerprintService.Stub { - @Override // Binder call - public void enroll(IBinder token, long timeout, int userId) { - checkPermission(ENROLL_FINGERPRINT); - startEnroll(token, timeout, userId); + @Override + public long preEnroll(IBinder token) { + checkPermission(MANAGE_FINGERPRINT); + return startPreEnroll(token); + } + + @Override + // Binder call + public void enroll(final IBinder token, final byte[] cryptoToken, final int groupId, + final IFingerprintServiceReceiver receiver, final int flags) { + checkPermission(MANAGE_FINGERPRINT); + final byte [] cryptoClone = Arrays.copyOf(cryptoToken, cryptoToken.length); + mHandler.post(new Runnable() { + @Override + public void run() { + startEnrollment(token, cryptoClone, groupId, receiver, flags); + } + }); + } + + @Override + // Binder call + public void cancelEnrollment(final IBinder token) { + checkPermission(MANAGE_FINGERPRINT); + mHandler.post(new Runnable() { + @Override + public void run() { + stopEnrollment(token, true); + } + }); } - @Override // Binder call - public void enrollCancel(IBinder token,int userId) { - checkPermission(ENROLL_FINGERPRINT); - startEnrollCancel(token, userId); + @Override + // Binder call + public void authenticate(final IBinder token, final long opId, final int groupId, + final IFingerprintServiceReceiver receiver, final int flags) { + checkPermission(USE_FINGERPRINT); + mHandler.post(new Runnable() { + @Override + public void run() { + startAuthentication(token, opId, groupId, receiver, flags); + } + }); + } + + @Override + + // Binder call + public void cancelAuthentication(final IBinder token) { + checkPermission(USE_FINGERPRINT); + mHandler.post(new Runnable() { + @Override + public void run() { + stopAuthentication(token, true); + } + }); + } + + @Override + // Binder call + public void remove(final IBinder token, final int fingerId, final int groupId, + final IFingerprintServiceReceiver receiver) { + checkPermission(MANAGE_FINGERPRINT); // TODO: Maybe have another permission + mHandler.post(new Runnable() { + @Override + public void run() { + startRemove(token, fingerId, groupId, receiver); + } + }); + + } + + @Override + // Binder call + public boolean isHardwareDetected(long deviceId) { + checkPermission(USE_FINGERPRINT); + return mHalDeviceId != 0; // TODO } - @Override // Binder call - public void remove(IBinder token, int fingerprintId, int userId) { - checkPermission(ENROLL_FINGERPRINT); // TODO: Maybe have another permission - startRemove(token, fingerprintId, userId); + @Override + // Binder call + public void rename(final int fingerId, final int groupId, final String name) { + checkPermission(MANAGE_FINGERPRINT); + mHandler.post(new Runnable() { + @Override + public void run() { + Slog.w(TAG, "rename id=" + fingerId + ",gid=" + groupId + ",name=" + name); + } + }); + } + + @Override + // Binder call + public List<Fingerprint> getEnrolledFingerprints(int groupId) { + checkPermission(USE_FINGERPRINT); + return FingerprintService.this.getEnrolledFingerprints(groupId); } - @Override // Binder call - public void startListening(IBinder token, IFingerprintServiceReceiver receiver, int userId) - { + @Override + // Binder call + public boolean hasEnrolledFingerprints(int groupId) { checkPermission(USE_FINGERPRINT); - addListener(token, receiver, userId); + return FingerprintService.this.hasEnrolledFingerprints(groupId); } - @Override // Binder call - public void stopListening(IBinder token, int userId) { + @Override + public long getAuthenticatorId() { checkPermission(USE_FINGERPRINT); - removeListener(token, userId); + return nativeGetAuthenticatorId(); } } @Override public void onStart() { - publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); - nativeOpenHal(); + publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper()); + mHalDeviceId = nativeOpenHal(); + if (DEBUG) Slog.v(TAG, "Fingerprint HAL id: " + mHalDeviceId); } } diff --git a/services/core/java/com/android/server/firewall/SenderFilter.java b/services/core/java/com/android/server/firewall/SenderFilter.java index c0eee69..0074119 100644 --- a/services/core/java/com/android/server/firewall/SenderFilter.java +++ b/services/core/java/com/android/server/firewall/SenderFilter.java @@ -45,7 +45,8 @@ class SenderFilter { IPackageManager pm = AppGlobals.getPackageManager(); try { - return (pm.getFlagsForUid(callerUid) & ApplicationInfo.FLAG_PRIVILEGED) != 0; + return (pm.getPrivateFlagsForUid(callerUid) & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) + != 0; } catch (RemoteException ex) { Slog.e(IntentFirewall.TAG, "Remote exception while retrieving uid flags", ex); diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java index 7f48768..01547c1 100644 --- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java +++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java @@ -68,8 +68,12 @@ final class ActiveSourceHandler { } if (!tv.isProhibitMode()) { + ActiveSource old = ActiveSource.of(tv.getActiveSource()); tv.updateActiveSource(newActive); boolean notifyInputChange = (mCallback == null); + if (!old.equals(newActive)) { + tv.setPrevPortId(tv.getActivePortId()); + } tv.updateActiveInput(newActive.physicalAddress, notifyInputChange); invokeCallback(HdmiControlManager.RESULT_SUCCESS); } else { diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 0c86aed..e434f39 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -214,6 +214,10 @@ final class Constants { // values which denotes the device type in HDMI Spec 1.4. static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type"; + // Set to false to allow playback device to go to suspend mode even + // when it's an active source. True by default. + static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake"; + static final int RECORDING_TYPE_DIGITAL_RF = 1; static final int RECORDING_TYPE_ANALOGUE_RF = 2; static final int RECORDING_TYPE_EXTERNAL_PHYSICAL_ADDRESS = 3; diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java index 77ffe0b..2c1a7d5 100644 --- a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java +++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java @@ -17,8 +17,6 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiDeviceInfo; -import android.util.Slog; - import java.util.ArrayList; import java.util.Iterator; diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index d17e9b3..8031c05 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -71,6 +71,9 @@ abstract class HdmiCecLocalDevice { logicalAddress = logical; physicalAddress = physical; } + public static ActiveSource of(ActiveSource source) { + return new ActiveSource(source.logicalAddress, source.physicalAddress); + } public static ActiveSource of(int logical, int physical) { return new ActiveSource(logical, physical); } @@ -102,10 +105,10 @@ abstract class HdmiCecLocalDevice { StringBuffer s = new StringBuffer(); String logicalAddressString = (logicalAddress == Constants.ADDR_INVALID) ? "invalid" : String.format("0x%02x", logicalAddress); - s.append("logical_address: ").append(logicalAddressString); + s.append("(").append(logicalAddressString); String physicalAddressString = (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) ? "invalid" : String.format("0x%04x", physicalAddress); - s.append(", physical_address: ").append(physicalAddressString); + s.append(", ").append(physicalAddressString).append(")"); return s.toString(); } } @@ -173,6 +176,7 @@ abstract class HdmiCecLocalDevice { void init() { assertRunOnServiceThread(); mPreferredAddress = getPreferredAddress(); + mPendingActionClearedCallback = null; } /** @@ -636,7 +640,7 @@ abstract class HdmiCecLocalDevice { void addAndStartAction(final HdmiCecFeatureAction action) { assertRunOnServiceThread(); mActions.add(action); - if (mService.isPowerStandbyOrTransient()) { + if (mService.isPowerStandby()) { Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action); return; } @@ -834,16 +838,16 @@ abstract class HdmiCecLocalDevice { * * @param initiatedByCec true if this sequence is initiated * by the reception the CEC messages like <Standby> - * @param origialCallback callback interface to get notified when all pending actions are + * @param originalCallback callback interface to get notified when all pending actions are * cleared */ protected void disableDevice(boolean initiatedByCec, - final PendingActionClearedCallback origialCallback) { + final PendingActionClearedCallback originalCallback) { mPendingActionClearedCallback = new PendingActionClearedCallback() { @Override public void onCleared(HdmiCecLocalDevice device) { mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT); - origialCallback.onCleared(device); + originalCallback.onCleared(device); } }; mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), @@ -862,6 +866,9 @@ abstract class HdmiCecLocalDevice { action.finish(false); iter.remove(); } + if (mPendingActionClearedCallback != null) { + mPendingActionClearedCallback.onCleared(this); + } } /** diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 8034809..89ffe45 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -38,9 +38,11 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { // Used to keep the device awake while it is the active source. For devices that // cannot wake up via CEC commands, this address the inconvenience of having to - // turn them on. + // turn them on. True by default, and can be disabled (i.e. device can go to sleep + // in active device status) by explicitly setting the system property + // persist.sys.hdmi.keep_awake to false. // Lazily initialized - should call getWakeLock() to get the instance. - private WakeLock mWakeLock; + private ActiveWakeLock mWakeLock; HdmiCecLocalDevicePlayback(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_PLAYBACK); @@ -142,19 +144,30 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { mIsActiveSource = on; if (on) { getWakeLock().acquire(); - HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource); } else { getWakeLock().release(); - HdmiLogger.debug("Wake lock released"); } } @ServiceThreadOnly - private WakeLock getWakeLock() { + private ActiveWakeLock getWakeLock() { assertRunOnServiceThread(); if (mWakeLock == null) { - mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mWakeLock.setReferenceCounted(false); + if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) { + mWakeLock = new SystemWakeLock(); + } else { + // Create a dummy lock object that doesn't do anything about wake lock, + // hence allows the device to go to sleep even if it's the active source. + mWakeLock = new ActiveWakeLock() { + @Override + public void acquire() { } + @Override + public void release() { } + @Override + public boolean isHeld() { return false; } + }; + HdmiLogger.debug("No wakelock is used to keep the display on."); + } } return mWakeLock; } @@ -253,6 +266,16 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { @Override @ServiceThreadOnly + protected void sendStandby(int deviceId) { + assertRunOnServiceThread(); + + // Playback device can send <Standby> to TV only. Ignore the parameter. + int targetAddress = Constants.ADDR_TV; + mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress)); + } + + @Override + @ServiceThreadOnly protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { super.disableDevice(initiatedByCec, callback); @@ -270,4 +293,36 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { super.dump(pw); pw.println("mIsActiveSource: " + mIsActiveSource); } + + // Wrapper interface over PowerManager.WakeLock + private interface ActiveWakeLock { + void acquire(); + void release(); + boolean isHeld(); + } + + private class SystemWakeLock implements ActiveWakeLock { + private final WakeLock mWakeLock; + public SystemWakeLock() { + mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mWakeLock.setReferenceCounted(false); + } + + @Override + public void acquire() { + mWakeLock.acquire(); + HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource); + } + + @Override + public void release() { + mWakeLock.release(); + HdmiLogger.debug("Wake lock released"); + } + + @Override + public boolean isHeld() { + return mWakeLock.isHeld(); + } + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 8241cdc..5ac027d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -30,8 +30,6 @@ import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANAL import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; -import android.annotation.Nullable; -import android.content.Context; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -41,22 +39,19 @@ import android.hardware.hdmi.IHdmiControlCallback; import android.media.AudioManager; import android.media.AudioSystem; import android.media.tv.TvInputInfo; -import android.media.tv.TvInputManager; import android.media.tv.TvInputManager.TvInputCallback; import android.os.RemoteException; -import android.os.SystemProperties; import android.provider.Settings.Global; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiControlService.SendMessageCallback; -import com.android.server.SystemService; - import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; @@ -77,9 +72,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly private boolean mArcEstablished = false; - // Whether ARC feature is enabled or not. The default value is true. - // TODO: once adding system setting for it, read the value to it. - private boolean mArcFeatureEnabled = true; + // Stores whether ARC feature is enabled per port. True by default for all the ARC-enabled ports. + private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray(); // Whether System audio mode is activated or not. // This becomes true only when all system audio sequences are finished. @@ -196,6 +190,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected void onAddressAllocated(int logicalAddress, int reason) { assertRunOnServiceThread(); + List<HdmiPortInfo> ports = mService.getPortInfo(); + for (HdmiPortInfo port : ports) { + mArcFeatureEnabled.put(port.getId(), port.isArcSupported()); + } mService.registerTvInputCallback(mTvInputCallback); mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, mService.getPhysicalAddress(), mDeviceType)); @@ -208,7 +206,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { reason != HdmiControlService.INITIATED_BY_BOOT_UP); mLocalDeviceAddresses = initLocalDeviceAddresses(); launchDeviceDiscovery(); - startQueuedActions(); } @@ -258,7 +255,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } int targetAddress = targetDevice.getLogicalAddress(); ActiveSource active = getActiveSource(); - if (active.isValid() && targetAddress == active.logicalAddress) { + if (targetDevice.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON + && active.isValid() + && targetAddress == active.logicalAddress) { invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); return; } @@ -348,7 +347,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { void updateActiveInput(int path, boolean notifyInputChange) { assertRunOnServiceThread(); // Seq #15 - setPrevPortId(getActivePortId()); setActivePath(path); // TODO: Handle PAP/PIP case. // Show OSD port change banner @@ -787,6 +785,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiDeviceInfo avr = getAvrDeviceInfo(); if (avr != null) { onNewAvrAdded(avr); + } else { + setSystemAudioMode(false, true); } } }); @@ -799,7 +799,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (getSystemAudioModeSetting() && !isSystemAudioActivated()) { addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress())); } - if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) { + if (isArcFeatureEnabled(avr.getPortId()) + && !hasAction(SetArcTransmissionStateAction.class)) { startArcAction(true); } } @@ -881,7 +882,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled); boolean oldStatus = mArcEstablished; // 1. Enable/disable ARC circuit. - mService.setAudioReturnChannel(getAvrDeviceInfo().getPortId(), enabled); + setAudioReturnChannel(enabled); // 2. Notify arc status to audio service. notifyArcStatusToAudioService(enabled); // 3. Update arc status; @@ -889,39 +890,66 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return oldStatus; } + /** + * Switch hardware ARC circuit in the system. + */ + @ServiceThreadOnly + void setAudioReturnChannel(boolean enabled) { + assertRunOnServiceThread(); + HdmiDeviceInfo avr = getAvrDeviceInfo(); + if (avr != null) { + mService.setAudioReturnChannel(avr.getPortId(), enabled); + } + } + @ServiceThreadOnly private void updateArcFeatureStatus(int portId, boolean isConnected) { assertRunOnServiceThread(); + HdmiDeviceInfo avr = getAvrDeviceInfo(); + if (avr == null) { + return; + } // HEAC 2.4, HEACT 5-15 // Should not activate ARC if +5V status is false. HdmiPortInfo portInfo = mService.getPortInfo(portId); - if (portInfo.isArcSupported()) { - changeArcFeatureEnabled(isConnected); + if (avr.getPortId() == portId && portInfo.isArcSupported()) { + changeArcFeatureEnabled(portId, isConnected); } } + @ServiceThreadOnly + boolean isConnected(int portId) { + assertRunOnServiceThread(); + return mService.isConnected(portId); + } + private void notifyArcStatusToAudioService(boolean enabled) { // Note that we don't set any name to ARC. mService.getAudioManager().setWiredDeviceConnectionState( AudioSystem.DEVICE_OUT_HDMI_ARC, - enabled ? 1 : 0, ""); + enabled ? 1 : 0, "", ""); } /** - * Returns whether ARC is enabled or not. + * Returns true if ARC is currently established on a certain port. */ @ServiceThreadOnly - boolean isArcEstabilished() { + boolean isArcEstablished() { assertRunOnServiceThread(); - return mArcFeatureEnabled && mArcEstablished; + if (mArcEstablished) { + for (int i = 0; i < mArcFeatureEnabled.size(); i++) { + if (mArcFeatureEnabled.valueAt(i)) return true; + } + } + return false; } @ServiceThreadOnly - void changeArcFeatureEnabled(boolean enabled) { + void changeArcFeatureEnabled(int portId, boolean enabled) { assertRunOnServiceThread(); - if (mArcFeatureEnabled != enabled) { - mArcFeatureEnabled = enabled; + if (mArcFeatureEnabled.get(portId) != enabled) { + mArcFeatureEnabled.put(portId, enabled); if (enabled) { if (!mArcEstablished) { startArcAction(true); @@ -935,9 +963,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @ServiceThreadOnly - boolean isArcFeatureEnabled() { + boolean isArcFeatureEnabled(int portId) { assertRunOnServiceThread(); - return mArcFeatureEnabled; + return mArcFeatureEnabled.get(portId); } @ServiceThreadOnly @@ -1072,7 +1100,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { && isConnectedToArcPort(avr.getPhysicalAddress()) && isDirectConnectAddress(avr.getPhysicalAddress())) { if (shouldCheckArcFeatureEnabled) { - return isArcFeatureEnabled(); + return isArcFeatureEnabled(avr.getPortId()); } else { return true; } @@ -1085,11 +1113,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleTerminateArc(HdmiCecMessage message) { assertRunOnServiceThread(); - // In cast of termination, do not check ARC configuration in that AVR device - // might be removed already. - - // In case where <Terminate Arc> is started by <Request ARC Termination> - // need to clean up RequestArcInitiationAction. + if (mService .isPowerStandbyOrTransient()) { + setArcStatus(false); + return true; + } + // Do not check ARC configuration since the AVR might have been already removed. + // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by + // <Request ARC Termination>. removeAction(RequestArcTerminationAction.class); SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, message.getSource(), false); @@ -1565,7 +1595,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { - super.disableDevice(initiatedByCec, callback); assertRunOnServiceThread(); mService.unregisterTvInputCallback(mTvInputCallback); // Remove any repeated working actions. @@ -1581,6 +1610,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { disableSystemAudioIfExist(); disableArcIfExist(); + + super.disableDevice(initiatedByCec, callback); clearDeviceInfoList(); checkIfPendingActionsCleared(); } @@ -1598,10 +1629,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { removeAction(SystemAudioAutoInitiationAction.class); removeAction(SystemAudioStatusAction.class); removeAction(VolumeControlAction.class); - - // Turn off the mode but do not write it the settings, so that the next time TV powers on - // the system audio mode setting can be restored automatically. - setSystemAudioMode(false, false); } @ServiceThreadOnly @@ -1614,7 +1641,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Seq #44. removeAction(RequestArcInitiationAction.class); - if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) { + if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) { addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 49a96d8..2cbc1b9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -780,6 +780,12 @@ public final class HdmiControlService extends SystemService { return false; } + @ServiceThreadOnly + boolean isConnected(int portId) { + assertRunOnServiceThread(); + return mCecController.isConnected(portId); + } + void runOnServiceThread(Runnable runnable) { mHandler.post(runnable); } diff --git a/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java b/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java index 3883200..f19b19b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java +++ b/services/core/java/com/android/server/hdmi/HdmiMhlControllerStub.java @@ -20,7 +20,6 @@ import android.hardware.hdmi.HdmiPortInfo; import android.util.SparseArray; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.hdmi.HdmiControlService.SendMessageCallback; /** * A handler class for MHL control command. It converts user's command into MHL command and pass it diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index 1bbd038..5f2d651 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -156,10 +156,13 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { int index = -1; while ((index = removed.nextSetBit(index + 1)) != -1) { if (index == Constants.ADDR_AUDIO_SYSTEM) { - ++mAvrStatusCount; - Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount); - if (mAvrStatusCount < AVR_COUNT_MAX) { - continue; + HdmiDeviceInfo avr = tv().getAvrDeviceInfo(); + if (avr != null && tv().isConnected(avr.getPortId())) { + ++mAvrStatusCount; + Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount); + if (mAvrStatusCount < AVR_COUNT_MAX) { + continue; + } } } Slog.v(TAG, "Remove device by hot-plug detection:" + index); @@ -261,7 +264,8 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { // Turn off system audio mode and update settings. tv().setSystemAudioMode(false, true); - if (tv().isArcEstabilished()) { + if (tv().isArcEstablished()) { + tv().setAudioReturnChannel(false); addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); } } diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java index 31322a9..75a79cb 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java @@ -17,7 +17,6 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiDeviceInfo; -import android.util.Slog; /** * Base feature action class for <Request ARC Initiation>/<Request ARC Termination>. @@ -59,14 +58,16 @@ abstract class RequestArcAction extends HdmiCecFeatureAction { // received without <Request ARC Initiation> or <Request ARC Termination>. case Constants.MESSAGE_FEATURE_ABORT: int originalOpcode = cmd.getParams()[0] & 0xFF; - if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION - || originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) { + if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) { disableArcTransmission(); finish(); return true; - } else { - return false; + } else if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION) { + tv().setArcStatus(false); + finish(); + return true; } + return false; } return false; } @@ -83,7 +84,7 @@ abstract class RequestArcAction extends HdmiCecFeatureAction { if (mState != state || state != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE) { return; } - HdmiLogger.debug("[T]RequestArcAction."); + HdmiLogger.debug("[T] RequestArcAction."); disableArcTransmission(); finish(); } diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java index d9e1f24..f69f975 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java @@ -35,6 +35,7 @@ final class RequestArcInitiationAction extends RequestArcAction { @Override boolean start() { + // Seq #38 mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE; addTimer(mState, HdmiConfig.TIMEOUT_MS); @@ -44,9 +45,8 @@ final class RequestArcInitiationAction extends RequestArcAction { @Override public void onSendCompleted(int error) { if (error != Constants.SEND_RESULT_SUCCESS) { - // If failed to send <Request ARC Initiation>, start "Disabled" - // ARC transmission action. - disableArcTransmission(); + // Turn off ARC status if <Request ARC Initiation> fails. + tv().setArcStatus(false); finish(); } } diff --git a/services/core/java/com/android/server/hdmi/RoutingControlAction.java b/services/core/java/com/android/server/hdmi/RoutingControlAction.java index ce5b9ab..6c8694e 100644 --- a/services/core/java/com/android/server/hdmi/RoutingControlAction.java +++ b/services/core/java/com/android/server/hdmi/RoutingControlAction.java @@ -119,7 +119,7 @@ final class RoutingControlAction extends HdmiCecFeatureAction { private void handleReportPowerStatus(int devicePowerStatus) { if (isPowerOnOrTransient(getTvPowerStatus())) { - tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); + updateActiveInput(); if (isPowerOnOrTransient(devicePowerStatus)) { sendSetStreamPath(); } @@ -127,6 +127,12 @@ final class RoutingControlAction extends HdmiCecFeatureAction { finishWithCallback(HdmiControlManager.RESULT_SUCCESS); } + private void updateActiveInput() { + HdmiCecLocalDeviceTv tv = tv(); + tv.setPrevPortId(tv.getActivePortId()); + tv.updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); + } + private int getTvPowerStatus() { return tv().getPowerStatus(); } @@ -165,13 +171,13 @@ final class RoutingControlAction extends HdmiCecFeatureAction { } }); } else { - tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); + updateActiveInput(); finishWithCallback(HdmiControlManager.RESULT_SUCCESS); } return; case STATE_WAIT_FOR_REPORT_POWER_STATUS: if (isPowerOnOrTransient(getTvPowerStatus())) { - tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); + updateActiveInput(); sendSetStreamPath(); } finishWithCallback(HdmiControlManager.RESULT_SUCCESS); @@ -189,7 +195,7 @@ final class RoutingControlAction extends HdmiCecFeatureAction { mState = STATE_WAIT_FOR_REPORT_POWER_STATUS; addTimer(mState, TIMEOUT_REPORT_POWER_STATUS_MS); } else { - tv().updateActiveInput(mCurrentRoutingPath, mNotifyInputChange); + updateActiveInput(); sendSetStreamPath(); finishWithCallback(HdmiControlManager.RESULT_SUCCESS); } diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index bffa854..9b4950b 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -52,8 +52,9 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { @Override boolean start() { + // Seq #37. if (mEnabled) { - // Enable ARC status immediately after sending <Report Arc Initiated>. + // Enable ARC status immediately before sending <Report Arc Initiated>. // If AVR responds with <Feature Abort>, disable ARC status again. // This is different from spec that says that turns ARC status to // "Enabled" if <Report ARC Initiated> is acknowledged and no @@ -79,12 +80,21 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { sendCommand(command, new HdmiControlService.SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error != Constants.SEND_RESULT_SUCCESS) { - // If fails to send <Report ARC Initiated>, disable ARC and - // send <Report ARC Terminated> directly. - setArcStatus(false); - HdmiLogger.debug("Failed to send <Report Arc Initiated>."); - finish(); + switch (error) { + case Constants.SEND_RESULT_SUCCESS: + case Constants.SEND_RESULT_BUSY: + case Constants.SEND_RESULT_FAILURE: + // The result of the command transmission, unless it is an obvious + // failure indicated by the target device (or lack thereof), should + // not affect the ARC status. Ignores it silently. + break; + case Constants.SEND_RESULT_NAK: + // If <Report ARC Initiated> is negatively ack'ed, disable ARC and + // send <Report ARC Terminated> directly. + setArcStatus(false); + HdmiLogger.debug("Failed to send <Report Arc Initiated>."); + finish(); + break; } } }); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 15dcd44..17b4f9c 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -796,7 +796,7 @@ public class InputManagerService extends IInputManager.Stub .setContentIntent(keyboardLayoutIntent) .setSmallIcon(R.drawable.ic_settings_language) .setPriority(Notification.PRIORITY_LOW) - .setColor(mContext.getResources().getColor( + .setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)) .build(); mNotificationManager.notifyAsUser(null, diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 83d6986..ecda36a 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import android.app.ActivityManager; import android.app.AppGlobals; import android.app.job.JobInfo; import android.app.job.JobScheduler; @@ -40,6 +41,7 @@ import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -49,6 +51,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.app.IBatteryStats; +import com.android.server.job.controllers.AppIdleController; import com.android.server.job.controllers.BatteryController; import com.android.server.job.controllers.ConnectivityController; import com.android.server.job.controllers.IdleController; @@ -72,7 +75,8 @@ public class JobSchedulerService extends com.android.server.SystemService implements StateChangedListener, JobCompletedListener { static final boolean DEBUG = false; /** The number of concurrent jobs we run at one time. */ - private static final int MAX_JOB_CONTEXTS_COUNT = 3; + private static final int MAX_JOB_CONTEXTS_COUNT + = ActivityManager.isLowRamDeviceStatic() ? 1 : 3; static final String TAG = "JobSchedulerService"; /** Master list of jobs. */ final JobStore mJobs; @@ -107,21 +111,22 @@ public class JobSchedulerService extends com.android.server.SystemService * Track Services that have currently active or pending jobs. The index is provided by * {@link JobStatus#getServiceToken()} */ - final List<JobServiceContext> mActiveServices = new ArrayList<JobServiceContext>(); + final List<JobServiceContext> mActiveServices = new ArrayList<>(); /** List of controllers that will notify this service of updates to jobs. */ List<StateController> mControllers; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. */ - final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>(); + final ArrayList<JobStatus> mPendingJobs = new ArrayList<>(); - final ArrayList<Integer> mStartedUsers = new ArrayList(); + final ArrayList<Integer> mStartedUsers = new ArrayList<>(); final JobHandler mHandler; final JobSchedulerStub mJobSchedulerStub; IBatteryStats mBatteryStats; + PowerManager mPowerManager; /** * Set to true once we are allowed to run third party apps. @@ -129,6 +134,11 @@ public class JobSchedulerService extends com.android.server.SystemService boolean mReadyToRock; /** + * True when in device idle mode, so we don't want to schedule any jobs. + */ + boolean mDeviceIdleMode; + + /** * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we * still clean up. On reinstall the package will have a new uid. */ @@ -152,6 +162,8 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "Removing jobs for user: " + userId); } cancelJobsForUser(userId); + } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) { + updateIdleMode(mPowerManager != null ? mPowerManager.isDeviceIdleMode() : false); } } }; @@ -197,7 +209,7 @@ public class JobSchedulerService extends com.android.server.SystemService return outList; } - private void cancelJobsForUser(int userHandle) { + void cancelJobsForUser(int userHandle) { List<JobStatus> jobsForUser; synchronized (mJobs) { jobsForUser = mJobs.getJobsByUser(userHandle); @@ -255,6 +267,40 @@ public class JobSchedulerService extends com.android.server.SystemService } } + void updateIdleMode(boolean enabled) { + boolean changed = false; + boolean rocking; + synchronized (mJobs) { + if (mDeviceIdleMode != enabled) { + changed = true; + } + rocking = mReadyToRock; + } + if (changed) { + if (rocking) { + for (int i=0; i<mControllers.size(); i++) { + mControllers.get(i).deviceIdleModeChanged(enabled); + } + } + synchronized (mJobs) { + mDeviceIdleMode = enabled; + if (enabled) { + // When becoming idle, make sure no jobs are actively running. + for (int i=0; i<mActiveServices.size(); i++) { + JobServiceContext jsc = mActiveServices.get(i); + final JobStatus executing = jsc.getRunningJob(); + if (executing != null) { + jsc.cancelExecutingJob(); + } + } + } else { + // When coming out of idle, allow thing to start back up. + mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); + } + } + } + } + /** * Initializes the system service. * <p> @@ -272,6 +318,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(TimeController.get(this)); mControllers.add(IdleController.get(this)); mControllers.add(BatteryController.get(this)); + mControllers.add(AppIdleController.get(this)); mHandler = new JobHandler(context.getMainLooper()); mJobSchedulerStub = new JobSchedulerStub(); @@ -292,8 +339,10 @@ public class JobSchedulerService extends com.android.server.SystemService getContext().registerReceiverAsUser( mBroadcastReceiver, UserHandle.ALL, filter, null, null); final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); + userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); getContext().registerReceiverAsUser( mBroadcastReceiver, UserHandle.ALL, userFilter, null, null); + mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE); } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { synchronized (mJobs) { // Let's go! @@ -311,6 +360,7 @@ public class JobSchedulerService extends com.android.server.SystemService for (int i=0; i<jobs.size(); i++) { JobStatus job = jobs.valueAt(i); for (int controller=0; controller<mControllers.size(); controller++) { + mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode); mControllers.get(controller).maybeStartTrackingJob(job); } } @@ -640,7 +690,6 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean jobPending = mPendingJobs.contains(job); final boolean jobActive = isCurrentlyActiveLocked(job); final boolean userRunning = mStartedUsers.contains(job.getUserId()); - if (DEBUG) { Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() + " ready=" + jobReady + " pending=" + jobPending @@ -665,6 +714,10 @@ public class JobSchedulerService extends com.android.server.SystemService */ private void maybeRunPendingJobsH() { synchronized (mJobs) { + if (mDeviceIdleMode) { + // If device is idle, we will not schedule jobs to run. + return; + } Iterator<JobStatus> it = mPendingJobs.iterator(); if (DEBUG) { Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); @@ -686,6 +739,10 @@ public class JobSchedulerService extends com.android.server.SystemService } } if (availableContext != null) { + if (DEBUG) { + Slog.d(TAG, "About to run job " + + nextPending.getJob().getService().toString()); + } if (!availableContext.executeRunnableJob(nextPending)) { if (DEBUG) { Slog.d(TAG, "Error executing " + nextPending); @@ -876,6 +933,7 @@ public class JobSchedulerService extends com.android.server.SystemService } pw.println(); pw.print("mReadyToRock="); pw.println(mReadyToRock); + pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode); } pw.println(); } diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 63c8d92..53ceb2e 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -153,8 +153,9 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne } mRunningJob = job; - mParams = new JobParameters(this, job.getJobId(), job.getExtras(), - !job.isConstraintsSatisfied()); + final boolean isDeadlineExpired = + job.getLatestRunTimeElapsed() >= SystemClock.elapsedRealtime(); + mParams = new JobParameters(this, job.getJobId(), job.getExtras(), isDeadlineExpired); mExecutionStartTimeElapsed = SystemClock.elapsedRealtime(); mVerb = VERB_BINDING; diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java new file mode 100644 index 0000000..98fb11b --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015 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.job.controllers; + +import android.app.usage.UsageStatsManagerInternal; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.job.JobSchedulerService; +import com.android.server.job.StateChangedListener; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Controls when apps are considered idle and if jobs pertaining to those apps should + * be executed. Apps that haven't been actively launched or accessed from a foreground app + * for a certain amount of time (maybe hours or days) are considered idle. When the app comes + * out of idle state, it will be allowed to run scheduled jobs. + */ +public class AppIdleController extends StateController + implements UsageStatsManagerInternal.AppIdleStateChangeListener { + + private static final String LOG_TAG = "AppIdleController"; + private static final boolean DEBUG = false; + + // Singleton factory + private static Object sCreationLock = new Object(); + private static volatile AppIdleController sController; + final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); + private final UsageStatsManagerInternal mUsageStatsInternal; + private final BatteryManager mBatteryManager; + private boolean mPluggedIn; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + onPluggedIn(mBatteryManager.isCharging()); + } + }; + + public static AppIdleController get(JobSchedulerService service) { + synchronized (sCreationLock) { + if (sController == null) { + sController = new AppIdleController(service, service.getContext()); + } + return sController; + } + } + + private AppIdleController(StateChangedListener stateChangedListener, Context context) { + super(stateChangedListener, context); + mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class); + mBatteryManager = context.getSystemService(BatteryManager.class); + mPluggedIn = mBatteryManager.isCharging(); + mUsageStatsInternal.addAppIdleStateChangeListener(this); + registerReceivers(); + } + + private void registerReceivers() { + // Monitor battery charging state + IntentFilter filter = new IntentFilter(BatteryManager.ACTION_CHARGING); + filter.addAction(BatteryManager.ACTION_DISCHARGING); + mContext.registerReceiver(mReceiver, filter); + } + + @Override + public void maybeStartTrackingJob(JobStatus jobStatus) { + synchronized (mTrackedTasks) { + mTrackedTasks.add(jobStatus); + String packageName = jobStatus.job.getService().getPackageName(); + final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName, + jobStatus.getUserId()); + if (DEBUG) { + Slog.d(LOG_TAG, "Start tracking, setting idle state of " + + packageName + " to " + appIdle); + } + jobStatus.appNotIdleConstraintSatisfied.set(!appIdle); + } + } + + @Override + public void maybeStopTrackingJob(JobStatus jobStatus) { + synchronized (mTrackedTasks) { + mTrackedTasks.remove(jobStatus); + } + } + + @Override + public void dumpControllerState(PrintWriter pw) { + pw.println("AppIdle"); + pw.println("Plugged In: " + mPluggedIn); + synchronized (mTrackedTasks) { + for (JobStatus task : mTrackedTasks) { + pw.print(task.job.getService().getPackageName()); + pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get()); + pw.print(", "); + } + pw.println(); + } + } + + @Override + public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { + boolean changed = false; + synchronized (mTrackedTasks) { + // If currently plugged in, we don't care about app idle state + if (mPluggedIn) { + return; + } + for (JobStatus task : mTrackedTasks) { + if (task.job.getService().getPackageName().equals(packageName) + && task.getUserId() == userId) { + if (task.appNotIdleConstraintSatisfied.get() != !idle) { + if (DEBUG) { + Slog.d(LOG_TAG, "App Idle state changed, setting idle state of " + + packageName + " to " + idle); + } + task.appNotIdleConstraintSatisfied.set(!idle); + changed = true; + } + } + } + } + if (changed) { + mStateChangedListener.onControllerStateChanged(); + } + } + + void onPluggedIn(boolean pluggedIn) { + // Flag if any app's idle state has changed + boolean changed = false; + synchronized (mTrackedTasks) { + if (mPluggedIn == pluggedIn) { + return; + } + mPluggedIn = pluggedIn; + for (JobStatus task : mTrackedTasks) { + String packageName = task.job.getService().getPackageName(); + final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName, + task.getUserId()); + if (DEBUG) { + Slog.d(LOG_TAG, "Plugged in " + pluggedIn + ", setting idle state of " + + packageName + " to " + appIdle); + } + if (task.appNotIdleConstraintSatisfied.get() == appIdle) { + task.appNotIdleConstraintSatisfied.set(!appIdle); + changed = true; + } + } + } + if (changed) { + mStateChangedListener.onControllerStateChanged(); + } + } +} diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java index 309e034..7c2aead 100644 --- a/services/core/java/com/android/server/job/controllers/BatteryController.java +++ b/services/core/java/com/android/server/job/controllers/BatteryController.java @@ -47,10 +47,6 @@ public class BatteryController extends StateController { private static final Object sCreationLock = new Object(); private static volatile BatteryController sController; - private static final String ACTION_CHARGING_STABLE = - "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE"; - /** Wait this long after phone is plugged in before doing any work. */ - private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes. private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); private ChargingTracker mChargeTracker; @@ -91,9 +87,6 @@ public class BatteryController extends StateController { taskStatus.chargingConstraintSatisfied.set(isOnStablePower); } } - if (isOnStablePower) { - mChargeTracker.setStableChargingAlarm(); - } } @Override @@ -131,8 +124,6 @@ public class BatteryController extends StateController { } public class ChargingTracker extends BroadcastReceiver { - private final AlarmManager mAlarm; - private final PendingIntent mStableChargingTriggerIntent; /** * Track whether we're "charging", where charging means that we're ready to commit to * doing work. @@ -142,9 +133,6 @@ public class BatteryController extends StateController { private boolean mBatteryHealthy; public ChargingTracker() { - mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(ACTION_CHARGING_STABLE); - mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); } public void startTracking() { @@ -154,10 +142,8 @@ public class BatteryController extends StateController { filter.addAction(Intent.ACTION_BATTERY_LOW); filter.addAction(Intent.ACTION_BATTERY_OKAY); // Charging/not charging. - filter.addAction(Intent.ACTION_POWER_CONNECTED); - filter.addAction(Intent.ACTION_POWER_DISCONNECTED); - // Charging stable. - filter.addAction(ACTION_CHARGING_STABLE); + filter.addAction(BatteryManager.ACTION_CHARGING); + filter.addAction(BatteryManager.ACTION_DISCHARGING); mContext.registerReceiver(this, filter); // Initialise tracker state. @@ -195,44 +181,20 @@ public class BatteryController extends StateController { } mBatteryHealthy = true; maybeReportNewChargingState(); - } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) { + } else if (BatteryManager.ACTION_CHARGING.equals(action)) { if (DEBUG) { - Slog.d(TAG, "Received charging intent, setting alarm for " - + STABLE_CHARGING_THRESHOLD_MILLIS); + Slog.d(TAG, "Received charging intent, fired @ " + + SystemClock.elapsedRealtime()); } - // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks - // here if the user unplugs the phone immediately. - setStableChargingAlarm(); mCharging = true; - } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) { + maybeReportNewChargingState(); + } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { if (DEBUG) { - Slog.d(TAG, "Disconnected from power, cancelling any set alarms."); + Slog.d(TAG, "Disconnected from power."); } - // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted. - mAlarm.cancel(mStableChargingTriggerIntent); mCharging = false; maybeReportNewChargingState(); - }else if (ACTION_CHARGING_STABLE.equals(action)) { - // Here's where we actually do the notify for a task being ready. - if (DEBUG) { - Slog.d(TAG, "Stable charging fired @ " + SystemClock.elapsedRealtime() - + " charging: " + mCharging); - } - if (mCharging) { // Should never receive this intent if mCharging is false. - maybeReportNewChargingState(); - } - } - } - - void setStableChargingAlarm() { - final long alarmTriggerElapsed = - SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS; - if (DEBUG) { - Slog.d(TAG, "Setting stable alarm to go off in " + - (STABLE_CHARGING_THRESHOLD_MILLIS / 1000) + "s"); } - mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTriggerElapsed, - mStableChargingTriggerIntent); } } diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java index 7b71027..8e2ca18 100644 --- a/services/core/java/com/android/server/job/controllers/IdleController.java +++ b/services/core/java/com/android/server/job/controllers/IdleController.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index e3c55b6..69c63f3 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -54,6 +54,7 @@ public class JobStatus { final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean unmeteredConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean(); + final AtomicBoolean appNotIdleConstraintSatisfied = new AtomicBoolean(); /** * Earliest point in the future at which this job will be eligible to run. A value of 0 @@ -199,8 +200,11 @@ public class JobStatus { * the constraints are satisfied <strong>or</strong> the deadline on the job has expired. */ public synchronized boolean isReady() { - return isConstraintsSatisfied() - || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get()); + // Deadline constraint trumps other constraints + // AppNotIdle implicit constraint trumps all! + return (isConstraintsSatisfied() + || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get())) + && appNotIdleConstraintSatisfied.get(); } /** @@ -229,6 +233,7 @@ public class JobStatus { + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging() + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures + ",P=" + job.isPersisted() + + ",ANI=" + appNotIdleConstraintSatisfied.get() + (isReady() ? "(READY)" : "") + "]"; } diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java index ca56886..cda7c32 100644 --- a/services/core/java/com/android/server/job/controllers/StateController.java +++ b/services/core/java/com/android/server/job/controllers/StateController.java @@ -18,7 +18,6 @@ package com.android.server.job.controllers; import android.content.Context; -import com.android.server.job.JobSchedulerService; import com.android.server.job.StateChangedListener; import java.io.PrintWriter; @@ -32,15 +31,20 @@ public abstract class StateController { protected static final boolean DEBUG = false; protected Context mContext; protected StateChangedListener mStateChangedListener; + protected boolean mDeviceIdleMode; public StateController(StateChangedListener stateChangedListener, Context context) { mStateChangedListener = stateChangedListener; mContext = context; } + public void deviceIdleModeChanged(boolean enabled) { + mDeviceIdleMode = enabled; + } + /** * Implement the logic here to decide whether a job should be tracked by this controller. - * This logic is put here so the JobManger can be completely agnostic of Controller logic. + * This logic is put here so the JobManager can be completely agnostic of Controller logic. * Also called when updating a task, so implementing controllers have to be aware of * preexisting tasks. */ @@ -51,5 +55,4 @@ public abstract class StateController { public abstract void maybeStopTrackingJob(JobStatus jobStatus); public abstract void dumpControllerState(PrintWriter pw); - } diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java index 4c6cb17..b3d7287 100644 --- a/services/core/java/com/android/server/job/controllers/TimeController.java +++ b/services/core/java/com/android/server/job/controllers/TimeController.java @@ -91,14 +91,20 @@ public class TimeController extends StateController { public synchronized void maybeStartTrackingJob(JobStatus job) { if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) { maybeStopTrackingJob(job); + boolean isInsert = false; ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size()); while (it.hasPrevious()) { JobStatus ts = it.previous(); if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) { // Insert + isInsert = true; break; } } + if(isInsert) + { + it.next(); + } it.add(job); maybeUpdateAlarms( job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE, diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java index 9dcc529..ed884ef 100644 --- a/services/core/java/com/android/server/lights/LightsService.java +++ b/services/core/java/com/android/server/lights/LightsService.java @@ -19,16 +19,11 @@ package com.android.server.lights; import com.android.server.SystemService; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Handler; -import android.os.IHardwareService; import android.os.Message; import android.os.Trace; import android.util.Slog; -import java.io.FileInputStream; -import java.io.FileOutputStream; - public class LightsService extends SystemService { static final String TAG = "LightsService"; static final boolean DEBUG = false; @@ -106,7 +101,8 @@ public class LightsService extends SystemService { mMode = mode; mOnMS = onMS; mOffMS = offMS; - Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", " + color + ")"); + Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x" + + Integer.toHexString(color) + ")"); try { setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode); } finally { @@ -123,46 +119,6 @@ public class LightsService extends SystemService { private boolean mFlashing; } - /* This class implements an obsolete API that was removed after eclair and re-added during the - * final moments of the froyo release to support flashlight apps that had been using the private - * IHardwareService API. This is expected to go away in the next release. - */ - private final IHardwareService.Stub mLegacyFlashlightHack = new IHardwareService.Stub() { - - private static final String FLASHLIGHT_FILE = "/sys/class/leds/spotlight/brightness"; - - public boolean getFlashlightEnabled() { - try { - FileInputStream fis = new FileInputStream(FLASHLIGHT_FILE); - int result = fis.read(); - fis.close(); - return (result != '0'); - } catch (Exception e) { - return false; - } - } - - public void setFlashlightEnabled(boolean on) { - final Context context = getContext(); - if (context.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT) - != PackageManager.PERMISSION_GRANTED && - context.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission"); - } - try { - FileOutputStream fos = new FileOutputStream(FLASHLIGHT_FILE); - byte[] bytes = new byte[2]; - bytes[0] = (byte)(on ? '1' : '0'); - bytes[1] = '\n'; - fos.write(bytes); - fos.close(); - } catch (Exception e) { - // fail silently - } - } - }; - public LightsService(Context context) { super(context); @@ -175,13 +131,12 @@ public class LightsService extends SystemService { @Override public void onStart() { - publishBinderService("hardware", mLegacyFlashlightHack); publishLocalService(LightsManager.class, mService); } private final LightsManager mService = new LightsManager() { @Override - public com.android.server.lights.Light getLight(int id) { + public Light getLight(int id) { if (id < LIGHT_ID_COUNT) { return mLights[id]; } else { diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java index 530ad4b..1fb22be 100644 --- a/services/core/java/com/android/server/location/FlpHardwareProvider.java +++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java @@ -42,8 +42,13 @@ import android.util.Log; * {@hide} */ public class FlpHardwareProvider { + private static final int FIRST_VERSION_WITH_FLUSH_LOCATIONS = 2; private GeofenceHardwareImpl mGeofenceHardwareSink = null; private IFusedLocationHardwareSink mLocationSink = null; + // Capabilities provided by FlpCallbacks + private boolean mHaveBatchingCapabilities; + private int mBatchingCapabilities; + private int mVersion; private static FlpHardwareProvider sSingletonInstance = null; @@ -124,6 +129,52 @@ public class FlpHardwareProvider { } } + private void onBatchingCapabilities(int capabilities) { + synchronized (mLocationSinkLock) { + mHaveBatchingCapabilities = true; + mBatchingCapabilities = capabilities; + } + + maybeSendCapabilities(); + } + + private void onBatchingStatus(int status) { + IFusedLocationHardwareSink sink; + synchronized (mLocationSinkLock) { + sink = mLocationSink; + } + try { + if (sink != null) { + sink.onStatusChanged(status); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling onBatchingStatus"); + } + } + + private void setVersion(int version) { + mVersion = version; + getGeofenceHardwareSink().setVersion(version); + } + + private void maybeSendCapabilities() { + IFusedLocationHardwareSink sink; + boolean haveBatchingCapabilities; + int batchingCapabilities; + synchronized (mLocationSinkLock) { + sink = mLocationSink; + haveBatchingCapabilities = mHaveBatchingCapabilities; + batchingCapabilities = mBatchingCapabilities; + } + try { + if (sink != null && haveBatchingCapabilities) { + sink.onCapabilities(batchingCapabilities); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling onLocationAvailable"); + } + } + // FlpDiagnosticCallbacks members private void onDataReport(String data) { IFusedLocationHardwareSink sink; @@ -209,6 +260,10 @@ public class FlpHardwareProvider { translateToGeofenceHardwareStatus(result)); } + private void onGeofencingCapabilities(int capabilities) { + getGeofenceHardwareSink().onCapabilities(capabilities); + } + /** * Private native methods accessing FLP HAL. */ @@ -225,6 +280,7 @@ public class FlpHardwareProvider { private native void nativeUpdateBatchingOptions(int requestId, FusedBatchOptions optionsObject); private native void nativeStopBatching(int id); private native void nativeRequestBatchedLocation(int lastNLocations); + private native void nativeFlushBatchedLocations(); private native void nativeInjectLocation(Location location); // TODO [Fix] sort out the lifetime of the instance private native void nativeCleanup(); @@ -277,6 +333,7 @@ public class FlpHardwareProvider { mLocationSink = eventSink; } + maybeSendCapabilities(); } @Override @@ -315,6 +372,16 @@ public class FlpHardwareProvider { } @Override + public void flushBatchedLocations() { + if (mVersion >= FIRST_VERSION_WITH_FLUSH_LOCATIONS) { + nativeFlushBatchedLocations(); + } else { + Log.wtf(TAG, + "Tried to call flushBatchedLocations on an unsupported implementation"); + } + } + + @Override public boolean supportsDiagnosticDataInjection() { return nativeIsDiagnosticSupported(); } @@ -333,6 +400,11 @@ public class FlpHardwareProvider { public void injectDeviceContext(int deviceEnabledContext) { nativeInjectDeviceContext(deviceEnabledContext); } + + @Override + public int getVersion() { + return mVersion; + } }; private final IFusedGeofenceHardware mGeofenceHardwareService = diff --git a/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java b/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java index 389bd24..a08d326 100644 --- a/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java +++ b/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java @@ -116,4 +116,16 @@ public class FusedLocationHardwareSecure extends IFusedLocationHardware.Stub { checkPermissions(); mLocationHardware.injectDeviceContext(deviceEnabledContext); } + + @Override + public void flushBatchedLocations() throws RemoteException { + checkPermissions(); + mLocationHardware.flushBatchedLocations(); + } + + @Override + public int getVersion() throws RemoteException { + checkPermissions(); + return mLocationHardware.getVersion(); + } } diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index 5ae6300..d3240ec 100644 --- a/services/core/java/com/android/server/location/GpsLocationProvider.java +++ b/services/core/java/com/android/server/location/GpsLocationProvider.java @@ -22,10 +22,8 @@ import com.android.internal.location.GpsNetInitiatedHandler; import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; -import com.android.internal.R; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; -import com.android.internal.telephony.TelephonyIntents; import android.app.AlarmManager; import android.app.AppOpsManager; @@ -72,7 +70,6 @@ import android.provider.Settings; import android.provider.Telephony.Carriers; import android.provider.Telephony.Sms.Intents; import android.telephony.SmsMessage; -import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; import android.telephony.TelephonyManager; @@ -91,7 +88,6 @@ import java.io.StringReader; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Date; -import java.util.List; import java.util.Map.Entry; import java.util.Properties; @@ -201,6 +197,8 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final int REMOVE_LISTENER = 9; private static final int INJECT_NTP_TIME_FINISHED = 10; private static final int DOWNLOAD_XTRA_DATA_FINISHED = 11; + private static final int SUBSCRIPTION_OR_SIM_CHANGED = 12; + private static final int INITIALIZE_HANDLER = 13; // Request setid private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1; @@ -342,8 +340,12 @@ public class GpsLocationProvider implements LocationProviderInterface { // True if gps should be disabled (used to support battery saver mode in settings). private boolean mDisableGps = false; - // properties loaded from PROPERTIES_FILE + /** + * Properties loaded from PROPERTIES_FILE. + * It must be accessed only inside {@link #mHandler}. + */ private Properties mProperties; + private String mSuplServerHost; private int mSuplServerPort = TCP_MIN_PORT; private String mC2KServerHost; @@ -395,7 +397,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() { @Override - public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException { + public void addGpsStatusListener(IGpsStatusListener listener) { mListenerHelper.addListener(listener); } @@ -434,7 +436,7 @@ public class GpsLocationProvider implements LocationProviderInterface { checkSmsSuplInit(intent); } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) { checkWapSuplInit(intent); - } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE)) { + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { // retrieve NetworkInfo result for this UID NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); @@ -466,7 +468,7 @@ public class GpsLocationProvider implements LocationProviderInterface { new OnSubscriptionsChangedListener() { @Override public void onSubscriptionsChanged() { - subscriptionOrSimChanged(mContext); + sendMessage(SUBSCRIPTION_OR_SIM_CHANGED, 0, null); } }; @@ -548,14 +550,19 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - try { - // Convert properties to string contents and send it to HAL. - ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); - properties.store(baos, null); - native_configuration_update(baos.toString()); - Log.d(TAG, "final config = " + baos.toString()); - } catch (IOException ex) { - Log.w(TAG, "failed to dump properties contents"); + if (native_is_gnss_configuration_supported()) { + try { + // Convert properties to string contents and send it to HAL. + ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); + properties.store(baos, null); + native_configuration_update(baos.toString()); + Log.d(TAG, "final config = " + baos.toString()); + } catch (IOException ex) { + Log.w(TAG, "failed to dump properties contents"); + } + } else if (DEBUG) { + Log.d(TAG, "Skipped configuration update because GNSS configuration in GPS HAL is not" + + " supported"); } // SUPL_ES configuration. @@ -631,57 +638,26 @@ public class GpsLocationProvider implements LocationProviderInterface { mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); - // Load GPS configuration. + // Construct internal handler + mHandler = new ProviderHandler(looper); + + // Load GPS configuration and register listeners in the background: + // some operations, such as opening files and registering broadcast receivers, can take a + // relative long time, so the ctor() is kept to create objects needed by this instance, + // while IO initialization and registration is delegated to our internal handler + // this approach is just fine because events are posted to our handler anyway mProperties = new Properties(); - reloadGpsProperties(mContext, mProperties); + sendMessage(INITIALIZE_HANDLER, 0, null); // Create a GPS net-initiated handler. mNIHandler = new GpsNetInitiatedHandler(context, mNetInitiatedListener, mSuplEsEnabled); - // TODO: When this object "finishes" we should unregister by invoking - // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener); - // This is not strictly necessary because it will be unregistered if the - // notification fails but it is good form. - - // Register for SubscriptionInfo list changes which is guaranteed - // to invoke onSubscriptionsChanged the first time. - SubscriptionManager.from(mContext) - .addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); - - // construct handler, listen for events - mHandler = new ProviderHandler(looper); - listenForBroadcasts(); - - // also listen for PASSIVE_PROVIDER updates - mHandler.post(new Runnable() { - @Override - public void run() { - LocationManager locManager = - (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); - locManager.requestLocationUpdates( - request, - new NetworkLocationListener(), - mHandler.getLooper()); - } - }); - mListenerHelper = new GpsStatusListenerHelper(mHandler) { @Override protected boolean isAvailableInPlatform() { - return GpsLocationProvider.isSupported(); + return isSupported(); } @Override @@ -735,33 +711,6 @@ public class GpsLocationProvider implements LocationProviderInterface { }; } - private void listenForBroadcasts() { - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION); - intentFilter.addDataScheme("sms"); - intentFilter.addDataAuthority("localhost","7275"); - mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler); - - intentFilter = new IntentFilter(); - intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); - try { - intentFilter.addDataType("application/vnd.omaloc-supl-init"); - } catch (IntentFilter.MalformedMimeTypeException e) { - Log.w(TAG, "Malformed SUPL init mime type"); - } - mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler); - - intentFilter = new IntentFilter(); - intentFilter.addAction(ALARM_WAKEUP); - intentFilter.addAction(ALARM_TIMEOUT); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE); - intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); - intentFilter.addAction(Intent.ACTION_SCREEN_ON); - intentFilter.addAction(SIM_STATE_CHANGED); - mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler); - } - /** * Returns the name of this provider. */ @@ -788,16 +737,21 @@ public class GpsLocationProvider implements LocationProviderInterface { } if (info != null) { - boolean dataEnabled = TelephonyManager.getDefault().getDataEnabled(); - boolean networkAvailable = info.isAvailable() && dataEnabled; - String defaultApn = getSelectedApn(); - if (defaultApn == null) { - defaultApn = "dummy-apn"; - } + if (native_is_agps_ril_supported()) { + boolean dataEnabled = TelephonyManager.getDefault().getDataEnabled(); + boolean networkAvailable = info.isAvailable() && dataEnabled; + String defaultApn = getSelectedApn(); + if (defaultApn == null) { + defaultApn = "dummy-apn"; + } - native_update_network_state(info.isConnected(), info.getType(), - info.isRoaming(), networkAvailable, - info.getExtraInfo(), defaultApn); + native_update_network_state(info.isConnected(), info.getType(), + info.isRoaming(), networkAvailable, + info.getExtraInfo(), defaultApn); + } else if (DEBUG) { + Log.d(TAG, "Skipped network state update because AGPS-RIL in GPS HAL is not" + + " supported"); + } } if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL @@ -914,7 +868,7 @@ public class GpsLocationProvider implements LocationProviderInterface { AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { @Override public void run() { - GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties); + GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mProperties); byte[] data = xtraDownloader.downloadXtraData(); if (data != null) { if (DEBUG) { @@ -1025,6 +979,9 @@ public class GpsLocationProvider implements LocationProviderInterface { if (mC2KServerHost != null) { native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); } + + mGpsMeasurementsProvider.onGpsEnabledChanged(); + mGpsNavigationMessageProvider.onGpsEnabledChanged(); } else { synchronized (mLock) { mEnabled = false; @@ -1058,6 +1015,9 @@ public class GpsLocationProvider implements LocationProviderInterface { // do this before releasing wakelock native_cleanup(); + + mGpsMeasurementsProvider.onGpsEnabledChanged(); + mGpsNavigationMessageProvider.onGpsEnabledChanged(); } @Override @@ -1477,9 +1437,7 @@ public class GpsLocationProvider implements LocationProviderInterface { } if (wasNavigating != mNavigating) { - mListenerHelper.onGpsEnabledChanged(mNavigating); - mGpsMeasurementsProvider.onGpsEnabledChanged(mNavigating); - mGpsNavigationMessageProvider.onGpsEnabledChanged(mNavigating); + mListenerHelper.onStatusChanged(mNavigating); // send an intent to notify that the GPS has been enabled or disabled Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); @@ -1804,7 +1762,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // NI Client support //============================================================= private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { - // Sends a response for an NI reqeust to HAL. + // Sends a response for an NI request to HAL. @Override public boolean sendNiResponse(int notificationId, int userResponse) { @@ -1895,7 +1853,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private void requestSetID(int flags) { TelephonyManager phone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - int type = AGPS_SETID_TYPE_NONE; + int type = AGPS_SETID_TYPE_NONE; String data = ""; if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) { @@ -2012,13 +1970,91 @@ public class GpsLocationProvider implements LocationProviderInterface { case UPDATE_LOCATION: handleUpdateLocation((Location)msg.obj); break; + case SUBSCRIPTION_OR_SIM_CHANGED: + subscriptionOrSimChanged(mContext); + break; + case INITIALIZE_HANDLER: + initialize(); + break; } if (msg.arg2 == 1) { // wakelock was taken for this message, release it mWakeLock.release(); } } - }; + + /** + * This method is bound to {@link #GpsLocationProvider(Context, ILocationManager, Looper)}. + * It is in charge of loading properties and registering for events that will be posted to + * this handler. + */ + private void initialize() { + // load default GPS configuration + // (this configuration might change in the future based on SIM changes) + reloadGpsProperties(mContext, mProperties); + + // TODO: When this object "finishes" we should unregister by invoking + // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener); + // This is not strictly necessary because it will be unregistered if the + // notification fails but it is good form. + + // Register for SubscriptionInfo list changes which is guaranteed + // to invoke onSubscriptionsChanged the first time. + SubscriptionManager.from(mContext) + .addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); + + // listen for events + IntentFilter intentFilter; + if (native_is_agps_ril_supported()) { + intentFilter = new IntentFilter(); + intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION); + intentFilter.addDataScheme("sms"); + intentFilter.addDataAuthority("localhost", "7275"); + mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this); + + intentFilter = new IntentFilter(); + intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION); + try { + intentFilter.addDataType("application/vnd.omaloc-supl-init"); + } catch (IntentFilter.MalformedMimeTypeException e) { + Log.w(TAG, "Malformed SUPL init mime type"); + } + mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this); + } else if (DEBUG) { + Log.d(TAG, "Skipped registration for SMS/WAP-PUSH messages because AGPS Ril in GPS" + + " HAL is not supported"); + } + + intentFilter = new IntentFilter(); + intentFilter.addAction(ALARM_WAKEUP); + intentFilter.addAction(ALARM_TIMEOUT); + intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(SIM_STATE_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this); + + // listen for PASSIVE_PROVIDER updates + LocationManager locManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + long minTime = 0; + float minDistance = 0; + 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(), + getLooper()); + } + } private final class NetworkLocationListener implements LocationListener { @Override @@ -2167,6 +2203,8 @@ public class GpsLocationProvider implements LocationProviderInterface { static { class_init_native(); } private static native void class_init_native(); private static native boolean native_is_supported(); + private static native boolean native_is_agps_ril_supported(); + private static native boolean native_is_gnss_configuration_supported(); private native boolean native_init(); private native void native_cleanup(); diff --git a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java index 0514e0c..b327ca2 100644 --- a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java @@ -33,7 +33,7 @@ public abstract class GpsMeasurementsProvider extends RemoteListenerHelper<IGpsMeasurementsListener> { private static final String TAG = "GpsMeasurementsProvider"; - public GpsMeasurementsProvider(Handler handler) { + protected GpsMeasurementsProvider(Handler handler) { super(handler, TAG); } @@ -49,15 +49,19 @@ public abstract class GpsMeasurementsProvider } public void onCapabilitiesUpdated(boolean isGpsMeasurementsSupported) { - int status = isGpsMeasurementsSupported ? - GpsMeasurementsEvent.STATUS_READY : - GpsMeasurementsEvent.STATUS_NOT_SUPPORTED; - setSupported(isGpsMeasurementsSupported, new StatusChangedOperation(status)); + setSupported(isGpsMeasurementsSupported); + updateResult(); + } + + public void onGpsEnabledChanged() { + if (tryUpdateRegistrationWithService()) { + updateResult(); + } } @Override protected ListenerOperation<IGpsMeasurementsListener> getHandlerOperation(int result) { - final int status; + int status; switch (result) { case RESULT_SUCCESS: status = GpsMeasurementsEvent.STATUS_READY; @@ -70,6 +74,8 @@ public abstract class GpsMeasurementsProvider case RESULT_GPS_LOCATION_DISABLED: status = GpsMeasurementsEvent.STATUS_GPS_LOCATION_DISABLED; break; + case RESULT_UNKNOWN: + return null; default: Log.v(TAG, "Unhandled addListener result: " + result); return null; @@ -77,15 +83,8 @@ public abstract class GpsMeasurementsProvider return new StatusChangedOperation(status); } - @Override - protected void handleGpsEnabledChanged(boolean enabled) { - int status = enabled ? - GpsMeasurementsEvent.STATUS_READY : - GpsMeasurementsEvent.STATUS_GPS_LOCATION_DISABLED; - foreach(new StatusChangedOperation(status)); - } - - private class StatusChangedOperation implements ListenerOperation<IGpsMeasurementsListener> { + private static class StatusChangedOperation + implements ListenerOperation<IGpsMeasurementsListener> { private final int mStatus; public StatusChangedOperation(int status) { diff --git a/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java b/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java index 13d22fc..e6bbe56 100644 --- a/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java @@ -33,7 +33,7 @@ public abstract class GpsNavigationMessageProvider extends RemoteListenerHelper<IGpsNavigationMessageListener> { private static final String TAG = "GpsNavigationMessageProvider"; - public GpsNavigationMessageProvider(Handler handler) { + protected GpsNavigationMessageProvider(Handler handler) { super(handler, TAG); } @@ -50,15 +50,19 @@ public abstract class GpsNavigationMessageProvider } public void onCapabilitiesUpdated(boolean isGpsNavigationMessageSupported) { - int status = isGpsNavigationMessageSupported ? - GpsNavigationMessageEvent.STATUS_READY : - GpsNavigationMessageEvent.STATUS_NOT_SUPPORTED; - setSupported(isGpsNavigationMessageSupported, new StatusChangedOperation(status)); + setSupported(isGpsNavigationMessageSupported); + updateResult(); + } + + public void onGpsEnabledChanged() { + if (tryUpdateRegistrationWithService()) { + updateResult(); + } } @Override protected ListenerOperation<IGpsNavigationMessageListener> getHandlerOperation(int result) { - final int status; + int status; switch (result) { case RESULT_SUCCESS: status = GpsNavigationMessageEvent.STATUS_READY; @@ -71,6 +75,8 @@ public abstract class GpsNavigationMessageProvider case RESULT_GPS_LOCATION_DISABLED: status = GpsNavigationMessageEvent.STATUS_GPS_LOCATION_DISABLED; break; + case RESULT_UNKNOWN: + return null; default: Log.v(TAG, "Unhandled addListener result: " + result); return null; @@ -78,15 +84,7 @@ public abstract class GpsNavigationMessageProvider return new StatusChangedOperation(status); } - @Override - protected void handleGpsEnabledChanged(boolean enabled) { - int status = enabled ? - GpsNavigationMessageEvent.STATUS_READY : - GpsNavigationMessageEvent.STATUS_GPS_LOCATION_DISABLED; - foreach(new StatusChangedOperation(status)); - } - - private class StatusChangedOperation + private static class StatusChangedOperation implements ListenerOperation<IGpsNavigationMessageListener> { private final int mStatus; diff --git a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java index 376b4a5..53ff6c2 100644 --- a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java +++ b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java @@ -24,14 +24,9 @@ import android.os.RemoteException; * Implementation of a handler for {@link IGpsStatusListener}. */ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusListener> { - public GpsStatusListenerHelper(Handler handler) { + protected GpsStatusListenerHelper(Handler handler) { super(handler, "GpsStatusListenerHelper"); - - Operation nullOperation = new Operation() { - @Override - public void execute(IGpsStatusListener iGpsStatusListener) throws RemoteException {} - }; - setSupported(GpsLocationProvider.isSupported(), nullOperation); + setSupported(GpsLocationProvider.isSupported()); } @Override @@ -47,10 +42,9 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi return null; } - @Override - protected void handleGpsEnabledChanged(boolean enabled) { + public void onStatusChanged(boolean isNavigating) { Operation operation; - if (enabled) { + if (isNavigating) { operation = new Operation() { @Override public void execute(IGpsStatusListener listener) throws RemoteException { @@ -114,5 +108,5 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi foreach(operation); } - private abstract class Operation implements ListenerOperation<IGpsStatusListener> { } + private interface Operation extends ListenerOperation<IGpsStatusListener> {} } diff --git a/services/core/java/com/android/server/location/GpsXtraDownloader.java b/services/core/java/com/android/server/location/GpsXtraDownloader.java index a5eef6a..3585049 100644 --- a/services/core/java/com/android/server/location/GpsXtraDownloader.java +++ b/services/core/java/com/android/server/location/GpsXtraDownloader.java @@ -16,21 +16,13 @@ package com.android.server.location; -import android.content.Context; -import android.net.Proxy; -import android.net.http.AndroidHttpClient; import android.text.TextUtils; import android.util.Log; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.params.ConnRouteParams; +import java.net.HttpURLConnection; +import java.net.URL; +import libcore.io.Streams; -import java.io.DataInputStream; import java.io.IOException; import java.util.Properties; import java.util.Random; @@ -46,15 +38,12 @@ public class GpsXtraDownloader { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String DEFAULT_USER_AGENT = "Android"; - private final Context mContext; private final String[] mXtraServers; // to load balance our server requests private int mNextServerIndex; private final String mUserAgent; - GpsXtraDownloader(Context context, Properties properties) { - mContext = context; - + GpsXtraDownloader(Properties properties) { // read XTRA servers from the Properties object int count = 0; String server1 = properties.getProperty("XTRA_SERVER_1"); @@ -75,7 +64,6 @@ public class GpsXtraDownloader { if (count == 0) { Log.e(TAG, "No XTRA servers were specified in the GPS configuration"); mXtraServers = null; - return; } else { mXtraServers = new String[count]; count = 0; @@ -90,9 +78,6 @@ public class GpsXtraDownloader { } byte[] downloadXtraData() { - String proxyHost = Proxy.getHost(mContext); - int proxyPort = Proxy.getPort(mContext); - boolean useProxy = (proxyHost != null && proxyPort != -1); byte[] result = null; int startIndex = mNextServerIndex; @@ -102,7 +87,7 @@ public class GpsXtraDownloader { // load balance our requests among the available servers while (result == null) { - result = doDownload(mXtraServers[mNextServerIndex], useProxy, proxyHost, proxyPort); + result = doDownload(mXtraServers[mNextServerIndex]); // increment mNextServerIndex and wrap around if necessary mNextServerIndex++; @@ -116,65 +101,32 @@ public class GpsXtraDownloader { return result; } - protected byte[] doDownload(String url, boolean isProxySet, - String proxyHost, int proxyPort) { + protected byte[] doDownload(String url) { if (DEBUG) Log.d(TAG, "Downloading XTRA data from " + url); - AndroidHttpClient client = null; + HttpURLConnection connection = null; try { - if (DEBUG) Log.d(TAG, "XTRA user agent: " + mUserAgent); - client = AndroidHttpClient.newInstance(mUserAgent); - HttpUriRequest req = new HttpGet(url); - - if (isProxySet) { - HttpHost proxy = new HttpHost(proxyHost, proxyPort); - ConnRouteParams.setDefaultProxy(req.getParams(), proxy); - } - - req.addHeader( + connection = (HttpURLConnection) (new URL(url)).openConnection(); + connection.setRequestProperty( "Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"); - - req.addHeader( + connection.setRequestProperty( "x-wap-profile", "http://www.openmobilealliance.org/tech/profiles/UAPROF/ccppschema-20021212#"); - HttpResponse response = client.execute(req); - StatusLine status = response.getStatusLine(); - if (status.getStatusCode() != 200) { // HTTP 200 is success. - if (DEBUG) Log.d(TAG, "HTTP error: " + status.getReasonPhrase()); + connection.connect(); + int statusCode = connection.getResponseCode(); + if (statusCode != HttpURLConnection.HTTP_OK) { + if (DEBUG) Log.d(TAG, "HTTP error downloading gps XTRA: " + statusCode); return null; } - HttpEntity entity = response.getEntity(); - byte[] body = null; - if (entity != null) { - try { - if (entity.getContentLength() > 0) { - body = new byte[(int) entity.getContentLength()]; - DataInputStream dis = new DataInputStream(entity.getContent()); - try { - dis.readFully(body); - } finally { - try { - dis.close(); - } catch (IOException e) { - Log.e(TAG, "Unexpected IOException.", e); - } - } - } - } finally { - if (entity != null) { - entity.consumeContent(); - } - } - } - return body; - } catch (Exception e) { - if (DEBUG) Log.d(TAG, "error " + e); + return Streams.readFully(connection.getInputStream()); + } catch (IOException ioe) { + if (DEBUG) Log.d(TAG, "Error downloading gps XTRA: ", ioe); } finally { - if (client != null) { - client.close(); + if (connection != null) { + connection.disconnect(); } } return null; diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java index 402b601..ec2828b 100644 --- a/services/core/java/com/android/server/location/RemoteListenerHelper.java +++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java @@ -26,26 +26,31 @@ import android.os.RemoteException; import android.util.Log; import java.util.HashMap; +import java.util.Map; /** * A helper class, that handles operations in remote listeners, and tracks for remote process death. */ abstract class RemoteListenerHelper<TListener extends IInterface> { + protected static final int RESULT_SUCCESS = 0; protected static final int RESULT_NOT_AVAILABLE = 1; protected static final int RESULT_NOT_SUPPORTED = 2; protected static final int RESULT_GPS_LOCATION_DISABLED = 3; protected static final int RESULT_INTERNAL_ERROR = 4; + protected static final int RESULT_UNKNOWN = 5; private final Handler mHandler; private final String mTag; - private final HashMap<IBinder, LinkedListener> mListenerMap = new HashMap<>(); + private final Map<IBinder, LinkedListener> mListenerMap = new HashMap<>(); private boolean mIsRegistered; private boolean mHasIsSupported; private boolean mIsSupported; + private int mLastReportedResult = RESULT_UNKNOWN; + protected RemoteListenerHelper(Handler handler, String name) { Preconditions.checkNotNull(name); mHandler = handler; @@ -110,33 +115,11 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { } } - public void onGpsEnabledChanged(boolean enabled) { - // handle first the sub-class implementation, so any error in registration can take - // precedence - handleGpsEnabledChanged(enabled); - synchronized (mListenerMap) { - if (!enabled) { - tryUnregister(); - return; - } - if (mListenerMap.isEmpty()) { - return; - } - if (tryRegister()) { - // registration was successful, there is no need to update the state - return; - } - ListenerOperation<TListener> operation = getHandlerOperation(RESULT_INTERNAL_ERROR); - foreachUnsafe(operation); - } - } - protected abstract boolean isAvailableInPlatform(); protected abstract boolean isGpsEnabled(); protected abstract boolean registerWithService(); protected abstract void unregisterFromService(); protected abstract ListenerOperation<TListener> getHandlerOperation(int result); - protected abstract void handleGpsEnabledChanged(boolean enabled); protected interface ListenerOperation<TListener extends IInterface> { void execute(TListener listener) throws RemoteException; @@ -148,11 +131,40 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { } } - protected void setSupported(boolean value, ListenerOperation<TListener> notifier) { + protected void setSupported(boolean value) { synchronized (mListenerMap) { mHasIsSupported = true; mIsSupported = value; - foreachUnsafe(notifier); + } + } + + protected boolean tryUpdateRegistrationWithService() { + synchronized (mListenerMap) { + if (!isGpsEnabled()) { + tryUnregister(); + return true; + } + if (mListenerMap.isEmpty()) { + return true; + } + if (tryRegister()) { + // registration was successful, there is no need to update the state + return true; + } + ListenerOperation<TListener> operation = getHandlerOperation(RESULT_INTERNAL_ERROR); + foreachUnsafe(operation); + return false; + } + } + + protected void updateResult() { + synchronized (mListenerMap) { + int newResult = calculateCurrentResultUnsafe(); + if (mLastReportedResult == newResult) { + return; + } + foreachUnsafe(getHandlerOperation(newResult)); + mLastReportedResult = newResult; } } @@ -183,6 +195,24 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { mIsRegistered = false; } + private int calculateCurrentResultUnsafe() { + // update statuses we already know about, starting from the ones that will never change + if (!isAvailableInPlatform()) { + return RESULT_NOT_AVAILABLE; + } + if (!mHasIsSupported || mListenerMap.isEmpty()) { + // we'll update once we have a supported status available + return RESULT_UNKNOWN; + } + if (!mIsSupported) { + return RESULT_NOT_SUPPORTED; + } + if (!isGpsEnabled()) { + return RESULT_GPS_LOCATION_DISABLED; + } + return RESULT_SUCCESS; + } + private class LinkedListener implements IBinder.DeathRecipient { private final TListener mListener; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 53ae1ab..09d0501 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -17,7 +17,6 @@ package com.android.server.media; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -28,6 +27,9 @@ import android.media.MediaDescription; import android.media.MediaMetadata; import android.media.Rating; import android.media.VolumeProvider; +import android.media.routing.IMediaRouter; +import android.media.routing.IMediaRouterDelegate; +import android.media.routing.IMediaRouterStateCallback; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionController; @@ -35,7 +37,6 @@ import android.media.session.ISessionControllerCallback; import android.media.session.MediaController; import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession; -import android.media.session.MediaSessionManager; import android.media.session.ParcelableVolumeInfo; import android.media.session.PlaybackState; import android.media.AudioAttributes; @@ -58,7 +59,6 @@ import com.android.server.LocalServices; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.UUID; /** * This is the system implementation of a Session. Apps will interact with the @@ -91,7 +91,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private final SessionStub mSession; private final SessionCb mSessionCb; private final MediaSessionService mService; - private final boolean mUseMasterVolume; private final Object mLock = new Object(); private final ArrayList<ISessionControllerCallback> mControllerCallbacks = @@ -141,8 +140,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { mAudioManager = (AudioManager) service.getContext().getSystemService(Context.AUDIO_SERVICE); mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); - mUseMasterVolume = service.getContext().getResources().getBoolean( - com.android.internal.R.bool.config_useMasterVolume); } /** @@ -246,77 +243,30 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { if (isPlaybackActive(false) || hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) { flags &= ~AudioManager.FLAG_PLAY_SOUND; } - boolean isMute = direction == MediaSessionManager.DIRECTION_MUTE; - if (direction > 1) { - direction = 1; - } else if (direction < -1) { - direction = -1; - } if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { - if (mUseMasterVolume) { - // If this device only uses master volume and playback is local - // just adjust the master volume and return. - boolean isMasterMute = mAudioManager.isMasterMute(); - if (isMute) { - mAudioManagerInternal.setMasterMuteForUid(!isMasterMute, - flags, packageName, mService.mICallback, uid); - } else { - mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName, - uid); - if (isMasterMute) { - mAudioManagerInternal.setMasterMuteForUid(false, - flags, packageName, mService.mICallback, uid); - } - } - return; - } int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); - boolean isStreamMute = mAudioManager.isStreamMute(stream); if (useSuggested) { if (AudioSystem.isStreamActive(stream, 0)) { - if (isMute) { - mAudioManager.setStreamMute(stream, !isStreamMute); - } else { - mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction, - flags, packageName, uid); - if (isStreamMute && direction != 0) { - mAudioManager.setStreamMute(stream, false); - } - } + mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction, + flags, packageName, uid); } else { flags |= previousFlagPlaySound; - isStreamMute = - mAudioManager.isStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE); - if (isMute) { - mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE, - !isStreamMute); - } else { - mAudioManagerInternal.adjustSuggestedStreamVolumeForUid( - AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName, - uid); - if (isStreamMute && direction != 0) { - mAudioManager.setStreamMute(AudioManager.USE_DEFAULT_STREAM_TYPE, - false); - } - } + mAudioManagerInternal.adjustSuggestedStreamVolumeForUid( + AudioManager.USE_DEFAULT_STREAM_TYPE, direction, flags, packageName, + uid); } } else { - if (isMute) { - mAudioManager.setStreamMute(stream, !isStreamMute); - } else { - mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, - packageName, uid); - if (isStreamMute && direction != 0) { - mAudioManager.setStreamMute(stream, false); - } - } + mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, + packageName, uid); } } else { if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) { // Nothing to do, the volume cannot be changed return; } - if (isMute) { + if (direction == AudioManager.ADJUST_TOGGLE_MUTE + || direction == AudioManager.ADJUST_MUTE + || direction == AudioManager.ADJUST_UNMUTE) { Log.w(TAG, "Muting remote playback is not supported"); return; } @@ -768,6 +718,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override + public void setMediaRouter(IMediaRouter router) { + mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); + } + + @Override public void setMediaButtonReceiver(PendingIntent pi) { mMediaButtonReceiver = pi; } @@ -854,6 +809,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } if (typeChanged) { mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this); + mHandler.post(MessageHandler.MSG_UPDATE_VOLUME); } } @@ -868,6 +824,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } if (typeChanged) { mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this); + mHandler.post(MessageHandler.MSG_UPDATE_VOLUME); } } } @@ -931,6 +888,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } } + public void playFromUri(Uri uri, Bundle extras) { + try { + mCb.onPlayFromUri(uri, extras); + } catch (RemoteException e) { + Slog.e(TAG, "Remote failure in playFromUri.", e); + } + } + public void skipToTrack(long id) { try { mCb.onSkipToTrack(id); @@ -1147,6 +1112,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override + public void playFromUri(Uri uri, Bundle extras) throws RemoteException { + mSessionCb.playFromUri(uri, extras); + } + + @Override public void skipToQueueItem(long id) { mSessionCb.skipToTrack(id); } @@ -1239,6 +1209,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { public boolean isTransportControlEnabled() { return MediaSessionRecord.this.isTransportControlEnabled(); } + + @Override + public IMediaRouterDelegate createMediaRouterDelegate( + IMediaRouterStateCallback callback) { + // todo + return null; + } } private class MessageHandler extends Handler { diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 72e4b4b..65949bf 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -31,6 +31,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.media.AudioManager; +import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IRemoteVolumeController; @@ -38,9 +39,7 @@ import android.media.session.IActiveSessionsListener; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionManager; -import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession; -import android.media.session.MediaSessionManager; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -60,6 +59,7 @@ import android.util.Slog; import android.util.SparseArray; import android.view.KeyEvent; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; import com.android.server.Watchdog.Monitor; @@ -90,11 +90,10 @@ public class MediaSessionService extends SystemService implements Monitor { private final Object mLock = new Object(); private final MessageHandler mHandler = new MessageHandler(); private final PowerManager.WakeLock mMediaEventWakeLock; - private final boolean mUseMasterVolume; private KeyguardManager mKeyguardManager; private IAudioService mAudioService; - private AudioManager mAudioManager; + private AudioManagerInternal mAudioManagerInternal; private ContentResolver mContentResolver; private SettingsObserver mSettingsObserver; @@ -110,22 +109,21 @@ public class MediaSessionService extends SystemService implements Monitor { mPriorityStack = new MediaSessionStack(); PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); - mUseMasterVolume = context.getResources().getBoolean( - com.android.internal.R.bool.config_useMasterVolume); } @Override public void onStart() { publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl); Watchdog.getInstance().addMonitor(this); - updateUser(); mKeyguardManager = (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); mAudioService = getAudioService(); - mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); mContentResolver = getContext().getContentResolver(); mSettingsObserver = new SettingsObserver(); mSettingsObserver.observe(); + + updateUser(); } private IAudioService getAudioService() { @@ -334,6 +332,7 @@ public class MediaSessionService extends SystemService implements Monitor { */ private void enforceMediaPermissions(ComponentName compName, int pid, int uid, int resolvedUserId) { + if (isCurrentVolumeController(uid)) return; if (getContext() .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) != PackageManager.PERMISSION_GRANTED @@ -343,7 +342,18 @@ public class MediaSessionService extends SystemService implements Monitor { } } - private void enforceStatusBarPermission(String action, int pid, int uid) { + private boolean isCurrentVolumeController(int uid) { + if (mAudioManagerInternal != null) { + final int vcuid = mAudioManagerInternal.getVolumeControllerUid(); + if (vcuid > 0 && uid == vcuid) { + return true; + } + } + return false; + } + + private void enforceSystemUiPermission(String action, int pid, int uid) { + if (isCurrentVolumeController(uid)) return; if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE, pid, uid) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Only system ui may " + action); @@ -454,11 +464,6 @@ public class MediaSessionService extends SystemService implements Monitor { return -1; } - private boolean isSessionDiscoverable(MediaSessionRecord record) { - // TODO probably want to check more than if it's active. - return record.isActive(); - } - private void pushSessionsChanged(int userId) { synchronized (mLock) { List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId); @@ -502,6 +507,12 @@ public class MediaSessionService extends SystemService implements Monitor { UserRecord user = mUserRecords.get(record.getUserId()); if (receiver != null && user != null) { user.mLastMediaButtonReceiver = receiver; + ComponentName component = receiver.getIntent().getComponent(); + if (component != null && record.getPackageName().equals(component.getPackageName())) { + Settings.Secure.putStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, component.flattenToString(), + record.getUserId()); + } } } @@ -512,14 +523,17 @@ public class MediaSessionService extends SystemService implements Monitor { final class UserRecord { private final int mUserId; private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); + private final Context mContext; private PendingIntent mLastMediaButtonReceiver; + private ComponentName mRestoredMediaButtonReceiver; public UserRecord(Context context, int userId) { + mContext = context; mUserId = userId; + restoreMediaButtonReceiver(); } public void startLocked() { - // nothing for now } public void stopLocked() { @@ -549,6 +563,7 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(prefix + "Record for user " + mUserId); String indent = prefix + " "; pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver); + pw.println(indent + "Restored ButtonReceiver:" + mRestoredMediaButtonReceiver); int size = mSessions.size(); pw.println(indent + size + " Sessions:"); for (int i = 0; i < size; i++) { @@ -557,6 +572,19 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(indent + mSessions.get(i).toString()); } } + + private void restoreMediaButtonReceiver() { + String receiverName = Settings.Secure.getStringForUser(mContentResolver, + Settings.System.MEDIA_BUTTON_RECEIVER, UserHandle.USER_CURRENT); + if (!TextUtils.isEmpty(receiverName)) { + ComponentName eventReceiver = ComponentName.unflattenFromString(receiverName); + if (eventReceiver == null) { + // an invalid name was persisted + return; + } + mRestoredMediaButtonReceiver = eventReceiver; + } + } } final class SessionsListenerRecord implements IBinder.DeathRecipient { @@ -721,6 +749,10 @@ public class MediaSessionService extends SystemService implements Monitor { final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { + if (DEBUG) { + Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event=" + + keyEvent); + } if (!isUserSetupComplete()) { // Global media key handling can have the side-effect of starting new // activities which is undesirable while setup is in progress. @@ -732,8 +764,9 @@ public class MediaSessionService extends SystemService implements Monitor { synchronized (mLock) { // If we don't have a media button receiver to fall back on // include non-playing sessions for dispatching - boolean useNotPlayingSessions = mUserRecords.get( - ActivityManager.getCurrentUser()).mLastMediaButtonReceiver == null; + UserRecord ur = mUserRecords.get(ActivityManager.getCurrentUser()); + boolean useNotPlayingSessions = ur.mLastMediaButtonReceiver == null + && ur.mRestoredMediaButtonReceiver == null; MediaSessionRecord session = mPriorityStack .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions); if (isVoiceKey(keyEvent.getKeyCode())) { @@ -769,7 +802,7 @@ public class MediaSessionService extends SystemService implements Monitor { final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - enforceStatusBarPermission("listen for volume changes", pid, uid); + enforceSystemUiPermission("listen for volume changes", pid, uid); mRvc = rvc; } finally { Binder.restoreCallingIdentity(token); @@ -807,7 +840,7 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println("User Records:"); count = mUserRecords.size(); for (int i = 0; i < count; i++) { - UserRecord user = mUserRecords.get(i); + UserRecord user = mUserRecords.get(mUserRecords.keyAt(i)); user.dumpLocked(pw, ""); } } @@ -855,32 +888,8 @@ public class MediaSessionService extends SystemService implements Monitor { } try { String packageName = getContext().getOpPackageName(); - if (mUseMasterVolume) { - boolean isMasterMute = mAudioService.isMasterMute(); - if (direction == MediaSessionManager.DIRECTION_MUTE) { - mAudioService.setMasterMute(!isMasterMute, flags, packageName, mICallback); - } else { - mAudioService.adjustMasterVolume(direction, flags, packageName); - // Do not call setMasterMute when direction = 0 which is used just to - // show the UI. - if (isMasterMute && direction != 0) { - mAudioService.setMasterMute(false, flags, packageName, mICallback); - } - } - } else { - boolean isStreamMute = mAudioService.isStreamMute(suggestedStream); - if (direction == MediaSessionManager.DIRECTION_MUTE) { - mAudioManager.setStreamMute(suggestedStream, !isStreamMute); - } else { - mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, - flags, packageName); - // Do not call setStreamMute when direction = 0 which is used just to - // show the UI. - if (isStreamMute && direction != 0) { - mAudioManager.setStreamMute(suggestedStream, false); - } - } - } + mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, + flags, packageName, TAG); } catch (RemoteException e) { Log.e(TAG, "Error adjusting default volume.", e); } @@ -938,9 +947,12 @@ public class MediaSessionService extends SystemService implements Monitor { // Launch the last PendingIntent we had with priority int userId = ActivityManager.getCurrentUser(); UserRecord user = mUserRecords.get(userId); - if (user.mLastMediaButtonReceiver != null) { + if (user.mLastMediaButtonReceiver != null + || user.mRestoredMediaButtonReceiver != null) { if (DEBUG) { - Log.d(TAG, "Sending media key to last known PendingIntent"); + Log.d(TAG, "Sending media key to last known PendingIntent " + + user.mLastMediaButtonReceiver + " or restored Intent " + + user.mRestoredMediaButtonReceiver); } if (needWakeLock) { mKeyEventReceiver.aquireWakeLockLocked(); @@ -948,9 +960,15 @@ public class MediaSessionService extends SystemService implements Monitor { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); try { - user.mLastMediaButtonReceiver.send(getContext(), - needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, - mediaButtonIntent, mKeyEventReceiver, null); + if (user.mLastMediaButtonReceiver != null) { + user.mLastMediaButtonReceiver.send(getContext(), + needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, + mediaButtonIntent, mKeyEventReceiver, null); + } else { + mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver); + getContext().sendBroadcastAsUser(mediaButtonIntent, + new UserHandle(userId)); + } } catch (CanceledException e) { Log.i(TAG, "Error sending key event to media button receiver " + user.mLastMediaButtonReceiver, e); diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index bfdc400..a029b0e 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -33,9 +33,7 @@ import android.media.projection.MediaProjectionManager; 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.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; @@ -45,9 +43,6 @@ import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import java.util.Map; /** diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java index b5a450d..90e26d8 100644 --- a/services/core/java/com/android/server/net/IpConfigStore.java +++ b/services/core/java/com/android/server/net/IpConfigStore.java @@ -24,25 +24,19 @@ import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.StaticIpConfiguration; -import android.os.Handler; -import android.os.HandlerThread; -import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import com.android.server.net.DelayedDiskWrite; import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.Inet4Address; -import java.util.Map; public class IpConfigStore { private static final String TAG = "IpConfigStore"; diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 752614f..6ffe6ac 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -341,7 +341,7 @@ public class LockdownVpnTracker { .setOngoing(true) .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent) - .setColor(mContext.getResources().getColor( + .setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)); NotificationManager.from(mContext).notify(TAG, 0, builder.build()); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 2be591b..818f0aa 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -27,10 +27,8 @@ import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.ACTION_USER_ADDED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; -import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; -import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIMAX; import static android.net.ConnectivityManager.isNetworkTypeMobile; import static android.net.NetworkPolicy.CYCLE_NONE; @@ -46,7 +44,6 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.dumpPolicy; import static android.net.NetworkPolicyManager.dumpRules; -import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; import static android.net.NetworkTemplate.MATCH_MOBILE_4G; import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; @@ -60,7 +57,6 @@ import static android.net.wifi.WifiManager.EXTRA_CHANGE_REASON; import static android.net.wifi.WifiManager.EXTRA_NETWORK_INFO; import static android.net.wifi.WifiManager.EXTRA_WIFI_CONFIGURATION; import static android.net.wifi.WifiManager.EXTRA_WIFI_INFO; -import static android.telephony.TelephonyManager.SIM_STATE_READY; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static com.android.internal.util.ArrayUtils.appendInt; import static com.android.internal.util.Preconditions.checkNotNull; @@ -75,7 +71,10 @@ import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UP import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; +import android.Manifest; import android.app.ActivityManager; +import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.IProcessObserver; @@ -87,6 +86,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; @@ -122,7 +122,6 @@ import android.os.UserManager; import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.text.TextUtils; import android.text.format.Formatter; import android.text.format.Time; import android.util.ArrayMap; @@ -138,6 +137,7 @@ import android.util.SparseIntArray; import android.util.TrustedTime; import android.util.Xml; +import com.android.server.AppOpsService; import libcore.io.IoUtils; import com.android.internal.R; @@ -248,9 +248,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final Object mRulesLock = new Object(); + volatile boolean mSystemReady; volatile boolean mScreenOn; volatile boolean mRestrictBackground; volatile boolean mRestrictPower; + volatile boolean mDeviceIdleMode; private final boolean mSuppressDefaultPolicy; @@ -292,6 +294,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private final AtomicFile mPolicyFile; + private final AppOpsManager mAppOps; + // TODO: keep whitelist of system-critical services that should never have // rules enforced, such as system, phone, and radio UIDs. @@ -326,6 +330,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mSuppressDefaultPolicy = suppressDefaultPolicy; mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml")); + + mAppOps = context.getSystemService(AppOpsManager.class); } public void bindConnectivityManager(IConnectivityManager connManager) { @@ -372,11 +378,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } }); mRestrictPower = mPowerManagerInternal.getLowPowerModeEnabled(); + mSystemReady = true; // read policy from disk readPolicyLocked(); - if (mRestrictBackground || mRestrictPower) { + if (mRestrictBackground || mRestrictPower || mDeviceIdleMode) { updateRulesForGlobalChangeLocked(true); updateNotificationsLocked(); } @@ -400,7 +407,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mContext.registerReceiver(mScreenReceiver, screenFilter); // watch for network interfaces to be claimed - final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE); + final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION); mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler); // listen for package changes to update policy @@ -794,7 +801,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final Notification.Builder builder = new Notification.Builder(mContext); builder.setOnlyAlertOnce(true); builder.setWhen(0L); - builder.setColor(mContext.getResources().getColor( + builder.setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)); final Resources res = mContext.getResources(); @@ -921,7 +928,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { builder.setTicker(title); builder.setContentTitle(title); builder.setContentText(body); - builder.setColor(mContext.getResources().getColor( + builder.setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)); final Intent intent = buildAllowBackgroundDataIntent(); @@ -1036,7 +1043,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // will not have a bandwidth limit. Also only do this if restrict // background data use is *not* enabled, since that takes precendence // use over those networks can have a cost associated with it). - final boolean powerSave = mRestrictPower && !mRestrictBackground; + final boolean powerSave = (mRestrictPower || mDeviceIdleMode) && !mRestrictBackground; // First, generate identities of all connected networks so we can // quickly compare them against all defined policies below. @@ -1592,16 +1599,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } void addNetworkPolicyLocked(NetworkPolicy policy) { - NetworkPolicy[] policies = getNetworkPolicies(); + NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName()); policies = ArrayUtils.appendElement(NetworkPolicy.class, policies, policy); setNetworkPolicies(policies); } @Override - public NetworkPolicy[] getNetworkPolicies() { + public NetworkPolicy[] getNetworkPolicies(String callingPackage) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, TAG); + if (mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(), + callingPackage) != AppOpsManager.MODE_ALLOWED) { + return new NetworkPolicy[0]; + } + synchronized (mRulesLock) { final int size = mNetworkPolicy.size(); final NetworkPolicy[] policies = new NetworkPolicy[size]; @@ -1613,7 +1625,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } private void normalizePoliciesLocked() { - normalizePoliciesLocked(getNetworkPolicies()); + normalizePoliciesLocked(getNetworkPolicies(mContext.getOpPackageName())); } private void normalizePoliciesLocked(NetworkPolicy[] policies) { @@ -1701,6 +1713,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + @Override + public void setDeviceIdleMode(boolean enabled) { + mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + + synchronized (mRulesLock) { + if (mDeviceIdleMode != enabled) { + mDeviceIdleMode = enabled; + if (mSystemReady) { + updateRulesForGlobalChangeLocked(true); + } + } + } + } + private NetworkPolicy findPolicyForNetworkLocked(NetworkIdentity ident) { for (int i = mNetworkPolicy.size()-1; i >= 0; i--) { NetworkPolicy policy = mNetworkPolicy.valueAt(i); @@ -1806,8 +1832,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return; } + fout.print("System ready: "); fout.println(mSystemReady); fout.print("Restrict background: "); fout.println(mRestrictBackground); fout.print("Restrict power: "); fout.println(mRestrictPower); + fout.print("Device idle: "); fout.println(mDeviceIdleMode); fout.print("Current foreground state: "); fout.println(mCurForegroundState); fout.println("Network policies:"); fout.increaseIndent(); @@ -1957,8 +1985,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** - * Update rules that might be changed by {@link #mRestrictBackground} - * or {@link #mRestrictPower} value. + * Update rules that might be changed by {@link #mRestrictBackground}, + * {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value. */ void updateRulesForGlobalChangeLocked(boolean restrictedNetworksChanged) { final PackageManager pm = mContext.getPackageManager(); @@ -1967,7 +1995,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // If we are in restrict power mode, we allow all important apps // to have data access. Otherwise, we restrict data access to only // the top apps. - mCurForegroundState = (!mRestrictBackground && mRestrictPower) + mCurForegroundState = (!mRestrictBackground && (mRestrictPower || mDeviceIdleMode)) ? ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND : ActivityManager.PROCESS_STATE_TOP; @@ -2007,6 +2035,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { void updateRulesForUidLocked(int uid) { if (!isUidValidForRules(uid)) return; + // quick check: if this uid doesn't have INTERNET permission, it doesn't have + // network access anyway, so it is a waste to mess with it here. + final IPackageManager ipm = AppGlobals.getPackageManager(); + try { + if (ipm.checkUidPermission(Manifest.permission.INTERNET, uid) + != PackageManager.PERMISSION_GRANTED) { + return; + } + } catch (RemoteException e) { + } + final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE); final boolean uidForeground = isUidForegroundLocked(uid); @@ -2020,7 +2059,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // uid in background, and global background disabled uidRules = RULE_REJECT_METERED; } - } else if (mRestrictPower) { + } else if (mRestrictPower || mDeviceIdleMode) { final boolean whitelisted = mPowerSaveWhitelistAppIds.get(UserHandle.getAppId(uid)); if (!whitelisted && !uidForeground && (uidPolicy & POLICY_ALLOW_BACKGROUND_BATTERY_SAVE) == 0) { @@ -2262,4 +2301,29 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } fout.print("]"); } + + @Override + public void factoryReset(String subscriber) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + // Turn mobile data limit off + NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName()); + NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriber); + for (NetworkPolicy policy : policies) { + if (policy.template.equals(template)) { + policy.limitBytes = NetworkPolicy.LIMIT_DISABLED; + policy.inferred = false; + policy.clearSnooze(); + } + } + setNetworkPolicies(policies); + + // Turn restrict background data off + setRestrictBackground(false); + + // Remove app's "restrict background data" flag + for (int uid : getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) { + setUidPolicy(uid, POLICY_NONE); + } + } } diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index 3ac20f7..15b68c7 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -22,6 +22,7 @@ import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_REMOVED; +import static android.net.TrafficStats.UID_TETHERING; import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS; @@ -31,14 +32,18 @@ import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; +import android.os.Binder; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.AtomicFile; +import android.util.IntArray; import libcore.io.IoUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; + import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -131,6 +136,23 @@ public class NetworkStatsCollection implements FileRotator.Reader { return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; } + public int[] getRelevantUids() { + final int callerUid = Binder.getCallingUid(); + IntArray uids = new IntArray(); + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); + if (isAccessibleToUser(key.uid, callerUid)) { + int j = uids.binarySearch(key.uid); + + if (j < 0) { + j = ~j; + uids.add(j, key.uid); + } + } + } + return uids.toArray(); + } + /** * Combine all {@link NetworkStatsHistory} in this collection which match * the requested parameters. @@ -146,12 +168,21 @@ public class NetworkStatsCollection implements FileRotator.Reader { */ public NetworkStatsHistory getHistory( NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) { + final int callerUid = Binder.getCallingUid(); + if (!isAccessibleToUser(uid, callerUid)) { + throw new SecurityException("Network stats history of uid " + uid + + " is forbidden for caller " + callerUid); + } + final NetworkStatsHistory combined = new NetworkStatsHistory( - mBucketDuration, estimateBuckets(), fields); + mBucketDuration, start == end ? 1 : estimateBuckets(), fields); + + // shortcut when we know stats will be empty + if (start == end) return combined; + for (int i = 0; i < mStats.size(); i++) { final Key key = mStats.keyAt(i); - final boolean setMatches = set == SET_ALL || key.set == set; - if (key.uid == uid && setMatches && key.tag == tag + if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag && templateMatches(template, key.ident)) { final NetworkStatsHistory value = mStats.valueAt(i); combined.recordHistory(value, start, end); @@ -168,15 +199,17 @@ public class NetworkStatsCollection implements FileRotator.Reader { final long now = System.currentTimeMillis(); final NetworkStats stats = new NetworkStats(end - start, 24); - final NetworkStats.Entry entry = new NetworkStats.Entry(); - NetworkStatsHistory.Entry historyEntry = null; - // shortcut when we know stats will be empty if (start == end) return stats; + final NetworkStats.Entry entry = new NetworkStats.Entry(); + NetworkStatsHistory.Entry historyEntry = null; + + final int callerUid = Binder.getCallingUid(); for (int i = 0; i < mStats.size(); i++) { final Key key = mStats.keyAt(i); - if (templateMatches(template, key.ident)) { + if (templateMatches(template, key.ident) && isAccessibleToUser(key.uid, callerUid) + && key.set < NetworkStats.SET_DEBUG_START) { final NetworkStatsHistory value = mStats.valueAt(i); historyEntry = value.getValues(start, end, now, historyEntry); @@ -509,6 +542,7 @@ public class NetworkStatsCollection implements FileRotator.Reader { final NetworkStatsHistory value = mStats.valueAt(i); if (!templateMatches(groupTemplate, key.ident)) continue; + if (key.set >= NetworkStats.SET_DEBUG_START) continue; final Key groupKey = new Key(null, key.uid, key.set, key.tag); NetworkStatsHistory groupHistory = grouped.get(groupKey); @@ -536,6 +570,12 @@ public class NetworkStatsCollection implements FileRotator.Reader { } } + private static boolean isAccessibleToUser(int uid, int callerUid) { + return UserHandle.getAppId(callerUid) == android.os.Process.SYSTEM_UID || + uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED || uid == UID_TETHERING + || UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid); + } + /** * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} * in the given {@link NetworkIdentitySet}. diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index d5d7667..6490865 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -31,6 +31,7 @@ import android.util.Log; import android.util.MathUtils; import android.util.Slog; +import com.android.internal.net.VpnInfo; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; import com.google.android.collect.Sets; @@ -163,7 +164,8 @@ public class NetworkStatsRecorder { * snapshot is considered bootstrap, and is not counted as delta. */ public void recordSnapshotLocked(NetworkStats snapshot, - Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) { + Map<String, NetworkIdentitySet> ifaceIdent, VpnInfo[] vpnArray, + long currentTimeMillis) { final HashSet<String> unknownIfaces = Sets.newHashSet(); // skip recording when snapshot missing @@ -182,6 +184,12 @@ public class NetworkStatsRecorder { final long end = currentTimeMillis; final long start = end - delta.getElapsedRealtime(); + if (vpnArray != null) { + for (VpnInfo info : vpnArray) { + delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface); + } + } + NetworkStats.Entry entry = null; for (int i = 0; i < delta.size(); i++) { entry = delta.getValues(i, entry); diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 856a076..50e03a2 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -62,9 +62,13 @@ import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats; import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet; +import android.Manifest; import android.app.AlarmManager; +import android.app.AppOpsManager; import android.app.IAlarmManager; import android.app.PendingIntent; +import android.app.admin.DeviceAdminInfo; +import android.app.admin.DevicePolicyManagerInternal; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -93,7 +97,9 @@ import android.os.HandlerThread; import android.os.INetworkManagementService; import android.os.Message; import android.os.PowerManager; +import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -111,10 +117,12 @@ import android.util.SparseIntArray; import android.util.TrustedTime; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.net.VpnInfo; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; +import com.android.server.LocalServices; import com.android.server.connectivity.Tethering; import java.io.File; @@ -428,7 +436,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public INetworkStatsSession openSession() { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + return openSessionForUsageStats(null); + } + + @Override + public INetworkStatsSession openSessionForUsageStats(final String callingPackage) { assertBandwidthControlEnabled(); // return an IBinder which holds strong references to any loaded stats @@ -437,6 +449,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return new INetworkStatsSession.Stub() { private NetworkStatsCollection mUidComplete; private NetworkStatsCollection mUidTagComplete; + private String mCallingPackage = callingPackage; private NetworkStatsCollection getUidComplete() { synchronized (mStatsLock) { @@ -457,8 +470,29 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override + public int[] getRelevantUids() { + enforcePermissionForManagedAdmin(mCallingPackage); + return getUidComplete().getRelevantUids(); + } + + @Override + public NetworkStats getDeviceSummaryForNetwork(NetworkTemplate template, long start, + long end) { + enforcePermission(mCallingPackage); + NetworkStats result = new NetworkStats(end - start, 1); + final long ident = Binder.clearCallingIdentity(); + try { + result.combineAllValues(internalGetSummaryForNetwork(template, start, end)); + } finally { + Binder.restoreCallingIdentity(ident); + } + return result; + } + + @Override public NetworkStats getSummaryForNetwork( NetworkTemplate template, long start, long end) { + enforcePermission(mCallingPackage); return internalGetSummaryForNetwork(template, start, end); } @@ -470,6 +504,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { + enforcePermissionForManagedAdmin(mCallingPackage); final NetworkStats stats = getUidComplete().getSummary(template, start, end); if (includeTags) { final NetworkStats tagStats = getUidTagComplete() @@ -482,6 +517,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForUid( NetworkTemplate template, int uid, int set, int tag, int fields) { + enforcePermissionForManagedAdmin(mCallingPackage); if (tag == TAG_NONE) { return getUidComplete().getHistory(template, uid, set, tag, fields); } else { @@ -497,6 +533,53 @@ public class NetworkStatsService extends INetworkStatsService.Stub { }; } + private boolean hasAppOpsPermission(String callingPackage) { + final int callingUid = Binder.getCallingUid(); + boolean appOpsAllow = false; + if (callingPackage != null) { + AppOpsManager appOps = (AppOpsManager) mContext.getSystemService( + Context.APP_OPS_SERVICE); + + final int mode = appOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, + callingUid, callingPackage); + if (mode == AppOpsManager.MODE_DEFAULT) { + // The default behavior here is to check if PackageManager has given the app + // permission. + final int permissionCheck = mContext.checkCallingPermission( + Manifest.permission.PACKAGE_USAGE_STATS); + appOpsAllow = permissionCheck == PackageManager.PERMISSION_GRANTED; + } + appOpsAllow = (mode == AppOpsManager.MODE_ALLOWED); + } + return appOpsAllow; + } + + private void enforcePermissionForManagedAdmin(String callingPackage) { + boolean hasPermission = hasAppOpsPermission(callingPackage); + if (!hasPermission) { + // Profile and device owners are exempt from permission checking. + final int callingUid = Binder.getCallingUid(); + final DevicePolicyManagerInternal dpmi = LocalServices.getService( + DevicePolicyManagerInternal.class); + if (dpmi.isActiveAdminWithPolicy(callingUid, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) + || dpmi.isActiveAdminWithPolicy(callingUid, + DeviceAdminInfo.USES_POLICY_DEVICE_OWNER)) { + return; + } + } + if (!hasPermission) { + mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + } + } + + private void enforcePermission(String callingPackage) { + boolean appOpsAllow = hasAppOpsPermission(callingPackage); + if (!appOpsAllow) { + mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + } + } + + /** * Return network summary, splicing between DEV and XT stats when * appropriate. @@ -855,6 +938,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return ident; } + private void recordSnapshotLocked(long currentTime) throws RemoteException { + // snapshot and record current counters; read UID stats first to + // avoid overcounting dev stats. + final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); + final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt(); + final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev(); + + VpnInfo[] vpnArray = mConnManager.getAllVpnInfo(); + mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, null, currentTime); + mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, null, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, vpnArray, currentTime); + } + /** * Bootstrap initial stats snapshot, usually during {@link #systemReady()} * so we have baseline values without double-counting. @@ -864,17 +961,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { : System.currentTimeMillis(); try { - // snapshot and record current counters; read UID stats first to - // avoid overcounting dev stats. - final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); - final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt(); - final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev(); - - mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); - mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime); - mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime); - mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime); - + recordSnapshotLocked(currentTime); } catch (IllegalStateException e) { Slog.w(TAG, "problem reading network stats: " + e); } catch (RemoteException e) { @@ -918,17 +1005,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { : System.currentTimeMillis(); try { - // snapshot and record current counters; read UID stats first to - // avoid overcounting dev stats. - final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); - final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt(); - final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev(); - - mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); - mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime); - mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime); - mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveUidIfaces, currentTime); - + recordSnapshotLocked(currentTime); } catch (IllegalStateException e) { Log.wtf(TAG, "problem reading network stats", e); return; diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index ab53fbc..b36fcd2 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -25,12 +25,10 @@ import android.os.IInterface; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; -import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ConditionProviderService; import android.service.notification.IConditionListener; import android.service.notification.IConditionProvider; -import android.service.notification.ZenModeConfig; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; @@ -41,50 +39,44 @@ import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -import java.util.Objects; public class ConditionProviders extends ManagedServices { - private static final Condition[] NO_CONDITIONS = new Condition[0]; - - private final ZenModeHelper mZenModeHelper; - private final ArrayMap<IBinder, IConditionListener> mListeners - = new ArrayMap<IBinder, IConditionListener>(); - private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>(); - private final ArraySet<String> mSystemConditionProviders; - private final CountdownConditionProvider mCountdown; - private final DowntimeConditionProvider mDowntime; - private final NextAlarmConditionProvider mNextAlarm; - private final NextAlarmTracker mNextAlarmTracker; - - private Condition mExitCondition; - private ComponentName mExitConditionComponent; - - public ConditionProviders(Context context, Handler handler, - UserProfiles userProfiles, ZenModeHelper zenModeHelper) { + private final ArrayList<ConditionRecord> mRecords = new ArrayList<>(); + private final ArrayMap<IBinder, IConditionListener> mListeners = new ArrayMap<>(); + private final ArraySet<String> mSystemConditionProviderNames; + private final ArraySet<SystemConditionProviderService> mSystemConditionProviders + = new ArraySet<>(); + + private Callback mCallback; + + public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) { super(context, handler, new Object(), userProfiles); - mZenModeHelper = zenModeHelper; - mZenModeHelper.addCallback(new ZenModeHelperCallback()); - mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext, + mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext, "system.condition.providers", R.array.config_system_condition_providers)); - final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH); - final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH); - final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH); - mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null; - mCountdown = countdown ? new CountdownConditionProvider() : null; - mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker, - mZenModeHelper) : null; - mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null; - loadZenConfig(); } - public boolean isSystemConditionProviderEnabled(String path) { - return mSystemConditionProviders.contains(path); + public void setCallback(Callback callback) { + mCallback = callback; + } + + public boolean isSystemProviderEnabled(String path) { + return mSystemConditionProviderNames.contains(path); + } + + public void addSystemProvider(SystemConditionProviderService service) { + mSystemConditionProviders.add(service); + service.attachBase(mContext); + registerService(service.asInterface(), service.getComponent(), UserHandle.USER_OWNER); + } + + public Iterable<SystemConditionProviderService> getSystemProviders() { + return mSystemConditionProviders; } @Override protected Config getConfig() { - Config c = new Config(); + final Config c = new Config(); c.caption = "condition provider"; c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE; c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS; @@ -98,12 +90,6 @@ public class ConditionProviders extends ManagedServices { public void dump(PrintWriter pw, DumpFilter filter) { super.dump(pw, filter); synchronized(mMutex) { - if (filter == null) { - pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):"); - for (int i = 0; i < mListeners.size(); i++) { - pw.print(" "); pw.println(mListeners.keyAt(i)); - } - } pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):"); for (int i = 0; i < mRecords.size(); i++) { final ConditionRecord r = mRecords.get(i); @@ -115,18 +101,15 @@ public class ConditionProviders extends ManagedServices { } } } - pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviders); - if (mCountdown != null) { - mCountdown.dump(pw, filter); - } - if (mDowntime != null) { - mDowntime.dump(pw, filter); - } - if (mNextAlarm != null) { - mNextAlarm.dump(pw, filter); + if (filter == null) { + pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):"); + for (int i = 0; i < mListeners.size(); i++) { + pw.print(" "); pw.println(mListeners.keyAt(i)); + } } - if (mNextAlarmTracker != null) { - mNextAlarmTracker.dump(pw, filter); + pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames); + for (int i = 0; i < mSystemConditionProviders.size(); i++) { + mSystemConditionProviders.valueAt(i).dump(pw, filter); } } @@ -138,31 +121,16 @@ public class ConditionProviders extends ManagedServices { @Override public void onBootPhaseAppsCanStart() { super.onBootPhaseAppsCanStart(); - if (mNextAlarmTracker != null) { - mNextAlarmTracker.init(); - } - if (mCountdown != null) { - mCountdown.attachBase(mContext); - registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT, - UserHandle.USER_OWNER); - } - if (mDowntime != null) { - mDowntime.attachBase(mContext); - registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT, - UserHandle.USER_OWNER); - } - if (mNextAlarm != null) { - mNextAlarm.attachBase(mContext); - registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT, - UserHandle.USER_OWNER); + if (mCallback != null) { + mCallback.onBootComplete(); } } @Override public void onUserSwitched() { super.onUserSwitched(); - if (mNextAlarmTracker != null) { - mNextAlarmTracker.onUserSwitched(); + if (mCallback != null) { + mCallback.onUserSwitched(); } } @@ -174,23 +142,8 @@ public class ConditionProviders extends ManagedServices { } catch (RemoteException e) { // we tried } - synchronized (mMutex) { - if (info.component.equals(mExitConditionComponent)) { - // ensure record exists, we'll wire it up and subscribe below - final ConditionRecord manualRecord = - getRecordLocked(mExitCondition.id, mExitConditionComponent); - manualRecord.isManual = true; - } - final int N = mRecords.size(); - for(int i = 0; i < N; i++) { - final ConditionRecord r = mRecords.get(i); - if (!r.component.equals(info.component)) continue; - r.info = info; - // if automatic or manual, auto-subscribe - if (r.isAutomatic || r.isManual) { - subscribeLocked(r); - } - } + if (mCallback != null) { + mCallback.onServiceAdded(info.component); } } @@ -200,15 +153,6 @@ public class ConditionProviders extends ManagedServices { for (int i = mRecords.size() - 1; i >= 0; i--) { final ConditionRecord r = mRecords.get(i); if (!r.component.equals(removed.component)) continue; - if (r.isManual) { - // removing the current manual condition, exit zen - onManualConditionClearing(); - mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved"); - } - if (r.isAutomatic) { - // removing an automatic condition, exit zen - mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved"); - } mRecords.remove(i); } } @@ -219,9 +163,9 @@ public class ConditionProviders extends ManagedServices { } } - public void requestZenModeConditions(IConditionListener callback, int relevance) { + public void requestConditions(IConditionListener callback, int relevance) { synchronized(mMutex) { - if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback + if (DEBUG) Slog.d(TAG, "requestConditions callback=" + callback + " relevance=" + Condition.relevanceToString(relevance)); if (callback == null) return; relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS); @@ -262,7 +206,8 @@ public class ConditionProviders extends ManagedServices { return rt; } - private ConditionRecord getRecordLocked(Uri id, ComponentName component) { + private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) { + if (id == null || component == null) return null; final int N = mRecords.size(); for (int i = 0; i < N; i++) { final ConditionRecord r = mRecords.get(i); @@ -270,9 +215,12 @@ public class ConditionProviders extends ManagedServices { return r; } } - final ConditionRecord r = new ConditionRecord(id, component); - mRecords.add(r); - return r; + if (create) { + final ConditionRecord r = new ConditionRecord(id, component); + mRecords.add(r); + return r; + } + return null; } public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) { @@ -291,99 +239,58 @@ public class ConditionProviders extends ManagedServices { } for (int i = 0; i < N; i++) { final Condition c = conditions[i]; - final ConditionRecord r = getRecordLocked(c.id, info.component); - final Condition oldCondition = r.condition; - final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c); + final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/); r.info = info; r.condition = c; - // if manual, exit zen if false (or failed), update if true (and changed) - if (r.isManual) { - if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) { - final boolean failed = c.state == Condition.STATE_ERROR; - if (failed) { - Slog.w(TAG, "Exit zen: manual condition failed: " + c); - } else if (DEBUG) { - Slog.d(TAG, "Exit zen: manual condition false: " + c); - } - onManualConditionClearing(); - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, - "manualConditionExit"); - unsubscribeLocked(r); - r.isManual = false; - } else if (c.state == Condition.STATE_TRUE && conditionUpdate) { - if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old=" - + oldCondition + " new=" + c); - setZenModeCondition(c, "conditionUpdate"); - } - } - // if automatic, exit zen if false (or failed), enter zen if true - if (r.isAutomatic) { - if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) { - final boolean failed = c.state == Condition.STATE_ERROR; - if (failed) { - Slog.w(TAG, "Exit zen: automatic condition failed: " + c); - } else if (DEBUG) { - Slog.d(TAG, "Exit zen: automatic condition false: " + c); - } - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF, - "automaticConditionExit"); - } else if (c.state == Condition.STATE_TRUE) { - Slog.d(TAG, "Enter zen: automatic condition true: " + c); - mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, - "automaticConditionEnter"); - } + if (mCallback != null) { + mCallback.onConditionChanged(c.id, c); } } } } - private void ensureRecordExists(Condition condition, IConditionProvider provider, - ComponentName component) { + public IConditionProvider findConditionProvider(ComponentName component) { + if (component == null) return null; + for (ManagedServiceInfo service : mServices) { + if (component.equals(service.component)) { + return provider(service); + } + } + return null; + } + + public void ensureRecordExists(ComponentName component, Uri conditionId, + IConditionProvider provider) { // constructed by convention, make sure the record exists... - final ConditionRecord r = getRecordLocked(condition.id, component); + final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/); if (r.info == null) { // ... and is associated with the in-process service r.info = checkServiceTokenLocked(provider); } } - public void setZenModeCondition(Condition condition, String reason) { - if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason); - synchronized(mMutex) { - ComponentName conditionComponent = null; - if (condition != null) { - if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) { - ensureRecordExists(condition, mCountdown.asInterface(), - CountdownConditionProvider.COMPONENT); - } - if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) { - ensureRecordExists(condition, mDowntime.asInterface(), - DowntimeConditionProvider.COMPONENT); - } - } - final int N = mRecords.size(); - for (int i = 0; i < N; i++) { - final ConditionRecord r = mRecords.get(i); - final boolean idEqual = condition != null && r.id.equals(condition.id); - if (r.isManual && !idEqual) { - // was previous manual condition, unsubscribe - unsubscribeLocked(r); - r.isManual = false; - } else if (idEqual && !r.isManual) { - // is new manual condition, subscribe - subscribeLocked(r); - r.isManual = true; - } - if (idEqual) { - conditionComponent = r.component; - } + public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) { + synchronized (mMutex) { + final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/); + if (r == null) { + Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId); + return false; } - if (!Objects.equals(mExitCondition, condition)) { - mExitCondition = condition; - mExitConditionComponent = conditionComponent; - ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason); - saveZenConfigLocked(); + if (r.subscribed) return true; + subscribeLocked(r); + return r.subscribed; + } + } + + public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) { + synchronized (mMutex) { + final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/); + if (r == null) { + Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId); + return; } + if (!r.subscribed) return; + unsubscribeLocked(r);; } } @@ -393,8 +300,9 @@ public class ConditionProviders extends ManagedServices { RemoteException re = null; if (provider != null) { try { - Slog.d(TAG, "Subscribing to " + r.id + " with " + provider); + Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component); provider.onSubscribe(r.id); + r.subscribed = true; } catch (RemoteException e) { Slog.w(TAG, "Error subscribing to " + r, e); re = e; @@ -417,53 +325,6 @@ public class ConditionProviders extends ManagedServices { return rt; } - public void setAutomaticZenModeConditions(Uri[] conditionIds) { - setAutomaticZenModeConditions(conditionIds, true /*save*/); - } - - private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) { - if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions " - + (conditionIds == null ? null : Arrays.asList(conditionIds))); - synchronized(mMutex) { - final ArraySet<Uri> newIds = safeSet(conditionIds); - final int N = mRecords.size(); - boolean changed = false; - for (int i = 0; i < N; i++) { - final ConditionRecord r = mRecords.get(i); - final boolean automatic = newIds.contains(r.id); - if (!r.isAutomatic && automatic) { - // subscribe to new automatic - subscribeLocked(r); - r.isAutomatic = true; - changed = true; - } else if (r.isAutomatic && !automatic) { - // unsubscribe from old automatic - unsubscribeLocked(r); - r.isAutomatic = false; - changed = true; - } - } - if (save && changed) { - saveZenConfigLocked(); - } - } - } - - public Condition[] getAutomaticZenModeConditions() { - synchronized(mMutex) { - final int N = mRecords.size(); - ArrayList<Condition> rt = null; - for (int i = 0; i < N; i++) { - final ConditionRecord r = mRecords.get(i); - if (r.isAutomatic && r.condition != null) { - if (rt == null) rt = new ArrayList<Condition>(); - rt.add(r.condition); - } - } - return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]); - } - } - private void unsubscribeLocked(ConditionRecord r) { if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r); final IConditionProvider provider = provider(r); @@ -475,6 +336,7 @@ public class ConditionProviders extends ManagedServices { Slog.w(TAG, "Error unsubscribing to " + r, e); re = e; } + r.subscribed = false; } ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re); } @@ -495,7 +357,7 @@ public class ConditionProviders extends ManagedServices { for (int i = mRecords.size() - 1; i >= 0; i--) { final ConditionRecord r = mRecords.get(i); if (r.info != info) continue; - if (r.isManual || r.isAutomatic) continue; + if (r.subscribed) continue; mRecords.remove(i); } try { @@ -506,103 +368,12 @@ public class ConditionProviders extends ManagedServices { } } - private void loadZenConfig() { - final ZenModeConfig config = mZenModeHelper.getConfig(); - if (config == null) { - if (DEBUG) Slog.d(TAG, "loadZenConfig: no config"); - return; - } - synchronized (mMutex) { - final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition); - mExitCondition = config.exitCondition; - mExitConditionComponent = config.exitConditionComponent; - if (changingExit) { - ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config"); - } - if (mDowntime != null) { - mDowntime.setConfig(config); - } - if (config.conditionComponents == null || config.conditionIds == null - || config.conditionComponents.length != config.conditionIds.length) { - if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions"); - setAutomaticZenModeConditions(null, false /*save*/); - return; - } - final ArraySet<Uri> newIds = new ArraySet<Uri>(); - final int N = config.conditionComponents.length; - for (int i = 0; i < N; i++) { - final ComponentName component = config.conditionComponents[i]; - final Uri id = config.conditionIds[i]; - if (component != null && id != null) { - getRecordLocked(id, component); // ensure record exists - newIds.add(id); - } - } - if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N); - setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/); - } - } - - private void saveZenConfigLocked() { - ZenModeConfig config = mZenModeHelper.getConfig(); - if (config == null) return; - config = config.copy(); - final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>(); - final int automaticN = mRecords.size(); - for (int i = 0; i < automaticN; i++) { - final ConditionRecord r = mRecords.get(i); - if (r.isAutomatic) { - automatic.add(r); - } - } - if (automatic.isEmpty()) { - config.conditionComponents = null; - config.conditionIds = null; - } else { - final int N = automatic.size(); - config.conditionComponents = new ComponentName[N]; - config.conditionIds = new Uri[N]; - for (int i = 0; i < N; i++) { - final ConditionRecord r = automatic.get(i); - config.conditionComponents[i] = r.component; - config.conditionIds[i] = r.id; - } - } - config.exitCondition = mExitCondition; - config.exitConditionComponent = mExitConditionComponent; - if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config); - mZenModeHelper.setConfig(config); - } - - private void onManualConditionClearing() { - if (mDowntime != null) { - mDowntime.onManualConditionClearing(); - } - } - - private class ZenModeHelperCallback extends ZenModeHelper.Callback { - @Override - void onConfigChanged() { - loadZenConfig(); - } - - @Override - void onZenModeChanged() { - final int mode = mZenModeHelper.getZenMode(); - if (mode == Global.ZEN_MODE_OFF) { - // ensure any manual condition is cleared - setZenModeCondition(null, "zenOff"); - } - } - } - private static class ConditionRecord { public final Uri id; public final ComponentName component; public Condition condition; public ManagedServiceInfo info; - public boolean isAutomatic; - public boolean isManual; + public boolean subscribed; private ConditionRecord(Uri id, ComponentName component) { this.id = id; @@ -612,10 +383,17 @@ public class ConditionProviders extends ManagedServices { @Override public String toString() { final StringBuilder sb = new StringBuilder("ConditionRecord[id=") - .append(id).append(",component=").append(component); - if (isAutomatic) sb.append(",automatic"); - if (isManual) sb.append(",manual"); + .append(id).append(",component=").append(component) + .append(",subscribed=").append(subscribed); return sb.append(']').toString(); } } + + public interface Callback { + void onBootComplete(); + void onServiceAdded(ComponentName component); + void onConditionChanged(Uri id, Condition condition); + void onUserSwitched(); + } + } diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java index 37aacaa..6a04688 100644 --- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java +++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.service.notification.Condition; -import android.service.notification.ConditionProviderService; import android.service.notification.IConditionProvider; import android.service.notification.ZenModeConfig; import android.text.format.DateUtils; @@ -38,8 +37,8 @@ import java.io.PrintWriter; import java.util.Date; /** Built-in zen condition provider for simple time-based conditions */ -public class CountdownConditionProvider extends ConditionProviderService { - private static final String TAG = "CountdownConditions"; +public class CountdownConditionProvider extends SystemConditionProviderService { + private static final String TAG = "ConditionProviders"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final ComponentName COMPONENT = @@ -59,6 +58,27 @@ public class CountdownConditionProvider extends ConditionProviderService { if (DEBUG) Slog.d(TAG, "new CountdownConditionProvider()"); } + @Override + public ComponentName getComponent() { + return COMPONENT; + } + + @Override + public boolean isValidConditionid(Uri id) { + return ZenModeConfig.isValidCountdownConditionId(id); + } + + @Override + public void attachBase(Context base) { + attachBaseContext(base); + } + + @Override + public IConditionProvider asInterface() { + return (IConditionProvider) onBind(null); + } + + @Override public void dump(PrintWriter pw, DumpFilter filter) { pw.println(" CountdownConditionProvider:"); pw.print(" mConnected="); pw.println(mConnected); @@ -154,11 +174,4 @@ public class CountdownConditionProvider extends ConditionProviderService { return new Date(time) + " (" + time + ")"; } - public void attachBase(Context base) { - attachBaseContext(base); - } - - public IConditionProvider asInterface() { - return (IConditionProvider) onBind(null); - } } diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java deleted file mode 100644 index df4ecfd..0000000 --- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java +++ /dev/null @@ -1,409 +0,0 @@ -/** - * Copyright (c) 2014, 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.notification; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.app.AlarmManager.AlarmClockInfo; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.provider.Settings.Global; -import android.service.notification.Condition; -import android.service.notification.ConditionProviderService; -import android.service.notification.IConditionProvider; -import android.service.notification.ZenModeConfig; -import android.service.notification.ZenModeConfig.DowntimeInfo; -import android.text.format.DateFormat; -import android.util.ArraySet; -import android.util.Log; -import android.util.Slog; -import android.util.TimeUtils; - -import com.android.internal.R; -import com.android.server.notification.NotificationManagerService.DumpFilter; - -import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Objects; -import java.util.TimeZone; - -/** Built-in zen condition provider for managing downtime */ -public class DowntimeConditionProvider extends ConditionProviderService { - private static final String TAG = "DowntimeConditions"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - public static final ComponentName COMPONENT = - new ComponentName("android", DowntimeConditionProvider.class.getName()); - - private static final String ENTER_ACTION = TAG + ".enter"; - private static final int ENTER_CODE = 100; - private static final String EXIT_ACTION = TAG + ".exit"; - private static final int EXIT_CODE = 101; - private static final String EXTRA_TIME = "time"; - - private static final long SECONDS = 1000; - private static final long MINUTES = 60 * SECONDS; - private static final long HOURS = 60 * MINUTES; - - private final Context mContext = this; - private final DowntimeCalendar mCalendar = new DowntimeCalendar(); - private final FiredAlarms mFiredAlarms = new FiredAlarms(); - private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); - private final ConditionProviders mConditionProviders; - private final NextAlarmTracker mTracker; - private final ZenModeHelper mZenModeHelper; - - private boolean mConnected; - private long mLookaheadThreshold; - private ZenModeConfig mConfig; - private boolean mDowntimed; - private boolean mConditionClearing; - private boolean mRequesting; - - public DowntimeConditionProvider(ConditionProviders conditionProviders, - NextAlarmTracker tracker, ZenModeHelper zenModeHelper) { - if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()"); - mConditionProviders = conditionProviders; - mTracker = tracker; - mZenModeHelper = zenModeHelper; - } - - public void dump(PrintWriter pw, DumpFilter filter) { - pw.println(" DowntimeConditionProvider:"); - pw.print(" mConnected="); pw.println(mConnected); - pw.print(" mSubscriptions="); pw.println(mSubscriptions); - pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold); - pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")"); - pw.print(" mCalendar="); pw.println(mCalendar); - pw.print(" mFiredAlarms="); pw.println(mFiredAlarms); - pw.print(" mDowntimed="); pw.println(mDowntimed); - pw.print(" mConditionClearing="); pw.println(mConditionClearing); - pw.print(" mRequesting="); pw.println(mRequesting); - } - - public void attachBase(Context base) { - attachBaseContext(base); - } - - public IConditionProvider asInterface() { - return (IConditionProvider) onBind(null); - } - - @Override - public void onConnected() { - if (DEBUG) Slog.d(TAG, "onConnected"); - mConnected = true; - mLookaheadThreshold = PropConfig.getInt(mContext, "downtime.condition.lookahead", - R.integer.config_downtime_condition_lookahead_threshold_hrs) * HOURS; - final IntentFilter filter = new IntentFilter(); - filter.addAction(ENTER_ACTION); - filter.addAction(EXIT_ACTION); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - mContext.registerReceiver(mReceiver, filter); - mTracker.addCallback(mTrackerCallback); - mZenModeHelper.addCallback(mZenCallback); - init(); - } - - @Override - public void onDestroy() { - if (DEBUG) Slog.d(TAG, "onDestroy"); - mTracker.removeCallback(mTrackerCallback); - mZenModeHelper.removeCallback(mZenCallback); - mConnected = false; - } - - @Override - public void onRequestConditions(int relevance) { - if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); - if (!mConnected) return; - mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0; - evaluateSubscriptions(); - } - - @Override - public void onSubscribe(Uri conditionId) { - if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId); - final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId); - if (downtime == null) return; - mFiredAlarms.clear(); - mSubscriptions.add(conditionId); - notifyCondition(downtime); - } - - private boolean shouldShowCondition() { - final long now = System.currentTimeMillis(); - if (DEBUG) Slog.d(TAG, "shouldShowCondition now=" + mCalendar.isInDowntime(now) - + " lookahead=" - + (mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold))); - return mCalendar.isInDowntime(now) - || mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold); - } - - private void notifyCondition(DowntimeInfo downtime) { - if (mConfig == null) { - // we don't know yet - notifyCondition(createCondition(downtime, Condition.STATE_UNKNOWN)); - return; - } - if (!downtime.equals(mConfig.toDowntimeInfo())) { - // not the configured downtime, consider it false - notifyCondition(createCondition(downtime, Condition.STATE_FALSE)); - return; - } - if (!shouldShowCondition()) { - // configured downtime, but not within the time range - notifyCondition(createCondition(downtime, Condition.STATE_FALSE)); - return; - } - if (isZenNone() && mFiredAlarms.findBefore(System.currentTimeMillis())) { - // within the configured time range, but wake up if none and the next alarm is fired - notifyCondition(createCondition(downtime, Condition.STATE_FALSE)); - return; - } - // within the configured time range, condition still valid - notifyCondition(createCondition(downtime, Condition.STATE_TRUE)); - } - - private boolean isZenNone() { - return mZenModeHelper.getZenMode() == Global.ZEN_MODE_NO_INTERRUPTIONS; - } - - private boolean isZenOff() { - return mZenModeHelper.getZenMode() == Global.ZEN_MODE_OFF; - } - - private void evaluateSubscriptions() { - ArraySet<Uri> conditions = mSubscriptions; - if (mConfig != null && mRequesting && shouldShowCondition()) { - final Uri id = ZenModeConfig.toDowntimeConditionId(mConfig.toDowntimeInfo()); - if (!conditions.contains(id)) { - conditions = new ArraySet<Uri>(conditions); - conditions.add(id); - } - } - for (Uri conditionId : conditions) { - final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId); - if (downtime != null) { - notifyCondition(downtime); - } - } - } - - @Override - public void onUnsubscribe(Uri conditionId) { - final boolean current = mSubscriptions.contains(conditionId); - if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId + " current=" + current); - mSubscriptions.remove(conditionId); - mFiredAlarms.clear(); - } - - public void setConfig(ZenModeConfig config) { - if (Objects.equals(mConfig, config)) return; - final boolean downtimeChanged = mConfig == null || config == null - || !mConfig.toDowntimeInfo().equals(config.toDowntimeInfo()); - mConfig = config; - if (DEBUG) Slog.d(TAG, "setConfig downtimeChanged=" + downtimeChanged); - if (mConnected && downtimeChanged) { - mDowntimed = false; - init(); - } - // when active, mark downtime as entered for today - if (mConfig != null && mConfig.exitCondition != null - && ZenModeConfig.isValidDowntimeConditionId(mConfig.exitCondition.id)) { - mDowntimed = true; - } - } - - public void onManualConditionClearing() { - mConditionClearing = true; - } - - private Condition createCondition(DowntimeInfo downtime, int state) { - if (downtime == null) return null; - final Uri id = ZenModeConfig.toDowntimeConditionId(downtime); - final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma"; - final Locale locale = Locale.getDefault(); - final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton); - final long now = System.currentTimeMillis(); - long endTime = mCalendar.getNextTime(now, downtime.endHour, downtime.endMinute); - if (isZenNone()) { - final AlarmClockInfo nextAlarm = mTracker.getNextAlarm(); - final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0; - if (nextAlarmTime > now && nextAlarmTime < endTime) { - endTime = nextAlarmTime; - } - } - final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(endTime)); - final String summary = mContext.getString(R.string.downtime_condition_summary, formatted); - final String line1 = mContext.getString(R.string.downtime_condition_line_one); - return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW); - } - - private void init() { - mCalendar.setDowntimeInfo(mConfig != null ? mConfig.toDowntimeInfo() : null); - evaluateSubscriptions(); - updateAlarms(); - evaluateAutotrigger(); - } - - private void updateAlarms() { - if (mConfig == null) return; - updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute); - updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute); - } - - - private void updateAlarm(String action, int requestCode, int hr, int min) { - final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final long now = System.currentTimeMillis(); - final long time = mCalendar.getNextTime(now, hr, min); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, - new Intent(action) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .putExtra(EXTRA_TIME, time), - PendingIntent.FLAG_UPDATE_CURRENT); - alarms.cancel(pendingIntent); - if (mConfig.sleepMode != null) { - if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, in %s, now=%s", - action, ts(time), NextAlarmTracker.formatDuration(time - now), ts(now))); - alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); - } - } - - private static String ts(long time) { - return new Date(time) + " (" + time + ")"; - } - - private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { - if (!booted) return; // we don't know yet - if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm)); - if (nextAlarm != null && wakeupTime > 0 && System.currentTimeMillis() > wakeupTime) { - if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime)); - mFiredAlarms.add(wakeupTime); - } - evaluateSubscriptions(); - } - - private void evaluateAutotrigger() { - String skipReason = null; - if (mConfig == null) { - skipReason = "no config"; - } else if (mDowntimed) { - skipReason = "already downtimed"; - } else if (mZenModeHelper.getZenMode() != Global.ZEN_MODE_OFF) { - skipReason = "already in zen"; - } else if (!mCalendar.isInDowntime(System.currentTimeMillis())) { - skipReason = "not in downtime"; - } - if (skipReason != null) { - ZenLog.traceDowntimeAutotrigger("Autotrigger skipped: " + skipReason); - return; - } - ZenLog.traceDowntimeAutotrigger("Autotrigger fired"); - mZenModeHelper.setZenMode(mConfig.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS - : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtime"); - final Condition condition = createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE); - mConditionProviders.setZenModeCondition(condition, "downtime"); - } - - private BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - final long now = System.currentTimeMillis(); - if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) { - final long schTime = intent.getLongExtra(EXTRA_TIME, 0); - if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", - action, ts(schTime), ts(now), now - schTime)); - if (ENTER_ACTION.equals(action)) { - evaluateAutotrigger(); - } else /*EXIT_ACTION*/ { - mDowntimed = false; - } - mFiredAlarms.clear(); - } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { - if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault()); - mCalendar.setTimeZone(TimeZone.getDefault()); - mFiredAlarms.clear(); - } else if (Intent.ACTION_TIME_CHANGED.equals(action)) { - if (DEBUG) Slog.d(TAG, "time changed to " + now); - mFiredAlarms.clear(); - } else { - if (DEBUG) Slog.d(TAG, action + " fired at " + now); - } - evaluateSubscriptions(); - updateAlarms(); - } - }; - - private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() { - @Override - public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { - DowntimeConditionProvider.this.onEvaluateNextAlarm(nextAlarm, wakeupTime, booted); - } - }; - - private final ZenModeHelper.Callback mZenCallback = new ZenModeHelper.Callback() { - @Override - void onZenModeChanged() { - if (mConditionClearing && isZenOff()) { - evaluateAutotrigger(); - } - mConditionClearing = false; - evaluateSubscriptions(); - } - }; - - private class FiredAlarms { - private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>(); - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mFiredAlarms.size(); i++) { - if (i > 0) sb.append(','); - sb.append(mTracker.formatAlarmDebug(mFiredAlarms.valueAt(i))); - } - return sb.toString(); - } - - public void add(long firedAlarm) { - mFiredAlarms.add(firedAlarm); - } - - public void clear() { - mFiredAlarms.clear(); - } - - public boolean findBefore(long time) { - for (int i = 0; i < mFiredAlarms.size(); i++) { - if (mFiredAlarms.valueAt(i) < time) { - return true; - } - } - return false; - } - } -} diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0c7d71b..9ccb2ea 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -18,10 +18,12 @@ package com.android.server.notification; import android.app.ActivityManager; 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.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -51,6 +53,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -74,6 +77,7 @@ abstract public class ManagedServices { private final UserProfiles mUserProfiles; private final SettingsObserver mSettingsObserver; private final Config mConfig; + private ArraySet<String> mRestored; // contains connections to all connected services, including app services // and system services @@ -91,6 +95,8 @@ abstract public class ManagedServices { // user change). private int[] mLastSeenProfileIds; + private final BroadcastReceiver mRestoreReceiver; + public ManagedServices(Context context, Handler handler, Object mutex, UserProfiles userProfiles) { mContext = context; @@ -98,6 +104,24 @@ abstract public class ManagedServices { mUserProfiles = userProfiles; mConfig = getConfig(); mSettingsObserver = new SettingsObserver(handler); + + mRestoreReceiver = new SettingRestoredReceiver(); + IntentFilter filter = new IntentFilter(Intent.ACTION_SETTING_RESTORED); + context.registerReceiver(mRestoreReceiver, filter); + } + + class SettingRestoredReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) { + String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); + if (Objects.equals(element, mConfig.secureSettingName)) { + String prevValue = intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE); + String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE); + settingRestored(element, prevValue, newValue, getSendingUserId()); + } + } + } } abstract protected Config getConfig(); @@ -140,6 +164,31 @@ abstract public class ManagedServices { } } + // By convention, restored settings are replicated to another settings + // entry, named similarly but with a disambiguation suffix. + public static final String restoredSettingName(Config config) { + return config.secureSettingName + ":restored"; + } + + // The OS has done a restore of this service's saved state. We clone it to the + // 'restored' reserve, and then once we return and the actual write to settings is + // performed, our observer will do the work of maintaining the restored vs live + // settings data. + public void settingRestored(String element, String oldValue, String newValue, int userid) { + if (DEBUG) Slog.d(TAG, "Restored managed service setting: " + element + + " ovalue=" + oldValue + " nvalue=" + newValue); + if (mConfig.secureSettingName.equals(element)) { + if (element != null) { + mRestored = null; + Settings.Secure.putStringForUser(mContext.getContentResolver(), + restoredSettingName(mConfig), + newValue, + userid); + disableNonexistentServices(userid); + } + } + } + public void onPackagesChanged(boolean queryReplace, String[] pkgList) { if (DEBUG) Slog.d(TAG, "onPackagesChanged queryReplace=" + queryReplace + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList)) @@ -211,8 +260,23 @@ abstract public class ManagedServices { } private void disableNonexistentServices(int userId) { + final ContentResolver cr = mContext.getContentResolver(); + boolean restoredChanged = false; + if (mRestored == null) { + String restoredSetting = Settings.Secure.getStringForUser( + cr, + restoredSettingName(mConfig), + userId); + if (!TextUtils.isEmpty(restoredSetting)) { + if (DEBUG) Slog.d(TAG, "restored: " + restoredSetting); + String[] restored = restoredSetting.split(ENABLED_SERVICES_SEPARATOR); + mRestored = new ArraySet<String>(Arrays.asList(restored)); + } else { + mRestored = new ArraySet<String>(); + } + } String flatIn = Settings.Secure.getStringForUser( - mContext.getContentResolver(), + cr, mConfig.secureSettingName, userId); if (!TextUtils.isEmpty(flatIn)) { @@ -228,14 +292,16 @@ abstract public class ManagedServices { ResolveInfo resolveInfo = installedServices.get(i); ServiceInfo info = resolveInfo.serviceInfo; + ComponentName component = new ComponentName(info.packageName, info.name); if (!mConfig.bindPermission.equals(info.permission)) { Slog.w(TAG, "Skipping " + getCaption() + " service " + info.packageName + "/" + info.name + ": it does not require the permission " + mConfig.bindPermission); + restoredChanged |= mRestored.remove(component.flattenToString()); continue; } - installed.add(new ComponentName(info.packageName, info.name)); + installed.add(component); } String flatOut = ""; @@ -246,16 +312,27 @@ abstract public class ManagedServices { ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]); if (installed.contains(enabledComponent)) { remaining.add(enabled[i]); + restoredChanged |= mRestored.remove(enabled[i]); } } + remaining.addAll(mRestored); flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining); } if (DEBUG) Slog.v(TAG, "flat after: " + flatOut); if (!flatIn.equals(flatOut)) { - Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.putStringForUser(cr, mConfig.secureSettingName, flatOut, userId); } + if (restoredChanged) { + if (DEBUG) Slog.d(TAG, "restored changed; rewriting"); + final String flatRestored = TextUtils.join(ENABLED_SERVICES_SEPARATOR, + mRestored.toArray()); + Settings.Secure.putStringForUser(cr, + restoredSettingName(mConfig), + flatRestored, + userId); + } } } diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java deleted file mode 100644 index 1634c65..0000000 --- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2014 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.notification; - -import android.app.AlarmManager; -import android.app.AlarmManager.AlarmClockInfo; -import android.content.ComponentName; -import android.content.Context; -import android.net.Uri; -import android.service.notification.Condition; -import android.service.notification.ConditionProviderService; -import android.service.notification.IConditionProvider; -import android.service.notification.ZenModeConfig; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.Log; -import android.util.Slog; -import android.util.TimeUtils; - -import com.android.internal.R; -import com.android.server.notification.NotificationManagerService.DumpFilter; - -import java.io.PrintWriter; - -/** - * Built-in zen condition provider for alarm-clock-based conditions. - * - * <p>If the user's next alarm is within a lookahead threshold (config, default 12hrs), advertise - * it as an exit condition for zen mode. - * - * <p>The next alarm is defined as {@link AlarmManager#getNextAlarmClock(int)}, which does not - * survive a reboot. Maintain the illusion of a consistent next alarm value by holding on to - * a persisted condition until we receive the first value after reboot, or timeout with no value. - */ -public class NextAlarmConditionProvider extends ConditionProviderService { - private static final String TAG = "NextAlarmConditions"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private static final long SECONDS = 1000; - private static final long MINUTES = 60 * SECONDS; - private static final long HOURS = 60 * MINUTES; - - private static final long BAD_CONDITION = -1; - - public static final ComponentName COMPONENT = - new ComponentName("android", NextAlarmConditionProvider.class.getName()); - - private final Context mContext = this; - private final NextAlarmTracker mTracker; - private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); - - private boolean mConnected; - private long mLookaheadThreshold; - private boolean mRequesting; - - public NextAlarmConditionProvider(NextAlarmTracker tracker) { - if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()"); - mTracker = tracker; - } - - public void dump(PrintWriter pw, DumpFilter filter) { - pw.println(" NextAlarmConditionProvider:"); - pw.print(" mConnected="); pw.println(mConnected); - pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold); - pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")"); - pw.print(" mSubscriptions="); pw.println(mSubscriptions); - pw.print(" mRequesting="); pw.println(mRequesting); - } - - @Override - public void onConnected() { - if (DEBUG) Slog.d(TAG, "onConnected"); - mLookaheadThreshold = PropConfig.getInt(mContext, "nextalarm.condition.lookahead", - R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS; - mConnected = true; - mTracker.addCallback(mTrackerCallback); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (DEBUG) Slog.d(TAG, "onDestroy"); - mTracker.removeCallback(mTrackerCallback); - mConnected = false; - } - - @Override - public void onRequestConditions(int relevance) { - if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); - if (!mConnected) return; - mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0; - mTracker.evaluate(); - } - - @Override - public void onSubscribe(Uri conditionId) { - if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); - if (tryParseNextAlarmCondition(conditionId) == BAD_CONDITION) { - notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition"); - return; - } - mSubscriptions.add(conditionId); - mTracker.evaluate(); - } - - @Override - public void onUnsubscribe(Uri conditionId) { - if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); - mSubscriptions.remove(conditionId); - } - - public void attachBase(Context base) { - attachBaseContext(base); - } - - public IConditionProvider asInterface() { - return (IConditionProvider) onBind(null); - } - - private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) { - if (alarm == null) return false; - final long delta = NextAlarmTracker.getEarlyTriggerTime(alarm) - System.currentTimeMillis(); - return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold); - } - - private void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) { - final String formattedAlarm = alarm == null ? "" : mTracker.formatAlarm(alarm); - if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state) - + " alarm=" + formattedAlarm + " reason=" + reason); - notifyCondition(new Condition(id, - mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm), - mContext.getString(R.string.zen_mode_next_alarm_line_one), - formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW)); - } - - private Uri newConditionId(AlarmClockInfo nextAlarm) { - return new Uri.Builder().scheme(Condition.SCHEME) - .authority(ZenModeConfig.SYSTEM_AUTHORITY) - .appendPath(ZenModeConfig.NEXT_ALARM_PATH) - .appendPath(Integer.toString(mTracker.getCurrentUserId())) - .appendPath(Long.toString(nextAlarm.getTriggerTime())) - .build(); - } - - private long tryParseNextAlarmCondition(Uri conditionId) { - return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME) - && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) - && conditionId.getPathSegments().size() == 3 - && conditionId.getPathSegments().get(0).equals(ZenModeConfig.NEXT_ALARM_PATH) - && conditionId.getPathSegments().get(1) - .equals(Integer.toString(mTracker.getCurrentUserId())) - ? tryParseLong(conditionId.getPathSegments().get(2), BAD_CONDITION) - : BAD_CONDITION; - } - - private static long tryParseLong(String value, long defValue) { - if (TextUtils.isEmpty(value)) return defValue; - try { - return Long.valueOf(value); - } catch (NumberFormatException e) { - return defValue; - } - } - - private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { - final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm); - final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0; - if (DEBUG) Slog.d(TAG, "onEvaluate mSubscriptions=" + mSubscriptions - + " nextAlarmTime=" + mTracker.formatAlarmDebug(nextAlarmTime) - + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime) - + " withinThreshold=" + withinThreshold - + " booted=" + booted); - - ArraySet<Uri> conditions = mSubscriptions; - if (mRequesting && nextAlarm != null && withinThreshold) { - final Uri id = newConditionId(nextAlarm); - if (!conditions.contains(id)) { - conditions = new ArraySet<Uri>(conditions); - conditions.add(id); - } - } - for (Uri conditionId : conditions) { - final long time = tryParseNextAlarmCondition(conditionId); - if (time == BAD_CONDITION) { - notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "badCondition"); - } else if (!booted) { - // we don't know yet - if (mSubscriptions.contains(conditionId)) { - notifyCondition(conditionId, nextAlarm, Condition.STATE_UNKNOWN, "!booted"); - } - } else if (time != nextAlarmTime) { - // next alarm changed since subscription, consider obsolete - notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "changed"); - } else if (!withinThreshold) { - // next alarm outside threshold or in the past, condition = false - notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "!within"); - } else { - // next alarm within threshold and in the future, condition = true - notifyCondition(conditionId, nextAlarm, Condition.STATE_TRUE, "within"); - } - } - } - - private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() { - @Override - public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { - NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted); - } - }; -} diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java deleted file mode 100644 index 234f545..0000000 --- a/services/core/java/com/android/server/notification/NextAlarmTracker.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (C) 2014 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.notification; - -import android.app.ActivityManager; -import android.app.AlarmManager; -import android.app.AlarmManager.AlarmClockInfo; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Handler; -import android.os.Message; -import android.os.PowerManager; -import android.os.UserHandle; -import android.text.format.DateFormat; -import android.util.Log; -import android.util.Slog; -import android.util.TimeUtils; - -import com.android.server.notification.NotificationManagerService.DumpFilter; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Locale; - -/** Helper for tracking updates to the current user's next alarm. */ -public class NextAlarmTracker { - private static final String TAG = "NextAlarmTracker"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private static final String ACTION_TRIGGER = TAG + ".trigger"; - private static final String EXTRA_TRIGGER = "trigger"; - private static final int REQUEST_CODE = 100; - - private static final long SECONDS = 1000; - private static final long MINUTES = 60 * SECONDS; - private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS; // treat clear+set as update - private static final long EARLY = 5 * SECONDS; // fire early, ensure alarm stream is unmuted - private static final long WAIT_AFTER_INIT = 5 * MINUTES;// for initial alarm re-registration - private static final long WAIT_AFTER_BOOT = 20 * SECONDS; // for initial alarm re-registration - - private final Context mContext; - private final H mHandler = new H(); - private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); - - private long mInit; - private boolean mRegistered; - private AlarmManager mAlarmManager; - private int mCurrentUserId; - private long mScheduledAlarmTime; - private long mBootCompleted; - private PowerManager.WakeLock mWakeLock; - - public NextAlarmTracker(Context context) { - mContext = context; - } - - public void dump(PrintWriter pw, DumpFilter filter) { - pw.println(" NextAlarmTracker:"); - pw.print(" len(mCallbacks)="); pw.println(mCallbacks.size()); - pw.print(" mRegistered="); pw.println(mRegistered); - pw.print(" mInit="); pw.println(mInit); - pw.print(" mBootCompleted="); pw.println(mBootCompleted); - pw.print(" mCurrentUserId="); pw.println(mCurrentUserId); - pw.print(" mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime)); - pw.print(" mWakeLock="); pw.println(mWakeLock); - } - - public void addCallback(Callback callback) { - mCallbacks.add(callback); - } - - public void removeCallback(Callback callback) { - mCallbacks.remove(callback); - } - - public int getCurrentUserId() { - return mCurrentUserId; - } - - public AlarmClockInfo getNextAlarm() { - return mAlarmManager.getNextAlarmClock(mCurrentUserId); - } - - public void onUserSwitched() { - reset(); - } - - public void init() { - mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mInit = System.currentTimeMillis(); - reset(); - } - - public void reset() { - if (mRegistered) { - mContext.unregisterReceiver(mReceiver); - } - mCurrentUserId = ActivityManager.getCurrentUser(); - final IntentFilter filter = new IntentFilter(); - filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED); - filter.addAction(ACTION_TRIGGER); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - filter.addAction(Intent.ACTION_BOOT_COMPLETED); - mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null, - null); - mRegistered = true; - evaluate(); - } - - public void destroy() { - if (mRegistered) { - mContext.unregisterReceiver(mReceiver); - mRegistered = false; - } - } - - public void evaluate() { - mHandler.postEvaluate(0); - } - - private void fireEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { - for (Callback callback : mCallbacks) { - callback.onEvaluate(nextAlarm, wakeupTime, booted); - } - } - - private void handleEvaluate() { - final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId); - final long triggerTime = getEarlyTriggerTime(nextAlarm); - final long now = System.currentTimeMillis(); - final boolean alarmUpcoming = triggerTime > now; - final boolean booted = isDoneWaitingAfterBoot(now); - if (DEBUG) Slog.d(TAG, "handleEvaluate nextAlarm=" + formatAlarmDebug(triggerTime) - + " alarmUpcoming=" + alarmUpcoming - + " booted=" + booted); - fireEvaluate(nextAlarm, triggerTime, booted); - if (!booted) { - // recheck after boot - final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT; - rescheduleAlarm(recheckTime); - return; - } - if (alarmUpcoming) { - // wake up just before the next alarm - rescheduleAlarm(triggerTime); - } - } - - public static long getEarlyTriggerTime(AlarmClockInfo alarm) { - return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0; - } - - private boolean isDoneWaitingAfterBoot(long time) { - if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT; - if (mInit > 0) return (time - mInit) > WAIT_AFTER_INIT; - return true; - } - - public static String formatDuration(long millis) { - final StringBuilder sb = new StringBuilder(); - TimeUtils.formatDuration(millis, sb); - return sb.toString(); - } - - public String formatAlarm(AlarmClockInfo alarm) { - return alarm != null ? formatAlarm(alarm.getTriggerTime()) : null; - } - - private String formatAlarm(long time) { - return formatAlarm(time, "Hm", "hma"); - } - - private String formatAlarm(long time, String skeleton24, String skeleton12) { - final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12; - final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); - return DateFormat.format(pattern, time).toString(); - } - - public String formatAlarmDebug(AlarmClockInfo alarm) { - return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0); - } - - public String formatAlarmDebug(long time) { - if (time <= 0) return Long.toString(time); - return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa")); - } - - private void rescheduleAlarm(long time) { - if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time); - final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE, - new Intent(ACTION_TRIGGER) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .putExtra(EXTRA_TRIGGER, time), - PendingIntent.FLAG_UPDATE_CURRENT); - alarms.cancel(pendingIntent); - mScheduledAlarmTime = time; - if (time > 0) { - if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)", - formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis()))); - alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); - } - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (DEBUG) Slog.d(TAG, "onReceive " + action); - long delay = 0; - if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) { - delay = NEXT_ALARM_UPDATE_DELAY; - if (DEBUG) Slog.d(TAG, String.format(" next alarm for user %s: %s", - mCurrentUserId, - formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId)))); - } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - mBootCompleted = System.currentTimeMillis(); - } - mHandler.postEvaluate(delay); - mWakeLock.acquire(delay + 5000); // stay awake during evaluate - } - }; - - private class H extends Handler { - private static final int MSG_EVALUATE = 1; - - public void postEvaluate(long delay) { - removeMessages(MSG_EVALUATE); - sendEmptyMessageDelayed(MSG_EVALUATE, delay); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_EVALUATE) { - handleEvaluate(); - } - } - } - - public interface Callback { - void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted); - } -} diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java index 1335706..22cdd58 100644 --- a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java @@ -18,6 +18,7 @@ package com.android.server.notification; import android.app.Notification; import android.content.Context; +import android.util.Log; import android.util.Slog; /** @@ -25,8 +26,8 @@ import android.util.Slog; * notifications and marks them to get a temporary ranking bump. */ public class NotificationIntrusivenessExtractor implements NotificationSignalExtractor { - private static final String TAG = "NotificationNoiseExtractor"; - private static final boolean DBG = false; + private static final String TAG = "IntrusivenessExtractor"; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); /** Length of time (in milliseconds) that an intrusive or noisy notification will stay at the top of the ranking order, before it falls back to its natural position. */ @@ -48,7 +49,7 @@ public class NotificationIntrusivenessExtractor implements NotificationSignalExt (notification.defaults & Notification.DEFAULT_SOUND) != 0 || notification.sound != null || notification.fullScreenIntent != null) { - record.setRecentlyIntusive(true); + record.setRecentlyIntrusive(true); } return new RankingReconsideration(record.getKey(), HANG_TIME_MS) { @@ -59,7 +60,7 @@ public class NotificationIntrusivenessExtractor implements NotificationSignalExt @Override public void applyChangesLocked(NotificationRecord record) { - record.setRecentlyIntusive(false); + record.setRecentlyIntrusive(false); } }; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f49d77d..1008653 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -29,9 +29,11 @@ import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.INotificationManager; +import android.app.INotificationManagerCallback; import android.app.ITransientNotification; import android.app.Notification; import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; @@ -50,10 +52,12 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioAttributes; import android.media.AudioManager; +import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -64,6 +68,7 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; @@ -124,6 +129,8 @@ import java.util.Objects; public class NotificationManagerService extends SystemService { static final String TAG = "NotificationService"; static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); + public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE + && SystemProperties.getBoolean("debug.child_notifs", false); static final int MAX_PACKAGE_NOTIFICATIONS = 50; @@ -179,6 +186,7 @@ public class NotificationManagerService extends SystemService { private IActivityManager mAm; AudioManager mAudioManager; + AudioManagerInternal mAudioManagerInternal; StatusBarManagerInternal mStatusBar; Vibrator mVibrator; @@ -207,7 +215,7 @@ public class NotificationManagerService extends SystemService { private final ArraySet<ManagedServiceInfo> mListenersDisablingEffects = new ArraySet<>(); private ComponentName mEffectsSuppressor; private int mListenerHints; // right now, all hints are global - private int mInterruptionFilter; // current ZEN mode as communicated to listeners + private int mInterruptionFilter = NotificationListenerService.INTERRUPTION_FILTER_UNKNOWN; // for enabling and disabling notification pulse behavior private boolean mScreenOn = true; @@ -221,6 +229,8 @@ public class NotificationManagerService extends SystemService { new ArrayMap<String, NotificationRecord>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>(); final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>(); + private final ArrayMap<String, Policy.Token> mPolicyTokens = new ArrayMap<>(); + // The last key in this list owns the hardware. ArrayList<String> mLights = new ArrayList<>(); @@ -873,7 +883,8 @@ public class NotificationManagerService extends SystemService { mRankingHelper = new RankingHelper(getContext(), new RankingWorkerHandler(mRankingThread.getLooper()), extractorNames); - mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper()); + mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles); + mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders); mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override public void onConfigChanged() { @@ -886,6 +897,13 @@ public class NotificationManagerService extends SystemService { updateInterruptionFilterLocked(); } } + + @Override + void onPolicyChanged() { + getContext().sendBroadcast( + new Intent(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)); + } }); final File systemDir = new File(Environment.getDataDirectory(), "system"); mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml")); @@ -894,8 +912,6 @@ public class NotificationManagerService extends SystemService { importOldBlockDb(); mListeners = new NotificationListeners(); - mConditionProviders = new ConditionProviders(getContext(), - mHandler, mUserProfiles, mZenModeHelper); mStatusBar = getLocalService(StatusBarManagerInternal.class); mStatusBar.setNotificationDelegate(mNotificationDelegate); @@ -930,7 +946,7 @@ public class NotificationManagerService extends SystemService { Settings.Global.DEVICE_PROVISIONED, 0)) { mDisableNotificationEffects = true; } - mZenModeHelper.readZenModeFromSetting(); + mZenModeHelper.initZenMode(); mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter(); mUserProfiles.updateCache(getContext()); @@ -997,6 +1013,7 @@ public class NotificationManagerService extends SystemService { // Grab our optional AudioService mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + mAudioManagerInternal = getLocalService(AudioManagerInternal.class); mZenModeHelper.onSystemReady(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { // This observer will force an update when observe is called, causing us to @@ -1023,6 +1040,7 @@ public class NotificationManagerService extends SystemService { private void updateListenerHintsLocked() { final int hints = mListenersDisablingEffects.isEmpty() ? 0 : HINT_HOST_DISABLE_EFFECTS; if (hints == mListenerHints) return; + ZenLog.traceListenerHintsChanged(mListenerHints, hints, mListenersDisablingEffects.size()); mListenerHints = hints; scheduleListenerHintsChanged(hints); } @@ -1031,6 +1049,7 @@ public class NotificationManagerService extends SystemService { final ComponentName suppressor = !mListenersDisablingEffects.isEmpty() ? mListenersDisablingEffects.valueAt(0).component : null; if (Objects.equals(suppressor, mEffectsSuppressor)) return; + ZenLog.traceEffectsSuppressorChanged(mEffectsSuppressor, suppressor); mEffectsSuppressor = suppressor; mZenModeHelper.setEffectsSuppressed(suppressor != null); getContext().sendBroadcast(new Intent(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED) @@ -1207,6 +1226,19 @@ public class NotificationManagerService extends SystemService { } @Override + public void setPackagePeekable(String pkg, int uid, boolean peekable) { + checkCallerIsSystem(); + + mRankingHelper.setPackagePeekable(pkg, uid, peekable); + } + + @Override + public boolean getPackagePeekable(String pkg, int uid) { + checkCallerIsSystem(); + return mRankingHelper.getPackagePeekable(pkg, uid); + } + + @Override public void setPackageVisibilityOverride(String pkg, int uid, int visibility) { checkCallerIsSystem(); mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility); @@ -1250,6 +1282,44 @@ public class NotificationManagerService extends SystemService { } /** + * Public API for getting a list of current notifications for the calling package/uid. + * + * @returns A list of all the package's notifications, in natural order. + */ + @Override + public ParceledListSlice<StatusBarNotification> getAppActiveNotifications(String pkg, + int incomingUserId) { + checkCallerIsSystemOrSameApp(pkg); + int userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), incomingUserId, true, false, + "getAppActiveNotifications", pkg); + + final int N = mNotificationList.size(); + final ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(N); + + synchronized (mNotificationList) { + for (int i = 0; i < N; i++) { + final StatusBarNotification sbn = mNotificationList.get(i).sbn; + if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId) { + // We could pass back a cloneLight() but clients might get confused and + // try to send this thing back to notify() again, which would not work + // very well. + final StatusBarNotification sbnOut = new StatusBarNotification( + sbn.getPackageName(), + sbn.getOpPkg(), + sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), + 0, // hide score from apps + sbn.getNotification().clone(), + sbn.getUser(), sbn.getPostTime()); + list.add(sbnOut); + } + } + } + + return new ParceledListSlice<StatusBarNotification>(list); + } + + /** * System-only API for getting a list of recent (cleared, no longer shown) notifications. * * Requires ACCESS_NOTIFICATIONS which is signature|system. @@ -1468,57 +1538,60 @@ public class NotificationManagerService extends SystemService { } @Override + public int getZenMode() { + return mZenModeHelper.getZenMode(); + } + + @Override public ZenModeConfig getZenModeConfig() { - enforceSystemOrSystemUI("INotificationManager.getZenModeConfig"); + enforceSystemOrSystemUIOrVolume("INotificationManager.getZenModeConfig"); return mZenModeHelper.getConfig(); } @Override - public boolean setZenModeConfig(ZenModeConfig config) { + public boolean setZenModeConfig(ZenModeConfig config, String reason) { checkCallerIsSystem(); - return mZenModeHelper.setConfig(config); + return mZenModeHelper.setConfig(config, reason); } @Override - public void notifyConditions(String pkg, IConditionProvider provider, - Condition[] conditions) { - final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider); - checkCallerIsSystemOrSameApp(pkg); + public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException { + enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode"); final long identity = Binder.clearCallingIdentity(); try { - mConditionProviders.notifyConditions(pkg, info, conditions); + mZenModeHelper.setManualZenMode(mode, conditionId, reason); } finally { Binder.restoreCallingIdentity(identity); } } @Override - public void requestZenModeConditions(IConditionListener callback, int relevance) { - enforceSystemOrSystemUI("INotificationManager.requestZenModeConditions"); - mConditionProviders.requestZenModeConditions(callback, relevance); - } - - @Override - public void setZenModeCondition(Condition condition) { - enforceSystemOrSystemUI("INotificationManager.setZenModeCondition"); + public void notifyConditions(String pkg, IConditionProvider provider, + Condition[] conditions) { + final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider); + checkCallerIsSystemOrSameApp(pkg); final long identity = Binder.clearCallingIdentity(); try { - mConditionProviders.setZenModeCondition(condition, "binderCall"); + mConditionProviders.notifyConditions(pkg, info, conditions); } finally { Binder.restoreCallingIdentity(identity); } } @Override - public void setAutomaticZenModeConditions(Uri[] conditionIds) { - enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions"); - mConditionProviders.setAutomaticZenModeConditions(conditionIds); + public void requestZenModeConditions(IConditionListener callback, int relevance) { + enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions"); + mZenModeHelper.requestZenModeConditions(callback, relevance); } - @Override - public Condition[] getAutomaticZenModeConditions() { - enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions"); - return mConditionProviders.getAutomaticZenModeConditions(); + private void enforceSystemOrSystemUIOrVolume(String message) { + if (mAudioManagerInternal != null) { + final int vcuid = mAudioManagerInternal.getVolumeControllerUid(); + if (vcuid > 0 && Binder.getCallingUid() == vcuid) { + return; + } + } + enforceSystemOrSystemUI(message); } private void enforceSystemOrSystemUI(String message) { @@ -1527,6 +1600,18 @@ public class NotificationManagerService extends SystemService { message); } + private void enforcePolicyToken(Policy.Token token, String method) { + if (!checkPolicyToken(token)) { + Slog.w(TAG, "Invalid notification policy token calling " + method); + throw new SecurityException("Invalid notification policy token"); + } + } + + private boolean checkPolicyToken(Policy.Token token) { + return mPolicyTokens.containsValue(token) + || mListeners.mPolicyTokens.containsValue(token); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -1542,7 +1627,7 @@ public class NotificationManagerService extends SystemService { @Override public ComponentName getEffectsSuppressor() { - enforceSystemOrSystemUI("INotificationManager.getEffectsSuppressor"); + enforceSystemOrSystemUIOrVolume("INotificationManager.getEffectsSuppressor"); return mEffectsSuppressor; } @@ -1559,27 +1644,88 @@ public class NotificationManagerService extends SystemService { @Override public boolean isSystemConditionProviderEnabled(String path) { - enforceSystemOrSystemUI("INotificationManager.isSystemConditionProviderEnabled"); - return mConditionProviders.isSystemConditionProviderEnabled(path); + enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled"); + return mConditionProviders.isSystemProviderEnabled(path); } - }; - private String[] getActiveNotificationKeys(INotificationListener token) { - final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); - final ArrayList<String> keys = new ArrayList<String>(); - if (info.isEnabledForCurrentProfiles()) { - synchronized (mNotificationList) { - final int N = mNotificationList.size(); - for (int i = 0; i < N; i++) { - final StatusBarNotification sbn = mNotificationList.get(i).sbn; - if (info.enabledAndUserMatches(sbn.getUserId())) { - keys.add(sbn.getKey()); + // Backup/restore interface + @Override + public byte[] getBackupPayload(int user) { + // TODO: build a payload of whatever is appropriate + return null; + } + + @Override + public void applyRestore(byte[] payload, int user) { + // TODO: apply the restored payload as new current state + } + + @Override + public Policy.Token getPolicyTokenFromListener(INotificationListener listener) { + final long identity = Binder.clearCallingIdentity(); + try { + return mListeners.getPolicyToken(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void requestNotificationPolicyToken(String pkg, + INotificationManagerCallback callback) throws RemoteException { + if (callback == null) { + Slog.w(TAG, "requestNotificationPolicyToken: no callback specified"); + return; + } + if (pkg == null) { + Slog.w(TAG, "requestNotificationPolicyToken denied: no package specified"); + callback.onPolicyToken(null); + return; + } + Policy.Token token = null; + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mNotificationList) { + token = mPolicyTokens.get(pkg); + if (token == null) { + token = new Policy.Token(new Binder()); + mPolicyTokens.put(pkg, token); } + if (DBG) Slog.w(TAG, "requestNotificationPolicyToken granted for " + pkg); } + } finally { + Binder.restoreCallingIdentity(identity); } + callback.onPolicyToken(token); } - return keys.toArray(new String[keys.size()]); - } + + @Override + public boolean isNotificationPolicyTokenValid(String pkg, Policy.Token token) { + return checkPolicyToken(token); + } + + @Override + public Policy getNotificationPolicy(Policy.Token token) { + enforcePolicyToken(token, "getNotificationPolicy"); + final long identity = Binder.clearCallingIdentity(); + try { + return mZenModeHelper.getNotificationPolicy(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void setNotificationPolicy(Policy.Token token, Policy policy) { + enforcePolicyToken(token, "setNotificationPolicy"); + final long identity = Binder.clearCallingIdentity(); + try { + mZenModeHelper.setNotificationPolicy(policy); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + }; private String disableNotificationEffects(NotificationRecord record) { if (mDisableNotificationEffects) { @@ -1694,6 +1840,10 @@ public class NotificationManagerService extends SystemService { pw.print(listener.component); } pw.println(')'); + pw.print(" mPolicyTokens.keys: "); + pw.println(TextUtils.join(",", mPolicyTokens.keySet())); + pw.print(" mListeners.mPolicyTokens.keys: "); + pw.println(TextUtils.join(",", mListeners.mPolicyTokens.keySet())); } pw.println("\n Condition providers:"); @@ -1815,6 +1965,14 @@ public class NotificationManagerService extends SystemService { notification.priority = Notification.PRIORITY_HIGH; } } + // force no heads up per package config + if (!mRankingHelper.getPackagePeekable(pkg, callingUid)) { + if (notification.extras == null) { + notification.extras = new Bundle(); + } + notification.extras.putInt(Notification.EXTRA_AS_HEADS_UP, + Notification.HEADS_UP_NEVER); + } // 1. initial score: buckets of 10, around the app [-20..20] final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; @@ -1981,35 +2139,37 @@ public class NotificationManagerService extends SystemService { */ private boolean removeUnusedGroupedNotificationLocked(NotificationRecord r, NotificationRecord old, int callingUid, int callingPid) { - // No optimizations are possible if listeners want groups. - if (mListeners.notificationGroupsDesired()) { - return false; - } + if (!ENABLE_CHILD_NOTIFICATIONS) { + // No optimizations are possible if listeners want groups. + if (mListeners.notificationGroupsDesired()) { + return false; + } - StatusBarNotification sbn = r.sbn; - String group = sbn.getGroupKey(); - boolean isSummary = sbn.getNotification().isGroupSummary(); - boolean isChild = sbn.getNotification().isGroupChild(); + StatusBarNotification sbn = r.sbn; + String group = sbn.getGroupKey(); + boolean isSummary = sbn.getNotification().isGroupSummary(); + boolean isChild = sbn.getNotification().isGroupChild(); - NotificationRecord summary = mSummaryByGroupKey.get(group); - if (isChild && summary != null) { - // Child with an active summary -> ignore - if (DBG) { - Slog.d(TAG, "Ignoring group child " + sbn.getKey() + " due to existing summary " - + summary.getKey()); - } - // Make sure we don't leave an old version of the notification around. - if (old != null) { + NotificationRecord summary = mSummaryByGroupKey.get(group); + if (isChild && summary != null) { + // Child with an active summary -> ignore if (DBG) { - Slog.d(TAG, "Canceling old version of ignored group child " + sbn.getKey()); + Slog.d(TAG, "Ignoring group child " + sbn.getKey() + " due to existing summary " + + summary.getKey()); } - cancelNotificationLocked(old, false, REASON_GROUP_OPTIMIZATION); + // Make sure we don't leave an old version of the notification around. + if (old != null) { + if (DBG) { + Slog.d(TAG, "Canceling old version of ignored group child " + sbn.getKey()); + } + cancelNotificationLocked(old, false, REASON_GROUP_OPTIMIZATION); + } + return true; + } else if (isSummary) { + // Summary -> cancel children + cancelGroupChildrenLocked(r, callingUid, callingPid, null, + REASON_GROUP_OPTIMIZATION); } - return true; - } else if (isSummary) { - // Summary -> cancel children - cancelGroupChildrenLocked(r, callingUid, callingPid, null, - REASON_GROUP_OPTIMIZATION); } return false; } @@ -2541,7 +2701,8 @@ public class NotificationManagerService extends SystemService { // Save it for users of getHistoricalNotifications() mArchive.record(r.sbn); - EventLogTags.writeNotificationCanceled(canceledKey, reason); + int lifespan = (int) (System.currentTimeMillis() - r.getCreationTimeMs()); + EventLogTags.writeNotificationCanceled(canceledKey, reason, lifespan); } /** @@ -2560,8 +2721,8 @@ public class NotificationManagerService extends SystemService { @Override public void run() { String listenerName = listener == null ? null : listener.component.toShortString(); - EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag, userId, - mustHaveFlags, mustNotHaveFlags, reason, listenerName); + if (DBG) EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag, + userId, mustHaveFlags, mustNotHaveFlags, reason, listenerName); synchronized (mNotificationList) { int index = indexOfNotificationLocked(pkg, tag, id, userId); @@ -2936,12 +3097,18 @@ public class NotificationManagerService extends SystemService { public class NotificationListeners extends ManagedServices { private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>(); + private final ArrayMap<ComponentName, Policy.Token> mPolicyTokens = new ArrayMap<>(); private boolean mNotificationGroupsDesired; public NotificationListeners() { super(getContext(), mHandler, mNotificationList, mUserProfiles); } + public Policy.Token getPolicyToken(INotificationListener listener) { + final ManagedServiceInfo info = checkServiceTokenLocked(listener); + return info == null ? null : mPolicyTokens.get(info.component); + } + @Override protected Config getConfig() { Config c = new Config(); @@ -2966,6 +3133,7 @@ public class NotificationManagerService extends SystemService { synchronized (mNotificationList) { updateNotificationGroupsDesiredLocked(); update = makeRankingUpdateLocked(info); + mPolicyTokens.put(info.component, new Policy.Token(new Binder())); } try { listener.onListenerConnected(update); @@ -2982,6 +3150,7 @@ public class NotificationManagerService extends SystemService { } mLightTrimListeners.remove(removed); updateNotificationGroupsDesiredLocked(); + mPolicyTokens.remove(removed.component); } public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index ea6f2db..5569a09 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -62,6 +62,9 @@ public final class NotificationRecord { // The timestamp used for ranking. private long mRankingTimeMs; + // The first post time, stable across updates. + private long mCreationTimeMs; + // Is this record an update of an old record? public boolean isUpdate; private int mPackagePriority; @@ -77,6 +80,7 @@ public final class NotificationRecord { this.score = score; mOriginalFlags = sbn.getNotification().flags; mRankingTimeMs = calculateRankingTimeMs(0L); + mCreationTimeMs = sbn.getPostTime(); } // copy any notes that the ranking system may have made before the update @@ -87,6 +91,7 @@ public final class NotificationRecord { mPackageVisibility = previous.mPackageVisibility; mIntercept = previous.mIntercept; mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs()); + mCreationTimeMs = previous.mCreationTimeMs; // Don't copy mGlobalSortKey, recompute it. } @@ -167,6 +172,7 @@ public final class NotificationRecord { pw.println(prefix + " mIntercept=" + mIntercept); pw.println(prefix + " mGlobalSortKey=" + mGlobalSortKey); pw.println(prefix + " mRankingTimeMs=" + mRankingTimeMs); + pw.println(prefix + " mCreationTimeMs=" + mCreationTimeMs); } @@ -209,7 +215,7 @@ public final class NotificationRecord { return mContactAffinity; } - public void setRecentlyIntusive(boolean recentlyIntrusive) { + public void setRecentlyIntrusive(boolean recentlyIntrusive) { mRecentlyIntrusive = recentlyIntrusive; } @@ -263,6 +269,13 @@ public final class NotificationRecord { } /** + * Returns the timestamp of the first post, ignoring updates. + */ + public long getCreationTimeMs() { + return mCreationTimeMs; + } + + /** * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()} * of the previous notification record, 0 otherwise */ diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index 8278c4f..4696771 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -28,6 +28,7 @@ import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.util.Log; +import com.android.internal.logging.MetricsLogger; import com.android.server.notification.NotificationManagerService.DumpFilter; import java.io.PrintWriter; @@ -49,15 +50,17 @@ public class NotificationUsageStats { // WARNING: Aggregated stats can grow unboundedly with pkg+id+tag. // Don't enable on production builds. private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = false; - private static final boolean ENABLE_SQLITE_LOG = false; + private static final boolean ENABLE_SQLITE_LOG = true; private static final AggregatedStats[] EMPTY_AGGREGATED_STATS = new AggregatedStats[0]; // Guarded by synchronized(this). private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>(); private final SQLiteLog mSQLiteLog; + private final Context mContext; public NotificationUsageStats(Context context) { + mContext = context; mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null; } @@ -103,6 +106,8 @@ public class NotificationUsageStats { * Called when the user dismissed the notification via the UI. */ public synchronized void registerDismissedByUser(NotificationRecord notification) { + MetricsLogger.histogram(mContext, "note_dismiss_longevity", + (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000)); notification.stats.onDismiss(); for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { stats.numDismissedByUser++; @@ -117,6 +122,8 @@ public class NotificationUsageStats { * Called when the user clicked the notification in the UI. */ public synchronized void registerClickedByUser(NotificationRecord notification) { + MetricsLogger.histogram(mContext, "note_click_longevity", + (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000)); notification.stats.onClick(); for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { stats.numClickedByUser++; @@ -222,7 +229,7 @@ public class NotificationUsageStats { public void collect(SingleNotificationStats singleNotificationStats) { posttimeMs.addSample( - SystemClock.elapsedRealtime() - singleNotificationStats.posttimeElapsedMs); + SystemClock.elapsedRealtime() - singleNotificationStats.posttimeElapsedMs); if (singleNotificationStats.posttimeToDismissMs >= 0) { posttimeToDismissMs.addSample(singleNotificationStats.posttimeToDismissMs); } diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index aea137b..803db10 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -20,6 +20,10 @@ public interface RankingConfig { void setPackagePriority(String packageName, int uid, int priority); + boolean getPackagePeekable(String packageName, int uid); + + void setPackagePeekable(String packageName, int uid, boolean peekable); + int getPackageVisibilityOverride(String packageName, int uid); void setPackageVisibilityOverride(String packageName, int uid, int visibility); diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 6a96f85..88055ba 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -23,10 +23,8 @@ import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; import android.util.Slog; -import android.util.SparseIntArray; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -35,12 +33,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; -import java.util.Set; import java.util.concurrent.TimeUnit; public class RankingHelper implements RankingConfig { private static final String TAG = "RankingHelper"; - private static final boolean DEBUG = false; private static final int XML_VERSION = 1; @@ -51,16 +47,20 @@ public class RankingHelper implements RankingConfig { private static final String ATT_NAME = "name"; private static final String ATT_UID = "uid"; private static final String ATT_PRIORITY = "priority"; + private static final String ATT_PEEKABLE = "peekable"; private static final String ATT_VISIBILITY = "visibility"; + private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; + private static final boolean DEFAULT_PEEKABLE = true; + private static final int DEFAULT_VISIBILITY = + NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; + private final NotificationSignalExtractor[] mSignalExtractors; private final NotificationComparator mPreliminaryComparator = new NotificationComparator(); private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator(); - // Package name to uid, to priority. Would be better as Table<String, Int, Int> - private final ArrayMap<String, SparseIntArray> mPackagePriorities; - private final ArrayMap<String, SparseIntArray> mPackageVisibilities; - private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp; + private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record + private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>(); private final Context mContext; private final Handler mRankingHandler; @@ -68,8 +68,6 @@ public class RankingHelper implements RankingConfig { public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) { mContext = context; mRankingHandler = rankingHandler; - mPackagePriorities = new ArrayMap<String, SparseIntArray>(); - mPackageVisibilities = new ArrayMap<String, SparseIntArray>(); final int N = extractorNames.length; mSignalExtractors = new NotificationSignalExtractor[N]; @@ -89,9 +87,9 @@ public class RankingHelper implements RankingConfig { Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e); } } - mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>(); } + @SuppressWarnings("unchecked") public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) { final int N = mSignalExtractors.length; for (int i = 0; i < N; i++) { @@ -126,8 +124,7 @@ public class RankingHelper implements RankingConfig { if (type != XmlPullParser.START_TAG) return; String tag = parser.getName(); if (!TAG_RANKING.equals(tag)) return; - mPackagePriorities.clear(); - final int version = safeInt(parser, ATT_VERSION, XML_VERSION); + mRecords.clear(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { tag = parser.getName(); if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { @@ -136,27 +133,20 @@ public class RankingHelper implements RankingConfig { if (type == XmlPullParser.START_TAG) { if (TAG_PACKAGE.equals(tag)) { int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL); - int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT); - int vis = safeInt(parser, ATT_VISIBILITY, - NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); + int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY); + boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE); + int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY); String name = parser.getAttributeValue(null, ATT_NAME); if (!TextUtils.isEmpty(name)) { - if (priority != Notification.PRIORITY_DEFAULT) { - SparseIntArray priorityByUid = mPackagePriorities.get(name); - if (priorityByUid == null) { - priorityByUid = new SparseIntArray(); - mPackagePriorities.put(name, priorityByUid); - } - priorityByUid.put(uid, priority); + if (priority != DEFAULT_PRIORITY) { + getOrCreateRecord(name, uid).priority = priority; + } + if (peekable != DEFAULT_PEEKABLE) { + getOrCreateRecord(name, uid).peekable = peekable; } - if (vis != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) { - SparseIntArray visibilityByUid = mPackageVisibilities.get(name); - if (visibilityByUid == null) { - visibilityByUid = new SparseIntArray(); - mPackageVisibilities.put(name, visibilityByUid); - } - visibilityByUid.put(uid, vis); + if (vis != DEFAULT_VISIBILITY) { + getOrCreateRecord(name, uid).visibility = vis; } } } @@ -165,49 +155,53 @@ public class RankingHelper implements RankingConfig { throw new IllegalStateException("Failed to reach END_DOCUMENT"); } + private static String recordKey(String pkg, int uid) { + return pkg + "|" + uid; + } + + private Record getOrCreateRecord(String pkg, int uid) { + final String key = recordKey(pkg, uid); + Record r = mRecords.get(key); + if (r == null) { + r = new Record(); + r.pkg = pkg; + r.uid = uid; + mRecords.put(key, r); + } + return r; + } + + private void removeDefaultRecords() { + final int N = mRecords.size(); + for (int i = N - 1; i >= 0; i--) { + final Record r = mRecords.valueAt(i); + if (r.priority == DEFAULT_PRIORITY && r.peekable == DEFAULT_PEEKABLE + && r.visibility == DEFAULT_VISIBILITY) { + mRecords.remove(i); + } + } + } + public void writeXml(XmlSerializer out) throws IOException { out.startTag(null, TAG_RANKING); out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION)); - final Set<String> packageNames = new ArraySet<>(mPackagePriorities.size() - + mPackageVisibilities.size()); - packageNames.addAll(mPackagePriorities.keySet()); - packageNames.addAll(mPackageVisibilities.keySet()); - final Set<Integer> packageUids = new ArraySet<>(); - for (String packageName : packageNames) { - packageUids.clear(); - SparseIntArray priorityByUid = mPackagePriorities.get(packageName); - SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName); - if (priorityByUid != null) { - final int M = priorityByUid.size(); - for (int j = 0; j < M; j++) { - packageUids.add(priorityByUid.keyAt(j)); - } + final int N = mRecords.size(); + for (int i = 0; i < N; i++) { + final Record r = mRecords.valueAt(i); + out.startTag(null, TAG_PACKAGE); + out.attribute(null, ATT_NAME, r.pkg); + if (r.priority != DEFAULT_PRIORITY) { + out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority)); } - if (visibilityByUid != null) { - final int M = visibilityByUid.size(); - for (int j = 0; j < M; j++) { - packageUids.add(visibilityByUid.keyAt(j)); - } + if (r.peekable != DEFAULT_PEEKABLE) { + out.attribute(null, ATT_PEEKABLE, Boolean.toString(r.peekable)); } - for (Integer uid : packageUids) { - out.startTag(null, TAG_PACKAGE); - out.attribute(null, ATT_NAME, packageName); - if (priorityByUid != null) { - final int priority = priorityByUid.get(uid); - if (priority != Notification.PRIORITY_DEFAULT) { - out.attribute(null, ATT_PRIORITY, Integer.toString(priority)); - } - } - if (visibilityByUid != null) { - final int visibility = visibilityByUid.get(uid); - if (visibility != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) { - out.attribute(null, ATT_VISIBILITY, Integer.toString(visibility)); - } - } - out.attribute(null, ATT_UID, Integer.toString(uid)); - out.endTag(null, TAG_PACKAGE); + if (r.visibility != DEFAULT_VISIBILITY) { + out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility)); } + out.attribute(null, ATT_UID, Integer.toString(r.uid)); + out.endTag(null, TAG_PACKAGE); } out.endTag(null, TAG_RANKING); } @@ -296,14 +290,20 @@ public class RankingHelper implements RankingConfig { } } + private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) { + final String val = parser.getAttributeValue(null, att); + return tryParseBool(val, defValue); + } + + private static boolean tryParseBool(String value, boolean defValue) { + if (TextUtils.isEmpty(value)) return defValue; + return Boolean.valueOf(value); + } + @Override public int getPackagePriority(String packageName, int uid) { - int priority = Notification.PRIORITY_DEFAULT; - SparseIntArray priorityByUid = mPackagePriorities.get(packageName); - if (priorityByUid != null) { - priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT); - } - return priority; + final Record r = mRecords.get(recordKey(packageName, uid)); + return r != null ? r.priority : DEFAULT_PRIORITY; } @Override @@ -311,24 +311,31 @@ public class RankingHelper implements RankingConfig { if (priority == getPackagePriority(packageName, uid)) { return; } - SparseIntArray priorityByUid = mPackagePriorities.get(packageName); - if (priorityByUid == null) { - priorityByUid = new SparseIntArray(); - mPackagePriorities.put(packageName, priorityByUid); + getOrCreateRecord(packageName, uid).priority = priority; + removeDefaultRecords(); + updateConfig(); + } + + @Override + public boolean getPackagePeekable(String packageName, int uid) { + final Record r = mRecords.get(recordKey(packageName, uid)); + return r != null ? r.peekable : DEFAULT_PEEKABLE; + } + + @Override + public void setPackagePeekable(String packageName, int uid, boolean peekable) { + if (peekable == getPackagePeekable(packageName, uid)) { + return; } - priorityByUid.put(uid, priority); + getOrCreateRecord(packageName, uid).peekable = peekable; + removeDefaultRecords(); updateConfig(); } @Override public int getPackageVisibilityOverride(String packageName, int uid) { - int visibility = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE; - SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName); - if (visibilityByUid != null) { - visibility = visibilityByUid.get(uid, - NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); - } - return visibility; + final Record r = mRecords.get(recordKey(packageName, uid)); + return r != null ? r.visibility : DEFAULT_VISIBILITY; } @Override @@ -336,12 +343,8 @@ public class RankingHelper implements RankingConfig { if (visibility == getPackageVisibilityOverride(packageName, uid)) { return; } - SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName); - if (visibilityByUid == null) { - visibilityByUid = new SparseIntArray(); - mPackageVisibilities.put(packageName, visibilityByUid); - } - visibilityByUid.put(uid, visibility); + getOrCreateRecord(packageName, uid).visibility = visibility; + removeDefaultRecords(); updateConfig(); } @@ -357,28 +360,42 @@ public class RankingHelper implements RankingConfig { pw.println(mSignalExtractors[i]); } } - final int N = mPackagePriorities.size(); if (filter == null) { pw.print(prefix); - pw.println("package priorities:"); + pw.println("per-package config:"); } + final int N = mRecords.size(); for (int i = 0; i < N; i++) { - String name = mPackagePriorities.keyAt(i); - if (filter == null || filter.matches(name)) { - SparseIntArray priorityByUid = mPackagePriorities.get(name); - final int M = priorityByUid.size(); - for (int j = 0; j < M; j++) { - int uid = priorityByUid.keyAt(j); - int priority = priorityByUid.get(uid); - pw.print(prefix); - pw.print(" "); - pw.print(name); - pw.print(" ("); - pw.print(uid); - pw.print(") has priority: "); - pw.println(priority); + final Record r = mRecords.valueAt(i); + if (filter == null || filter.matches(r.pkg)) { + pw.print(prefix); + pw.print(" "); + pw.print(r.pkg); + pw.print(" ("); + pw.print(r.uid); + pw.print(')'); + if (r.priority != DEFAULT_PRIORITY) { + pw.print(" priority="); + pw.print(Notification.priorityToString(r.priority)); + } + if (r.peekable != DEFAULT_PEEKABLE) { + pw.print(" peekable="); + pw.print(r.peekable); + } + if (r.visibility != DEFAULT_VISIBILITY) { + pw.print(" visibility="); + pw.print(Notification.visibilityToString(r.visibility)); } + pw.println(); } } } + + private static class Record { + String pkg; + int uid; + int priority = DEFAULT_PRIORITY; + boolean peekable = DEFAULT_PEEKABLE; + int visibility = DEFAULT_VISIBILITY; + } } diff --git a/services/core/java/com/android/server/notification/DowntimeCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java index d14fd40..cea611d 100644 --- a/services/core/java/com/android/server/notification/DowntimeCalendar.java +++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java @@ -16,38 +16,36 @@ package com.android.server.notification; +import android.service.notification.ZenModeConfig.ScheduleInfo; +import android.util.ArraySet; + import java.util.Calendar; import java.util.Objects; import java.util.TimeZone; -import android.service.notification.ZenModeConfig; -import android.service.notification.ZenModeConfig.DowntimeInfo; -import android.util.ArraySet; - -public class DowntimeCalendar { - +public class ScheduleCalendar { private final ArraySet<Integer> mDays = new ArraySet<Integer>(); private final Calendar mCalendar = Calendar.getInstance(); - private DowntimeInfo mInfo; + private ScheduleInfo mSchedule; @Override public String toString() { - return "DowntimeCalendar[mDays=" + mDays + "]"; + return "ScheduleCalendar[mDays=" + mDays + "]"; } - public void setDowntimeInfo(DowntimeInfo info) { - if (Objects.equals(mInfo, info)) return; - mInfo = info; + public void setSchedule(ScheduleInfo schedule) { + if (Objects.equals(mSchedule, schedule)) return; + mSchedule = schedule; updateDays(); } - public long nextDowntimeStart(long time) { - if (mInfo == null || mDays.size() == 0) return Long.MAX_VALUE; - final long start = getTime(time, mInfo.startHour, mInfo.startMinute); + public long nextScheduleStart(long time) { + if (mSchedule == null || mDays.size() == 0) return Long.MAX_VALUE; + final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute); for (int i = 0; i < Calendar.SATURDAY; i++) { final long t = addDays(start, i); - if (t > time && isInDowntime(t)) { + if (t > time && isInSchedule(t)) { return t; } } @@ -58,7 +56,14 @@ public class DowntimeCalendar { mCalendar.setTimeZone(tz); } - public long getNextTime(long now, int hr, int min) { + public long getNextChangeTime(long now) { + if (mSchedule == null) return 0; + final long nextStart = getNextTime(now, mSchedule.startHour, mSchedule.startMinute); + final long nextEnd = getNextTime(now, mSchedule.endHour, mSchedule.endMinute); + return Math.min(nextStart, nextEnd); + } + + private long getNextTime(long now, int hr, int min) { final long time = getTime(now, hr, min); return time <= now ? addDays(time, 1) : time; } @@ -72,17 +77,17 @@ public class DowntimeCalendar { return mCalendar.getTimeInMillis(); } - public boolean isInDowntime(long time) { - if (mInfo == null || mDays.size() == 0) return false; - final long start = getTime(time, mInfo.startHour, mInfo.startMinute); - long end = getTime(time, mInfo.endHour, mInfo.endMinute); + public boolean isInSchedule(long time) { + if (mSchedule == null || mDays.size() == 0) return false; + final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute); + long end = getTime(time, mSchedule.endHour, mSchedule.endMinute); if (end <= start) { end = addDays(end, 1); } - return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end); + return isInSchedule(-1, time, start, end) || isInSchedule(0, time, start, end); } - private boolean isInDowntime(int daysOffset, long time, long start, long end) { + private boolean isInSchedule(int daysOffset, long time, long start, long end) { final int n = Calendar.SATURDAY; final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1; start = addDays(start, daysOffset); @@ -97,10 +102,9 @@ public class DowntimeCalendar { private void updateDays() { mDays.clear(); - if (mInfo != null) { - final int[] days = ZenModeConfig.tryParseDays(mInfo.mode); - for (int i = 0; days != null && i < days.length; i++) { - mDays.add(days[i]); + if (mSchedule != null && mSchedule.days != null) { + for (int i = 0; i < mSchedule.days.length; i++) { + mDays.add(mSchedule.days[i]); } } } @@ -110,4 +114,4 @@ public class DowntimeCalendar { mCalendar.add(Calendar.DATE, days); return mCalendar.getTimeInMillis(); } -} +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java new file mode 100644 index 0000000..383d56c --- /dev/null +++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2015 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.notification; + +import android.app.AlarmManager; +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.net.Uri; +import android.service.notification.Condition; +import android.service.notification.IConditionProvider; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ScheduleInfo; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; +import android.util.TimeUtils; + +import com.android.server.notification.NotificationManagerService.DumpFilter; + +import java.io.PrintWriter; +import java.util.Date; +import java.util.TimeZone; + +/** + * Built-in zen condition provider for daily scheduled time-based conditions. + */ +public class ScheduleConditionProvider extends SystemConditionProviderService { + private static final String TAG = "ConditionProviders"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + public static final ComponentName COMPONENT = + new ComponentName("android", ScheduleConditionProvider.class.getName()); + private static final String NOT_SHOWN = "..."; + private static final String ACTION_EVALUATE = TAG + ".EVALUATE"; + private static final int REQUEST_CODE_EVALUATE = 1; + private static final String EXTRA_TIME = "time"; + + private final Context mContext = this; + private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>(); + + private boolean mConnected; + private boolean mRegistered; + + public ScheduleConditionProvider() { + if (DEBUG) Slog.d(TAG, "new ScheduleConditionProvider()"); + } + + @Override + public ComponentName getComponent() { + return COMPONENT; + } + + @Override + public boolean isValidConditionid(Uri id) { + return ZenModeConfig.isValidScheduleConditionId(id); + } + + @Override + public void dump(PrintWriter pw, DumpFilter filter) { + pw.println(" ScheduleConditionProvider:"); + pw.print(" mConnected="); pw.println(mConnected); + pw.print(" mRegistered="); pw.println(mRegistered); + pw.println(" mSubscriptions="); + final long now = System.currentTimeMillis(); + for (Uri conditionId : mSubscriptions) { + pw.print(" "); + pw.print(meetsSchedule(conditionId, now) ? "* " : " "); + pw.println(conditionId); + } + } + + @Override + public void onConnected() { + if (DEBUG) Slog.d(TAG, "onConnected"); + mConnected = true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (DEBUG) Slog.d(TAG, "onDestroy"); + mConnected = false; + } + + @Override + public void onRequestConditions(int relevance) { + if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); + // does not advertise conditions + } + + @Override + public void onSubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); + if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) { + notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition"); + return; + } + mSubscriptions.add(conditionId); + evaluateSubscriptions(); + } + + @Override + public void onUnsubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); + mSubscriptions.remove(conditionId); + evaluateSubscriptions(); + } + + @Override + public void attachBase(Context base) { + attachBaseContext(base); + } + + @Override + public IConditionProvider asInterface() { + return (IConditionProvider) onBind(null); + } + + private void evaluateSubscriptions() { + setRegistered(!mSubscriptions.isEmpty()); + final long now = System.currentTimeMillis(); + long nextAlarmTime = 0; + for (Uri conditionId : mSubscriptions) { + final ScheduleCalendar cal = toScheduleCalendar(conditionId); + if (cal != null && cal.isInSchedule(now)) { + notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule"); + } else { + notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule"); + } + if (cal != null) { + final long nextChangeTime = cal.getNextChangeTime(now); + if (nextChangeTime > 0 && nextChangeTime > now) { + if (nextAlarmTime == 0 || nextChangeTime < nextAlarmTime) { + nextAlarmTime = nextChangeTime; + } + } + } + } + updateAlarm(now, nextAlarmTime); + } + + private void updateAlarm(long now, long time) { + final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, + REQUEST_CODE_EVALUATE, + new Intent(ACTION_EVALUATE) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .putExtra(EXTRA_TIME, time), + PendingIntent.FLAG_UPDATE_CURRENT); + alarms.cancel(pendingIntent); + if (time > now) { + if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s", + ts(time), formatDuration(time - now), ts(now))); + alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); + } else { + if (DEBUG) Slog.d(TAG, "Not scheduling evaluate"); + } + } + + private static String ts(long time) { + return new Date(time) + " (" + time + ")"; + } + + private static String formatDuration(long millis) { + final StringBuilder sb = new StringBuilder(); + TimeUtils.formatDuration(millis, sb); + return sb.toString(); + } + + private static boolean meetsSchedule(Uri conditionId, long time) { + final ScheduleCalendar cal = toScheduleCalendar(conditionId); + return cal != null && cal.isInSchedule(time); + } + + private static ScheduleCalendar toScheduleCalendar(Uri conditionId) { + final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId); + if (schedule == null || schedule.days == null || schedule.days.length == 0) return null; + final ScheduleCalendar sc = new ScheduleCalendar(); + sc.setSchedule(schedule); + sc.setTimeZone(TimeZone.getDefault()); + return sc; + } + + private void setRegistered(boolean registered) { + if (mRegistered == registered) return; + if (DEBUG) Slog.d(TAG, "setRegistered " + registered); + mRegistered = registered; + if (mRegistered) { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(ACTION_EVALUATE); + registerReceiver(mReceiver, filter); + } else { + unregisterReceiver(mReceiver); + } + } + + private void notifyCondition(Uri conditionId, int state, String reason) { + if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state) + + " reason=" + reason); + notifyCondition(createCondition(conditionId, state)); + } + + private Condition createCondition(Uri id, int state) { + final String summary = NOT_SHOWN; + final String line1 = NOT_SHOWN; + final String line2 = NOT_SHOWN; + return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS); + } + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction()); + evaluateSubscriptions(); + } + }; + +} diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java new file mode 100644 index 0000000..a217623 --- /dev/null +++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 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.notification; + +import android.content.ComponentName; +import android.content.Context; +import android.net.Uri; +import android.service.notification.ConditionProviderService; +import android.service.notification.IConditionProvider; + +import com.android.server.notification.NotificationManagerService.DumpFilter; + +import java.io.PrintWriter; + +public abstract class SystemConditionProviderService extends ConditionProviderService { + + abstract public void dump(PrintWriter pw, DumpFilter filter); + abstract public void attachBase(Context context); + abstract public IConditionProvider asInterface(); + abstract public ComponentName getComponent(); + abstract public boolean isValidConditionid(Uri id); +} diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index 11d00cf..10f1696 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -34,6 +34,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.LruCache; import android.util.Slog; +import com.android.internal.logging.MetricsLogger; import java.util.ArrayList; import java.util.LinkedList; @@ -219,7 +220,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return null; } - if (INFO) Slog.i(TAG, "Validating: " + key); + if (INFO) Slog.i(TAG, "Validating: " + key + " for " + context.getUserId()); final LinkedList<String> pendingLookups = new LinkedList<String>(); for (int personIdx = 0; personIdx < people.length && personIdx < MAX_PEOPLE; personIdx++) { final String handle = people[personIdx]; @@ -244,6 +245,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { if (pendingLookups.isEmpty()) { if (INFO) Slog.i(TAG, "final affinity: " + affinity); + if (affinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1); return null; } @@ -443,13 +445,18 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { final String cacheKey = getCacheKey(mContext.getUserId(), handle); mPeopleCache.put(cacheKey, lookupResult); } + if (DEBUG) Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity()); mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity()); + } else { + if (DEBUG) Slog.d(TAG, "lookupResult is null"); } } if (DEBUG) { Slog.d(TAG, "Validation finished in " + (System.currentTimeMillis() - timeStartMs) + "ms"); } + + if (mContactAffinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1); } @Override diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index dda0b37..1e318ef 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -24,8 +24,8 @@ import android.os.RemoteException; import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.IConditionProvider; +import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; -import android.util.ArraySet; import android.util.Slog; import java.io.PrintWriter; @@ -57,6 +57,8 @@ public class ZenLog { private static final int TYPE_CONFIG = 11; private static final int TYPE_NOT_INTERCEPTED = 12; private static final int TYPE_DISABLE_EFFECTS = 13; + private static final int TYPE_SUPPRESSOR_CHANGED = 14; + private static final int TYPE_LISTENER_HINTS_CHANGED = 15; private static int sNext; private static int sSize; @@ -121,6 +123,17 @@ public class ZenLog { append(TYPE_DISABLE_EFFECTS, record.getKey() + "," + reason); } + public static void traceEffectsSuppressorChanged(ComponentName oldSuppressor, + ComponentName newSuppressor) { + append(TYPE_SUPPRESSOR_CHANGED, componentToString(oldSuppressor) + "->" + + componentToString(newSuppressor)); + } + + public static void traceListenerHintsChanged(int oldHints, int newHints, int listenerCount) { + append(TYPE_LISTENER_HINTS_CHANGED, hintsToString(oldHints) + "->" + + hintsToString(newHints) + ",listeners=" + listenerCount); + } + private static String subscribeResult(IConditionProvider provider, RemoteException e) { return provider == null ? "no provider" : e != null ? e.getMessage() : "ok"; } @@ -140,6 +153,8 @@ public class ZenLog { case TYPE_CONFIG: return "config"; case TYPE_NOT_INTERCEPTED: return "not_intercepted"; case TYPE_DISABLE_EFFECTS: return "disable_effects"; + case TYPE_SUPPRESSOR_CHANGED: return "suppressor_changed"; + case TYPE_LISTENER_HINTS_CHANGED: return "listener_hints_changed"; default: return "unknown"; } } @@ -157,11 +172,20 @@ public class ZenLog { switch (zenMode) { case Global.ZEN_MODE_OFF: return "off"; case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return "important_interruptions"; + case Global.ZEN_MODE_ALARMS: return "alarms"; case Global.ZEN_MODE_NO_INTERRUPTIONS: return "no_interruptions"; default: return "unknown"; } } + private static String hintsToString(int hints) { + switch (hints) { + case 0 : return "none"; + case NotificationListenerService.HINT_HOST_DISABLE_EFFECTS : return "disable_effects"; + default: return Integer.toString(hints); + } + } + private static String componentToString(ComponentName component) { return component != null ? component.toShortString() : null; } diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java new file mode 100644 index 0000000..766d6c5 --- /dev/null +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -0,0 +1,188 @@ +/** + * Copyright (c) 2015, 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.notification; + +import android.content.ComponentName; +import android.net.Uri; +import android.service.notification.Condition; +import android.service.notification.IConditionListener; +import android.service.notification.IConditionProvider; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ZenRule; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import java.io.PrintWriter; +import java.util.Objects; + +public class ZenModeConditions implements ConditionProviders.Callback { + private static final String TAG = ZenModeHelper.TAG; + private static final boolean DEBUG = ZenModeHelper.DEBUG; + + private final ZenModeHelper mHelper; + private final ConditionProviders mConditionProviders; + private final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>(); + + private CountdownConditionProvider mCountdown; + private ScheduleConditionProvider mSchedule; + private boolean mFirstEvaluation = true; + + public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) { + mHelper = helper; + mConditionProviders = conditionProviders; + if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.COUNTDOWN_PATH)) { + mCountdown = new CountdownConditionProvider(); + mConditionProviders.addSystemProvider(mCountdown); + } + if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.SCHEDULE_PATH)) { + mSchedule = new ScheduleConditionProvider(); + mConditionProviders.addSystemProvider(mSchedule); + } + mConditionProviders.setCallback(this); + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mSubscriptions="); pw.println(mSubscriptions); + } + + public void requestConditions(IConditionListener callback, int relevance) { + mConditionProviders.requestConditions(callback, relevance); + } + + public void evaluateConfig(ZenModeConfig config) { + if (config == null) return; + if (config.manualRule != null && config.manualRule.condition != null + && !config.manualRule.isTrueOrUnknown()) { + if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule"); + config.manualRule = null; + } + final ArraySet<Uri> current = new ArraySet<>(); + evaluateRule(config.manualRule, current); + for (ZenRule automaticRule : config.automaticRules.values()) { + evaluateRule(automaticRule, current); + updateSnoozing(automaticRule); + } + final int N = mSubscriptions.size(); + for (int i = N - 1; i >= 0; i--) { + final Uri id = mSubscriptions.keyAt(i); + final ComponentName component = mSubscriptions.valueAt(i); + if (!current.contains(id)) { + mConditionProviders.unsubscribeIfNecessary(component, id); + mSubscriptions.removeAt(i); + } + } + mFirstEvaluation = false; + } + + @Override + public void onBootComplete() { + // noop + } + + @Override + public void onUserSwitched() { + // noop + } + + @Override + public void onServiceAdded(ComponentName component) { + if (DEBUG) Log.d(TAG, "onServiceAdded " + component); + if (isAutomaticActive(component)) { + mHelper.setConfig(mHelper.getConfig(), "zmc.onServiceAdded"); + } + } + + @Override + public void onConditionChanged(Uri id, Condition condition) { + if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition); + ZenModeConfig config = mHelper.getConfig(); + if (config == null) return; + config = config.copy(); + boolean updated = updateCondition(id, condition, config.manualRule); + for (ZenRule automaticRule : config.automaticRules.values()) { + updated |= updateCondition(id, condition, automaticRule); + updated |= updateSnoozing(automaticRule); + } + if (updated) { + mHelper.setConfig(config, "conditionChanged"); + } + } + + private void evaluateRule(ZenRule rule, ArraySet<Uri> current) { + if (rule == null || rule.conditionId == null) return; + final Uri id = rule.conditionId; + boolean isSystemCondition = false; + for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) { + if (sp.isValidConditionid(id)) { + mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface()); + rule.component = sp.getComponent(); + isSystemCondition = true; + } + } + if (!isSystemCondition) { + final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component); + if (DEBUG) Log.d(TAG, "Ensure external rule exists: " + (cp != null) + " for " + id); + if (cp != null) { + mConditionProviders.ensureRecordExists(rule.component, id, cp); + } + } + if (rule.component == null) { + Log.w(TAG, "No component found for automatic rule: " + rule.conditionId); + rule.enabled = false; + return; + } + if (current != null) { + current.add(id); + } + if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) { + mSubscriptions.put(rule.conditionId, rule.component); + } else { + if (DEBUG) Log.d(TAG, "zmc failed to subscribe"); + } + } + + private boolean isAutomaticActive(ComponentName component) { + if (component == null) return false; + final ZenModeConfig config = mHelper.getConfig(); + if (config == null) return false; + for (ZenRule rule : config.automaticRules.values()) { + if (component.equals(rule.component) && rule.isAutomaticActive()) { + return true; + } + } + return false; + } + + private boolean updateSnoozing(ZenRule rule) { + if (rule != null && rule.snoozing && (mFirstEvaluation || !rule.isTrueOrUnknown())) { + rule.snoozing = false; + if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId); + return true; + } + return false; + } + + private boolean updateCondition(Uri id, Condition condition, ZenRule rule) { + if (id == null || rule == null || rule.conditionId == null) return false; + if (!rule.conditionId.equals(id)) return false; + if (Objects.equals(condition, rule.condition)) return false; + rule.condition = condition; + return true; + } + +} diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java new file mode 100644 index 0000000..2aaeb9d --- /dev/null +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -0,0 +1,279 @@ +/** + * Copyright (c) 2015, 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.notification; + +import android.app.Notification; +import android.content.ComponentName; +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings.Global; +import android.provider.Settings.Secure; +import android.service.notification.ZenModeConfig; +import android.telecom.TelecomManager; +import android.util.ArrayMap; +import android.util.Slog; + +import java.io.PrintWriter; +import java.util.Date; +import java.util.Objects; + +public class ZenModeFiltering { + private static final String TAG = ZenModeHelper.TAG; + private static final boolean DEBUG = ZenModeHelper.DEBUG; + + static final RepeatCallers REPEAT_CALLERS = new RepeatCallers(); + + private final Context mContext; + + private ComponentName mDefaultPhoneApp; + + public ZenModeFiltering(Context context) { + mContext = context; + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp); + pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes="); + pw.println(REPEAT_CALLERS.mThresholdMinutes); + synchronized (REPEAT_CALLERS) { + if (!REPEAT_CALLERS.mCalls.isEmpty()) { + pw.print(prefix); pw.println("RepeatCallers.mCalls="); + for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) { + pw.print(prefix); pw.print(" "); + pw.print(REPEAT_CALLERS.mCalls.keyAt(i)); + pw.print(" at "); + pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i))); + } + } + } + } + + private static String ts(long time) { + return new Date(time) + " (" + time + ")"; + } + + /** + * @param extras extras of the notification with EXTRA_PEOPLE populated + * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response + * @param timeoutAffinity affinity to return when the timeout specified via + * <code>contactsTimeoutMs</code> is hit + */ + public static boolean matchesCallFilter(Context context, int zen, ZenModeConfig config, + UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator, + int contactsTimeoutMs, float timeoutAffinity) { + if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through + if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm + if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { + if (config.allowRepeatCallers && REPEAT_CALLERS.isRepeat(context, extras)) return true; + if (!config.allowCalls) return false; // no other calls get through + if (validator != null) { + final float contactAffinity = validator.getContactAffinity(userHandle, extras, + contactsTimeoutMs, timeoutAffinity); + return audienceMatches(config, contactAffinity); + } + } + return true; + } + + private static Bundle extras(NotificationRecord record) { + return record != null && record.sbn != null && record.sbn.getNotification() != null + ? record.sbn.getNotification().extras : null; + } + + public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) { + if (isSystem(record)) { + return false; + } + switch (zen) { + case Global.ZEN_MODE_NO_INTERRUPTIONS: + // #notevenalarms + ZenLog.traceIntercepted(record, "none"); + return true; + case Global.ZEN_MODE_ALARMS: + if (isAlarm(record)) { + // Alarms only + return false; + } + ZenLog.traceIntercepted(record, "alarmsOnly"); + return true; + case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + if (isAlarm(record)) { + // Alarms are always priority + return false; + } + // allow user-prioritized packages through in priority mode + if (record.getPackagePriority() == Notification.PRIORITY_MAX) { + ZenLog.traceNotIntercepted(record, "priorityApp"); + return false; + } + if (isCall(record)) { + if (config.allowRepeatCallers + && REPEAT_CALLERS.isRepeat(mContext, extras(record))) { + ZenLog.traceNotIntercepted(record, "repeatCaller"); + return false; + } + if (!config.allowCalls) { + ZenLog.traceIntercepted(record, "!allowCalls"); + return true; + } + return shouldInterceptAudience(config, record); + } + if (isMessage(record)) { + if (!config.allowMessages) { + ZenLog.traceIntercepted(record, "!allowMessages"); + return true; + } + return shouldInterceptAudience(config, record); + } + if (isEvent(record)) { + if (!config.allowEvents) { + ZenLog.traceIntercepted(record, "!allowEvents"); + return true; + } + return false; + } + if (isReminder(record)) { + if (!config.allowReminders) { + ZenLog.traceIntercepted(record, "!allowReminders"); + return true; + } + return false; + } + ZenLog.traceIntercepted(record, "!priority"); + return true; + default: + return false; + } + } + + private static boolean shouldInterceptAudience(ZenModeConfig config, + NotificationRecord record) { + if (!audienceMatches(config, record.getContactAffinity())) { + ZenLog.traceIntercepted(record, "!audienceMatches"); + return true; + } + return false; + } + + private static boolean isSystem(NotificationRecord record) { + return record.isCategory(Notification.CATEGORY_SYSTEM); + } + + private static boolean isAlarm(NotificationRecord record) { + return record.isCategory(Notification.CATEGORY_ALARM) + || record.isAudioStream(AudioManager.STREAM_ALARM) + || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM); + } + + private static boolean isEvent(NotificationRecord record) { + return record.isCategory(Notification.CATEGORY_EVENT); + } + + private static boolean isReminder(NotificationRecord record) { + return record.isCategory(Notification.CATEGORY_REMINDER); + } + + public boolean isCall(NotificationRecord record) { + return record != null && (isDefaultPhoneApp(record.sbn.getPackageName()) + || record.isCategory(Notification.CATEGORY_CALL)); + } + + private boolean isDefaultPhoneApp(String pkg) { + if (mDefaultPhoneApp == null) { + final TelecomManager telecomm = + (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); + mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; + if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); + } + return pkg != null && mDefaultPhoneApp != null + && pkg.equals(mDefaultPhoneApp.getPackageName()); + } + + @SuppressWarnings("deprecation") + private boolean isDefaultMessagingApp(NotificationRecord record) { + final int userId = record.getUserId(); + if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; + final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(), + Secure.SMS_DEFAULT_APPLICATION, userId); + return Objects.equals(defaultApp, record.sbn.getPackageName()); + } + + private boolean isMessage(NotificationRecord record) { + return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); + } + + private static boolean audienceMatches(ZenModeConfig config, float contactAffinity) { + switch (config.allowFrom) { + case ZenModeConfig.SOURCE_ANYONE: + return true; + case ZenModeConfig.SOURCE_CONTACT: + return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT; + case ZenModeConfig.SOURCE_STAR: + return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT; + default: + Slog.w(TAG, "Encountered unknown source: " + config.allowFrom); + return true; + } + } + + private static class RepeatCallers { + private final ArrayMap<String, Long> mCalls = new ArrayMap<>(); + private int mThresholdMinutes; + + private synchronized boolean isRepeat(Context context, Bundle extras) { + if (mThresholdMinutes <= 0) { + mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer + .config_zen_repeat_callers_threshold); + } + if (mThresholdMinutes <= 0 || extras == null) return false; + final String peopleString = peopleString(extras); + if (peopleString == null) return false; + final long now = System.currentTimeMillis(); + final int N = mCalls.size(); + for (int i = N - 1; i >= 0; i--) { + final long time = mCalls.valueAt(i); + if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) { + mCalls.removeAt(i); + } + } + final boolean isRepeat = mCalls.containsKey(peopleString); + mCalls.put(peopleString, now); + return isRepeat; + } + + private static String peopleString(Bundle extras) { + final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); + if (extraPeople == null || extraPeople.length == 0) return null; + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < extraPeople.length; i++) { + String extraPerson = extraPeople[i]; + if (extraPerson == null) continue; + extraPerson = extraPerson.trim(); + if (extraPerson.isEmpty()) continue; + if (sb.length() > 0) { + sb.append('|'); + } + sb.append(extraPerson); + } + return sb.length() == 0 ? null : sb.toString(); + } + } + +} diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 841fc21..9cb8af5 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -21,16 +21,16 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import android.app.AppOpsManager; -import android.app.Notification; +import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.database.ContentObserver; -import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioManagerInternal; +import android.media.VolumePolicy; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -38,12 +38,13 @@ import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.provider.Settings.Global; -import android.provider.Settings.Secure; +import android.service.notification.IConditionListener; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; -import android.telecom.TelecomManager; +import android.service.notification.ZenModeConfig.ScheduleInfo; +import android.service.notification.ZenModeConfig.ZenRule; +import android.util.ArraySet; import android.util.Log; -import android.util.Slog; import com.android.internal.R; import com.android.server.LocalServices; @@ -62,9 +63,9 @@ import java.util.Objects; /** * NotificationManagerService helper for functionality related to zen mode. */ -public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { - private static final String TAG = "ZenModeHelper"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); +public class ZenModeHelper { + static final String TAG = "ZenModeHelper"; + static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final H mHandler; @@ -72,38 +73,46 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { private final AppOpsManager mAppOps; private final ZenModeConfig mDefaultConfig; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); + private final ZenModeFiltering mFiltering; + private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate(); + private final ZenModeConditions mConditions; - private ComponentName mDefaultPhoneApp; private int mZenMode; private ZenModeConfig mConfig; private AudioManagerInternal mAudioManager; private int mPreviousRingerMode = -1; private boolean mEffectsSuppressed; - public ZenModeHelper(Context context, Looper looper) { + public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) { mContext = context; mHandler = new H(looper); mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mDefaultConfig = readDefaultConfig(context.getResources()); + appendDefaultScheduleRules(mDefaultConfig); mConfig = mDefaultConfig; mSettingsObserver = new SettingsObserver(mHandler); mSettingsObserver.observe(); + mFiltering = new ZenModeFiltering(mContext); + mConditions = new ZenModeConditions(this, conditionProviders); } - public static ZenModeConfig readDefaultConfig(Resources resources) { - XmlResourceParser parser = null; - try { - parser = resources.getXml(R.xml.default_zen_mode_config); - while (parser.next() != XmlPullParser.END_DOCUMENT) { - final ZenModeConfig config = ZenModeConfig.readXml(parser); - if (config != null) return config; - } - } catch (Exception e) { - Slog.w(TAG, "Error reading default zen mode config from resource", e); - } finally { - IoUtils.closeQuietly(parser); - } - return new ZenModeConfig(); + @Override + public String toString() { + return TAG; + } + + public boolean matchesCallFilter(UserHandle userHandle, Bundle extras, + ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) { + return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle, extras, + validator, contactsTimeoutMs, timeoutAffinity); + } + + public boolean isCall(NotificationRecord record) { + return mFiltering.isCall(record); + } + + public boolean shouldIntercept(NotificationRecord record) { + return mFiltering.shouldIntercept(mZenMode, mConfig, record); } public void addCallback(Callback callback) { @@ -114,44 +123,32 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { mCallbacks.remove(callback); } + public void initZenMode() { + if (DEBUG) Log.d(TAG, "initZenMode"); + evaluateZenMode("init", true /*setRingerMode*/); + } + public void onSystemReady() { + if (DEBUG) Log.d(TAG, "onSystemReady"); mAudioManager = LocalServices.getService(AudioManagerInternal.class); if (mAudioManager != null) { - mAudioManager.setRingerModeDelegate(this); + mAudioManager.setRingerModeDelegate(mRingerModeDelegate); } } - public int getZenModeListenerInterruptionFilter() { - switch (mZenMode) { - case Global.ZEN_MODE_OFF: - return NotificationListenerService.INTERRUPTION_FILTER_ALL; - case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: - return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY; - case Global.ZEN_MODE_NO_INTERRUPTIONS: - return NotificationListenerService.INTERRUPTION_FILTER_NONE; - default: - return 0; - } + public void requestZenModeConditions(IConditionListener callback, int relevance) { + mConditions.requestConditions(callback, relevance); } - private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter, - int defValue) { - switch (listenerInterruptionFilter) { - case NotificationListenerService.INTERRUPTION_FILTER_ALL: - return Global.ZEN_MODE_OFF; - case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY: - return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - case NotificationListenerService.INTERRUPTION_FILTER_NONE: - return Global.ZEN_MODE_NO_INTERRUPTIONS; - default: - return defValue; - } + public int getZenModeListenerInterruptionFilter() { + return getZenModeListenerInterruptionFilter(mZenMode); } public void requestFromListener(ComponentName name, int interruptionFilter) { final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1); if (newZen != -1) { - setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null)); + setManualZenMode(newZen, null, + "listener:" + (name != null ? name.flattenToShortString() : null)); } } @@ -161,86 +158,163 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { applyRestrictions(); } - public boolean shouldIntercept(NotificationRecord record) { - if (isSystem(record)) { - return false; - } - switch (mZenMode) { - case Global.ZEN_MODE_NO_INTERRUPTIONS: - // #notevenalarms - ZenLog.traceIntercepted(record, "none"); - return true; - case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: - if (isAlarm(record)) { - // Alarms are always priority - return false; - } - // allow user-prioritized packages through in priority mode - if (record.getPackagePriority() == Notification.PRIORITY_MAX) { - ZenLog.traceNotIntercepted(record, "priorityApp"); - return false; - } - if (isCall(record)) { - if (!mConfig.allowCalls) { - ZenLog.traceIntercepted(record, "!allowCalls"); - return true; - } - return shouldInterceptAudience(record); - } - if (isMessage(record)) { - if (!mConfig.allowMessages) { - ZenLog.traceIntercepted(record, "!allowMessages"); - return true; - } - return shouldInterceptAudience(record); - } - if (isEvent(record)) { - if (!mConfig.allowEvents) { - ZenLog.traceIntercepted(record, "!allowEvents"); - return true; - } - return false; + public int getZenMode() { + return mZenMode; + } + + public void setManualZenMode(int zenMode, Uri conditionId, String reason) { + setManualZenMode(zenMode, conditionId, reason, true /*setRingerMode*/); + } + + private void setManualZenMode(int zenMode, Uri conditionId, String reason, + boolean setRingerMode) { + if (mConfig == null) return; + if (!Global.isValidZenMode(zenMode)) return; + if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode) + + " conditionId=" + conditionId + " reason=" + reason + + " setRingerMode=" + setRingerMode); + final ZenModeConfig newConfig = mConfig.copy(); + if (zenMode == Global.ZEN_MODE_OFF) { + newConfig.manualRule = null; + for (ZenRule automaticRule : newConfig.automaticRules.values()) { + if (automaticRule.isAutomaticActive()) { + automaticRule.snoozing = true; } - ZenLog.traceIntercepted(record, "!priority"); - return true; - default: - return false; + } + } else { + final ZenRule newRule = new ZenRule(); + newRule.enabled = true; + newRule.zenMode = zenMode; + newRule.conditionId = conditionId; + newConfig.manualRule = newRule; + } + setConfig(newConfig, reason, setRingerMode); + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mZenMode="); + pw.println(Global.zenModeToString(mZenMode)); + dump(pw, prefix, "mConfig", mConfig); + dump(pw, prefix, "mDefaultConfig", mDefaultConfig); + pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode); + pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed); + mFiltering.dump(pw, prefix); + mConditions.dump(pw, prefix); + } + + private static void dump(PrintWriter pw, String prefix, String var, ZenModeConfig config) { + pw.print(prefix); pw.print(var); pw.print('='); + if (config == null) { + pw.println(config); + return; + } + pw.printf("allow(calls=%s,repeatCallers=%s,events=%s,from=%s,messages=%s,reminders=%s)\n", + config.allowCalls, config.allowRepeatCallers, config.allowEvents, config.allowFrom, + config.allowMessages, config.allowReminders); + pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule); + if (config.automaticRules.isEmpty()) return; + final int N = config.automaticRules.size(); + for (int i = 0; i < N; i++) { + pw.print(prefix); pw.print(i == 0 ? " automaticRules=" : " "); + pw.println(config.automaticRules.valueAt(i)); } } - private boolean shouldInterceptAudience(NotificationRecord record) { - if (!audienceMatches(record.getContactAffinity())) { - ZenLog.traceIntercepted(record, "!audienceMatches"); - return true; + public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { + final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration); + if (config != null) { + if (DEBUG) Log.d(TAG, "readXml"); + setConfig(config, "readXml"); } - return false; } - public int getZenMode() { - return mZenMode; + public void writeXml(XmlSerializer out) throws IOException { + mConfig.writeXml(out); + } + + public Policy getNotificationPolicy() { + return getNotificationPolicy(mConfig); + } + + private static Policy getNotificationPolicy(ZenModeConfig config) { + return config == null ? null : config.toNotificationPolicy(); + } + + public void setNotificationPolicy(Policy policy) { + if (policy == null || mConfig == null) return; + final ZenModeConfig newConfig = mConfig.copy(); + newConfig.applyNotificationPolicy(policy); + setConfig(newConfig, "setNotificationPolicy"); + } + + public ZenModeConfig getConfig() { + return mConfig; + } + + public boolean setConfig(ZenModeConfig config, String reason) { + return setConfig(config, reason, true /*setRingerMode*/); + } + + private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) { + if (config == null || !config.isValid()) { + Log.w(TAG, "Invalid config in setConfig; " + config); + return false; + } + mConditions.evaluateConfig(config); // may modify config + if (config.equals(mConfig)) return true; + if (DEBUG) Log.d(TAG, "setConfig reason=" + reason); + ZenLog.traceConfig(mConfig, config); + final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig), + getNotificationPolicy(config)); + mConfig = config; + dispatchOnConfigChanged(); + if (policyChanged){ + dispatchOnPolicyChanged(); + } + final String val = Integer.toString(mConfig.hashCode()); + Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); + if (!evaluateZenMode(reason, setRingerMode)) { + applyRestrictions(); // evaluateZenMode will also apply restrictions if changed + } + return true; } - public void setZenMode(int zenMode, String reason) { - setZenMode(zenMode, reason, true); + private int getZenModeSetting() { + return Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF); } - private void setZenMode(int zenMode, String reason, boolean setRingerMode) { - ZenLog.traceSetZenMode(zenMode, reason); - if (mZenMode == zenMode) return; - ZenLog.traceUpdateZenMode(mZenMode, zenMode); - mZenMode = zenMode; - Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, mZenMode); + private void setZenModeSetting(int zen) { + Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen); + } + + private boolean evaluateZenMode(String reason, boolean setRingerMode) { + if (DEBUG) Log.d(TAG, "evaluateZenMode"); + final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>(); + final int zen = computeZenMode(automaticRules); + if (zen == mZenMode) return false; + ZenLog.traceSetZenMode(zen, reason); + mZenMode = zen; + setZenModeSetting(mZenMode); if (setRingerMode) { applyZenToRingerMode(); } applyRestrictions(); mHandler.postDispatchOnZenModeChanged(); + return true; } - public void readZenModeFromSetting() { - final int newMode = Global.getInt(mContext.getContentResolver(), - Global.ZEN_MODE, Global.ZEN_MODE_OFF); - setZenMode(newMode, "setting"); + private int computeZenMode(ArraySet<ZenRule> automaticRulesOut) { + if (mConfig == null) return Global.ZEN_MODE_OFF; + if (mConfig.manualRule != null) return mConfig.manualRule.zenMode; + int zen = Global.ZEN_MODE_OFF; + for (ZenRule automaticRule : mConfig.automaticRules.values()) { + if (automaticRule.isAutomaticActive()) { + if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) { + zen = automaticRule.zenMode; + } + } + } + return zen; } private void applyRestrictions() { @@ -251,7 +325,8 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { applyRestrictions(muteNotifications, USAGE_NOTIFICATION); // call restrictions - final boolean muteCalls = zen && !mConfig.allowCalls || mEffectsSuppressed; + final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers + || mEffectsSuppressed; applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE); // alarm restrictions @@ -269,43 +344,6 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { exceptionPackages); } - public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("mZenMode="); - pw.println(Global.zenModeToString(mZenMode)); - pw.print(prefix); pw.print("mConfig="); pw.println(mConfig); - pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig); - pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode); - pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp); - pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed); - } - - public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { - final ZenModeConfig config = ZenModeConfig.readXml(parser); - if (config != null) { - setConfig(config); - } - } - - public void writeXml(XmlSerializer out) throws IOException { - mConfig.writeXml(out); - } - - public ZenModeConfig getConfig() { - return mConfig; - } - - public boolean setConfig(ZenModeConfig config) { - if (config == null || !config.isValid()) return false; - if (config.equals(mConfig)) return true; - ZenLog.traceConfig(mConfig, config); - mConfig = config; - dispatchOnConfigChanged(); - final String val = Integer.toString(mConfig.hashCode()); - Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); - applyRestrictions(); - return true; - } - private void applyZenToRingerMode() { if (mAudioManager == null) return; // force the ringer mode into compliance @@ -313,6 +351,7 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { int newRingerModeInternal = ringerModeInternal; switch (mZenMode) { case Global.ZEN_MODE_NO_INTERRUPTIONS: + case Global.ZEN_MODE_ALARMS: if (ringerModeInternal != AudioManager.RINGER_MODE_SILENT) { mPreviousRingerMode = ringerModeInternal; newRingerModeInternal = AudioManager.RINGER_MODE_SILENT; @@ -332,82 +371,15 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { } } - @Override // RingerModeDelegate - public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller, - int ringerModeExternal) { - final boolean isChange = ringerModeOld != ringerModeNew; - - int ringerModeExternalOut = ringerModeNew; - - int newZen = -1; - switch (ringerModeNew) { - case AudioManager.RINGER_MODE_SILENT: - if (isChange) { - if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS) { - newZen = Global.ZEN_MODE_NO_INTERRUPTIONS; - } - } - break; - case AudioManager.RINGER_MODE_VIBRATE: - case AudioManager.RINGER_MODE_NORMAL: - if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT - && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { - newZen = Global.ZEN_MODE_OFF; - } else if (mZenMode != Global.ZEN_MODE_OFF) { - ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT; - } - break; - } - if (newZen != -1) { - setZenMode(newZen, "ringerModeInternal", false /*setRingerMode*/); - } - - if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) { - ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller, - ringerModeExternal, ringerModeExternalOut); - } - return ringerModeExternalOut; - } - - @Override // RingerModeDelegate - public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller, - int ringerModeInternal) { - int ringerModeInternalOut = ringerModeNew; - final boolean isChange = ringerModeOld != ringerModeNew; - final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; - - int newZen = -1; - switch (ringerModeNew) { - case AudioManager.RINGER_MODE_SILENT: - if (isChange) { - if (mZenMode == Global.ZEN_MODE_OFF) { - newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - } - ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE - : AudioManager.RINGER_MODE_NORMAL; - } else { - ringerModeInternalOut = ringerModeInternal; - } - break; - case AudioManager.RINGER_MODE_VIBRATE: - case AudioManager.RINGER_MODE_NORMAL: - if (mZenMode != Global.ZEN_MODE_OFF) { - newZen = Global.ZEN_MODE_OFF; - } - break; - } - if (newZen != -1) { - setZenMode(newZen, "ringerModeExternal", false /*setRingerMode*/); + private void dispatchOnConfigChanged() { + for (Callback callback : mCallbacks) { + callback.onConfigChanged(); } - - ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal, - ringerModeInternalOut); - return ringerModeInternalOut; } - private void dispatchOnConfigChanged() { + private void dispatchOnPolicyChanged() { for (Callback callback : mCallbacks) { - callback.onConfigChanged(); + callback.onPolicyChanged(); } } @@ -417,89 +389,210 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { } } - private static boolean isSystem(NotificationRecord record) { - return record.isCategory(Notification.CATEGORY_SYSTEM); + private static int getZenModeListenerInterruptionFilter(int zen) { + switch (zen) { + case Global.ZEN_MODE_OFF: + return NotificationListenerService.INTERRUPTION_FILTER_ALL; + case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY; + case Global.ZEN_MODE_ALARMS: + return NotificationListenerService.INTERRUPTION_FILTER_ALARMS; + case Global.ZEN_MODE_NO_INTERRUPTIONS: + return NotificationListenerService.INTERRUPTION_FILTER_NONE; + default: + return 0; + } } - private static boolean isAlarm(NotificationRecord record) { - return record.isCategory(Notification.CATEGORY_ALARM) - || record.isAudioStream(AudioManager.STREAM_ALARM) - || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM); + private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter, + int defValue) { + switch (listenerInterruptionFilter) { + case NotificationListenerService.INTERRUPTION_FILTER_ALL: + return Global.ZEN_MODE_OFF; + case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY: + return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + case NotificationListenerService.INTERRUPTION_FILTER_ALARMS: + return Global.ZEN_MODE_ALARMS; + case NotificationListenerService.INTERRUPTION_FILTER_NONE: + return Global.ZEN_MODE_NO_INTERRUPTIONS; + default: + return defValue; + } } - private static boolean isEvent(NotificationRecord record) { - return record.isCategory(Notification.CATEGORY_EVENT); + private ZenModeConfig readDefaultConfig(Resources resources) { + XmlResourceParser parser = null; + try { + parser = resources.getXml(R.xml.default_zen_mode_config); + while (parser.next() != XmlPullParser.END_DOCUMENT) { + final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration); + if (config != null) return config; + } + } catch (Exception e) { + Log.w(TAG, "Error reading default zen mode config from resource", e); + } finally { + IoUtils.closeQuietly(parser); + } + return new ZenModeConfig(); } - public boolean isCall(NotificationRecord record) { - return record != null && (isDefaultPhoneApp(record.sbn.getPackageName()) - || record.isCategory(Notification.CATEGORY_CALL)); + private void appendDefaultScheduleRules(ZenModeConfig config) { + if (config == null) return; + + final ScheduleInfo weeknights = new ScheduleInfo(); + weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS; + weeknights.startHour = 22; + weeknights.endHour = 7; + final ZenRule rule1 = new ZenRule(); + rule1.enabled = false; + rule1.name = mContext.getResources() + .getString(R.string.zen_mode_default_weeknights_name); + rule1.conditionId = ZenModeConfig.toScheduleConditionId(weeknights); + rule1.zenMode = Global.ZEN_MODE_ALARMS; + config.automaticRules.put(config.newRuleId(), rule1); + + final ScheduleInfo weekends = new ScheduleInfo(); + weekends.days = ZenModeConfig.WEEKEND_DAYS; + weekends.startHour = 23; + weekends.startMinute = 30; + weekends.endHour = 10; + final ZenRule rule2 = new ZenRule(); + rule2.enabled = false; + rule2.name = mContext.getResources() + .getString(R.string.zen_mode_default_weekends_name); + rule2.conditionId = ZenModeConfig.toScheduleConditionId(weekends); + rule2.zenMode = Global.ZEN_MODE_ALARMS; + config.automaticRules.put(config.newRuleId(), rule2); + } + + private static int zenSeverity(int zen) { + switch (zen) { + case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return 1; + case Global.ZEN_MODE_ALARMS: return 2; + case Global.ZEN_MODE_NO_INTERRUPTIONS: return 3; + default: return 0; + } } - private boolean isDefaultPhoneApp(String pkg) { - if (mDefaultPhoneApp == null) { - final TelecomManager telecomm = - (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); - mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; - if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); + private final ZenModeConfig.Migration mConfigMigration = new ZenModeConfig.Migration() { + @Override + public ZenModeConfig migrate(ZenModeConfig.XmlV1 v1) { + if (v1 == null) return null; + final ZenModeConfig rt = new ZenModeConfig(); + rt.allowCalls = v1.allowCalls; + rt.allowEvents = v1.allowEvents; + rt.allowFrom = v1.allowFrom; + rt.allowMessages = v1.allowMessages; + rt.allowReminders = v1.allowReminders; + // don't migrate current exit condition + final int[] days = ZenModeConfig.XmlV1.tryParseDays(v1.sleepMode); + if (days != null && days.length > 0) { + Log.i(TAG, "Migrating existing V1 downtime to single schedule"); + final ScheduleInfo schedule = new ScheduleInfo(); + schedule.days = days; + schedule.startHour = v1.sleepStartHour; + schedule.startMinute = v1.sleepStartMinute; + schedule.endHour = v1.sleepEndHour; + schedule.endMinute = v1.sleepEndMinute; + final ZenRule rule = new ZenRule(); + rule.enabled = true; + rule.name = mContext.getResources() + .getString(R.string.zen_mode_downtime_feature_name); + rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule); + rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS + : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + rt.automaticRules.put(rt.newRuleId(), rule); + } else { + Log.i(TAG, "No existing V1 downtime found, generating default schedules"); + appendDefaultScheduleRules(rt); + } + return rt; } - return pkg != null && mDefaultPhoneApp != null - && pkg.equals(mDefaultPhoneApp.getPackageName()); - } + }; - private boolean isDefaultMessagingApp(NotificationRecord record) { - final int userId = record.getUserId(); - if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; - final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(), - Secure.SMS_DEFAULT_APPLICATION, userId); - return Objects.equals(defaultApp, record.sbn.getPackageName()); - } + private final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate { + @Override + public String toString() { + return TAG; + } - private boolean isMessage(NotificationRecord record) { - return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); - } + @Override + public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller, + int ringerModeExternal, VolumePolicy policy) { + final boolean isChange = ringerModeOld != ringerModeNew; + + int ringerModeExternalOut = ringerModeNew; + + int newZen = -1; + switch (ringerModeNew) { + case AudioManager.RINGER_MODE_SILENT: + if (isChange && policy.doNotDisturbWhenSilent) { + if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS + && mZenMode != Global.ZEN_MODE_ALARMS) { + newZen = Global.ZEN_MODE_ALARMS; + } + } + break; + case AudioManager.RINGER_MODE_VIBRATE: + case AudioManager.RINGER_MODE_NORMAL: + if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT + && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS + || mZenMode == Global.ZEN_MODE_ALARMS)) { + newZen = Global.ZEN_MODE_OFF; + } else if (mZenMode != Global.ZEN_MODE_OFF) { + ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT; + } + break; + } + if (newZen != -1) { + setManualZenMode(newZen, null, "ringerModeInternal", false /*setRingerMode*/); + } - /** - * @param extras extras of the notification with EXTRA_PEOPLE populated - * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response - * @param timeoutAffinity affinity to return when the timeout specified via - * <code>contactsTimeoutMs</code> is hit - */ - public boolean matchesCallFilter(UserHandle userHandle, Bundle extras, - ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) { - final int zen = mZenMode; - if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through - if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { - if (!mConfig.allowCalls) return false; // no calls get through - if (validator != null) { - final float contactAffinity = validator.getContactAffinity(userHandle, extras, - contactsTimeoutMs, timeoutAffinity); - return audienceMatches(contactAffinity); + if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) { + ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller, + ringerModeExternal, ringerModeExternalOut); } + return ringerModeExternalOut; } - return true; - } - @Override - public String toString() { - return TAG; - } + @Override + public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller, + int ringerModeInternal, VolumePolicy policy) { + int ringerModeInternalOut = ringerModeNew; + final boolean isChange = ringerModeOld != ringerModeNew; + final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; + + int newZen = -1; + switch (ringerModeNew) { + case AudioManager.RINGER_MODE_SILENT: + if (isChange) { + if (mZenMode == Global.ZEN_MODE_OFF) { + newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + } + ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE + : AudioManager.RINGER_MODE_NORMAL; + } else { + ringerModeInternalOut = ringerModeInternal; + } + break; + case AudioManager.RINGER_MODE_VIBRATE: + case AudioManager.RINGER_MODE_NORMAL: + if (mZenMode != Global.ZEN_MODE_OFF) { + newZen = Global.ZEN_MODE_OFF; + } + break; + } + if (newZen != -1) { + setManualZenMode(newZen, null, "ringerModeExternal", false /*setRingerMode*/); + } - private boolean audienceMatches(float contactAffinity) { - switch (mConfig.allowFrom) { - case ZenModeConfig.SOURCE_ANYONE: - return true; - case ZenModeConfig.SOURCE_CONTACT: - return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT; - case ZenModeConfig.SOURCE_STAR: - return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT; - default: - Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom); - return true; + ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, + ringerModeInternal, ringerModeInternalOut); + return ringerModeInternalOut; } } - private class SettingsObserver extends ContentObserver { + private final class SettingsObserver extends ContentObserver { private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE); public SettingsObserver(Handler handler) { @@ -519,12 +612,15 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { public void update(Uri uri) { if (ZEN_MODE.equals(uri)) { - readZenModeFromSetting(); + if (mZenMode != getZenModeSetting()) { + if (DEBUG) Log.d(TAG, "Fixing zen mode setting"); + setZenModeSetting(mZenMode); + } } } } - private class H extends Handler { + private final class H extends Handler { private static final int MSG_DISPATCH = 1; private H(Looper looper) { @@ -549,5 +645,7 @@ public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate { public static class Callback { void onConfigChanged() {} void onZenModeChanged() {} + void onPolicyChanged() {} } + } diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java index 4f27408..30f8b37 100644 --- a/services/core/java/com/android/server/pm/BasePermission.java +++ b/services/core/java/com/android/server/pm/BasePermission.java @@ -18,6 +18,9 @@ package com.android.server.pm; import android.content.pm.PackageParser; import android.content.pm.PermissionInfo; +import android.os.UserHandle; + +import com.android.internal.util.ArrayUtils; final class BasePermission { final static int TYPE_NORMAL = 0; @@ -40,9 +43,17 @@ final class BasePermission { PermissionInfo pendingInfo; + /** UID that owns the definition of this permission */ int uid; - int[] gids; + /** Additional GIDs given to apps granted this permission */ + private int[] gids; + + /** + * Flag indicating that {@link #gids} should be adjusted based on the + * {@link UserHandle} the granted app is running as. + */ + private boolean perUser; BasePermission(String _name, String _sourcePackage, int _type) { name = _name; @@ -52,8 +63,31 @@ final class BasePermission { protectionLevel = PermissionInfo.PROTECTION_SIGNATURE; } + @Override public String toString() { return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name + "}"; } + + public void setGids(int[] gids, boolean perUser) { + this.gids = gids; + this.perUser = perUser; + } + + public int[] computeGids(int userId) { + if (perUser) { + final int[] userGids = new int[gids.length]; + for (int i = 0; i < gids.length; i++) { + userGids[i] = UserHandle.getUid(userId, gids[i]); + } + return userGids; + } else { + return gids; + } + } + + public boolean isRuntime() { + return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) + == PermissionInfo.PROTECTION_DANGEROUS; + } } diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java index 6d18531..ff4049b 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java @@ -33,7 +33,6 @@ import android.os.UserHandle; class CrossProfileIntentFilter extends IntentFilter { private static final String ATTR_TARGET_USER_ID = "targetUserId"; private static final String ATTR_FLAGS = "flags"; - private static final String ATTR_OWNER_USER_ID = "ownerUserId"; private static final String ATTR_OWNER_PACKAGE = "ownerPackage"; private static final String ATTR_FILTER = "filter"; @@ -41,15 +40,13 @@ class CrossProfileIntentFilter extends IntentFilter { // If the intent matches the IntentFilter, then it can be forwarded to this userId. final int mTargetUserId; - final int mOwnerUserId; // userId of the app which has set this CrossProfileIntentFilter. final String mOwnerPackage; // packageName of the app. final int mFlags; - CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int ownerUserId, - int targetUserId, int flags) { + CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId, + int flags) { super(filter); mTargetUserId = targetUserId; - mOwnerUserId = ownerUserId; mOwnerPackage = ownerPackage; mFlags = flags; } @@ -62,17 +59,12 @@ class CrossProfileIntentFilter extends IntentFilter { return mFlags; } - public int getOwnerUserId() { - return mOwnerUserId; - } - public String getOwnerPackage() { return mOwnerPackage; } CrossProfileIntentFilter(XmlPullParser parser) throws XmlPullParserException, IOException { mTargetUserId = getIntFromXml(parser, ATTR_TARGET_USER_ID, UserHandle.USER_NULL); - mOwnerUserId = getIntFromXml(parser, ATTR_OWNER_USER_ID, UserHandle.USER_NULL); mOwnerPackage = getStringFromXml(parser, ATTR_OWNER_PACKAGE, ""); mFlags = getIntFromXml(parser, ATTR_FLAGS, 0); @@ -129,7 +121,6 @@ class CrossProfileIntentFilter extends IntentFilter { public void writeToXml(XmlSerializer serializer) throws IOException { serializer.attribute(null, ATTR_TARGET_USER_ID, Integer.toString(mTargetUserId)); serializer.attribute(null, ATTR_FLAGS, Integer.toString(mFlags)); - serializer.attribute(null, ATTR_OWNER_USER_ID, Integer.toString(mOwnerUserId)); serializer.attribute(null, ATTR_OWNER_PACKAGE, mOwnerPackage); serializer.startTag(null, ATTR_FILTER); super.writeToXml(serializer); @@ -144,7 +135,6 @@ class CrossProfileIntentFilter extends IntentFilter { boolean equalsIgnoreFilter(CrossProfileIntentFilter other) { return mTargetUserId == other.mTargetUserId - && mOwnerUserId == other.mOwnerUserId && mOwnerPackage.equals(other.mOwnerPackage) && mFlags == other.mFlags; } diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java index a335d3a..0e0096d 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java @@ -18,7 +18,6 @@ package com.android.server.pm; -import java.io.PrintWriter; import com.android.server.IntentResolver; import java.util.List; diff --git a/services/core/java/com/android/server/pm/GrantedPermissions.java b/services/core/java/com/android/server/pm/GrantedPermissions.java deleted file mode 100644 index 8f0f935..0000000 --- a/services/core/java/com/android/server/pm/GrantedPermissions.java +++ /dev/null @@ -1,50 +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.pm; - -import android.content.pm.ApplicationInfo; -import android.util.ArraySet; - -class GrantedPermissions { - int pkgFlags; - - ArraySet<String> grantedPermissions = new ArraySet<String>(); - - int[] gids; - - GrantedPermissions(int pkgFlags) { - setFlags(pkgFlags); - } - - @SuppressWarnings("unchecked") - GrantedPermissions(GrantedPermissions base) { - pkgFlags = base.pkgFlags; - grantedPermissions = new ArraySet<>(base.grantedPermissions); - - if (base.gids != null) { - gids = base.gids.clone(); - } - } - - 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/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 31c604f..0f3b4e6 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -16,10 +16,13 @@ package com.android.server.pm; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageStats; import android.os.Build; +import android.text.TextUtils; import android.util.Slog; + import dalvik.system.VMRuntime; import com.android.internal.os.InstallerConnection; @@ -41,9 +44,27 @@ public final class Installer extends SystemService { ping(); } + private static String escapeNull(String arg) { + if (TextUtils.isEmpty(arg)) { + return "!"; + } else { + if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) { + throw new IllegalArgumentException(arg); + } + return arg; + } + } + + @Deprecated public int install(String name, int uid, int gid, String seinfo) { + return install(null, name, uid, gid, seinfo); + } + + public int install(String uuid, String name, int uid, int gid, String seinfo) { StringBuilder builder = new StringBuilder("install"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(name); builder.append(' '); builder.append(uid); @@ -54,42 +75,26 @@ public final class Installer extends SystemService { return mInstaller.execute(builder.toString()); } - public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName, - String instructionSet) { - if (!isValidInstructionSet(instructionSet)) { - Slog.e(TAG, "Invalid instruction set: " + instructionSet); - return -1; - } - - return mInstaller.patchoat(apkPath, uid, isPublic, pkgName, instructionSet); - } - - public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) { - if (!isValidInstructionSet(instructionSet)) { - Slog.e(TAG, "Invalid instruction set: " + instructionSet); - return -1; - } - - return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet); - } - - public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) { + public int dexopt(String apkPath, int uid, boolean isPublic, + String instructionSet, int dexoptNeeded) { if (!isValidInstructionSet(instructionSet)) { Slog.e(TAG, "Invalid instruction set: " + instructionSet); return -1; } - return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet); + return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded); } public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName, - String instructionSet, boolean vmSafeMode) { + String instructionSet, int dexoptNeeded, boolean vmSafeMode, + boolean debuggable, @Nullable String outputPath) { if (!isValidInstructionSet(instructionSet)) { Slog.e(TAG, "Invalid instruction set: " + instructionSet); return -1; } - - return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, vmSafeMode); + return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, + instructionSet, dexoptNeeded, vmSafeMode, + debuggable, outputPath); } public int idmap(String targetApkPath, String overlayApkPath, int uid) { @@ -133,9 +138,26 @@ public final class Installer extends SystemService { return mInstaller.execute(builder.toString()); } + /** + * Removes packageDir or its subdirectory + */ + public int rmPackageDir(String packageDir) { + StringBuilder builder = new StringBuilder("rmpackagedir"); + builder.append(' '); + builder.append(packageDir); + return mInstaller.execute(builder.toString()); + } + + @Deprecated public int remove(String name, int userId) { + return remove(null, name, userId); + } + + public int remove(String uuid, String name, int userId) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(name); builder.append(' '); builder.append(userId); @@ -151,9 +173,16 @@ public final class Installer extends SystemService { return mInstaller.execute(builder.toString()); } + @Deprecated public int fixUid(String name, int uid, int gid) { + return fixUid(null, name, uid, gid); + } + + public int fixUid(String uuid, String name, int uid, int gid) { StringBuilder builder = new StringBuilder("fixuid"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(name); builder.append(' '); builder.append(uid); @@ -162,27 +191,48 @@ public final class Installer extends SystemService { return mInstaller.execute(builder.toString()); } + @Deprecated public int deleteCacheFiles(String name, int userId) { + return deleteCacheFiles(null, name, userId); + } + + public int deleteCacheFiles(String uuid, String name, int userId) { StringBuilder builder = new StringBuilder("rmcache"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(name); builder.append(' '); builder.append(userId); return mInstaller.execute(builder.toString()); } + @Deprecated public int deleteCodeCacheFiles(String name, int userId) { + return deleteCodeCacheFiles(null, name, userId); + } + + public int deleteCodeCacheFiles(String uuid, String name, int userId) { StringBuilder builder = new StringBuilder("rmcodecache"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(name); builder.append(' '); builder.append(userId); return mInstaller.execute(builder.toString()); } + @Deprecated public int createUserData(String name, int uid, int userId, String seinfo) { + return createUserData(null, name, uid, userId, seinfo); + } + + public int createUserData(String uuid, String name, int uid, int userId, String seinfo) { StringBuilder builder = new StringBuilder("mkuserdata"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(name); builder.append(' '); builder.append(uid); @@ -200,16 +250,46 @@ public final class Installer extends SystemService { return mInstaller.execute(builder.toString()); } + @Deprecated public int removeUserDataDirs(int userId) { + return removeUserDataDirs(null, userId); + } + + public int removeUserDataDirs(String uuid, int userId) { StringBuilder builder = new StringBuilder("rmuser"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(userId); return mInstaller.execute(builder.toString()); } + public int moveUserDataDirs(String fromUuid, String toUuid, String packageName, int appId, + String seinfo) { + StringBuilder builder = new StringBuilder("mvuserdata"); + builder.append(' '); + builder.append(escapeNull(fromUuid)); + builder.append(' '); + builder.append(escapeNull(toUuid)); + builder.append(' '); + builder.append(packageName); + builder.append(' '); + builder.append(appId); + builder.append(' '); + builder.append(seinfo); + return mInstaller.execute(builder.toString()); + } + + @Deprecated public int clearUserData(String name, int userId) { + return clearUserData(null, name, userId); + } + + public int clearUserData(String uuid, String name, int userId) { StringBuilder builder = new StringBuilder("rmuserdata"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(name); builder.append(' '); builder.append(userId); @@ -236,15 +316,30 @@ public final class Installer extends SystemService { } } + @Deprecated public int freeCache(long freeStorageSize) { + return freeCache(null, freeStorageSize); + } + + public int freeCache(String uuid, long freeStorageSize) { StringBuilder builder = new StringBuilder("freecache"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(String.valueOf(freeStorageSize)); return mInstaller.execute(builder.toString()); } + @Deprecated public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets, PackageStats pStats) { + return getSizeInfo(null, pkgName, persona, apkPath, libDirPath, fwdLockApkPath, asecPath, + instructionSets, pStats); + } + + public int getSizeInfo(String uuid, String pkgName, int persona, String apkPath, + String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets, + PackageStats pStats) { for (String instructionSet : instructionSets) { if (!isValidInstructionSet(instructionSet)) { Slog.e(TAG, "Invalid instruction set: " + instructionSet); @@ -254,6 +349,8 @@ public final class Installer extends SystemService { StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(pkgName); builder.append(' '); builder.append(persona); @@ -293,6 +390,11 @@ public final class Installer extends SystemService { return mInstaller.execute("movefiles"); } + @Deprecated + public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) { + return linkNativeLibraryDirectory(null, dataPath, nativeLibPath32, userId); + } + /** * Links the 32 bit native library directory in an application's data directory to the * real location for backward compatibility. Note that no such symlink is created for @@ -300,7 +402,8 @@ public final class Installer extends SystemService { * * @return -1 on error */ - public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) { + public int linkNativeLibraryDirectory(String uuid, String dataPath, String nativeLibPath32, + int userId) { if (dataPath == null) { Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null"); return -1; @@ -309,7 +412,10 @@ public final class Installer extends SystemService { return -1; } - StringBuilder builder = new StringBuilder("linklib "); + StringBuilder builder = new StringBuilder("linklib"); + builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(dataPath); builder.append(' '); builder.append(nativeLibPath32); @@ -319,9 +425,16 @@ public final class Installer extends SystemService { return mInstaller.execute(builder.toString()); } + @Deprecated public boolean restoreconData(String pkgName, String seinfo, int uid) { + return restoreconData(null, pkgName, seinfo, uid); + } + + public boolean restoreconData(String uuid, String pkgName, String seinfo, int uid) { StringBuilder builder = new StringBuilder("restorecondata"); builder.append(' '); + builder.append(escapeNull(uuid)); + builder.append(' '); builder.append(pkgName); builder.append(' '); builder.append(seinfo != null ? seinfo : "!"); @@ -330,6 +443,15 @@ public final class Installer extends SystemService { return (mInstaller.execute(builder.toString()) == 0); } + public int createOatDir(String oatDir, String dexInstructionSet) { + StringBuilder builder = new StringBuilder("createoatdir"); + builder.append(' '); + builder.append(oatDir); + builder.append(' '); + builder.append(dexInstructionSet); + return mInstaller.execute(builder.toString()); + } + /** * Returns true iff. {@code instructionSet} is a valid instruction set. */ diff --git a/services/core/java/com/android/server/pm/InstructionSets.java b/services/core/java/com/android/server/pm/InstructionSets.java new file mode 100644 index 0000000..5092ebf --- /dev/null +++ b/services/core/java/com/android/server/pm/InstructionSets.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 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.ApplicationInfo; +import android.os.Build; +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.ArraySet; + +import java.util.ArrayList; +import java.util.List; + +import dalvik.system.VMRuntime; + +/** + * Provides various methods for obtaining and converting of instruction sets. + * + * @hide + */ +public class InstructionSets { + private static final String PREFERRED_INSTRUCTION_SET = + VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);; + public static String[] getAppDexInstructionSets(ApplicationInfo info) { + if (info.primaryCpuAbi != null) { + if (info.secondaryCpuAbi != null) { + return new String[] { + VMRuntime.getInstructionSet(info.primaryCpuAbi), + VMRuntime.getInstructionSet(info.secondaryCpuAbi) }; + } else { + return new String[] { + VMRuntime.getInstructionSet(info.primaryCpuAbi) }; + } + } + + return new String[] { getPreferredInstructionSet() }; + } + + public static String[] getAppDexInstructionSets(PackageSetting ps) { + if (ps.primaryCpuAbiString != null) { + if (ps.secondaryCpuAbiString != null) { + return new String[] { + VMRuntime.getInstructionSet(ps.primaryCpuAbiString), + VMRuntime.getInstructionSet(ps.secondaryCpuAbiString) }; + } else { + return new String[] { + VMRuntime.getInstructionSet(ps.primaryCpuAbiString) }; + } + } + + return new String[] { getPreferredInstructionSet() }; + } + + public static String getPreferredInstructionSet() { + return PREFERRED_INSTRUCTION_SET; + } + + /** + * Returns the instruction set that should be used to compile dex code. In the presence of + * a native bridge this might be different than the one shared libraries use. + */ + public static String getDexCodeInstructionSet(String sharedLibraryIsa) { + // TODO b/19550105 Build mapping once instead of querying each time + String dexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + sharedLibraryIsa); + return TextUtils.isEmpty(dexCodeIsa) ? sharedLibraryIsa : dexCodeIsa; + } + + public static String[] getDexCodeInstructionSets(String[] instructionSets) { + ArraySet<String> dexCodeInstructionSets = new ArraySet<String>(instructionSets.length); + for (String instructionSet : instructionSets) { + dexCodeInstructionSets.add(getDexCodeInstructionSet(instructionSet)); + } + return dexCodeInstructionSets.toArray(new String[dexCodeInstructionSets.size()]); + } + + /** + * Returns deduplicated list of supported instructions for dex code. + */ + public static String[] getAllDexCodeInstructionSets() { + String[] supportedInstructionSets = new String[Build.SUPPORTED_ABIS.length]; + for (int i = 0; i < supportedInstructionSets.length; i++) { + String abi = Build.SUPPORTED_ABIS[i]; + supportedInstructionSets[i] = VMRuntime.getInstructionSet(abi); + } + return getDexCodeInstructionSets(supportedInstructionSets); + } + + public static List<String> getAllInstructionSets() { + final String[] allAbis = Build.SUPPORTED_ABIS; + final List<String> allInstructionSets = new ArrayList<String>(allAbis.length); + + for (String abi : allAbis) { + final String instructionSet = VMRuntime.getInstructionSet(abi); + if (!allInstructionSets.contains(instructionSet)) { + allInstructionSets.add(instructionSet); + } + } + + return allInstructionSets; + } + + public static String getPrimaryInstructionSet(ApplicationInfo info) { + if (info.primaryCpuAbi == null) { + return getPreferredInstructionSet(); + } + + return VMRuntime.getInstructionSet(info.primaryCpuAbi); + } + +} diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java b/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java new file mode 100644 index 0000000..399b03c --- /dev/null +++ b/services/core/java/com/android/server/pm/IntentFilterVerificationKey.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 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; + +/** + * This is the key for the map of {@link android.content.pm.IntentFilterVerificationInfo}s + * maintained by the {@link com.android.server.pm.PackageManagerService} + */ +class IntentFilterVerificationKey { + public String domains; + public String packageName; + public String className; + + public IntentFilterVerificationKey(String[] domains, String packageName, String className) { + StringBuilder sb = new StringBuilder(); + for (String host : domains) { + sb.append(host); + } + this.domains = sb.toString(); + this.packageName = packageName; + this.className = className; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + IntentFilterVerificationKey that = (IntentFilterVerificationKey) o; + + if (domains != null ? !domains.equals(that.domains) : that.domains != null) return false; + if (className != null ? !className.equals(that.className) : that.className != null) + return false; + if (packageName != null ? !packageName.equals(that.packageName) : that.packageName != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = domains != null ? domains.hashCode() : 0; + result = 31 * result + (packageName != null ? packageName.hashCode() : 0); + result = 31 * result + (className != null ? className.hashCode() : 0); + return result; + } +} diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java b/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java new file mode 100644 index 0000000..ead399b --- /dev/null +++ b/services/core/java/com/android/server/pm/IntentFilterVerificationResponse.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 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.List; + +/* package private */ class IntentFilterVerificationResponse { + public final int callerUid; + public final int code; + public final List<String> failedDomains; + + public IntentFilterVerificationResponse(int callerUid, int code, List<String> failedDomains) { + this.callerUid = callerUid; + this.code = code; + this.failedDomains = failedDomains; + } + + public String getFailedDomainsString() { + StringBuilder sb = new StringBuilder(); + for (String domain : failedDomains) { + if (sb.length() > 0) { + sb.append(" "); + } + sb.append(domain); + } + return sb.toString(); + } +} diff --git a/services/core/java/com/android/server/pm/IntentFilterVerificationState.java b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java new file mode 100644 index 0000000..c09d6ae --- /dev/null +++ b/services/core/java/com/android/server/pm/IntentFilterVerificationState.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015 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.PackageManager; +import android.content.pm.PackageParser; +import android.util.ArraySet; +import android.util.Log; + +import java.util.ArrayList; + +public class IntentFilterVerificationState { + static final String TAG = IntentFilterVerificationState.class.getName(); + + public final static int STATE_UNDEFINED = 0; + public final static int STATE_VERIFICATION_PENDING = 1; + public final static int STATE_VERIFICATION_SUCCESS = 2; + public final static int STATE_VERIFICATION_FAILURE = 3; + + private int mRequiredVerifierUid = 0; + + private int mState; + + private ArrayList<PackageParser.ActivityIntentInfo> mFilters = new ArrayList<>(); + private ArraySet<String> mHosts = new ArraySet<>(); + private int mUserId; + + private String mPackageName; + private boolean mVerificationComplete; + + public IntentFilterVerificationState(int verifierUid, int userId, String packageName) { + mRequiredVerifierUid = verifierUid; + mUserId = userId; + mPackageName = packageName; + mState = STATE_UNDEFINED; + mVerificationComplete = false; + } + + public void setState(int state) { + if (state > STATE_VERIFICATION_FAILURE || state < STATE_UNDEFINED) { + mState = STATE_UNDEFINED; + } else { + mState = state; + } + } + + public int getState() { + return mState; + } + + public void setPendingState() { + setState(STATE_VERIFICATION_PENDING); + } + + public ArrayList<PackageParser.ActivityIntentInfo> getFilters() { + return mFilters; + } + + public boolean isVerificationComplete() { + return mVerificationComplete; + } + + public boolean isVerified() { + if (mVerificationComplete) { + return (mState == STATE_VERIFICATION_SUCCESS); + } + return false; + } + + public int getUserId() { + return mUserId; + } + + public String getPackageName() { + return mPackageName; + } + + public String getHostsString() { + StringBuilder sb = new StringBuilder(); + final int count = mHosts.size(); + for (int i=0; i<count; i++) { + if (i > 0) { + sb.append(" "); + } + sb.append(mHosts.valueAt(i)); + } + return sb.toString(); + } + + public boolean setVerifierResponse(int callerUid, int code) { + if (mRequiredVerifierUid == callerUid) { + int state = STATE_UNDEFINED; + if (code == PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS) { + state = STATE_VERIFICATION_SUCCESS; + } else if (code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) { + state = STATE_VERIFICATION_FAILURE; + } + mVerificationComplete = true; + setState(state); + return true; + } + Log.d(TAG, "Cannot set verifier response with callerUid:" + callerUid + " and code:" + + code + " as required verifierUid is:" + mRequiredVerifierUid); + return false; + } + + public void addFilter(PackageParser.ActivityIntentInfo filter) { + mFilters.add(filter); + mHosts.addAll(filter.getHostsList()); + } +} diff --git a/services/core/java/com/android/server/pm/KeySetHandle.java b/services/core/java/com/android/server/pm/KeySetHandle.java index 640feb3..f34bd60 100644 --- a/services/core/java/com/android/server/pm/KeySetHandle.java +++ b/services/core/java/com/android/server/pm/KeySetHandle.java @@ -18,5 +18,46 @@ package com.android.server.pm; import android.os.Binder; -public class KeySetHandle extends Binder { -}
\ No newline at end of file +class KeySetHandle extends Binder{ + private final long mId; + private int mRefCount; + + protected KeySetHandle(long id) { + mId = id; + mRefCount = 1; + } + + /* + * Only used when reading state from packages.xml + */ + protected KeySetHandle(long id, int refCount) { + mId = id; + mRefCount = refCount; + } + + public long getId() { + return mId; + } + + protected int getRefCountLPr() { + return mRefCount; + } + + /* + * Only used when reading state from packages.xml + */ + protected void setRefCountLPw(int newCount) { + mRefCount = newCount; + return; + } + + protected void incrRefCountLPw() { + mRefCount++; + return; + } + + protected int decrRefCountLPw() { + mRefCount--; + return mRefCount; + } +} diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java index 4a8e318..c8e5c3a 100644 --- a/services/core/java/com/android/server/pm/KeySetManagerService.java +++ b/services/core/java/com/android/server/pm/KeySetManagerService.java @@ -17,7 +17,7 @@ package com.android.server.pm; import android.content.pm.PackageParser; -import android.os.Binder; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Base64; import android.util.Slog; @@ -26,7 +26,6 @@ import android.util.LongSparseArray; import java.io.IOException; import java.io.PrintWriter; import java.security.PublicKey; -import java.util.Map; import java.util.Set; import org.xmlpull.v1.XmlPullParser; @@ -53,19 +52,61 @@ public class KeySetManagerService { private final LongSparseArray<KeySetHandle> mKeySets; - private final LongSparseArray<PublicKey> mPublicKeys; + private final LongSparseArray<PublicKeyHandle> mPublicKeys; protected final LongSparseArray<ArraySet<Long>> mKeySetMapping; - private final Map<String, PackageSetting> mPackages; + private final ArrayMap<String, PackageSetting> mPackages; private static long lastIssuedKeySetId = 0; private static long lastIssuedKeyId = 0; - public KeySetManagerService(Map<String, PackageSetting> packages) { + class PublicKeyHandle { + private final PublicKey mKey; + private final long mId; + private int mRefCount; + + public PublicKeyHandle(long id, PublicKey key) { + mId = id; + mRefCount = 1; + mKey = key; + } + + /* + * Only used when reading state from packages.xml + */ + private PublicKeyHandle(long id, int refCount, PublicKey key) { + mId = id; + mRefCount = refCount; + mKey = key; + } + + public long getId() { + return mId; + } + + public PublicKey getKey() { + return mKey; + } + + public int getRefCountLPr() { + return mRefCount; + } + + public void incrRefCountLPw() { + mRefCount++; + return; + } + public long decrRefCountLPw() { + mRefCount--; + return mRefCount; + } + } + + public KeySetManagerService(ArrayMap<String, PackageSetting> packages) { mKeySets = new LongSparseArray<KeySetHandle>(); - mPublicKeys = new LongSparseArray<PublicKey>(); + mPublicKeys = new LongSparseArray<PublicKeyHandle>(); mKeySetMapping = new LongSparseArray<ArraySet<Long>>(); mPackages = packages; } @@ -93,7 +134,9 @@ public class KeySetManagerService { if (id == KEYSET_NOT_FOUND) { return false; } - return pkg.keySetData.packageIsSignedBy(id); + ArraySet<Long> pkgKeys = mKeySetMapping.get(pkg.keySetData.getProperSigningKeySet()); + ArraySet<Long> testKeys = mKeySetMapping.get(id); + return pkgKeys.containsAll(testKeys); } /** @@ -114,81 +157,45 @@ public class KeySetManagerService { || pkg.keySetData.getProperSigningKeySet() == PackageKeySetData.KEYSET_UNASSIGNED) { throw new NullPointerException("Package has no KeySet data"); - } + } long id = getIdByKeySetLPr(ks); - return pkg.keySetData.getProperSigningKeySet() == 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 addDefinedKeySetToPackageLPw(String packageName, - ArraySet<PublicKey> keys, String alias) { - if ((packageName == null) || (keys == null) || (alias == null)) { - Slog.w(TAG, "Got null argument for a defined keyset, ignoring!"); - return; - } - PackageSetting pkg = mPackages.get(packageName); - if (pkg == null) { - throw new NullPointerException("Unknown package"); - } - // Add to KeySets, then to package - KeySetHandle ks = addKeySetLPw(keys); - long id = getIdByKeySetLPr(ks); - pkg.keySetData.addDefinedKeySet(id, alias); - } - - /** - * This informs the system that the given package has defined a KeySet - * alias in its manifest to be an upgradeKeySet. This must be called - * after all of the defined KeySets have been added. - */ - public void addUpgradeKeySetToPackageLPw(String packageName, String alias) { - if ((packageName == null) || (alias == null)) { - Slog.w(TAG, "Got null argument for a defined keyset, ignoring!"); - return; - } - PackageSetting pkg = mPackages.get(packageName); - if (pkg == null) { - throw new NullPointerException("Unknown package"); + if (id == KEYSET_NOT_FOUND) { + return false; } - pkg.keySetData.addUpgradeKeySet(alias); + ArraySet<Long> pkgKeys = mKeySetMapping.get(pkg.keySetData.getProperSigningKeySet()); + ArraySet<Long> testKeys = mKeySetMapping.get(id); + return pkgKeys.equals(testKeys); } /** - * Similar to the above, this informs the system that the given package - * was signed by the provided KeySet. + * Informs the system that the given package was signed by the provided KeySet. */ public void addSigningKeySetToPackageLPw(String packageName, ArraySet<PublicKey> signingKeys) { - if ((packageName == null) || (signingKeys == null)) { - Slog.w(TAG, "Got null argument for a signing keyset, ignoring!"); - return; - } - // add the signing KeySet - KeySetHandle ks = addKeySetLPw(signingKeys); - long id = getIdByKeySetLPr(ks); - ArraySet<Long> publicKeyIds = mKeySetMapping.get(id); - if (publicKeyIds == null) { - throw new NullPointerException("Got invalid KeySet id"); - } - // attach it to the package + + /* check existing keyset for reuse or removal */ PackageSetting pkg = mPackages.get(packageName); - if (pkg == null) { - throw new NullPointerException("No such package!"); - } - pkg.keySetData.setProperSigningKeySet(id); - // for each KeySet which is a subset of the one above, add the - // KeySet id to the package's signing KeySets - for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) { - long keySetID = mKeySets.keyAt(keySetIndex); - ArraySet<Long> definedKeys = mKeySetMapping.get(keySetID); - if (publicKeyIds.containsAll(definedKeys)) { - pkg.keySetData.addSigningKeySet(keySetID); + long signingKeySetId = pkg.keySetData.getProperSigningKeySet(); + + if (signingKeySetId != PackageKeySetData.KEYSET_UNASSIGNED) { + ArraySet<PublicKey> existingKeys = getPublicKeysFromKeySetLPr(signingKeySetId); + if (existingKeys.equals(signingKeys)) { + + /* no change in signing keys, leave PackageSetting alone */ + return; + } else { + + /* old keyset no longer valid, remove ref */ + KeySetHandle ksh = mKeySets.get(signingKeySetId); + decrementKeySetLPw(signingKeySetId); } } + + /* create and add a new keyset */ + KeySetHandle ks = addKeySetLPw(signingKeys); + long id = ks.getId(); + pkg.keySetData.setProperSigningKeySet(id); + return; } /** @@ -205,25 +212,63 @@ public class KeySetManagerService { return KEYSET_NOT_FOUND; } + /* + * Inform the system that the given package defines the given KeySets. + * Remove any KeySets the package no longer defines. + */ + public void addDefinedKeySetsToPackageLPw(String packageName, + ArrayMap<String, ArraySet<PublicKey>> definedMapping) { + PackageSetting pkg = mPackages.get(packageName); + ArrayMap<String, Long> prevDefinedKeySets = pkg.keySetData.getAliases(); + + /* add all of the newly defined KeySets */ + ArrayMap<String, Long> newKeySetAliases = new ArrayMap<String, Long>(); + final int defMapSize = definedMapping.size(); + for (int i = 0; i < defMapSize; i++) { + String alias = definedMapping.keyAt(i); + ArraySet<PublicKey> pubKeys = definedMapping.valueAt(i); + if (alias != null && pubKeys != null && pubKeys.size() > 0) { + KeySetHandle ks = addKeySetLPw(pubKeys); + newKeySetAliases.put(alias, ks.getId()); + } + } + + /* remove each of the old references */ + final int prevDefSize = prevDefinedKeySets.size(); + for (int i = 0; i < prevDefSize; i++) { + decrementKeySetLPw(prevDefinedKeySets.valueAt(i)); + } + pkg.keySetData.removeAllUpgradeKeySets(); + + /* switch to the just-added */ + pkg.keySetData.setAliases(newKeySetAliases); + return; + } + /** - * Fetches the KeySet corresponding to the given stable identifier. - * - * Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't - * identify a {@link KeySet}. + * This informs the system that the given package has defined a KeySet + * alias in its manifest to be an upgradeKeySet. This must be called + * after all of the defined KeySets have been added. */ - public KeySetHandle getKeySetByIdLPr(long id) { - return mKeySets.get(id); + public void addUpgradeKeySetsToPackageLPw(String packageName, + ArraySet<String> upgradeAliases) { + PackageSetting pkg = mPackages.get(packageName); + final int uaSize = upgradeAliases.size(); + for (int i = 0; i < uaSize; i++) { + pkg.keySetData.addUpgradeKeySet(upgradeAliases.valueAt(i)); + } + return; } /** - * Fetches the {@link KeySetHandle} that a given package refers to by the - * provided alias. Returns null if the package is unknown or does not have a + * Fetched the {@link KeySetHandle} that a given package refers to by the + * provided alias. Returns null if the package is unknown or does not have a * KeySet corresponding to that alias. */ public KeySetHandle getKeySetByAliasAndPackageNameLPr(String packageName, String alias) { PackageSetting p = mPackages.get(packageName); if (p == null || p.keySetData == null) { - return null; + return null; } Long keySetId = p.keySetData.getAliases().get(alias); if (keySetId == null) { @@ -244,8 +289,10 @@ public class KeySetManagerService { return null; } ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>(); - for (long pkId : mKeySetMapping.get(id)) { - mPubKeys.add(mPublicKeys.get(pkId)); + ArraySet<Long> pkIds = mKeySetMapping.get(id); + final int pkSize = pkIds.size(); + for (int i = 0; i < pkSize; i++) { + mPubKeys.add(mPublicKeys.get(pkIds.valueAt(i)).getKey()); } return mPubKeys; } @@ -255,7 +302,7 @@ public class KeySetManagerService { * package. * * @throws IllegalArgumentException if the package has no keyset data. - * @throws NullPointerException if the package is unknown. + * @throws NullPointerException if the packgae is unknown. */ public KeySetHandle getSigningKeySetByPackageNameLPr(String packageName) { PackageSetting p = mPackages.get(packageName); @@ -269,99 +316,100 @@ public class KeySetManagerService { } /** - * Fetches all the known {@link KeySetHandle KeySets} that may upgrade the given - * package. - * - * @throws IllegalArgumentException if the package has no keyset data. - * @throws NullPointerException if the package is unknown. - */ - public ArraySet<KeySetHandle> getUpgradeKeySetsByPackageNameLPr(String packageName) { - ArraySet<KeySetHandle> upgradeKeySets = new ArraySet<KeySetHandle>(); - 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"); - } - if (p.keySetData.isUsingUpgradeKeySets()) { - for (long l : p.keySetData.getUpgradeKeySets()) { - upgradeKeySets.add(mKeySets.get(l)); - } - } - return upgradeKeySets; - } - - /** * 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. + * adds them. Otherwise, they're deduped and the reference count + * incremented. * * 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. - * - * If the KeySet isn't known to the system, this adds it to all appropriate - * signingKeySets + * mapping to the PublicKeys. If it is known, then it's deduped and the + * reference count is incremented. * * Throws if the provided set is {@code null}. */ private KeySetHandle addKeySetLPw(ArraySet<PublicKey> keys) { - if (keys == null) { - throw new NullPointerException("Provided keys cannot be null"); + if (keys == null || keys.size() == 0) { + throw new IllegalArgumentException("Cannot add an empty set of keys!"); } - // add each of the keys in the provided set + + /* add each of the keys in the provided set */ ArraySet<Long> addedKeyIds = new ArraySet<Long>(keys.size()); - for (PublicKey k : keys) { - long id = addPublicKeyLPw(k); + final int kSize = keys.size(); + for (int i = 0; i < kSize; i++) { + long id = addPublicKeyLPw(keys.valueAt(i)); addedKeyIds.add(id); } - // check to see if the resulting keyset is new + /* check to see if the resulting keyset is new */ long existingKeySetId = getIdFromKeyIdsLPr(addedKeyIds); if (existingKeySetId != KEYSET_NOT_FOUND) { - return mKeySets.get(existingKeySetId); + + /* public keys were incremented, but we aren't adding a new keyset: undo */ + for (int i = 0; i < kSize; i++) { + decrementPublicKeyLPw(addedKeyIds.valueAt(i)); + } + KeySetHandle ks = mKeySets.get(existingKeySetId); + ks.incrRefCountLPw(); + return ks; } - // create the KeySet object - KeySetHandle ks = new KeySetHandle(); - // get the first unoccupied slot in mKeySets + // get the next keyset id long id = getFreeKeySetIDLPw(); - // add the KeySet object to it + + // create the KeySet object and add to mKeySets and mapping + KeySetHandle ks = new KeySetHandle(id); mKeySets.put(id, ks); - // add the stable key ids to the mapping mKeySetMapping.put(id, addedKeyIds); - // add this KeySet id to all packages which are signed by it - for (String pkgName : mPackages.keySet()) { - PackageSetting p = mPackages.get(pkgName); - if (p.keySetData != null) { - long pProperSigning = p.keySetData.getProperSigningKeySet(); - if (pProperSigning != PackageKeySetData.KEYSET_UNASSIGNED) { - ArraySet<Long> pSigningKeys = mKeySetMapping.get(pProperSigning); - if (pSigningKeys.containsAll(addedKeyIds)) { - p.keySetData.addSigningKeySet(id); - } - } + return ks; + } + + /* + * Decrements the reference to KeySet represented by the given id. If this + * drops to zero, then also decrement the reference to each public key it + * contains and remove the KeySet. + */ + private void decrementKeySetLPw(long id) { + KeySetHandle ks = mKeySets.get(id); + if (ks.decrRefCountLPw() <= 0) { + ArraySet<Long> pubKeys = mKeySetMapping.get(id); + final int pkSize = pubKeys.size(); + for (int i = 0; i < pkSize; i++) { + decrementPublicKeyLPw(pubKeys.valueAt(i)); } + mKeySets.delete(id); + mKeySetMapping.delete(id); } - // go home - return ks; + return; + } + + /* + * Decrements the reference to PublicKey represented by the given id. If + * this drops to zero, then remove it. + */ + private void decrementPublicKeyLPw(long id) { + PublicKeyHandle pk = mPublicKeys.get(id); + if (pk.decrRefCountLPw() <= 0) { + mPublicKeys.delete(id); + } + return; } /** * Adds the given PublicKey to the system, deduping as it goes. */ private long addPublicKeyLPw(PublicKey key) { - // check if the public key is new - long existingKeyId = getIdForPublicKeyLPr(key); - if (existingKeyId != PUBLIC_KEY_NOT_FOUND) { - return existingKeyId; - } - // if it's new find the first unoccupied slot in the public keys - long id = getFreePublicKeyIdLPw(); - // add the public key to it - mPublicKeys.put(id, key); - // return the stable identifier + long id = getIdForPublicKeyLPr(key); + if (id != PUBLIC_KEY_NOT_FOUND) { + + /* We already know about this key, increment its ref count and ret */ + mPublicKeys.get(id).incrRefCountLPw(); + return id; + } + + /* if it's new find the first unoccupied slot in the public keys */ + id = getFreePublicKeyIdLPw(); + mPublicKeys.put(id, new PublicKeyHandle(id, key)); return id; } @@ -386,7 +434,7 @@ public class KeySetManagerService { private long getIdForPublicKeyLPr(PublicKey k) { String encodedPublicKey = new String(k.getEncoded()); for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) { - PublicKey value = mPublicKeys.valueAt(publicKeyIndex); + PublicKey value = mPublicKeys.valueAt(publicKeyIndex).getKey(); String encodedExistingKey = new String(value.getEncoded()); if (encodedPublicKey.equals(encodedExistingKey)) { return mPublicKeys.keyAt(publicKeyIndex); @@ -411,89 +459,42 @@ public class KeySetManagerService { return lastIssuedKeyId; } + /* + * This package is being removed from the system, so we need to + * remove its keyset and public key references, then remove its + * keyset data. + */ public void removeAppKeySetDataLPw(String packageName) { - // Get the package's known keys and KeySets - ArraySet<Long> deletableKeySets = getOriginalKeySetsByPackageNameLPr(packageName); - ArraySet<Long> deletableKeys = new ArraySet<Long>(); - ArraySet<Long> knownKeys = null; - for (Long ks : deletableKeySets) { - knownKeys = mKeySetMapping.get(ks); - if (knownKeys != null) { - deletableKeys.addAll(knownKeys); - } - } - - // Now remove the keys and KeySets on which any other package relies - for (String pkgName : mPackages.keySet()) { - if (pkgName.equals(packageName)) { - continue; - } - ArraySet<Long> knownKeySets = getOriginalKeySetsByPackageNameLPr(pkgName); - deletableKeySets.removeAll(knownKeySets); - knownKeys = new ArraySet<Long>(); - for (Long ks : knownKeySets) { - knownKeys = mKeySetMapping.get(ks); - if (knownKeys != null) { - deletableKeys.removeAll(knownKeys); - } - } - } - - // The remaining keys and KeySets are not relied on by 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 the deleted KeySets from each package's signingKeySets - for (String pkgName : mPackages.keySet()) { - PackageSetting p = mPackages.get(pkgName); - for (Long ks : deletableKeySets) { - p.keySetData.removeSigningKeySet(ks); - } + /* remove refs from common keysets and public keys */ + PackageSetting pkg = mPackages.get(packageName); + long signingKeySetId = pkg.keySetData.getProperSigningKeySet(); + decrementKeySetLPw(signingKeySetId); + ArrayMap<String, Long> definedKeySets = pkg.keySetData.getAliases(); + for (int i = 0; i < definedKeySets.size(); i++) { + decrementKeySetLPw(definedKeySets.valueAt(i)); } - // Finally, remove all KeySets from the original package - PackageSetting p = mPackages.get(packageName); - clearPackageKeySetDataLPw(p); - } - private void clearPackageKeySetDataLPw(PackageSetting p) { - p.keySetData.removeAllSigningKeySets(); - p.keySetData.removeAllUpgradeKeySets(); - p.keySetData.removeAllDefinedKeySets(); + /* remove from package */ + clearPackageKeySetDataLPw(pkg); return; } - private ArraySet<Long> getOriginalKeySetsByPackageNameLPr(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"); - } - ArraySet<Long> knownKeySets = new ArraySet<Long>(); - knownKeySets.add(p.keySetData.getProperSigningKeySet()); - if (p.keySetData.isUsingDefinedKeySets()) { - for (long ks : p.keySetData.getDefinedKeySets()) { - knownKeySets.add(ks); - } - } - return knownKeySets; + private void clearPackageKeySetDataLPw(PackageSetting pkg) { + pkg.keySetData.setProperSigningKeySet(PackageKeySetData.KEYSET_UNASSIGNED); + pkg.keySetData.removeAllDefinedKeySets(); + pkg.keySetData.removeAllUpgradeKeySets(); + return; } public String encodePublicKey(PublicKey k) throws IOException { - return new String(Base64.encode(k.getEncoded(), 0)); + return new String(Base64.encode(k.getEncoded(), Base64.NO_WRAP)); } public void dumpLPr(PrintWriter pw, String packageName, PackageManagerService.DumpState dumpState) { boolean printedHeader = false; - for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) { + for (ArrayMap.Entry<String, PackageSetting> e : mPackages.entrySet()) { String keySetPackage = e.getKey(); if (packageName != null && !packageName.equals(keySetPackage)) { continue; @@ -508,7 +509,7 @@ public class KeySetManagerService { pw.print(" ["); pw.print(keySetPackage); pw.println("]"); if (pkg.keySetData != null) { boolean printedLabel = false; - for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) { + for (ArrayMap.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) { if (!printedLabel) { pw.print(" KeySets Aliases: "); printedLabel = true; @@ -524,36 +525,26 @@ public class KeySetManagerService { } printedLabel = false; if (pkg.keySetData.isUsingDefinedKeySets()) { - for (long keySetId : pkg.keySetData.getDefinedKeySets()) { + ArrayMap<String, Long> definedKeySets = pkg.keySetData.getAliases(); + final int dksSize = definedKeySets.size(); + for (int i = 0; i < dksSize; i++) { if (!printedLabel) { pw.print(" Defined KeySets: "); printedLabel = true; } else { pw.print(", "); } - pw.print(Long.toString(keySetId)); - } - } - if (printedLabel) { - pw.println(""); - } - printedLabel = false; - final long[] signingKeySets = pkg.keySetData.getSigningKeySets(); - if (signingKeySets != null) { - for (long keySetId : signingKeySets) { - if (!printedLabel) { - pw.print(" Signing KeySets: "); - printedLabel = true; - } else { - pw.print(", "); - } - pw.print(Long.toString(keySetId)); + pw.print(Long.toString(definedKeySets.valueAt(i))); } } if (printedLabel) { pw.println(""); } printedLabel = false; + final long signingKeySet = pkg.keySetData.getProperSigningKeySet(); + pw.print(" Signing KeySets: "); + pw.print(Long.toString(signingKeySet)); + pw.println(""); if (pkg.keySetData.isUsingUpgradeKeySets()) { for (long keySetId : pkg.keySetData.getUpgradeKeySets()) { if (!printedLabel) { @@ -590,8 +581,8 @@ public class KeySetManagerService { 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); + PublicKeyHandle pkh = mPublicKeys.valueAt(pKeyIndex); + String encodedKey = encodePublicKey(pkh.getKey()); serializer.startTag(null, "public-key"); serializer.attribute(null, "identifier", Long.toString(id)); serializer.attribute(null, "value", encodedKey); @@ -617,17 +608,17 @@ public class KeySetManagerService { serializer.endTag(null, "keysets"); } - void readKeySetsLPw(XmlPullParser parser) + void readKeySetsLPw(XmlPullParser parser, ArrayMap<Long, Integer> keySetRefCounts) throws XmlPullParserException, IOException { int type; long currentKeySetId = 0; int outerDepth = parser.getDepth(); - String recordedVersion = parser.getAttributeValue(null, "version"); - if (recordedVersion == null || Integer.parseInt(recordedVersion) != CURRENT_VERSION) { + String recordedVersionStr = parser.getAttributeValue(null, "version"); + if (recordedVersionStr == null) { + // The keyset information comes from pre-versioned devices, and + // is inaccurate, don't collect any of it. while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - // Our version is different than the one which generated the old keyset data. - // We don't want any of the old data, but we must advance the parser continue; } // The KeySet information read previously from packages.xml is invalid. @@ -637,6 +628,7 @@ public class KeySetManagerService { } return; } + int recordedVersion = Integer.parseInt(recordedVersionStr); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -653,6 +645,8 @@ public class KeySetManagerService { lastIssuedKeySetId = Long.parseLong(parser.getAttributeValue(null, "value")); } } + + addRefCountsFromSavedPackagesLPw(keySetRefCounts); } void readKeysLPw(XmlPullParser parser) @@ -683,29 +677,49 @@ public class KeySetManagerService { } final String tagName = parser.getName(); if (tagName.equals("keyset")) { - currentKeySetId = readIdentifierLPw(parser); - mKeySets.put(currentKeySetId, new KeySetHandle()); + String encodedID = parser.getAttributeValue(null, "identifier"); + currentKeySetId = Long.parseLong(encodedID); + int refCount = 0; + mKeySets.put(currentKeySetId, new KeySetHandle(currentKeySetId, refCount)); mKeySetMapping.put(currentKeySetId, new ArraySet<Long>()); } else if (tagName.equals("key-id")) { - long id = readIdentifierLPw(parser); + String encodedID = parser.getAttributeValue(null, "identifier"); + long id = Long.parseLong(encodedID); 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); + int refCount = 0; String encodedPublicKey = parser.getAttributeValue(null, "value"); PublicKey pub = PackageParser.parsePublicKey(encodedPublicKey); if (pub != null) { - mPublicKeys.put(identifier, pub); + PublicKeyHandle pkh = new PublicKeyHandle(identifier, refCount, pub); + mPublicKeys.put(identifier, pkh); + } + } + + /* + * Set each KeySet ref count. Also increment all public keys in each keyset. + */ + private void addRefCountsFromSavedPackagesLPw(ArrayMap<Long, Integer> keySetRefCounts) { + final int numRefCounts = keySetRefCounts.size(); + for (int i = 0; i < numRefCounts; i++) { + KeySetHandle ks = mKeySets.get(keySetRefCounts.keyAt(i)); + ks.setRefCountLPw(keySetRefCounts.valueAt(i)); + } + + final int numKeySets = mKeySets.size(); + for (int i = 0; i < numKeySets; i++) { + ArraySet<Long> pubKeys = mKeySetMapping.valueAt(i); + final int pkSize = pubKeys.size(); + for (int j = 0; j < pkSize; j++) { + mPublicKeys.get(pubKeys.valueAt(j)).incrRefCountLPw(); + } } } } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java new file mode 100644 index 0000000..a42e4e7 --- /dev/null +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2015 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.annotation.Nullable; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageParser; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import dalvik.system.DexFile; +import dalvik.system.StaleDexCacheError; + +import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; +import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; + +/** + * Helper class for running dexopt command on packages. + */ +final class PackageDexOptimizer { + private static final String TAG = "PackageManager.DexOptimizer"; + static final String OAT_DIR_NAME = "oat"; + // TODO b/19550105 Remove error codes and use exceptions + static final int DEX_OPT_SKIPPED = 0; + static final int DEX_OPT_PERFORMED = 1; + static final int DEX_OPT_DEFERRED = 2; + static final int DEX_OPT_FAILED = -1; + + private final PackageManagerService mPackageManagerService; + private ArraySet<PackageParser.Package> mDeferredDexOpt; + + PackageDexOptimizer(PackageManagerService packageManagerService) { + this.mPackageManagerService = packageManagerService; + } + + /** + * Performs dexopt on all code paths and libraries of the specified package for specified + * instruction sets. + * + * <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on + * {@link PackageManagerService#mInstallLock}. + */ + int performDexOpt(PackageParser.Package pkg, String[] instructionSets, + boolean forceDex, boolean defer, boolean inclDependencies) { + ArraySet<String> done; + if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) { + done = new ArraySet<String>(); + done.add(pkg.packageName); + } else { + done = null; + } + synchronized (mPackageManagerService.mInstallLock) { + return performDexOptLI(pkg, instructionSets, forceDex, defer, done); + } + } + + private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets, + boolean forceDex, boolean defer, ArraySet<String> done) { + final String[] instructionSets = targetInstructionSets != null ? + targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); + + if (done != null) { + done.add(pkg.packageName); + if (pkg.usesLibraries != null) { + performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer, done); + } + if (pkg.usesOptionalLibraries != null) { + performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer, + done); + } + } + + if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) { + return DEX_OPT_SKIPPED; + } + + final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0; + final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + + final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); + boolean performedDexOpt = false; + // There are three basic cases here: + // 1.) we need to dexopt, either because we are forced or it is needed + // 2.) we are deferring a needed dexopt + // 3.) we are skipping an unneeded dexopt + final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); + for (String dexCodeInstructionSet : dexCodeInstructionSets) { + if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) { + continue; + } + + for (String path : paths) { + try { + final int dexoptNeeded; + if (forceDex) { + dexoptNeeded = DexFile.DEX2OAT_NEEDED; + } else { + dexoptNeeded = DexFile.getDexOptNeeded(path, + pkg.packageName, dexCodeInstructionSet, defer); + } + + if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { + // We're deciding to defer a needed dexopt. Don't bother dexopting for other + // paths and instruction sets. We'll deal with them all together when we process + // our list of deferred dexopts. + addPackageForDeferredDexopt(pkg); + return DEX_OPT_DEFERRED; + } + + if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { + final String dexoptType; + String oatDir = null; + if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) { + dexoptType = "dex2oat"; + oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet); + } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) { + dexoptType = "patchoat"; + } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) { + dexoptType = "self patchoat"; + } else { + throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded); + } + Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg=" + + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet + + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable + + " oatDir = " + oatDir); + final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid, + !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet, + dexoptNeeded, vmSafeMode, debuggable, oatDir); + if (ret < 0) { + return DEX_OPT_FAILED; + } + performedDexOpt = true; + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "Apk not found for dexopt: " + path); + return DEX_OPT_FAILED; + } catch (IOException e) { + Slog.w(TAG, "IOException reading apk: " + path, e); + return DEX_OPT_FAILED; + } catch (StaleDexCacheError e) { + Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e); + return DEX_OPT_FAILED; + } catch (Exception e) { + Slog.w(TAG, "Exception when doing dexopt : ", e); + return DEX_OPT_FAILED; + } + } + + // At this point we haven't failed dexopt and we haven't deferred dexopt. We must + // either have either succeeded dexopt, or have had getDexOptNeeded tell us + // it isn't required. We therefore mark that this package doesn't need dexopt unless + // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped + // it. + pkg.mDexOptPerformed.add(dexCodeInstructionSet); + } + + // If we've gotten here, we're sure that no error occurred and that we haven't + // deferred dex-opt. We've either dex-opted one more paths or instruction sets or + // we've skipped all of them because they are up to date. In both cases this + // package doesn't need dexopt any longer. + return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; + } + + /** + * Creates oat dir for the specified package. In certain cases oat directory + * <strong>cannot</strong> be created: + * <ul> + * <li>{@code pkg} is a system app, which is not updated.</li> + * <li>Package location is not a directory, i.e. monolithic install.</li> + * </ul> + * + * @return Absolute path to the oat directory or null, if oat directory + * cannot be created. + */ + @Nullable + private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) + throws IOException { + if ((pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) || pkg.isForwardLocked() + || pkg.applicationInfo.isExternalAsec()) { + return null; + } + File codePath = new File(pkg.codePath); + if (codePath.isDirectory()) { + File oatDir = getOatDir(codePath); + mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(), + dexInstructionSet); + return oatDir.getAbsolutePath(); + } + return null; + } + + static File getOatDir(File codePath) { + return new File(codePath, OAT_DIR_NAME); + } + + private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets, + boolean forceDex, boolean defer, ArraySet<String> done) { + for (String libName : libs) { + PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary( + libName); + if (libPkg != null && !done.contains(libName)) { + performDexOptLI(libPkg, instructionSets, forceDex, defer, done); + } + } + } + + /** + * Clears set of deferred dexopt packages. + * @return content of dexopt set if it was not empty + */ + public ArraySet<PackageParser.Package> clearDeferredDexOptPackages() { + ArraySet<PackageParser.Package> result = mDeferredDexOpt; + mDeferredDexOpt = null; + return result; + } + + public void addPackageForDeferredDexopt(PackageParser.Package pkg) { + if (mDeferredDexOpt == null) { + mDeferredDexOpt = new ArraySet<>(); + } + mDeferredDexOpt.add(pkg); + } +} diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 2150e9a..a406175 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -30,17 +30,24 @@ import static com.android.internal.util.XmlUtils.writeUriAttribute; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; +import android.Manifest; import android.app.ActivityManager; +import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PackageDeleteObserver; import android.app.PackageInstallObserver; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageInstaller; import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; +import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; @@ -53,7 +60,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; -import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -64,6 +70,8 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; @@ -76,15 +84,17 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.Xml; +import libcore.io.IoUtils; + +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageHelper; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.ImageUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.IoThread; import com.google.android.collect.Sets; -import libcore.io.IoUtils; - import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -132,6 +142,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private static final String ATTR_ORIGINATING_URI = "originatingUri"; private static final String ATTR_REFERRER_URI = "referrerUri"; private static final String ATTR_ABI_OVERRIDE = "abiOverride"; + private static final String ATTR_VOLUME_UUID = "volumeUuid"; /** Automatically destroy sessions older than this */ private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS; @@ -142,9 +153,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final PackageManagerService mPm; - private final AppOpsManager mAppOps; - private final File mStagingDir; + private AppOpsManager mAppOps; + private StorageManager mStorage; + private final HandlerThread mInstallThread; private final Handler mInstallHandler; @@ -187,12 +199,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } }; - public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) { + public PackageInstallerService(Context context, PackageManagerService pm) { mContext = context; mPm = pm; - mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - - mStagingDir = stagingDir; mInstallThread = new HandlerThread(TAG); mInstallThread.start(); @@ -209,8 +218,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub { synchronized (mSessions) { readSessionsLocked(); + final File internalStagingDir = buildInternalStagingDir(); final ArraySet<File> unclaimedStages = Sets.newArraySet( - mStagingDir.listFiles(sStageFilter)); + internalStagingDir.listFiles(sStageFilter)); final ArraySet<File> unclaimedIcons = Sets.newArraySet( mSessionsDir.listFiles()); @@ -225,9 +235,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub { for (File stage : unclaimedStages) { Slog.w(TAG, "Deleting orphan stage " + stage); if (stage.isDirectory()) { - FileUtils.deleteContents(stage); + mPm.mInstaller.rmPackageDir(stage.getAbsolutePath()); + } else { + stage.delete(); } - stage.delete(); } // Clean up orphaned icons @@ -238,6 +249,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + public void systemReady() { + mAppOps = mContext.getSystemService(AppOpsManager.class); + mStorage = mContext.getSystemService(StorageManager.class); + } + public void onSecureContainersAvailable() { synchronized (mSessions) { final ArraySet<String> unclaimed = new ArraySet<>(); @@ -275,13 +291,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Deprecated - public File allocateInternalStageDirLegacy() throws IOException { + public File allocateStageDirLegacy(String volumeUuid) throws IOException { synchronized (mSessions) { try { final int sessionId = allocateSessionIdLocked(); mLegacySessions.put(sessionId, true); - final File stageDir = buildInternalStageDir(sessionId); - prepareInternalStageDir(stageDir); + final File stageDir = buildStageDir(volumeUuid, sessionId); + prepareStageDir(stageDir); return stageDir; } catch (IllegalStateException e) { throw new IOException(e); @@ -322,11 +338,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub { Slog.w(TAG, "Abandoning old session first created at " + session.createdMillis); valid = false; - } else if (session.stageDir != null - && !session.stageDir.exists()) { - Slog.w(TAG, "Abandoning internal session with missing stage " - + session.stageDir); - valid = false; } else { valid = true; } @@ -378,6 +389,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI); params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI); params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE); + params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID); final File appIconFile = buildAppIconFile(sessionId); if (appIconFile.exists()) { @@ -448,6 +460,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri); writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri); writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride); + writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid); // Persist app icon if changed since last written final File appIconFile = buildAppIconFile(session.sessionId); @@ -516,6 +529,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub { params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; } + // Only system components can circumvent runtime permissions when installing. + if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 + && mContext.checkCallingOrSelfPermission(Manifest.permission + .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) { + throw new SecurityException("You need the " + + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission " + + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"); + } + // Defensively resize giant app icons if (params.appIcon != null) { final ActivityManager am = (ActivityManager) mContext.getSystemService( @@ -528,28 +550,40 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } - if (params.mode == SessionParams.MODE_FULL_INSTALL - || params.mode == SessionParams.MODE_INHERIT_EXISTING) { + switch (params.mode) { + case SessionParams.MODE_FULL_INSTALL: + case SessionParams.MODE_INHERIT_EXISTING: + break; + default: + throw new IllegalArgumentException("Invalid install mode: " + params.mode); + } + + // If caller requested explicit location, sanity check it, otherwise + // resolve the best internal or adopted location. + if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { + if (!PackageHelper.fitsOnInternal(mContext, params.sizeBytes)) { + throw new IOException("No suitable internal storage available"); + } + + } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { + if (!PackageHelper.fitsOnExternal(mContext, params.sizeBytes)) { + throw new IOException("No suitable external storage available"); + } + + } else { + // For now, installs to adopted media are treated as internal from + // an install flag point-of-view. + params.setInstallFlagsInternal(); + // Resolve best location for install, based on combination of // requested install flags, delta size, and manifest settings. final long ident = Binder.clearCallingIdentity(); try { - final int resolved = PackageHelper.resolveInstallLocation(mContext, - params.appPackageName, params.installLocation, params.sizeBytes, - params.installFlags); - - if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) { - params.setInstallFlagsInternal(); - } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { - params.setInstallFlagsExternal(); - } else { - throw new IOException("No storage with enough free space; res=" + resolved); - } + params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, + params.appPackageName, params.installLocation, params.sizeBytes); } finally { Binder.restoreCallingIdentity(ident); } - } else { - throw new IllegalArgumentException("Invalid install mode: " + params.mode); } final int sessionId; @@ -574,7 +608,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub { File stageDir = null; String stageCid = null; if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { - stageDir = buildInternalStageDir(sessionId); + stageDir = buildStageDir(params.volumeUuid, sessionId); } else { stageCid = buildExternalStageCid(sessionId); } @@ -673,11 +707,30 @@ public class PackageInstallerService extends IPackageInstaller.Stub { throw new IllegalStateException("Failed to allocate session ID"); } - private File buildInternalStageDir(int sessionId) { - return new File(mStagingDir, "vmdl" + sessionId + ".tmp"); + private File buildInternalStagingDir() { + return new File(Environment.getDataDirectory(), "app"); } - static void prepareInternalStageDir(File stageDir) throws IOException { + private File buildStagingDir(String volumeUuid) throws FileNotFoundException { + if (volumeUuid == null) { + return buildInternalStagingDir(); + } else { + final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); + if (vol != null && vol.type == VolumeInfo.TYPE_PRIVATE + && vol.isMountedWritable()) { + return new File(vol.path, "app"); + } else { + throw new FileNotFoundException("Failed to find volume for UUID " + volumeUuid); + } + } + } + + private File buildStageDir(String volumeUuid, int sessionId) throws FileNotFoundException { + final File stagingDir = buildStagingDir(volumeUuid); + return new File(stagingDir, "vmdl" + sessionId + ".tmp"); + } + + static void prepareStageDir(File stageDir) throws IOException { if (stageDir.exists()) { throw new IOException("Session dir already exists: " + stageDir); } @@ -749,16 +802,34 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } @Override - public void uninstall(String packageName, int flags, IntentSender statusReceiver, int userId) { - mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, true, "uninstall"); + public void uninstall(String packageName, String callerPackageName, int flags, + IntentSender statusReceiver, int userId) { + final int callingUid = Binder.getCallingUid(); + mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall"); + if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) { + mAppOps.checkPackage(callingUid, callerPackageName); + } + + // Check whether the caller is device owner + DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + boolean isDeviceOwner = (dpm != null) && dpm.isDeviceOwnerApp(callerPackageName); final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, - statusReceiver, packageName); + statusReceiver, packageName, isDeviceOwner, userId); if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES) == PackageManager.PERMISSION_GRANTED) { // Sweet, call straight through! mPm.deletePackage(packageName, adapter.getBinder(), userId, flags); - + } else if (isDeviceOwner) { + // Allow the DeviceOwner to silently delete packages + // Need to clear the calling identity to get DELETE_PACKAGES permission + long ident = Binder.clearCallingIdentity(); + try { + mPm.deletePackage(packageName, adapter.getBinder(), userId, flags); + } finally { + Binder.restoreCallingIdentity(ident); + } } else { // Take a short detour to confirm with user final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE); @@ -814,12 +885,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final IntentSender mTarget; private final String mPackageName; + private final Notification mNotification; public PackageDeleteObserverAdapter(Context context, IntentSender target, - String packageName) { + String packageName, boolean showNotification, int userId) { mContext = context; mTarget = target; mPackageName = packageName; + if (showNotification) { + mNotification = buildSuccessNotification(mContext, + mContext.getResources().getString(R.string.package_deleted_device_owner), + packageName, + userId); + } else { + mNotification = null; + } } @Override @@ -837,6 +917,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @Override public void onPackageDeleted(String basePackageName, int returnCode, String msg) { + if (PackageManager.DELETE_SUCCEEDED == returnCode && mNotification != null) { + NotificationManager notificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(basePackageName, 0, mNotification); + } final Intent fillIn = new Intent(); fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName); fillIn.putExtra(PackageInstaller.EXTRA_STATUS, @@ -855,11 +940,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub { private final Context mContext; private final IntentSender mTarget; private final int mSessionId; + private final boolean mShowNotification; + private final int mUserId; - public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId) { + public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId, + boolean showNotification, int userId) { mContext = context; mTarget = target; mSessionId = sessionId; + mShowNotification = showNotification; + mUserId = userId; } @Override @@ -878,6 +968,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub { @Override public void onPackageInstalled(String basePackageName, int returnCode, String msg, Bundle extras) { + if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) { + Notification notification = buildSuccessNotification(mContext, + mContext.getResources().getString(R.string.package_installed_device_owner), + basePackageName, + mUserId); + if (notification != null) { + NotificationManager notificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.notify(basePackageName, 0, notification); + } + } final Intent fillIn = new Intent(); fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); fillIn.putExtra(PackageInstaller.EXTRA_STATUS, @@ -899,6 +1000,40 @@ public class PackageInstallerService extends IPackageInstaller.Stub { } } + /** + * Build a notification for package installation / deletion by device owners that is shown if + * the operation succeeds. + */ + private static Notification buildSuccessNotification(Context context, String contentText, + String basePackageName, int userId) { + PackageInfo packageInfo = null; + try { + packageInfo = AppGlobals.getPackageManager().getPackageInfo( + basePackageName, 0, userId); + } catch (RemoteException ignored) { + } + if (packageInfo == null || packageInfo.applicationInfo == null) { + Slog.w(TAG, "Notification not built for package: " + basePackageName); + return null; + } + PackageManager pm = context.getPackageManager(); + Bitmap packageIcon = ImageUtils.buildScaledBitmap( + packageInfo.applicationInfo.loadIcon(pm), + context.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width), + context.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_height)); + CharSequence packageLabel = packageInfo.applicationInfo.loadLabel(pm); + return new Notification.Builder(context) + .setSmallIcon(R.drawable.ic_check_circle_24px) + .setColor(context.getResources().getColor( + R.color.system_notification_accent_color)) + .setContentTitle(packageLabel) + .setContentText(contentText) + .setLargeIcon(packageIcon) + .build(); + } + private static class Callbacks extends Handler { private static final int MSG_SESSION_CREATED = 1; private static final int MSG_SESSION_BADGING_CHANGED = 2; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index cc1b3ad..89ca00e 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -25,8 +25,9 @@ import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_WRONLY; import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid; -import static com.android.server.pm.PackageInstallerService.prepareInternalStageDir; +import static com.android.server.pm.PackageInstallerService.prepareStageDir; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.content.IntentSender; @@ -61,6 +62,9 @@ import android.util.ExceptionUtils; import android.util.MathUtils; import android.util.Slog; +import libcore.io.IoUtils; +import libcore.io.Libcore; + import com.android.internal.annotations.GuardedBy; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; @@ -69,9 +73,6 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter; -import libcore.io.IoUtils; -import libcore.io.Libcore; - import java.io.File; import java.io.FileDescriptor; import java.io.IOException; @@ -92,6 +93,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final Context mContext; private final PackageManagerService mPm; private final Handler mHandler; + private final boolean mIsInstallerDeviceOwner; final int sessionId; final int userId; @@ -208,8 +210,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPrepared = prepared; mSealed = sealed; + // Device owners are allowed to silently install packages, so the permission check is + // waived if the installer is the device owner. + DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerApp(installerPackageName); if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid) - == PackageManager.PERMISSION_GRANTED) || (installerUid == Process.ROOT_UID)) { + == PackageManager.PERMISSION_GRANTED) + || (installerUid == Process.ROOT_UID) + || mIsInstallerDeviceOwner) { mPermissionsAccepted = true; } else { mPermissionsAccepted = false; @@ -362,7 +371,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final long deltaBytes = lengthBytes - stat.st_size; // Only need to free up space when writing to internal stage if (stageDir != null && deltaBytes > 0) { - mPm.freeStorage(deltaBytes); + mPm.freeStorage(params.volumeUuid, deltaBytes); } Libcore.os.posix_fallocate(targetFd, 0, lengthBytes); } @@ -440,7 +449,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mActiveCount.incrementAndGet(); final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext, - statusReceiver, sessionId); + statusReceiver, sessionId, mIsInstallerDeviceOwner, userId); mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); } @@ -892,7 +901,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { if (!mPrepared) { if (stageDir != null) { - prepareInternalStageDir(stageDir); + prepareStageDir(stageDir); } else if (stageCid != null) { prepareExternalStageCid(stageCid, params.sizeBytes); diff --git a/services/core/java/com/android/server/pm/PackageKeySetData.java b/services/core/java/com/android/server/pm/PackageKeySetData.java index 8f12c03..a9126c0 100644 --- a/services/core/java/com/android/server/pm/PackageKeySetData.java +++ b/services/core/java/com/android/server/pm/PackageKeySetData.java @@ -27,12 +27,8 @@ public class PackageKeySetData { /* KeySet containing all signing keys - superset of the others */ private long mProperSigningKeySet; - private long[] mSigningKeySets; - private long[] mUpgradeKeySets; - private long[] mDefinedKeySets; - private final ArrayMap<String, Long> mKeySetAliases = new ArrayMap<String, Long>(); PackageKeySetData() { @@ -41,23 +37,12 @@ public class PackageKeySetData { PackageKeySetData(PackageKeySetData original) { mProperSigningKeySet = original.mProperSigningKeySet; - mSigningKeySets = ArrayUtils.cloneOrNull(original.mSigningKeySets); mUpgradeKeySets = ArrayUtils.cloneOrNull(original.mUpgradeKeySets); - mDefinedKeySets = ArrayUtils.cloneOrNull(original.mDefinedKeySets); mKeySetAliases.putAll(original.mKeySetAliases); } protected void setProperSigningKeySet(long ks) { - if (ks == mProperSigningKeySet) { - - /* nothing to change */ - return; - } - - /* otherwise, our current signing keysets are likely invalid */ - removeAllSigningKeySets(); mProperSigningKeySet = ks; - addSigningKeySet(ks); return; } @@ -65,15 +50,10 @@ public class PackageKeySetData { return mProperSigningKeySet; } - protected void addSigningKeySet(long ks) { - mSigningKeySets = ArrayUtils.appendLong(mSigningKeySets, ks); - } - - protected void removeSigningKeySet(long ks) { - mSigningKeySets = ArrayUtils.removeLong(mSigningKeySets, ks); - } - protected void addUpgradeKeySet(String alias) { + if (alias == null) { + return; + } /* must have previously been defined */ Long ks = mKeySetAliases.get(alias); @@ -89,19 +69,9 @@ public class PackageKeySetData { * Used only when restoring keyset data from persistent storage. Must * correspond to a defined-keyset. */ - protected void addUpgradeKeySetById(long ks) { - mSigningKeySets = ArrayUtils.appendLong(mSigningKeySets, ks); - } - - protected void addDefinedKeySet(long ks, String alias) { - mDefinedKeySets = ArrayUtils.appendLong(mDefinedKeySets, ks); - mKeySetAliases.put(alias, ks); - } - protected void removeAllSigningKeySets() { - mProperSigningKeySet = KEYSET_UNASSIGNED; - mSigningKeySets = null; - return; + protected void addUpgradeKeySetById(long ks) { + mUpgradeKeySets = ArrayUtils.appendLong(mUpgradeKeySets, ks); } protected void removeAllUpgradeKeySets() { @@ -109,36 +79,44 @@ public class PackageKeySetData { return; } - protected void removeAllDefinedKeySets() { - mDefinedKeySets = null; - mKeySetAliases.clear(); - return; + protected long[] getUpgradeKeySets() { + return mUpgradeKeySets; } - protected boolean packageIsSignedBy(long ks) { - return ArrayUtils.contains(mSigningKeySets, ks); + protected ArrayMap<String, Long> getAliases() { + return mKeySetAliases; } - protected long[] getSigningKeySets() { - return mSigningKeySets; - } + /* + * Replace defined keysets with new ones. + */ + protected void setAliases(ArrayMap<String, Long> newAliases) { - protected long[] getUpgradeKeySets() { - return mUpgradeKeySets; + /* remove old aliases */ + removeAllDefinedKeySets(); + + /* add new ones */ + final int newAliasSize = newAliases.size(); + for (int i = 0; i < newAliasSize; i++) { + mKeySetAliases.put(newAliases.keyAt(i), newAliases.valueAt(i));; + } } - protected long[] getDefinedKeySets() { - return mDefinedKeySets; + protected void addDefinedKeySet(long ks, String alias) { + mKeySetAliases.put(alias, ks); } - protected ArrayMap<String, Long> getAliases() { - return mKeySetAliases; + protected void removeAllDefinedKeySets() { + final int aliasSize = mKeySetAliases.size(); + for (int i = 0; i < aliasSize; i++) { + mKeySetAliases.removeAt(i); + } } protected boolean isUsingDefinedKeySets() { - /* should never be the case that mDefinedKeySets.length == 0 */ - return (mDefinedKeySets != null && mDefinedKeySets.length > 0); + /* should never be the case that mUpgradeKeySets.length == 0 */ + return (mKeySetAliases.size() > 0); } protected boolean isUsingUpgradeKeySets() { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6498dcc..bd22524 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -43,7 +43,16 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIB import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED; import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK; +import static android.content.pm.PackageManager.INSTALL_INTERNAL; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; +import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST; +import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR; +import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING; +import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; import static android.content.pm.PackageParser.isApkFile; import static android.os.Process.PACKAGE_INFO_GID; import static android.os.Process.SYSTEM_UID; @@ -54,31 +63,13 @@ import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME; import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; import static com.android.internal.util.ArrayUtils.appendInt; -import static com.android.internal.util.ArrayUtils.removeInt; - -import android.util.ArrayMap; - -import com.android.internal.R; -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.os.IParcelFileDescriptorFactory; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FastPrintWriter; -import com.android.internal.util.FastXmlSerializer; -import com.android.internal.util.IndentingPrintWriter; -import com.android.server.EventLogTags; -import com.android.server.IntentResolver; -import com.android.server.LocalServices; -import com.android.server.ServiceThread; -import com.android.server.SystemConfig; -import com.android.server.Watchdog; -import com.android.server.pm.Settings.DatabaseVersion; -import com.android.server.storage.DeviceStorageMonitorInternal; - -import org.xmlpull.v1.XmlSerializer; +import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; +import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet; +import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; +import static com.android.server.pm.InstructionSets.getPreferredInstructionSet; +import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet; +import android.Manifest; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AppGlobals; @@ -108,6 +99,7 @@ import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; +import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; import android.content.pm.ManifestDigest; import android.content.pm.PackageCleanItem; @@ -116,10 +108,10 @@ import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.LegacyPackageDeleteObserver; +import android.content.pm.PackageParser; import android.content.pm.PackageParser.ActivityIntentInfo; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; -import android.content.pm.PackageParser; import android.content.pm.PackageStats; import android.content.pm.PackageUserState; import android.content.pm.ParceledListSlice; @@ -139,11 +131,9 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.Debug; import android.os.Environment; import android.os.Environment.UserEnvironment; -import android.os.storage.IMountService; -import android.os.storage.StorageManager; -import android.os.Debug; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; @@ -152,6 +142,7 @@ import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SELinux; import android.os.ServiceManager; @@ -159,6 +150,10 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.IMountService; +import android.os.storage.StorageEventListener; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; import android.security.KeyStore; import android.security.SystemKeyStore; import android.system.ErrnoException; @@ -166,6 +161,7 @@ import android.system.Os; import android.system.StructStat; import android.text.TextUtils; import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.DisplayMetrics; @@ -177,14 +173,48 @@ import android.util.PrintStreamPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import android.util.Xml; import android.view.Display; +import dalvik.system.DexFile; +import dalvik.system.VMRuntime; + +import libcore.io.IoUtils; +import libcore.util.EmptyArray; + +import com.android.internal.R; +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.os.IParcelFileDescriptorFactory; +import com.android.internal.os.SomeArgs; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; +import com.android.server.EventLogTags; +import com.android.server.FgThread; +import com.android.server.IntentResolver; +import com.android.server.LocalServices; +import com.android.server.ServiceThread; +import com.android.server.SystemConfig; +import com.android.server.Watchdog; +import com.android.server.pm.Settings.DatabaseVersion; +import com.android.server.storage.DeviceStorageMonitorInternal; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlSerializer; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; @@ -210,15 +240,9 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import dalvik.system.DexFile; -import dalvik.system.StaleDexCacheError; -import dalvik.system.VMRuntime; - -import libcore.io.IoUtils; -import libcore.util.EmptyArray; - /** * Keep track of all those .apks everywhere. * @@ -236,6 +260,7 @@ public class PackageManagerService extends IPackageManager.Stub { static final boolean DEBUG_SETTINGS = false; static final boolean DEBUG_PREFERRED = false; static final boolean DEBUG_UPGRADE = false; + private static final boolean DEBUG_BACKUP = true; private static final boolean DEBUG_INSTALL = false; private static final boolean DEBUG_REMOVE = false; private static final boolean DEBUG_BROADCASTS = false; @@ -247,6 +272,8 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_DEXOPT = false; private static final boolean DEBUG_ABI_SELECTION = false; + static final boolean RUNTIME_PERMISSIONS_ENABLED = true; + private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; @@ -320,16 +347,29 @@ public class PackageManagerService extends IPackageManager.Stub { DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService"); + private static final String KILL_APP_REASON_GIDS_CHANGED = + "permission grant or revoke changed gids"; + + private static final String KILL_APP_REASON_PERMISSIONS_REVOKED = + "permissions revoked"; + private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; - private static String sPreferredInstructionSet; + /** Permission grant: not grant the permission. */ + private static final int GRANT_DENIED = 1; - final ServiceThread mHandlerThread; + /** Permission grant: grant the permission as an install permission. */ + private static final int GRANT_INSTALL = 2; + + /** Permission grant: grant the permission as a runtime one. */ + private static final int GRANT_RUNTIME = 3; - private static final String IDMAP_PREFIX = "/data/resource-cache/"; - private static final String IDMAP_SUFFIX = "@idmap"; + /** Permission grant: grant as runtime a permission that was granted as an install time one. */ + private static final int GRANT_UPGRADE = 4; + + final ServiceThread mHandlerThread; final PackageHandler mHandler; @@ -467,7 +507,10 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageInstallerService mInstallerService; - ArraySet<PackageParser.Package> mDeferredDexOpt = null; + private final PackageDexOptimizer mPackageDexOptimizer; + + private AtomicInteger mNextMoveId = new AtomicInteger(); + private final MoveCallbacks mMoveCallbacks; // Cache of users who need badging. SparseBooleanArray mUserNeedsBadging = new SparseBooleanArray(); @@ -488,6 +531,218 @@ public class PackageManagerService extends IPackageManager.Stub { boolean mResolverReplaced = false; + private final ComponentName mIntentFilterVerifierComponent; + private int mIntentFilterVerificationToken = 0; + + final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates + = new SparseArray<IntentFilterVerificationState>(); + + private interface IntentFilterVerifier<T extends IntentFilter> { + boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId, + T filter, String packageName); + void startVerifications(int userId); + void receiveVerificationResponse(int verificationId); + } + + private class IntentVerifierProxy implements IntentFilterVerifier<ActivityIntentInfo> { + private Context mContext; + private ComponentName mIntentFilterVerifierComponent; + private ArrayList<Integer> mCurrentIntentFilterVerifications = new ArrayList<Integer>(); + + public IntentVerifierProxy(Context context, ComponentName verifierComponent) { + mContext = context; + mIntentFilterVerifierComponent = verifierComponent; + } + + private String getDefaultScheme() { + // TODO: replace SCHEME_HTTP with SCHEME_HTTPS + return IntentFilter.SCHEME_HTTP; + } + + @Override + public void startVerifications(int userId) { + // Launch verifications requests + int count = mCurrentIntentFilterVerifications.size(); + for (int n=0; n<count; n++) { + int verificationId = mCurrentIntentFilterVerifications.get(n); + final IntentFilterVerificationState ivs = + mIntentFilterVerificationStates.get(verificationId); + + String packageName = ivs.getPackageName(); + + ArrayList<PackageParser.ActivityIntentInfo> filters = ivs.getFilters(); + final int filterCount = filters.size(); + ArraySet<String> domainsSet = new ArraySet<>(); + for (int m=0; m<filterCount; m++) { + PackageParser.ActivityIntentInfo filter = filters.get(m); + domainsSet.addAll(filter.getHostsList()); + } + ArrayList<String> domainsList = new ArrayList<>(domainsSet); + synchronized (mPackages) { + if (mSettings.createIntentFilterVerificationIfNeededLPw( + packageName, domainsList) != null) { + scheduleWriteSettingsLocked(); + } + } + sendVerificationRequest(userId, verificationId, ivs); + } + mCurrentIntentFilterVerifications.clear(); + } + + private void sendVerificationRequest(int userId, int verificationId, + IntentFilterVerificationState ivs) { + + Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION); + verificationIntent.putExtra( + PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, + verificationId); + verificationIntent.putExtra( + PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME, + getDefaultScheme()); + verificationIntent.putExtra( + PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS, + ivs.getHostsString()); + verificationIntent.putExtra( + PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME, + ivs.getPackageName()); + verificationIntent.setComponent(mIntentFilterVerifierComponent); + verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + + UserHandle user = new UserHandle(userId); + mContext.sendBroadcastAsUser(verificationIntent, user); + Slog.d(TAG, "Sending IntenFilter verification broadcast"); + } + + public void receiveVerificationResponse(int verificationId) { + IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId); + + final boolean verified = ivs.isVerified(); + + ArrayList<PackageParser.ActivityIntentInfo> filters = ivs.getFilters(); + final int count = filters.size(); + for (int n=0; n<count; n++) { + PackageParser.ActivityIntentInfo filter = filters.get(n); + filter.setVerified(verified); + + Slog.d(TAG, "IntentFilter " + filter.toString() + " verified with result:" + + verified + " and hosts:" + ivs.getHostsString()); + } + + mIntentFilterVerificationStates.remove(verificationId); + + final String packageName = ivs.getPackageName(); + IntentFilterVerificationInfo ivi = null; + + synchronized (mPackages) { + ivi = mSettings.getIntentFilterVerificationLPr(packageName); + } + if (ivi == null) { + Slog.w(TAG, "IntentFilterVerificationInfo not found for verificationId:" + + verificationId + " packageName:" + packageName); + return; + } + Slog.d(TAG, "Updating IntentFilterVerificationInfo for verificationId:" + + verificationId); + + synchronized (mPackages) { + if (verified) { + ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS); + } else { + ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK); + } + scheduleWriteSettingsLocked(); + + final int userId = ivs.getUserId(); + if (userId != UserHandle.USER_ALL) { + final int userStatus = + mSettings.getIntentFilterVerificationStatusLPr(packageName, userId); + + int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + boolean needUpdate = false; + + // We cannot override the STATUS_ALWAYS / STATUS_NEVER states if they have + // already been set by the User thru the Disambiguation dialog + switch (userStatus) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: + if (verified) { + updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; + } else { + updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK; + } + needUpdate = true; + break; + + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: + if (verified) { + updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; + needUpdate = true; + } + break; + + default: + // Nothing to do + } + + if (needUpdate) { + mSettings.updateIntentFilterVerificationStatusLPw( + packageName, updatedStatus, userId); + scheduleWritePackageRestrictionsLocked(userId); + } + } + } + } + + @Override + public boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId, + ActivityIntentInfo filter, String packageName) { + if (!(filter.hasDataScheme(IntentFilter.SCHEME_HTTP) || + filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) { + Slog.d(TAG, "IntentFilter does not contain HTTP nor HTTPS data scheme"); + return false; + } + IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId); + if (ivs == null) { + ivs = createDomainVerificationState(verifierId, userId, verificationId, + packageName); + } + if (!hasValidDomains(filter)) { + return false; + } + ivs.addFilter(filter); + return true; + } + + private IntentFilterVerificationState createDomainVerificationState(int verifierId, + int userId, int verificationId, String packageName) { + IntentFilterVerificationState ivs = new IntentFilterVerificationState( + verifierId, userId, packageName); + ivs.setPendingState(); + synchronized (mPackages) { + mIntentFilterVerificationStates.append(verificationId, ivs); + mCurrentIntentFilterVerifications.add(verificationId); + } + return ivs; + } + } + + private static boolean hasValidDomains(ActivityIntentInfo filter) { + return hasValidDomains(filter, true); + } + + private static boolean hasValidDomains(ActivityIntentInfo filter, boolean logging) { + boolean hasHTTPorHTTPS = filter.hasDataScheme(IntentFilter.SCHEME_HTTP) || + filter.hasDataScheme(IntentFilter.SCHEME_HTTPS); + if (!hasHTTPorHTTPS) { + if (logging) { + Slog.d(TAG, "IntentFilter does not contain any HTTP or HTTPS data scheme"); + } + return false; + } + return true; + } + + private IntentFilterVerifier mIntentFilterVerifier; + // Set of pending broadcasts for aggregating enable/disable of components. static class PendingPackageBroadcasts { // for each user id, a map of <package name -> components within that package> @@ -574,6 +829,8 @@ public class PackageManagerService extends IPackageManager.Stub { static final int WRITE_PACKAGE_RESTRICTIONS = 14; static final int PACKAGE_VERIFIED = 15; static final int CHECK_PENDING_VERIFICATION = 16; + static final int START_INTENT_FILTER_VERIFICATIONS = 17; + static final int INTENT_FILTER_VERIFIED = 18; static final int WRITE_SETTINGS_DELAY = 10*1000; // 10 seconds @@ -614,6 +871,9 @@ public class PackageManagerService extends IPackageManager.Stub { final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>(); int mNextInstallToken = 1; // nonzero; will be wrapped back to 1 when ++ overflows + // backup/restore of preferred activity state + private static final String TAG_PREFERRED_BACKUP = "pa"; + private final String mRequiredVerifierPackage; private final PackageUsage mPackageUsage = new PackageUsage(); @@ -999,6 +1259,15 @@ public class PackageManagerService extends IPackageManager.Stub { res.removedInfo.sendBroadcast(false, true, false); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); + + // Now that we successfully installed the package, grant runtime + // permissions if requested before broadcasting the install. + if ((args.installFlags + & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0) { + grantRequestedRuntimePermissions(res.pkg, + args.user.getIdentifier()); + } + // Determine the set of users who are adding this // package for the first time vs. those who are seeing // an update. @@ -1051,7 +1320,7 @@ public class PackageManagerService extends IPackageManager.Stub { res.pkg.applicationInfo.packageName, null, updateUsers); // treat asec-hosted packages like removable media on upgrade - if (isForwardLocked(res.pkg) || isExternal(res.pkg)) { + if (res.pkg.isForwardLocked() || isExternal(res.pkg)) { if (DEBUG_INSTALL) { Slog.i(TAG, "upgrading pkg " + res.pkg + " is ASEC-hosted -> AVAILABLE"); @@ -1215,6 +1484,103 @@ public class PackageManagerService extends IPackageManager.Stub { break; } + case START_INTENT_FILTER_VERIFICATIONS: { + int userId = msg.arg1; + int verifierUid = msg.arg2; + PackageParser.Package pkg = (PackageParser.Package)msg.obj; + + verifyIntentFiltersIfNeeded(userId, verifierUid, pkg); + break; + } + case INTENT_FILTER_VERIFIED: { + final int verificationId = msg.arg1; + + final IntentFilterVerificationState state = mIntentFilterVerificationStates.get( + verificationId); + if (state == null) { + Slog.w(TAG, "Invalid IntentFilter verification token " + + verificationId + " received"); + break; + } + + final int userId = state.getUserId(); + + Slog.d(TAG, "Processing IntentFilter verification with token:" + + verificationId + " and userId:" + userId); + + final IntentFilterVerificationResponse response = + (IntentFilterVerificationResponse) msg.obj; + + state.setVerifierResponse(response.callerUid, response.code); + + Slog.d(TAG, "IntentFilter verification with token:" + verificationId + + " and userId:" + userId + + " is settings verifier response with response code:" + + response.code); + + if (response.code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) { + Slog.d(TAG, "Domains failing verification: " + + response.getFailedDomainsString()); + } + + if (state.isVerificationComplete()) { + mIntentFilterVerifier.receiveVerificationResponse(verificationId); + } else { + Slog.d(TAG, "IntentFilter verification with token:" + verificationId + + " was not said to be complete"); + } + + break; + } + } + } + } + + private StorageEventListener mStorageListener = new StorageEventListener() { + @Override + public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { + if (vol.type == VolumeInfo.TYPE_PRIVATE) { + if (vol.state == VolumeInfo.STATE_MOUNTED) { + // TODO: ensure that private directories exist for all active users + // TODO: remove user data whose serial number doesn't match + loadPrivatePackages(vol); + } else if (vol.state == VolumeInfo.STATE_EJECTING) { + unloadPrivatePackages(vol); + } + } + + if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isPrimary()) { + if (vol.state == VolumeInfo.STATE_MOUNTED) { + updateExternalMediaStatus(true, false); + } else if (vol.state == VolumeInfo.STATE_EJECTING) { + updateExternalMediaStatus(false, false); + } + } + } + }; + + private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int userId) { + if (userId >= UserHandle.USER_OWNER) { + grantRequestedRuntimePermissionsForUser(pkg, userId); + } else if (userId == UserHandle.USER_ALL) { + for (int someUserId : UserManagerService.getInstance().getUserIds()) { + grantRequestedRuntimePermissionsForUser(pkg, someUserId); + } + } + } + + private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId) { + SettingBase sb = (SettingBase) pkg.mExtras; + if (sb == null) { + return; + } + + PermissionsState permissionsState = sb.getPermissionsState(); + + for (String permission : pkg.requestedPermissions) { + BasePermission bp = mSettings.mPermissions.get(permission); + if (bp != null && bp.isRuntime()) { + permissionsState.grantRuntimePermission(bp, userId); } } } @@ -1248,7 +1614,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - public static final PackageManagerService main(Context context, Installer installer, + public static PackageManagerService main(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); @@ -1298,19 +1664,19 @@ public class PackageManagerService extends IPackageManager.Stub { mOnlyCore = onlyCore; mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); mMetrics = new DisplayMetrics(); - mSettings = new Settings(context); + mSettings = new Settings(mPackages); mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, - ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, - ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.log", LOG_UID, - ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, - ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, - ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, - ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); // TODO: add a property to control this? long dexOptLRUThresholdInMinutes; @@ -1339,6 +1705,8 @@ public class PackageManagerService extends IPackageManager.Stub { } mInstaller = installer; + mPackageDexOptimizer = new PackageDexOptimizer(this); + mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); getDefaultDisplayMetrics(context, mMetrics); @@ -1378,7 +1746,7 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.mPermissions.put(perm.name, bp); } if (perm.gids != null) { - bp.gids = appendInts(bp.gids, perm.gids); + bp.setGids(perm.gids, perm.perUser); } } @@ -1439,9 +1807,10 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!"); } - final List<String> allInstructionSets = getAllInstructionSets(); + final List<String> allInstructionSets = InstructionSets.getAllInstructionSets(); final String[] dexCodeInstructionSets = - getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()])); + getDexCodeInstructionSets( + allInstructionSets.toArray(new String[allInstructionSets.size()])); /** * Ensure all external libraries have had dexopt run on them. @@ -1459,18 +1828,10 @@ public class PackageManagerService extends IPackageManager.Stub { } try { - byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null, - dexCodeInstructionSet, - false); - if (dexoptRequired != DexFile.UP_TO_DATE) { + int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false); + if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { alreadyDexOpted.add(lib); - - // The list of "shared libraries" we have at this point is - if (dexoptRequired == DexFile.DEXOPT_NEEDED) { - mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet); - } else { - mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet); - } + mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded); } } catch (FileNotFoundException e) { Slog.w(TAG, "Library not found: " + lib); @@ -1516,13 +1877,9 @@ public class PackageManagerService extends IPackageManager.Stub { continue; } try { - byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null, - dexCodeInstructionSet, - false); - if (dexoptRequired == DexFile.DEXOPT_NEEDED) { - mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet); - } else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) { - mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet); + int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false); + if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) { + mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded); } } catch (FileNotFoundException e) { Slog.w(TAG, "Jar not found: " + path); @@ -1621,7 +1978,7 @@ public class PackageManagerService extends IPackageManager.Stub { psit.remove(); logCriticalInfo(Log.WARN, "System package " + ps.name + " no longer exists; wiping its data"); - removeDataDirsLI(ps.name); + removeDataDirsLI(null, ps.name); } else { final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name); if (disabledPs.codePath == null || !disabledPs.codePath.exists()) { @@ -1666,7 +2023,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (deletedPkg == null) { msg = "Updated system package " + deletedAppName + " no longer exists; wiping its data"; - removeDataDirsLI(deletedAppName); + removeDataDirsLI(null, deletedAppName); } else { msg = "Updated system app + " + deletedAppName + " no longer present; removing system privileges for " @@ -1758,7 +2115,14 @@ public class PackageManagerService extends IPackageManager.Stub { + mSettings.mInternalSdkPlatform + " to " + mSdkVersion + "; regranting permissions for internal storage"); mSettings.mInternalSdkPlatform = mSdkVersion; - + + // For now runtime permissions are toggled via a system property. + if (!RUNTIME_PERMISSIONS_ENABLED) { + // Remove the runtime permissions state if the feature + // was disabled by flipping the system property. + mSettings.deleteRuntimePermissionsFiles(); + } + updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL | (regrantPermissions ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL) @@ -1775,8 +2139,9 @@ public class PackageManagerService extends IPackageManager.Stub { mIsUpgrade = !Build.FINGERPRINT.equals(mSettings.mFingerprint); if (mIsUpgrade && !onlyCore) { Slog.i(TAG, "Build fingerprint changed; clearing code caches"); - for (String pkgName : mSettings.mPackages.keySet()) { - deleteCodeCacheDirsLI(pkgName); + for (int i = 0; i < mSettings.mPackages.size(); i++) { + final PackageSetting ps = mSettings.mPackages.valueAt(i); + deleteCodeCacheDirsLI(ps.volumeUuid, ps.name); } mSettings.mFingerprint = Build.FINGERPRINT; } @@ -1790,13 +2155,19 @@ public class PackageManagerService extends IPackageManager.Stub { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY, SystemClock.uptimeMillis()); - mRequiredVerifierPackage = getRequiredVerifierLPr(); + + mInstallerService = new PackageInstallerService(context, this); + + mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr(); + mIntentFilterVerifier = new IntentVerifierProxy(mContext, + mIntentFilterVerifierComponent); + + primeDomainVerificationsLPw(false); + } // synchronized (mPackages) } // synchronized (mInstallLock) - mInstallerService = new PackageInstallerService(context, this, mAppInstallDir); - // Now after opening every single application zip, make sure they // are all flushed. Not really needed, but keeps things nice and // tidy. @@ -1835,14 +2206,8 @@ public class PackageManagerService extends IPackageManager.Stub { final String packageName = info.activityInfo.packageName; - final PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps == null) { - continue; - } - - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; - if (!gp.grantedPermissions - .contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) { + if (checkPermission(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, + packageName, UserHandle.USER_OWNER) != PackageManager.PERMISSION_GRANTED) { continue; } @@ -1856,6 +2221,90 @@ public class PackageManagerService extends IPackageManager.Stub { return requiredVerifier; } + private ComponentName getIntentFilterVerifierComponentNameLPr() { + final Intent verification = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION); + final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE, + PackageManager.GET_DISABLED_COMPONENTS, 0 /* userId */); + + ComponentName verifierComponentName = null; + + int priority = -1000; + final int N = receivers.size(); + for (int i = 0; i < N; i++) { + final ResolveInfo info = receivers.get(i); + + if (info.activityInfo == null) { + continue; + } + + final String packageName = info.activityInfo.packageName; + + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps == null) { + continue; + } + + if (checkPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT, + packageName, UserHandle.USER_OWNER) != PackageManager.PERMISSION_GRANTED) { + continue; + } + + // Select the IntentFilterVerifier with the highest priority + if (priority < info.priority) { + priority = info.priority; + verifierComponentName = new ComponentName(packageName, info.activityInfo.name); + Slog.d(TAG, "Selecting IntentFilterVerifier: " + verifierComponentName + + " with priority: " + info.priority); + } + } + + return verifierComponentName; + } + + private void primeDomainVerificationsLPw(boolean logging) { + Slog.d(TAG, "Start priming domain verification"); + boolean updated = false; + ArrayList<String> allHosts = new ArrayList<>(); + for (PackageParser.Package pkg : mPackages.values()) { + final String packageName = pkg.packageName; + if (!hasDomainURLs(pkg)) { + if (logging) { + Slog.d(TAG, "No priming domain verifications for " + + "package with no domain URLs: " + packageName); + } + continue; + } + for (PackageParser.Activity a : pkg.activities) { + for (ActivityIntentInfo filter : a.intents) { + if (hasValidDomains(filter, false)) { + allHosts.addAll(filter.getHostsList()); + } + } + } + if (allHosts.size() > 0) { + allHosts.add("*"); + } + IntentFilterVerificationInfo ivi = + mSettings.createIntentFilterVerificationIfNeededLPw(packageName, allHosts); + if (ivi != null) { + // We will always log this + Slog.d(TAG, "Priming domain verifications for package: " + packageName); + ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS); + updated = true; + } + else { + if (logging) { + Slog.d(TAG, "No priming domain verifications for package: " + packageName); + } + } + allHosts.clear(); + } + if (updated) { + scheduleWriteSettingsLocked(); + } + Slog.d(TAG, "End priming domain verification"); + } + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -1872,12 +2321,13 @@ public class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { logCriticalInfo(Log.WARN, "Cleaning up incompletely installed app: " + ps.name); - removeDataDirsLI(ps.name); + removeDataDirsLI(ps.volumeUuid, ps.name); if (ps.codePath != null) { if (ps.codePath.isDirectory()) { - FileUtils.deleteContents(ps.codePath); + mInstaller.rmPackageDir(ps.codePath.getAbsolutePath()); + } else { + ps.codePath.delete(); } - ps.codePath.delete(); } if (ps.resourcePath != null && !ps.resourcePath.equals(ps.codePath)) { if (ps.resourcePath.isDirectory()) { @@ -1898,27 +2348,21 @@ public class PackageManagerService extends IPackageManager.Stub { return cur; } - static int[] removeInts(int[] cur, int[] rem) { - if (rem == null) return cur; - if (cur == null) return cur; - final int N = rem.length; - for (int i=0; i<N; i++) { - cur = removeInt(cur, rem[i]); - } - return cur; - } - PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) { if (!sUserManager.exists(userId)) return null; final PackageSetting ps = (PackageSetting) p.mExtras; if (ps == null) { return null; } - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; + + final PermissionsState permissionsState = ps.getPermissionsState(); + + final int[] gids = permissionsState.computeGids(userId); + final Set<String> permissions = permissionsState.getPermissions(userId); final PackageUserState state = ps.readUserState(userId); - return PackageParser.generatePackageInfo(p, gp.gids, flags, - ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions, - state, userId); + + return PackageParser.generatePackageInfo(p, gids, flags, + ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId); } @Override @@ -1989,6 +2433,7 @@ public class PackageManagerService extends IPackageManager.Stub { public int getPackageUid(String packageName, int userId) { if (!sUserManager.exists(userId)) return -1; enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get package uid"); + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); @@ -2005,22 +2450,30 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public int[] getPackageGids(String packageName) { + public int[] getPackageGids(String packageName, int userId) throws RemoteException { + if (!sUserManager.exists(userId)) { + return null; + } + + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, + "getPackageGids"); + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); - if (DEBUG_PACKAGE_INFO) + if (DEBUG_PACKAGE_INFO) { Log.v(TAG, "getPackageGids" + packageName + ": " + p); + } if (p != null) { - final PackageSetting ps = (PackageSetting)p.mExtras; - return ps.getGids(); + PackageSetting ps = (PackageSetting) p.mExtras; + return ps.getPermissionsState().computeGids(userId); } } - // stupid thing to indicate an error. - return new int[0]; + + return null; } - static final PermissionInfo generatePermissionInfo( + static PermissionInfo generatePermissionInfo( BasePermission bp, int flags) { if (bp.perm != null) { return PackageParser.generatePermissionInfo(bp.perm, flags); @@ -2125,8 +2578,9 @@ public class PackageManagerService extends IPackageManager.Stub { pkg = new PackageParser.Package(packageName); pkg.applicationInfo.packageName = packageName; pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY; - pkg.applicationInfo.dataDir = - getDataPathForPackage(packageName, 0).getPath(); + pkg.applicationInfo.privateFlags = ps.pkgPrivateFlags; + pkg.applicationInfo.dataDir = PackageManager.getDataDirForUser(ps.volumeUuid, + packageName, userId).getAbsolutePath(); pkg.applicationInfo.primaryCpuAbi = ps.primaryCpuAbiString; pkg.applicationInfo.secondaryCpuAbi = ps.secondaryCpuAbiString; } @@ -2162,9 +2616,9 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - @Override - public void freeStorageAndNotify(final long freeStorageSize, final IPackageDataObserver observer) { + public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize, + final IPackageDataObserver observer) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CLEAR_APP_CACHE, null); // Queue up an async operation since clearing cache may take a little while. @@ -2173,7 +2627,7 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.removeCallbacks(this); int retCode = -1; synchronized (mInstallLock) { - retCode = mInstaller.freeCache(freeStorageSize); + retCode = mInstaller.freeCache(volumeUuid, freeStorageSize); if (retCode < 0) { Slog.w(TAG, "Couldn't clear application caches"); } @@ -2190,7 +2644,8 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public void freeStorage(final long freeStorageSize, final IntentSender pi) { + public void freeStorage(final String volumeUuid, final long freeStorageSize, + final IntentSender pi) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CLEAR_APP_CACHE, null); // Queue up an async operation since clearing cache may take a little while. @@ -2199,7 +2654,7 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.removeCallbacks(this); int retCode = -1; synchronized (mInstallLock) { - retCode = mInstaller.freeCache(freeStorageSize); + retCode = mInstaller.freeCache(volumeUuid, freeStorageSize); if (retCode < 0) { Slog.w(TAG, "Couldn't clear application caches"); } @@ -2218,9 +2673,9 @@ public class PackageManagerService extends IPackageManager.Stub { }); } - void freeStorage(long freeStorageSize) throws IOException { + void freeStorage(String volumeUuid, long freeStorageSize) throws IOException { synchronized (mInstallLock) { - if (mInstaller.freeCache(freeStorageSize) < 0) { + if (mInstaller.freeCache(volumeUuid, freeStorageSize) < 0) { throw new IOException("Failed to free enough space"); } } @@ -2335,6 +2790,19 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } + /** + * @hide + */ + PackageParser.Package findSharedNonSystemLibrary(String libName) { + synchronized (mPackages) { + PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName); + if (lib != null && lib.apk != null) { + return mPackages.get(lib.apk); + } + } + return null; + } + @Override public FeatureInfo[] getSystemAvailableFeatures() { Collection<FeatureInfo> featSet; @@ -2370,30 +2838,37 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public int checkPermission(String permName, String pkgName) { + public int checkPermission(String permName, String pkgName, int userId) { + if (!sUserManager.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + synchronized (mPackages) { - PackageParser.Package p = mPackages.get(pkgName); + final PackageParser.Package p = mPackages.get(pkgName); if (p != null && p.mExtras != null) { - PackageSetting ps = (PackageSetting)p.mExtras; - if (ps.sharedUser != null) { - if (ps.sharedUser.grantedPermissions.contains(permName)) { - return PackageManager.PERMISSION_GRANTED; - } - } else if (ps.grantedPermissions.contains(permName)) { + final PackageSetting ps = (PackageSetting) p.mExtras; + if (ps.getPermissionsState().hasPermission(permName, userId)) { return PackageManager.PERMISSION_GRANTED; } } } + return PackageManager.PERMISSION_DENIED; } @Override public int checkUidPermission(String permName, int uid) { + final int userId = UserHandle.getUserId(uid); + + if (!sUserManager.exists(userId)) { + return PackageManager.PERMISSION_DENIED; + } + synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); if (obj != null) { - GrantedPermissions gp = (GrantedPermissions)obj; - if (gp.grantedPermissions.contains(permName)) { + final SettingBase ps = (SettingBase) obj; + if (ps.getPermissionsState().hasPermission(permName, userId)) { return PackageManager.PERMISSION_GRANTED; } } else { @@ -2403,6 +2878,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + return PackageManager.PERMISSION_DENIED; } @@ -2609,121 +3085,132 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private static void checkGrantRevokePermissions(PackageParser.Package pkg, BasePermission bp) { + private static void enforceDeclaredAsUsedAndRuntimePermission(PackageParser.Package pkg, + BasePermission bp) { int index = pkg.requestedPermissions.indexOf(bp.name); if (index == -1) { throw new SecurityException("Package " + pkg.packageName + " has not requested permission " + bp.name); } - boolean isNormal = - ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) - == PermissionInfo.PROTECTION_NORMAL); - boolean isDangerous = - ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE) - == PermissionInfo.PROTECTION_DANGEROUS); - boolean isDevelopment = - ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0); - - if (!isNormal && !isDangerous && !isDevelopment) { + if (!bp.isRuntime()) { throw new SecurityException("Permission " + bp.name + " is not a changeable permission type"); } - - if (isNormal || isDangerous) { - if (pkg.requestedPermissionsRequired.get(index)) { - throw new SecurityException("Can't change " + bp.name - + ". It is required by the application"); - } - } } @Override - public void grantPermission(String packageName, String permissionName) { + public boolean grantPermission(String packageName, String name, int userId) { + if (!RUNTIME_PERMISSIONS_ENABLED) { + return false; + } + + if (!sUserManager.exists(userId)) { + return false; + } + mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null); + android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, + "grantPermission"); + + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, + "grantPermission"); + + boolean gidsChanged = false; + final SettingBase sb; + synchronized (mPackages) { final PackageParser.Package pkg = mPackages.get(packageName); if (pkg == null) { throw new IllegalArgumentException("Unknown package: " + packageName); } - final BasePermission bp = mSettings.mPermissions.get(permissionName); + + final BasePermission bp = mSettings.mPermissions.get(name); if (bp == null) { - throw new IllegalArgumentException("Unknown permission: " + permissionName); + throw new IllegalArgumentException("Unknown permission: " + name); } - checkGrantRevokePermissions(pkg, bp); + enforceDeclaredAsUsedAndRuntimePermission(pkg, bp); - final PackageSetting ps = (PackageSetting) pkg.mExtras; - if (ps == null) { - return; + sb = (SettingBase) pkg.mExtras; + if (sb == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); } - final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps; - if (gp.grantedPermissions.add(permissionName)) { - if (ps.haveGids) { - gp.gids = appendInts(gp.gids, bp.gids); + + final PermissionsState permissionsState = sb.getPermissionsState(); + + final int result = permissionsState.grantRuntimePermission(bp, userId); + switch (result) { + case PermissionsState.PERMISSION_OPERATION_FAILURE: { + return false; } - mSettings.writeLPr(); + + case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: { + gidsChanged = true; + } break; } + + // Not critical if that is lost - app has to request again. + mSettings.writeRuntimePermissionsForUserLPr(userId, false); + } + + if (gidsChanged) { + killSettingPackagesForUser(sb, userId, KILL_APP_REASON_GIDS_CHANGED); } + + return true; } @Override - public void revokePermission(String packageName, String permissionName) { - int changedAppId = -1; + public boolean revokePermission(String packageName, String name, int userId) { + if (!RUNTIME_PERMISSIONS_ENABLED) { + return false; + } + + if (!sUserManager.exists(userId)) { + return false; + } + + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, + "revokePermission"); + + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, + "revokePermission"); + + final SettingBase sb; synchronized (mPackages) { final PackageParser.Package pkg = mPackages.get(packageName); if (pkg == null) { throw new IllegalArgumentException("Unknown package: " + packageName); } - if (pkg.applicationInfo.uid != Binder.getCallingUid()) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null); - } - final BasePermission bp = mSettings.mPermissions.get(permissionName); + + final BasePermission bp = mSettings.mPermissions.get(name); if (bp == null) { - throw new IllegalArgumentException("Unknown permission: " + permissionName); + throw new IllegalArgumentException("Unknown permission: " + name); } - checkGrantRevokePermissions(pkg, bp); + enforceDeclaredAsUsedAndRuntimePermission(pkg, bp); - final PackageSetting ps = (PackageSetting) pkg.mExtras; - if (ps == null) { - return; - } - final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps; - if (gp.grantedPermissions.remove(permissionName)) { - gp.grantedPermissions.remove(permissionName); - if (ps.haveGids) { - gp.gids = removeInts(gp.gids, bp.gids); - } - mSettings.writeLPr(); - changedAppId = ps.appId; + sb = (SettingBase) pkg.mExtras; + if (sb == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); } - } - if (changedAppId >= 0) { - // We changed the perm on someone, kill its processes. - IActivityManager am = ActivityManagerNative.getDefault(); - if (am != null) { - final int callingUserId = UserHandle.getCallingUserId(); - final long ident = Binder.clearCallingIdentity(); - try { - //XXX we should only revoke for the calling user's app permissions, - // but for now we impact all users. - //am.killUid(UserHandle.getUid(callingUserId, changedAppId), - // "revoke " + permissionName); - int[] users = sUserManager.getUserIds(); - for (int user : users) { - am.killUid(UserHandle.getUid(user, changedAppId), - "revoke " + permissionName); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } + final PermissionsState permissionsState = sb.getPermissionsState(); + + if (permissionsState.revokeRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + return false; } + + // Critical, after this call all should never have the permission. + mSettings.writeRuntimePermissionsForUserLPr(userId, true); } + + killSettingPackagesForUser(sb, userId, KILL_APP_REASON_PERMISSIONS_REVOKED); + + return true; } @Override @@ -2783,6 +3270,46 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private void killSettingPackagesForUser(SettingBase sb, int userId, String reason) { + final long identity = Binder.clearCallingIdentity(); + try { + if (sb instanceof SharedUserSetting) { + SharedUserSetting sus = (SharedUserSetting) sb; + final int packageCount = sus.packages.size(); + for (int i = 0; i < packageCount; i++) { + PackageSetting susPs = sus.packages.valueAt(i); + if (userId == UserHandle.USER_ALL) { + killApplication(susPs.pkg.packageName, susPs.appId, reason); + } else { + final int uid = UserHandle.getUid(userId, susPs.appId); + killUid(uid, reason); + } + } + } else if (sb instanceof PackageSetting) { + PackageSetting ps = (PackageSetting) sb; + if (userId == UserHandle.USER_ALL) { + killApplication(ps.pkg.packageName, ps.appId, reason); + } else { + final int uid = UserHandle.getUid(userId, ps.appId); + killUid(uid, reason); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private static void killUid(int uid, String reason) { + IActivityManager am = ActivityManagerNative.getDefault(); + if (am != null) { + try { + am.killUid(uid, reason); + } catch (RemoteException e) { + /* ignore - same process */ + } + } + } + /** * Compares two sets of signatures. Returns: * <br /> @@ -2967,7 +3494,7 @@ public class PackageManagerService extends IPackageManager.Stub { } // reader synchronized (mPackages) { - final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, false); + final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, 0, false); if (suid == null) { return -1; } @@ -2991,6 +3518,21 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public int getPrivateFlagsForUid(int uid) { + synchronized (mPackages) { + Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); + if (obj instanceof SharedUserSetting) { + final SharedUserSetting sus = (SharedUserSetting) obj; + return sus.pkgPrivateFlags; + } else if (obj instanceof PackageSetting) { + final PackageSetting ps = (PackageSetting) obj; + return ps.pkgPrivateFlags; + } + } + return 0; + } + + @Override public boolean isUidPrivileged(int uid) { uid = UserHandle.getAppId(uid); // reader @@ -3382,7 +3924,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (resolveInfo != null) { List<ResolveInfo> result = new ArrayList<ResolveInfo>(1); result.add(resolveInfo); - return result; + return filterIfNotPrimaryUser(result, userId); } // Check for cross profile results. resolveInfo = queryCrossProfileIntents( @@ -3395,17 +3937,121 @@ public class PackageManagerService extends IPackageManager.Stub { result.add(resolveInfo); Collections.sort(result, mResolvePrioritySorter); } + result = filterIfNotPrimaryUser(result, userId); + if (result.size() > 1 && hasWebURI(intent)) { + return filterCandidatesWithDomainPreferedActivitiesLPr(result); + } return result; } final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return mActivities.queryIntentForPackage(intent, resolvedType, flags, - pkg.activities, userId); + return filterIfNotPrimaryUser( + mActivities.queryIntentForPackage( + intent, resolvedType, flags, pkg.activities, userId), + userId); } return new ArrayList<ResolveInfo>(); } } + /** + * Filter out activities with primaryUserOnly flag set, when current user is not the owner. + * + * @return filtered list + */ + private List<ResolveInfo> filterIfNotPrimaryUser(List<ResolveInfo> resolveInfos, int userId) { + if (userId == UserHandle.USER_OWNER) { + return resolveInfos; + } + for (int i = resolveInfos.size() - 1; i >= 0; i--) { + ResolveInfo info = resolveInfos.get(i); + if ((info.activityInfo.flags & ActivityInfo.FLAG_PRIMARY_USER_ONLY) != 0) { + resolveInfos.remove(i); + } + } + return resolveInfos; + } + + private static boolean hasWebURI(Intent intent) { + if (intent.getData() == null) { + return false; + } + final String scheme = intent.getScheme(); + if (TextUtils.isEmpty(scheme)) { + return false; + } + return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); + } + + private List<ResolveInfo> filterCandidatesWithDomainPreferedActivitiesLPr( + List<ResolveInfo> candidates) { + if (DEBUG_PREFERRED) { + Slog.v("TAG", "Filtering results with prefered activities. Candidates count: " + + candidates.size()); + } + + final int userId = UserHandle.getCallingUserId(); + ArrayList<ResolveInfo> result = new ArrayList<ResolveInfo>(); + ArrayList<ResolveInfo> undefinedList = new ArrayList<ResolveInfo>(); + ArrayList<ResolveInfo> neverList = new ArrayList<ResolveInfo>(); + ArrayList<ResolveInfo> matchAllList = new ArrayList<ResolveInfo>(); + + synchronized (mPackages) { + final int count = candidates.size(); + // First, try to use the domain prefered App + for (int n=0; n<count; n++) { + ResolveInfo info = candidates.get(n); + String packageName = info.activityInfo.packageName; + PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + // Try to get the status from User settings first + int status = getDomainVerificationStatusLPr(ps, userId); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) { + result.add(info); + } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { + neverList.add(info); + } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) { + undefinedList.add(info); + } + // Add to the special match all list (Browser use case) + if (info.handleAllWebDataURI) { + matchAllList.add(info); + } + } + } + // If there is nothing selected, add all candidates and remove the ones that the User + // has explicitely put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state and + // also remove any Browser Apps ones. + // If there is still none after this pass, add all undefined one and Browser Apps and + // let the User decide with the Disambiguation dialog if there are several ones. + if (result.size() == 0) { + result.addAll(candidates); + } + result.removeAll(neverList); + result.removeAll(matchAllList); + if (result.size() == 0) { + result.addAll(undefinedList); + result.addAll(matchAllList); + } + } + if (DEBUG_PREFERRED) { + Slog.v("TAG", "Filtered results with prefered activities. New candidates count: " + + result.size()); + } + return result; + } + + private int getDomainVerificationStatusLPr(PackageSetting ps, int userId) { + int status = ps.getDomainVerificationStatusForUser(userId); + // if none available, get the master status + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) { + if (ps.getIntentFilterVerificationInfo() != null) { + status = ps.getIntentFilterVerificationInfo().getStatus(); + } + } + return status; + } + private ResolveInfo querySkipCurrentProfileIntents( List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, int flags, int sourceUserId) { @@ -3828,9 +4474,10 @@ public class PackageManagerService extends IPackageManager.Stub { private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageSetting ps, String[] permissions, boolean[] tmp, int flags, int userId) { int numMatch = 0; - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; + final PermissionsState permissionsState = ps.getPermissionsState(); for (int i=0; i<permissions.length; i++) { - if (gp.grantedPermissions.contains(permissions[i])) { + final String permission = permissions[i]; + if (permissionsState.hasPermission(permission, userId)) { tmp[i] = true; numMatch++; } else { @@ -4164,9 +4811,10 @@ public class PackageManagerService extends IPackageManager.Stub { e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { logCriticalInfo(Log.WARN, "Deleting invalid package at " + file); if (file.isDirectory()) { - FileUtils.deleteContents(file); + mInstaller.rmPackageDir(file.getAbsolutePath()); + } else { + file.delete(); } - file.delete(); } } } @@ -4289,9 +4937,9 @@ public class PackageManagerService extends IPackageManager.Stub { // If new package is not located in "/system/priv-app" (e.g. due to an OTA), // it needs to drop FLAG_PRIVILEGED. if (locationIsPrivileged(scanFile)) { - updatedPkg.pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED; + updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } else { - updatedPkg.pkgFlags &= ~ApplicationInfo.FLAG_PRIVILEGED; + updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } if (ps != null && !ps.codePath.equals(scanFile)) { @@ -4311,6 +4959,8 @@ public class PackageManagerService extends IPackageManager.Stub { + " to " + scanFile); updatedPkg.codePath = scanFile; updatedPkg.codePathString = scanFile.toString(); + updatedPkg.resourcePath = scanFile; + updatedPkg.resourcePathString = scanFile.toString(); } updatedPkg.pkg = pkg; throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, null); @@ -4353,7 +5003,7 @@ public class PackageManagerService extends IPackageManager.Stub { // An updated privileged app will not have the PARSE_IS_PRIVILEGED // flag set initially - if ((updatedPkg.pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0) { + if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { parseFlags |= PackageParser.PARSE_IS_PRIVILEGED; } } @@ -4436,6 +5086,7 @@ public class PackageManagerService extends IPackageManager.Stub { } // Set application objects path explicitly. + pkg.applicationInfo.volumeUuid = pkg.volumeUuid; pkg.applicationInfo.setCodePath(pkg.codePath); pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath); pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths); @@ -4578,8 +5229,7 @@ public class PackageManagerService extends IPackageManager.Stub { final ArraySet<PackageParser.Package> pkgs; synchronized (mPackages) { - pkgs = mDeferredDexOpt; - mDeferredDexOpt = null; + pkgs = mPackageDexOptimizer.clearDeferredDexOptPackages(); } if (pkgs != null) { @@ -4613,7 +5263,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Give priority to system apps. for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) { PackageParser.Package pkg = it.next(); - if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { + if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) { if (DEBUG_DEXOPT) { Log.i(TAG, "Adding system app " + sortedPkgs.size() + ": " + pkg.packageName); } @@ -4624,7 +5274,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Give priority to updated system apps. for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) { PackageParser.Package pkg = it.next(); - if (isUpdatedSystemApp(pkg)) { + if (pkg.isUpdatedSystemApp()) { if (DEBUG_DEXOPT) { Log.i(TAG, "Adding updated system app " + sortedPkgs.size() + ": " + pkg.packageName); } @@ -4735,8 +5385,8 @@ public class PackageManagerService extends IPackageManager.Stub { } PackageParser.Package p = pkg; synchronized (mInstallLock) { - performDexOptLI(p, null /* instruction sets */, false /* force dex */, - false /* defer */, true /* include dependencies */); + mPackageDexOptimizer.performDexOpt(p, null /* instruction sets */, + false /* force dex */, false /* defer */, true /* include dependencies */); } } @@ -4745,14 +5395,6 @@ public class PackageManagerService extends IPackageManager.Stub { return performDexOpt(packageName, instructionSet, false); } - private static String getPrimaryInstructionSet(ApplicationInfo info) { - if (info.primaryCpuAbi == null) { - return getPreferredInstructionSet(); - } - - return VMRuntime.getInstructionSet(info.primaryCpuAbi); - } - public boolean performDexOpt(String packageName, String instructionSet, boolean backgroundDexopt) { boolean dexopt = mLazyDexOpt || backgroundDexopt; boolean updateUsage = !backgroundDexopt; // Don't update usage if this is just a backgroundDexopt @@ -4785,8 +5427,9 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { final String[] instructionSets = new String[] { targetInstructionSet }; - return performDexOptLI(p, instructionSets, false /* force dex */, false /* defer */, - true /* include dependencies */) == DEX_OPT_PERFORMED; + int result = mPackageDexOptimizer.performDexOpt(p, instructionSets, + false /* forceDex */, false /* defer */, true /* inclDependencies */); + return result == PackageDexOptimizer.DEX_OPT_PERFORMED; } } @@ -4813,226 +5456,6 @@ public class PackageManagerService extends IPackageManager.Stub { mPackageUsage.write(true); } - private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets, - boolean forceDex, boolean defer, ArraySet<String> done) { - for (int i=0; i<libs.size(); i++) { - PackageParser.Package libPkg; - String libName; - synchronized (mPackages) { - libName = libs.get(i); - SharedLibraryEntry lib = mSharedLibraries.get(libName); - if (lib != null && lib.apk != null) { - libPkg = mPackages.get(lib.apk); - } else { - libPkg = null; - } - } - if (libPkg != null && !done.contains(libName)) { - performDexOptLI(libPkg, instructionSets, forceDex, defer, done); - } - } - } - - static final int DEX_OPT_SKIPPED = 0; - static final int DEX_OPT_PERFORMED = 1; - static final int DEX_OPT_DEFERRED = 2; - static final int DEX_OPT_FAILED = -1; - - private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets, - boolean forceDex, boolean defer, ArraySet<String> done) { - final String[] instructionSets = targetInstructionSets != null ? - targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); - - if (done != null) { - done.add(pkg.packageName); - if (pkg.usesLibraries != null) { - performDexOptLibsLI(pkg.usesLibraries, instructionSets, forceDex, defer, done); - } - if (pkg.usesOptionalLibraries != null) { - performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, forceDex, defer, done); - } - } - - if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) { - return DEX_OPT_SKIPPED; - } - - final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0; - - final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); - boolean performedDexOpt = false; - // There are three basic cases here: - // 1.) we need to dexopt, either because we are forced or it is needed - // 2.) we are defering a needed dexopt - // 3.) we are skipping an unneeded dexopt - final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); - for (String dexCodeInstructionSet : dexCodeInstructionSets) { - if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) { - continue; - } - - for (String path : paths) { - try { - // This will return DEXOPT_NEEDED if we either cannot find any odex file for this - // patckage or the one we find does not match the image checksum (i.e. it was - // compiled against an old image). It will return PATCHOAT_NEEDED if we can find a - // odex file and it matches the checksum of the image but not its base address, - // meaning we need to move it. - final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path, - pkg.packageName, dexCodeInstructionSet, defer); - if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) { - Log.i(TAG, "Running dexopt on: " + path + " pkg=" - + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet - + " vmSafeMode=" + vmSafeMode); - final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - final int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg), - pkg.packageName, dexCodeInstructionSet, vmSafeMode); - - if (ret < 0) { - // Don't bother running dexopt again if we failed, it will probably - // just result in an error again. Also, don't bother dexopting for other - // paths & ISAs. - return DEX_OPT_FAILED; - } - - performedDexOpt = true; - } else if (!defer && isDexOptNeeded == DexFile.PATCHOAT_NEEDED) { - Log.i(TAG, "Running patchoat on: " + pkg.applicationInfo.packageName); - final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - final int ret = mInstaller.patchoat(path, sharedGid, !isForwardLocked(pkg), - pkg.packageName, dexCodeInstructionSet); - - if (ret < 0) { - // Don't bother running patchoat again if we failed, it will probably - // just result in an error again. Also, don't bother dexopting for other - // paths & ISAs. - return DEX_OPT_FAILED; - } - - performedDexOpt = true; - } - - // We're deciding to defer a needed dexopt. Don't bother dexopting for other - // paths and instruction sets. We'll deal with them all together when we process - // our list of deferred dexopts. - if (defer && isDexOptNeeded != DexFile.UP_TO_DATE) { - if (mDeferredDexOpt == null) { - mDeferredDexOpt = new ArraySet<PackageParser.Package>(); - } - mDeferredDexOpt.add(pkg); - return DEX_OPT_DEFERRED; - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "Apk not found for dexopt: " + path); - return DEX_OPT_FAILED; - } catch (IOException e) { - Slog.w(TAG, "IOException reading apk: " + path, e); - return DEX_OPT_FAILED; - } catch (StaleDexCacheError e) { - Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e); - return DEX_OPT_FAILED; - } catch (Exception e) { - Slog.w(TAG, "Exception when doing dexopt : ", e); - return DEX_OPT_FAILED; - } - } - - // At this point we haven't failed dexopt and we haven't deferred dexopt. We must - // either have either succeeded dexopt, or have had isDexOptNeededInternal tell us - // it isn't required. We therefore mark that this package doesn't need dexopt unless - // it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped - // it. - pkg.mDexOptPerformed.add(dexCodeInstructionSet); - } - - // If we've gotten here, we're sure that no error occurred and that we haven't - // deferred dex-opt. We've either dex-opted one more paths or instruction sets or - // we've skipped all of them because they are up to date. In both cases this - // package doesn't need dexopt any longer. - return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; - } - - private static String[] getAppDexInstructionSets(ApplicationInfo info) { - if (info.primaryCpuAbi != null) { - if (info.secondaryCpuAbi != null) { - return new String[] { - VMRuntime.getInstructionSet(info.primaryCpuAbi), - VMRuntime.getInstructionSet(info.secondaryCpuAbi) }; - } else { - return new String[] { - VMRuntime.getInstructionSet(info.primaryCpuAbi) }; - } - } - - return new String[] { getPreferredInstructionSet() }; - } - - private static String[] getAppDexInstructionSets(PackageSetting ps) { - if (ps.primaryCpuAbiString != null) { - if (ps.secondaryCpuAbiString != null) { - return new String[] { - VMRuntime.getInstructionSet(ps.primaryCpuAbiString), - VMRuntime.getInstructionSet(ps.secondaryCpuAbiString) }; - } else { - return new String[] { - VMRuntime.getInstructionSet(ps.primaryCpuAbiString) }; - } - } - - return new String[] { getPreferredInstructionSet() }; - } - - private static String getPreferredInstructionSet() { - if (sPreferredInstructionSet == null) { - sPreferredInstructionSet = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); - } - - return sPreferredInstructionSet; - } - - private static List<String> getAllInstructionSets() { - final String[] allAbis = Build.SUPPORTED_ABIS; - final List<String> allInstructionSets = new ArrayList<String>(allAbis.length); - - for (String abi : allAbis) { - final String instructionSet = VMRuntime.getInstructionSet(abi); - if (!allInstructionSets.contains(instructionSet)) { - allInstructionSets.add(instructionSet); - } - } - - return allInstructionSets; - } - - /** - * Returns the instruction set that should be used to compile dex code. In the presence of - * a native bridge this might be different than the one shared libraries use. - */ - private static String getDexCodeInstructionSet(String sharedLibraryIsa) { - String dexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + sharedLibraryIsa); - return (dexCodeIsa.isEmpty() ? sharedLibraryIsa : dexCodeIsa); - } - - private static String[] getDexCodeInstructionSets(String[] instructionSets) { - ArraySet<String> dexCodeInstructionSets = new ArraySet<String>(instructionSets.length); - for (String instructionSet : instructionSets) { - dexCodeInstructionSets.add(getDexCodeInstructionSet(instructionSet)); - } - return dexCodeInstructionSets.toArray(new String[dexCodeInstructionSets.size()]); - } - - /** - * Returns deduplicated list of supported instructions for dex code. - */ - public static String[] getAllDexCodeInstructionSets() { - String[] supportedInstructionSets = new String[Build.SUPPORTED_ABIS.length]; - for (int i = 0; i < supportedInstructionSets.length; i++) { - String abi = Build.SUPPORTED_ABIS[i]; - supportedInstructionSets[i] = VMRuntime.getInstructionSet(abi); - } - return getDexCodeInstructionSets(supportedInstructionSets); - } - @Override public void forceDexOpt(String packageName) { enforceSystemOrRoot("forceDexOpt"); @@ -5048,25 +5471,14 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { final String[] instructionSets = new String[] { getPrimaryInstructionSet(pkg.applicationInfo) }; - final int res = performDexOptLI(pkg, instructionSets, true, false, true); - if (res != DEX_OPT_PERFORMED) { + final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets, + true /*forceDex*/, false /* defer */, true /* inclDependencies */); + if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { throw new IllegalStateException("Failed to dexopt: " + res); } } } - private int performDexOptLI(PackageParser.Package pkg, String[] instructionSets, - boolean forceDex, boolean defer, boolean inclDependencies) { - ArraySet<String> done; - if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) { - done = new ArraySet<String>(); - done.add(pkg.packageName); - } else { - done = null; - } - return performDexOptLI(pkg, instructionSets, forceDex, defer, done); - } - private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) { if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { Slog.w(TAG, "Unable to update from " + oldPkg.name @@ -5082,33 +5494,15 @@ public class PackageManagerService extends IPackageManager.Stub { return true; } - File getDataPathForUser(int userId) { - return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId); - } - - private File getDataPathForPackage(String packageName, int userId) { - /* - * Until we fully support multiple users, return the directory we - * previously would have. The PackageManagerTests will need to be - * revised when this is changed back.. - */ - if (userId == 0) { - return new File(mAppDataDir, packageName); - } else { - return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId - + File.separator + packageName); - } - } - - private int createDataDirsLI(String packageName, int uid, String seinfo) { + private int createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) { int[] users = sUserManager.getUserIds(); - int res = mInstaller.install(packageName, uid, uid, seinfo); + int res = mInstaller.install(volumeUuid, packageName, uid, uid, seinfo); if (res < 0) { return res; } for (int user : users) { if (user != 0) { - res = mInstaller.createUserData(packageName, + res = mInstaller.createUserData(volumeUuid, packageName, UserHandle.getUid(user, uid), user, seinfo); if (res < 0) { return res; @@ -5118,11 +5512,11 @@ public class PackageManagerService extends IPackageManager.Stub { return res; } - private int removeDataDirsLI(String packageName) { + private int removeDataDirsLI(String volumeUuid, String packageName) { int[] users = sUserManager.getUserIds(); int res = 0; for (int user : users) { - int resInner = mInstaller.remove(packageName, user); + int resInner = mInstaller.remove(volumeUuid, packageName, user); if (resInner < 0) { res = resInner; } @@ -5131,11 +5525,11 @@ public class PackageManagerService extends IPackageManager.Stub { return res; } - private int deleteCodeCacheDirsLI(String packageName) { + private int deleteCodeCacheDirsLI(String volumeUuid, String packageName) { int[] users = sUserManager.getUserIds(); int res = 0; for (int user : users) { - int resInner = mInstaller.deleteCodeCacheFiles(packageName, user); + int resInner = mInstaller.deleteCodeCacheFiles(volumeUuid, packageName, user); if (resInner < 0) { res = resInner; } @@ -5270,7 +5664,7 @@ public class PackageManagerService extends IPackageManager.Stub { return res; } finally { if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) { - removeDataDirsLI(pkg.packageName); + removeDataDirsLI(pkg.volumeUuid, pkg.packageName); } } } @@ -5293,7 +5687,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if ((parseFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) { - pkg.applicationInfo.flags |= ApplicationInfo.FLAG_PRIVILEGED; + pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } if (mCustomResolverComponentName != null && @@ -5389,7 +5783,7 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { if (pkg.mSharedUserId != null) { - suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, true); + suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true); if (suid == null) { throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Creating application package " + pkg.packageName @@ -5463,7 +5857,8 @@ public class PackageManagerService extends IPackageManager.Stub { destResourceFile, pkg.applicationInfo.nativeLibraryRootDir, pkg.applicationInfo.primaryCpuAbi, pkg.applicationInfo.secondaryCpuAbi, - pkg.applicationInfo.flags, user, false); + pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags, + user, false); if (pkgSetting == null) { throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Creating application package " + pkg.packageName + " failed"); @@ -5618,7 +6013,8 @@ public class PackageManagerService extends IPackageManager.Stub { } else { // This is a normal package, need to make its data directory. - dataPath = getDataPathForPackage(pkg.packageName, 0); + dataPath = PackageManager.getDataDirForUser(pkg.volumeUuid, pkg.packageName, + UserHandle.USER_OWNER); boolean uidError = false; if (dataPath.exists()) { @@ -5638,8 +6034,8 @@ public class PackageManagerService extends IPackageManager.Stub { // This is probably because the system was stopped while // installd was in the middle of messing with its libs // directory. Ask installd to fix that. - int ret = mInstaller.fixUid(pkgName, pkg.applicationInfo.uid, - pkg.applicationInfo.uid); + int ret = mInstaller.fixUid(pkg.volumeUuid, pkgName, + pkg.applicationInfo.uid, pkg.applicationInfo.uid); if (ret >= 0) { recovered = true; String msg = "Package " + pkg.packageName @@ -5652,7 +6048,7 @@ public class PackageManagerService extends IPackageManager.Stub { || (scanFlags&SCAN_BOOTING) != 0)) { // If this is a system app, we can at least delete its // current data so the application will still work. - int ret = removeDataDirsLI(pkgName); + int ret = removeDataDirsLI(pkg.volumeUuid, pkgName); if (ret >= 0) { // TODO: Kill the processes first // Old data gone! @@ -5666,8 +6062,8 @@ public class PackageManagerService extends IPackageManager.Stub { recovered = true; // And now re-install the app. - ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid, - pkg.applicationInfo.seinfo); + ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid, + pkg.applicationInfo.seinfo); if (ret == -1) { // Ack should not happen! msg = prefix + pkg.packageName @@ -5710,8 +6106,8 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); if (mShouldRestoreconData) { Slog.i(TAG, "SELinux relabeling of " + pkg.packageName + " issued."); - mInstaller.restoreconData(pkg.packageName, pkg.applicationInfo.seinfo, - pkg.applicationInfo.uid); + mInstaller.restoreconData(pkg.volumeUuid, pkg.packageName, + pkg.applicationInfo.seinfo, pkg.applicationInfo.uid); } } else { if (DEBUG_PACKAGE_SCANNING) { @@ -5719,8 +6115,8 @@ public class PackageManagerService extends IPackageManager.Stub { Log.v(TAG, "Want this data dir: " + dataPath); } //invoke installer to do the actual installation - int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid, - pkg.applicationInfo.seinfo); + int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid, + pkg.applicationInfo.seinfo); if (ret < 0) { // Error from installer throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, @@ -5741,7 +6137,7 @@ public class PackageManagerService extends IPackageManager.Stub { final String path = scanFile.getPath(); final String codePath = pkg.applicationInfo.getCodePath(); final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting); - if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { + if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp()) { setBundledAppAbisAndRoots(pkg, pkgSetting); // If we haven't found any native libraries for the app, check if it has @@ -5774,7 +6170,7 @@ public class PackageManagerService extends IPackageManager.Stub { // pass once we've determined ABI below. setNativeLibraryPaths(pkg); - final boolean isAsec = isForwardLocked(pkg) || isExternal(pkg); + final boolean isAsec = pkg.isForwardLocked() || isExternal(pkg); final String nativeLibraryRootStr = pkg.applicationInfo.nativeLibraryRootDir; final boolean useIsaSpecificSubdirs = pkg.applicationInfo.nativeLibraryRootRequiresIsa; @@ -5897,7 +6293,8 @@ public class PackageManagerService extends IPackageManager.Stub { !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) { final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir; for (int userId : userIds) { - if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) { + if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName, + nativeLibPath, userId) < 0) { throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Failed linking native library dir (user=" + userId + ")"); } @@ -5950,12 +6347,12 @@ public class PackageManagerService extends IPackageManager.Stub { } if ((scanFlags & SCAN_NO_DEX) == 0) { - if (performDexOptLI(pkg, null /* instruction sets */, forceDex, - (scanFlags & SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) { + int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instruction sets */, + forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, false /* inclDependencies */); + if (result == PackageDexOptimizer.DEX_OPT_FAILED) { throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI"); } } - if (mFactoryTest && pkg.requestedPermissions.contains( android.Manifest.permission.FACTORY_TEST)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST; @@ -5971,7 +6368,7 @@ public class PackageManagerService extends IPackageManager.Stub { for (int i=0; i<pkg.libraryNames.size(); i++) { String name = pkg.libraryNames.get(i); boolean allowed = false; - if (isUpdatedSystemApp(pkg)) { + if (pkg.isUpdatedSystemApp()) { // New library entries can only be added through the // system image. This is important to get rid of a lot // of nasty edge cases: for example if we allowed a non- @@ -6025,8 +6422,10 @@ public class PackageManagerService extends IPackageManager.Stub { if ((scanFlags & SCAN_NO_DEX) == 0) { for (int i = 0; i < clientLibPkgs.size(); i++) { PackageParser.Package clientPkg = clientLibPkgs.get(i); - if (performDexOptLI(clientPkg, null /* instruction sets */, forceDex, - (scanFlags & SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) { + int result = mPackageDexOptimizer.performDexOpt(clientPkg, + null /* instruction sets */, forceDex, + (scanFlags & SCAN_DEFER_DEX) != 0, false); + if (result == PackageDexOptimizer.DEX_OPT_FAILED) { throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI failed to dexopt clientLibPkgs"); } @@ -6089,21 +6488,11 @@ public class PackageManagerService extends IPackageManager.Stub { // Add the package's KeySets to the global KeySetManagerService KeySetManagerService ksms = mSettings.mKeySetManagerService; try { - // Old KeySetData no longer valid. - ksms.removeAppKeySetDataLPw(pkg.packageName); ksms.addSigningKeySetToPackageLPw(pkg.packageName, pkg.mSigningKeys); if (pkg.mKeySetMapping != null) { - for (Map.Entry<String, ArraySet<PublicKey>> entry : - pkg.mKeySetMapping.entrySet()) { - if (entry.getValue() != null) { - ksms.addDefinedKeySetToPackageLPw(pkg.packageName, - entry.getValue(), entry.getKey()); - } - } + ksms.addDefinedKeySetsToPackageLPw(pkg.packageName, pkg.mKeySetMapping); if (pkg.mUpgradeKeySets != null) { - for (String upgradeAlias : pkg.mUpgradeKeySets) { - ksms.addUpgradeKeySetToPackageLPw(pkg.packageName, upgradeAlias); - } + ksms.addUpgradeKeySetsToPackageLPw(pkg.packageName, pkg.mUpgradeKeySets); } } } catch (NullPointerException e) { @@ -6270,86 +6659,94 @@ public class PackageManagerService extends IPackageManager.Stub { r = null; for (i=0; i<N; i++) { PackageParser.Permission p = pkg.permissions.get(i); + + // Now that permission groups have a special meaning, we ignore permission + // groups for legacy apps to prevent unexpected behavior. In particular, + // permissions for one app being granted to someone just becuase they happen + // to be in a group defined by another app (before this had no implications). + if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { + p.group = mPermissionGroups.get(p.info.group); + // Warn for a permission in an unknown group. + if (p.info.group != null && p.group == null) { + Slog.w(TAG, "Permission " + p.info.name + " from package " + + p.info.packageName + " in an unknown group " + p.info.group); + } + } + ArrayMap<String, BasePermission> permissionMap = p.tree ? mSettings.mPermissionTrees - : mSettings.mPermissions; - p.group = mPermissionGroups.get(p.info.group); - if (p.info.group == null || p.group != null) { - BasePermission bp = permissionMap.get(p.info.name); - - // Allow system apps to redefine non-system permissions - if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) { - final boolean currentOwnerIsSystem = (bp.perm != null - && isSystemApp(bp.perm.owner)); - if (isSystemApp(p.owner)) { - if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) { - // It's a built-in permission and no owner, take ownership now - bp.packageSetting = pkgSetting; - bp.perm = p; - bp.uid = pkg.applicationInfo.uid; - bp.sourcePackage = p.info.packageName; - } else if (!currentOwnerIsSystem) { - String msg = "New decl " + p.owner + " of permission " - + p.info.name + " is system; overriding " + bp.sourcePackage; - reportSettingsProblem(Log.WARN, msg); - bp = null; - } + : mSettings.mPermissions; + BasePermission bp = permissionMap.get(p.info.name); + + // Allow system apps to redefine non-system permissions + if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) { + final boolean currentOwnerIsSystem = (bp.perm != null + && isSystemApp(bp.perm.owner)); + if (isSystemApp(p.owner)) { + if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) { + // It's a built-in permission and no owner, take ownership now + bp.packageSetting = pkgSetting; + bp.perm = p; + bp.uid = pkg.applicationInfo.uid; + bp.sourcePackage = p.info.packageName; + } else if (!currentOwnerIsSystem) { + String msg = "New decl " + p.owner + " of permission " + + p.info.name + " is system; overriding " + bp.sourcePackage; + reportSettingsProblem(Log.WARN, msg); + bp = null; } } + } - if (bp == null) { - bp = new BasePermission(p.info.name, p.info.packageName, - BasePermission.TYPE_NORMAL); - permissionMap.put(p.info.name, bp); - } - - if (bp.perm == null) { - if (bp.sourcePackage == null - || bp.sourcePackage.equals(p.info.packageName)) { - BasePermission tree = findPermissionTreeLP(p.info.name); - if (tree == null - || tree.sourcePackage.equals(p.info.packageName)) { - bp.packageSetting = pkgSetting; - bp.perm = p; - bp.uid = pkg.applicationInfo.uid; - bp.sourcePackage = p.info.packageName; - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append(p.info.name); + if (bp == null) { + bp = new BasePermission(p.info.name, p.info.packageName, + BasePermission.TYPE_NORMAL); + permissionMap.put(p.info.name, bp); + } + + if (bp.perm == null) { + if (bp.sourcePackage == null + || bp.sourcePackage.equals(p.info.packageName)) { + BasePermission tree = findPermissionTreeLP(p.info.name); + if (tree == null + || tree.sourcePackage.equals(p.info.packageName)) { + bp.packageSetting = pkgSetting; + bp.perm = p; + bp.uid = pkg.applicationInfo.uid; + bp.sourcePackage = p.info.packageName; + if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); } - } else { - Slog.w(TAG, "Permission " + p.info.name + " from package " - + p.info.packageName + " ignored: base tree " - + tree.name + " is from package " - + tree.sourcePackage); + r.append(p.info.name); } } else { Slog.w(TAG, "Permission " + p.info.name + " from package " - + p.info.packageName + " ignored: original from " - + bp.sourcePackage); + + p.info.packageName + " ignored: base tree " + + tree.name + " is from package " + + tree.sourcePackage); } - } else if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append("DUP:"); - r.append(p.info.name); + } else { + Slog.w(TAG, "Permission " + p.info.name + " from package " + + p.info.packageName + " ignored: original from " + + bp.sourcePackage); } - if (bp.perm == p) { - bp.protectionLevel = p.info.protectionLevel; + } else if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); } - } else { - Slog.w(TAG, "Permission " + p.info.name + " from package " - + p.info.packageName + " ignored: no group " - + p.group); + r.append("DUP:"); + r.append(p.info.name); + } + if (bp.perm == p) { + bp.protectionLevel = p.info.protectionLevel; } } + if (r != null) { if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permissions: " + r); } @@ -6495,14 +6892,15 @@ public class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi; Slog.i(TAG, "Adjusting ABI for : " + ps.name + " to " + adjustedAbi); - if (performDexOptLI(ps.pkg, null /* instruction sets */, forceDexOpt, - deferDexOpt, true) == DEX_OPT_FAILED) { + int result = mPackageDexOptimizer.performDexOpt(ps.pkg, + null /* instruction sets */, forceDexOpt, deferDexOpt, true); + if (result == PackageDexOptimizer.DEX_OPT_FAILED) { ps.primaryCpuAbiString = null; ps.pkg.applicationInfo.primaryCpuAbi = null; return; } else { mInstaller.rmdex(ps.codePathString, - getDexCodeInstructionSet(getPreferredInstructionSet())); + getDexCodeInstructionSet(getPreferredInstructionSet())); } } } @@ -6574,8 +6972,8 @@ public class PackageManagerService extends IPackageManager.Stub { final ApplicationInfo info = pkg.applicationInfo; final String codePath = pkg.codePath; final File codeFile = new File(codePath); - final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info); - final boolean asecApp = isForwardLocked(info) || isExternal(info); + final boolean bundledApp = info.isSystemApp() && !info.isUpdatedSystemApp(); + final boolean asecApp = info.isForwardLocked() || isExternal(info); info.nativeLibraryRootDir = null; info.nativeLibraryRootRequiresIsa = false; @@ -7056,36 +7454,49 @@ public class PackageManagerService extends IPackageManager.Stub { private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace, String packageOfInterest) { + // IMPORTANT: There are two types of permissions: install and runtime. + // Install time permissions are granted when the app is installed to + // all device users and users added in the future. Runtime permissions + // are granted at runtime explicitly to specific users. Normal and signature + // protected permissions are install time permissions. Dangerous permissions + // are install permissions if the app's target SDK is Lollipop MR1 or older, + // otherwise they are runtime permissions. This function does not manage + // runtime permissions except for the case an app targeting Lollipop MR1 + // being upgraded to target a newer SDK, in which case dangerous permissions + // are transformed from install time to runtime ones. + final PackageSetting ps = (PackageSetting) pkg.mExtras; if (ps == null) { return; } - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; - ArraySet<String> origPermissions = gp.grantedPermissions; - boolean changedPermission = false; + + PermissionsState permissionsState = ps.getPermissionsState(); + PermissionsState origPermissions = permissionsState; + + final int[] currentUserIds = UserManagerService.getInstance().getUserIds(); + + int[] upgradeUserIds = PermissionsState.USERS_NONE; + int[] changedRuntimePermissionUserIds = PermissionsState.USERS_NONE; + + boolean changedInstallPermission = false; if (replace) { - ps.permissionsFixed = false; - if (gp == ps) { - origPermissions = new ArraySet<String>(gp.grantedPermissions); - gp.grantedPermissions.clear(); - gp.gids = mGlobalGids; + ps.installPermissionsFixed = false; + if (!ps.isSharedUser()) { + origPermissions = new PermissionsState(permissionsState); + permissionsState.reset(); } } - if (gp.gids == null) { - gp.gids = mGlobalGids; - } + permissionsState.setGlobalGids(mGlobalGids); final int N = pkg.requestedPermissions.size(); for (int i=0; i<N; i++) { final String name = pkg.requestedPermissions.get(i); - final boolean required = pkg.requestedPermissionsRequired.get(i); final BasePermission bp = mSettings.mPermissions.get(name); + if (DEBUG_INSTALL) { - if (gp != ps) { - Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); - } + Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); } if (bp == null || bp.packageSetting == null) { @@ -7097,10 +7508,11 @@ public class PackageManagerService extends IPackageManager.Stub { } final String perm = bp.name; - boolean allowed; boolean allowedSig = false; - if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) { - // Keep track of app op permissions. + int grant = GRANT_DENIED; + + // Keep track of app op permissions. + if ((bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) { ArraySet<String> pkgs = mAppOpPermissionPackages.get(bp.name); if (pkgs == null) { pkgs = new ArraySet<>(); @@ -7108,65 +7520,126 @@ public class PackageManagerService extends IPackageManager.Stub { } pkgs.add(pkg.packageName); } + final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; - if (level == PermissionInfo.PROTECTION_NORMAL - || level == PermissionInfo.PROTECTION_DANGEROUS) { - // We grant a normal or dangerous permission if any of the following - // are true: - // 1) The permission is required - // 2) The permission is optional, but was granted in the past - // 3) The permission is optional, but was requested by an - // app in /system (not /data) - // - // Otherwise, reject the permission. - allowed = (required || origPermissions.contains(perm) - || (isSystemApp(ps) && !isUpdatedSystemApp(ps))); - } else if (bp.packageSetting == null) { - // This permission is invalid; skip it. - allowed = false; - } else if (level == PermissionInfo.PROTECTION_SIGNATURE) { - allowed = grantSignaturePermission(perm, pkg, bp, origPermissions); - if (allowed) { - allowedSig = true; - } - } else { - allowed = false; + switch (level) { + case PermissionInfo.PROTECTION_NORMAL: { + // For all apps normal permissions are install time ones. + grant = GRANT_INSTALL; + } break; + + case PermissionInfo.PROTECTION_DANGEROUS: { + if (!RUNTIME_PERMISSIONS_ENABLED + || pkg.applicationInfo.targetSdkVersion + <= Build.VERSION_CODES.LOLLIPOP_MR1) { + // For legacy apps dangerous permissions are install time ones. + grant = GRANT_INSTALL; + } else if (ps.isSystem()) { + final int[] updatedUserIds = ps.getPermissionsUpdatedForUserIds(); + if (origPermissions.hasInstallPermission(bp.name)) { + // If a system app had an install permission, then the app was + // upgraded and we grant the permissions as runtime to all users. + grant = GRANT_UPGRADE; + upgradeUserIds = currentUserIds; + } else if (!Arrays.equals(updatedUserIds, currentUserIds)) { + // If users changed since the last permissions update for a + // system app, we grant the permission as runtime to the new users. + grant = GRANT_UPGRADE; + upgradeUserIds = currentUserIds; + for (int userId : updatedUserIds) { + upgradeUserIds = ArrayUtils.removeInt(upgradeUserIds, userId); + } + } else { + // Otherwise, we grant the permission as runtime if the app + // already had it, i.e. we preserve runtime permissions. + grant = GRANT_RUNTIME; + } + } else if (origPermissions.hasInstallPermission(bp.name)) { + // For legacy apps that became modern, install becomes runtime. + grant = GRANT_UPGRADE; + upgradeUserIds = currentUserIds; + } else if (replace) { + // For upgraded modern apps keep runtime permissions unchanged. + grant = GRANT_RUNTIME; + } + } break; + + case PermissionInfo.PROTECTION_SIGNATURE: { + // For all apps signature permissions are install time ones. + allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions); + if (allowedSig) { + grant = GRANT_INSTALL; + } + } break; } + if (DEBUG_INSTALL) { - if (gp != ps) { - Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); - } + Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); } - if (allowed) { - if (!isSystemApp(ps) && ps.permissionsFixed) { + + if (grant != GRANT_DENIED) { + if (!isSystemApp(ps) && ps.installPermissionsFixed) { // If this is an existing, non-system package, then // we can't add any new permissions to it. - if (!allowedSig && !gp.grantedPermissions.contains(perm)) { + if (!allowedSig && !origPermissions.hasInstallPermission(perm)) { // Except... if this is a permission that was added // to the platform (note: need to only do this when // updating the platform). - allowed = isNewPlatformPermissionForPackage(perm, pkg); + if (!isNewPlatformPermissionForPackage(perm, pkg)) { + grant = GRANT_DENIED; + } } } - if (allowed) { - if (!gp.grantedPermissions.contains(perm)) { - changedPermission = true; - gp.grantedPermissions.add(perm); - gp.gids = appendInts(gp.gids, bp.gids); - } else if (!ps.haveGids) { - gp.gids = appendInts(gp.gids, bp.gids); - } - } else { - if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) { - Slog.w(TAG, "Not granting permission " + perm - + " to package " + pkg.packageName - + " because it was previously installed without"); - } + + switch (grant) { + case GRANT_INSTALL: { + // Grant an install permission. + if (permissionsState.grantInstallPermission(bp) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + changedInstallPermission = true; + } + } break; + + case GRANT_RUNTIME: { + // Grant previously granted runtime permissions. + for (int userId : UserManagerService.getInstance().getUserIds()) { + if (origPermissions.hasRuntimePermission(bp.name, userId)) { + if (permissionsState.grantRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // If we cannot put the permission as it was, we have to write. + changedRuntimePermissionUserIds = ArrayUtils.appendInt( + changedRuntimePermissionUserIds, userId); + } + } + } + } break; + + case GRANT_UPGRADE: { + // Grant runtime permissions for a previously held install permission. + permissionsState.revokeInstallPermission(bp); + for (int userId : upgradeUserIds) { + if (permissionsState.grantRuntimePermission(bp, userId) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + // If we granted the permission, we have to write. + changedRuntimePermissionUserIds = ArrayUtils.appendInt( + changedRuntimePermissionUserIds, userId); + } + } + } break; + + default: { + if (packageOfInterest == null + || packageOfInterest.equals(pkg.packageName)) { + Slog.w(TAG, "Not granting permission " + perm + + " to package " + pkg.packageName + + " because it was previously installed without"); + } + } break; } } else { - if (gp.grantedPermissions.remove(perm)) { - changedPermission = true; - gp.gids = removeInts(gp.gids, bp.gids); + if (permissionsState.revokeInstallPermission(bp) != + PermissionsState.PERMISSION_OPERATION_FAILURE) { + changedInstallPermission = true; Slog.i(TAG, "Un-granting permission " + perm + " from package " + pkg.packageName + " (protectionLevel=" + bp.protectionLevel @@ -7186,14 +7659,22 @@ public class PackageManagerService extends IPackageManager.Stub { } } - if ((changedPermission || replace) && !ps.permissionsFixed && + if ((changedInstallPermission || replace) && !ps.installPermissionsFixed && !isSystemApp(ps) || isUpdatedSystemApp(ps)){ // This is the first that we have heard about this package, so the // permissions we have now selected are fixed until explicitly // changed. - ps.permissionsFixed = true; + ps.installPermissionsFixed = true; + } + + ps.setPermissionsUpdatedForUserIds(currentUserIds); + + // Persist the runtime permissions state for users with changes. + if (RUNTIME_PERMISSIONS_ENABLED) { + for (int userId : changedRuntimePermissionUserIds) { + mSettings.writeRuntimePermissionsForUserLPr(userId, true); + } } - ps.haveGids = true; } private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) { @@ -7214,7 +7695,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, - BasePermission bp, ArraySet<String> origPermissions) { + BasePermission bp, PermissionsState origPermissions) { boolean allowed; allowed = (compareSignatures( bp.packageSetting.signatures.mSignatures, pkg.mSignatures) @@ -7226,13 +7707,10 @@ public class PackageManagerService extends IPackageManager.Stub { if (isSystemApp(pkg)) { // For updated system applications, a system permission // is granted only if it had been defined by the original application. - if (isUpdatedSystemApp(pkg)) { + if (pkg.isUpdatedSystemApp()) { final PackageSetting sysPs = mSettings .getDisabledSystemPkgLPr(pkg.packageName); - final GrantedPermissions origGp = sysPs.sharedUser != null - ? sysPs.sharedUser : sysPs; - - if (origGp.grantedPermissions.contains(perm)) { + if (sysPs.getPermissionsState().hasInstallPermission(perm)) { // If the original was granted this permission, we take // that grant decision as read and propagate it to the // update. @@ -7266,7 +7744,7 @@ public class PackageManagerService extends IPackageManager.Stub { & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { // For development permissions, a development permission // is granted only if it was already granted. - allowed = origPermissions.contains(perm); + allowed = origPermissions.hasInstallPermission(perm); } return allowed; } @@ -7314,7 +7792,7 @@ public class PackageManagerService extends IPackageManager.Stub { } public final void addActivity(PackageParser.Activity a, String type) { - final boolean systemApp = isSystemApp(a.info.applicationInfo); + final boolean systemApp = a.info.applicationInfo.isSystemApp(); mActivities.put(a.getComponentName(), a); if (DEBUG_SHOW_INFO) Log.v( @@ -7428,6 +7906,9 @@ public class PackageManagerService extends IPackageManager.Stub { if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = info; } + if (info != null) { + res.handleAllWebDataURI = info.handleAllWebDataURI(); + } res.priority = info.getPriority(); res.preferredOrder = activity.owner.mPreferredOrder; //System.out.println("Result: " + res.activityInfo.className + @@ -7441,7 +7922,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { res.icon = info.icon; } - res.system = isSystemApp(res.activityInfo.applicationInfo); + res.system = res.activityInfo.applicationInfo.isSystemApp(); return res; } @@ -7650,14 +8131,12 @@ public class PackageManagerService extends IPackageManager.Stub { } res.priority = info.getPriority(); res.preferredOrder = service.owner.mPreferredOrder; - //System.out.println("Result: " + res.activityInfo.className + - // " = " + res.priority); res.match = match; res.isDefault = info.hasDefault; res.labelRes = info.labelRes; res.nonLocalizedLabel = info.nonLocalizedLabel; res.icon = info.icon; - res.system = isSystemApp(res.serviceInfo.applicationInfo); + res.system = res.serviceInfo.applicationInfo.isSystemApp(); return res; } @@ -7880,7 +8359,7 @@ public class PackageManagerService extends IPackageManager.Stub { res.labelRes = info.labelRes; res.nonLocalizedLabel = info.nonLocalizedLabel; res.icon = info.icon; - res.system = isSystemApp(res.providerInfo.applicationInfo); + res.system = res.providerInfo.applicationInfo.isSystemApp(); return res; } @@ -8072,8 +8551,8 @@ public class PackageManagerService extends IPackageManager.Stub { public void installPackage(String originPath, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride) { - installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams, - packageAbiOverride, UserHandle.getCallingUserId()); + installPackageAsUser(originPath, observer, installFlags, installerPackageName, + verificationParams, packageAbiOverride, UserHandle.getCallingUserId()); } @Override @@ -8113,6 +8592,15 @@ public class PackageManagerService extends IPackageManager.Stub { user = new UserHandle(userId); } + // Only system components can circumvent runtime permissions when installing. + if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 + && mContext.checkCallingOrSelfPermission(Manifest.permission + .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) { + throw new SecurityException("You need the " + + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission " + + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"); + } + verificationParams.setInstallerUid(callingUid); final File originFile = new File(originPath); @@ -8120,7 +8608,7 @@ public class PackageManagerService extends IPackageManager.Stub { final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(origin, observer, installFlags, - installerPackageName, verificationParams, user, packageAbiOverride); + installerPackageName, null, verificationParams, user, packageAbiOverride); mHandler.sendMessage(msg); } @@ -8139,7 +8627,7 @@ public class PackageManagerService extends IPackageManager.Stub { final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(origin, observer, params.installFlags, - installerPackageName, verifParams, user, params.abiOverride); + installerPackageName, params.volumeUuid, verifParams, user, params.abiOverride); mHandler.sendMessage(msg); } @@ -8270,7 +8758,6 @@ public class PackageManagerService extends IPackageManager.Stub { long callingId = Binder.clearCallingIdentity(); try { boolean sendAdded = false; - Bundle extras = new Bundle(1); // writer synchronized (mPackages) { @@ -8528,6 +9015,81 @@ public class PackageManagerService extends IPackageManager.Stub { android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 1; } + @Override + public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) + throws RemoteException { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT, + "Only intentfilter verification agents can verify applications"); + + final Message msg = mHandler.obtainMessage(INTENT_FILTER_VERIFIED); + final IntentFilterVerificationResponse response = new IntentFilterVerificationResponse( + Binder.getCallingUid(), verificationCode, failedDomains); + msg.arg1 = id; + msg.obj = response; + mHandler.sendMessage(msg); + } + + @Override + public int getIntentVerificationStatus(String packageName, int userId) { + synchronized (mPackages) { + return mSettings.getIntentFilterVerificationStatusLPr(packageName, userId); + } + } + + @Override + public boolean updateIntentVerificationStatus(String packageName, int status, int userId) { + boolean result = false; + synchronized (mPackages) { + result = mSettings.updateIntentFilterVerificationStatusLPw(packageName, status, userId); + } + scheduleWritePackageRestrictionsLocked(userId); + return result; + } + + @Override + public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) { + synchronized (mPackages) { + return mSettings.getIntentFilterVerificationsLPr(packageName); + } + } + + @Override + public List<IntentFilter> getAllIntentFilters(String packageName) { + if (TextUtils.isEmpty(packageName)) { + return Collections.<IntentFilter>emptyList(); + } + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(packageName); + if (pkg == null || pkg.activities == null) { + return Collections.<IntentFilter>emptyList(); + } + final int count = pkg.activities.size(); + ArrayList<IntentFilter> result = new ArrayList<>(); + for (int n=0; n<count; n++) { + PackageParser.Activity activity = pkg.activities.get(n); + if (activity.intents != null || activity.intents.size() > 0) { + result.addAll(activity.intents); + } + } + return result; + } + } + + @Override + public boolean setDefaultBrowserPackageName(String packageName, int userId) { + synchronized (mPackages) { + return mSettings.setDefaultBrowserPackageNameLPr(packageName, userId); + } + } + + @Override + public String getDefaultBrowserPackageName(int userId) { + synchronized (mPackages) { + return mSettings.getDefaultBrowserPackageNameLPw(userId); + } + } + /** * Get the "allow unknown sources" setting. * @@ -8902,19 +9464,21 @@ public class PackageManagerService extends IPackageManager.Stub { final IPackageInstallObserver2 observer; int installFlags; final String installerPackageName; + final String volumeUuid; final VerificationParams verificationParams; private InstallArgs mArgs; private int mRet; final String packageAbiOverride; InstallParams(OriginInfo origin, IPackageInstallObserver2 observer, int installFlags, - String installerPackageName, VerificationParams verificationParams, UserHandle user, - String packageAbiOverride) { + String installerPackageName, String volumeUuid, + VerificationParams verificationParams, UserHandle user, String packageAbiOverride) { super(user); this.origin = origin; this.observer = observer; this.installFlags = installFlags; this.installerPackageName = installerPackageName; + this.volumeUuid = volumeUuid; this.verificationParams = verificationParams; this.packageAbiOverride = packageAbiOverride; } @@ -9039,7 +9603,7 @@ public class PackageManagerService extends IPackageManager.Stub { final long sizeBytes = mContainerService.calculateInstalledSize( origin.resolvedPath, isForwardLocked(), packageAbiOverride); - if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) { + if (mInstaller.freeCache(null, sizeBytes + lowThreshold) >= 0) { pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride); } @@ -9268,7 +9832,7 @@ public class PackageManagerService extends IPackageManager.Stub { * @param installFlags package installation flags * @return true if should be installed on external storage */ - private static boolean installOnSd(int installFlags) { + private static boolean installOnExternalAsec(int installFlags) { if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) { return false; } @@ -9289,7 +9853,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private InstallArgs createInstallArgs(InstallParams params) { - if (installOnSd(params.installFlags) || params.isForwardLocked()) { + if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) { return new AsecInstallArgs(params); } else { return new FileInstallArgs(params); @@ -9303,7 +9867,7 @@ public class PackageManagerService extends IPackageManager.Stub { private InstallArgs createInstallArgsForExisting(int installFlags, String codePath, String resourcePath, String nativeLibraryRoot, String[] instructionSets) { final boolean isInAsec; - if (installOnSd(installFlags)) { + if (installOnExternalAsec(installFlags)) { /* Apps on SD card are always in ASEC containers. */ isInAsec = true; } else if (installForwardLocked(installFlags) @@ -9319,7 +9883,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (isInAsec) { return new AsecInstallArgs(codePath, instructionSets, - installOnSd(installFlags), installForwardLocked(installFlags)); + installOnExternalAsec(installFlags), installForwardLocked(installFlags)); } else { return new FileInstallArgs(codePath, resourcePath, nativeLibraryRoot, instructionSets); @@ -9334,6 +9898,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Always refers to PackageManager flags only final int installFlags; final String installerPackageName; + final String volumeUuid; final ManifestDigest manifestDigest; final UserHandle user; final String abiOverride; @@ -9344,12 +9909,13 @@ public class PackageManagerService extends IPackageManager.Stub { /* nullable */ String[] instructionSets; InstallArgs(OriginInfo origin, IPackageInstallObserver2 observer, int installFlags, - String installerPackageName, ManifestDigest manifestDigest, UserHandle user, - String[] instructionSets, String abiOverride) { + String installerPackageName, String volumeUuid, ManifestDigest manifestDigest, + UserHandle user, String[] instructionSets, String abiOverride) { this.origin = origin; this.installFlags = installFlags; this.observer = observer; this.installerPackageName = installerPackageName; + this.volumeUuid = volumeUuid; this.manifestDigest = manifestDigest; this.user = user; this.instructionSets = instructionSets; @@ -9401,7 +9967,7 @@ public class PackageManagerService extends IPackageManager.Stub { return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0; } - protected boolean isExternal() { + protected boolean isExternalAsec() { return (installFlags & PackageManager.INSTALL_EXTERNAL) != 0; } @@ -9410,6 +9976,25 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private void removeDexFiles(List<String> allCodePaths, String[] instructionSets) { + if (!allCodePaths.isEmpty()) { + if (instructionSets == null) { + throw new IllegalStateException("instructionSet == null"); + } + String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); + for (String codePath : allCodePaths) { + for (String dexCodeInstructionSet : dexCodeInstructionSets) { + int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet); + if (retCode < 0) { + Slog.w(TAG, "Couldn't remove dex file for package: " + + " at location " + codePath + ", retcode=" + retCode); + // we don't consider this to be a failure of the core package deletion + } + } + } + } + } + /** * Logic to handle installation of non-ASEC applications, including copying * and renaming logic. @@ -9429,8 +10014,8 @@ public class PackageManagerService extends IPackageManager.Stub { /** New install */ FileInstallArgs(InstallParams params) { super(params.origin, params.observer, params.installFlags, - params.installerPackageName, params.getManifestDigest(), params.getUser(), - null /* instruction sets */, params.packageAbiOverride); + params.installerPackageName, params.volumeUuid, params.getManifestDigest(), + params.getUser(), null /* instruction sets */, params.packageAbiOverride); if (isFwdLocked()) { throw new IllegalArgumentException("Forward locking only supported in ASEC"); } @@ -9439,7 +10024,7 @@ public class PackageManagerService extends IPackageManager.Stub { /** Existing install */ FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryPath, String[] instructionSets) { - super(OriginInfo.fromNothing(), null, 0, null, null, null, instructionSets, null); + super(OriginInfo.fromNothing(), null, 0, null, null, null, null, instructionSets, null); this.codeFile = (codePath != null) ? new File(codePath) : null; this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null; this.legacyNativeLibraryPath = (legacyNativeLibraryPath != null) ? @@ -9463,7 +10048,7 @@ public class PackageManagerService extends IPackageManager.Stub { } try { - final File tempDir = mInstallerService.allocateInternalStageDirLegacy(); + final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid); codeFile = tempDir; resourceFile = tempDir; } catch (IOException e) { @@ -9524,8 +10109,9 @@ public class PackageManagerService extends IPackageManager.Stub { cleanUp(); return false; } else { + final File targetDir = codeFile.getParentFile(); final File beforeCodeFile = codeFile; - final File afterCodeFile = getNextCodePath(pkg.packageName); + final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName); Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile); try { @@ -9552,6 +10138,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.splitCodePaths); // Reflect the rename in app info + pkg.applicationInfo.volumeUuid = pkg.volumeUuid; pkg.applicationInfo.setCodePath(pkg.codePath); pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath); pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths); @@ -9591,9 +10178,10 @@ public class PackageManagerService extends IPackageManager.Stub { } if (codeFile.isDirectory()) { - FileUtils.deleteContents(codeFile); + mInstaller.rmPackageDir(codeFile.getAbsolutePath()); + } else { + codeFile.delete(); } - codeFile.delete(); if (resourceFile != null && !FileUtils.contains(codeFile, resourceFile)) { resourceFile.delete(); @@ -9622,23 +10210,7 @@ public class PackageManagerService extends IPackageManager.Stub { } cleanUp(); - - if (!allCodePaths.isEmpty()) { - if (instructionSets == null) { - throw new IllegalStateException("instructionSet == null"); - } - String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); - for (String codePath : allCodePaths) { - for (String dexCodeInstructionSet : dexCodeInstructionSets) { - int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet); - if (retCode < 0) { - Slog.w(TAG, "Couldn't remove dex file for package: " - + " at location " + codePath + ", retcode=" + retCode); - // we don't consider this to be a failure of the core package deletion - } - } - } - } + removeDexFiles(allCodePaths, instructionSets); } boolean doPostDeleteLI(boolean delete) { @@ -9690,16 +10262,15 @@ public class PackageManagerService extends IPackageManager.Stub { /** New install */ AsecInstallArgs(InstallParams params) { super(params.origin, params.observer, params.installFlags, - params.installerPackageName, params.getManifestDigest(), - params.getUser(), null /* instruction sets */, - params.packageAbiOverride); + params.installerPackageName, params.volumeUuid, params.getManifestDigest(), + params.getUser(), null /* instruction sets */, params.packageAbiOverride); } /** Existing install */ AsecInstallArgs(String fullCodePath, String[] instructionSets, boolean isExternal, boolean isForwardLocked) { super(OriginInfo.fromNothing(), null, (isExternal ? INSTALL_EXTERNAL : 0) - | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, + | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, null, instructionSets, null); // Hackily pretend we're still looking at a full code path if (!fullCodePath.endsWith(RES_FILE_NAME)) { @@ -9716,7 +10287,7 @@ public class PackageManagerService extends IPackageManager.Stub { AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) { super(OriginInfo.fromNothing(), null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0) - | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, + | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, null, instructionSets, null); this.cid = cid; setMountPath(PackageHelper.getSdDir(cid)); @@ -9731,7 +10302,7 @@ public class PackageManagerService extends IPackageManager.Stub { abiOverride); final File target; - if (isExternal()) { + if (isExternalAsec()) { target = new UserEnvironment(UserHandle.USER_OWNER).getExternalStorageDirectory(); } else { target = Environment.getDataDirectory(); @@ -9760,7 +10331,7 @@ public class PackageManagerService extends IPackageManager.Stub { } final String newMountPath = imcs.copyPackageToContainer( - origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternal(), + origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternalAsec(), isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */)); if (newMountPath != null) { @@ -9858,6 +10429,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.splitCodePaths); // Reflect the rename in app info + pkg.applicationInfo.volumeUuid = pkg.volumeUuid; pkg.applicationInfo.setCodePath(pkg.codePath); pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath); pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths); @@ -9943,31 +10515,10 @@ public class PackageManagerService extends IPackageManager.Stub { private void cleanUpResourcesLI(List<String> allCodePaths) { cleanUp(); - - if (!allCodePaths.isEmpty()) { - if (instructionSets == null) { - throw new IllegalStateException("instructionSet == null"); - } - String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); - for (String codePath : allCodePaths) { - for (String dexCodeInstructionSet : dexCodeInstructionSets) { - int retCode = mInstaller.rmdex(codePath, dexCodeInstructionSet); - if (retCode < 0) { - Slog.w(TAG, "Couldn't remove dex file for package: " - + " at location " + codePath + ", retcode=" + retCode); - // we don't consider this to be a failure of the core package deletion - } - } - } - } + removeDexFiles(allCodePaths, instructionSets); } - boolean matchContainer(String app) { - if (cid.startsWith(app)) { - return true; - } - return false; - } + String getPackageName() { return getAsecPackageName(cid); @@ -10062,32 +10613,16 @@ public class PackageManagerService extends IPackageManager.Stub { return prefix + idxStr; } - private File getNextCodePath(String packageName) { + private File getNextCodePath(File targetDir, String packageName) { int suffix = 1; File result; do { - result = new File(mAppInstallDir, packageName + "-" + suffix); + result = new File(targetDir, packageName + "-" + suffix); suffix++; } while (result.exists()); return result; } - // Utility method used to ignore ADD/REMOVE events - // by directory observer. - private static boolean ignoreCodePath(String fullPathStr) { - String apkName = deriveCodePathName(fullPathStr); - int idx = apkName.lastIndexOf(INSTALL_PACKAGE_SUFFIX); - if (idx != -1 && ((idx+1) < apkName.length())) { - // Make sure the package ends with a numeral - String version = apkName.substring(idx+1); - try { - Integer.parseInt(version); - return true; - } catch (NumberFormatException e) {} - } - return false; - } - // Utility method that returns the relative package path with respect // to the installation directory. Like say for /data/data/com.test-1.apk // string com.test-1 is returned. @@ -10146,14 +10681,15 @@ public class PackageManagerService extends IPackageManager.Stub { /* * Install a non-existing package. */ - private void installNewPackageLI(PackageParser.Package pkg, - int parseFlags, int scanFlags, UserHandle user, - String installerPackageName, PackageInstalledInfo res) { + private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, + UserHandle user, String installerPackageName, String volumeUuid, + PackageInstalledInfo res) { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg); - boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists(); + final boolean dataDirExists = PackageManager.getDataDirForUser(volumeUuid, pkgName, + UserHandle.USER_OWNER).exists(); synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { // A package with the same name is already installed, though @@ -10177,7 +10713,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags, System.currentTimeMillis(), user); - updateSettingsLI(newPackage, installerPackageName, null, null, res); + updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user); // delete the partially installed application. the data directory will have to be // restored if it was already existing if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) { @@ -10209,9 +10745,9 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } - private void replacePackageLI(PackageParser.Package pkg, - int parseFlags, int scanFlags, UserHandle user, - String installerPackageName, PackageInstalledInfo res) { + private void replacePackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, + UserHandle user, String installerPackageName, String volumeUuid, + PackageInstalledInfo res) { PackageParser.Package oldPackage; String pkgName = pkg.packageName; int[] allUsers; @@ -10250,17 +10786,17 @@ public class PackageManagerService extends IPackageManager.Stub { boolean sysPkg = (isSystemApp(oldPackage)); if (sysPkg) { replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanFlags, - user, allUsers, perUserInstalled, installerPackageName, res); + user, allUsers, perUserInstalled, installerPackageName, volumeUuid, res); } else { replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanFlags, - user, allUsers, perUserInstalled, installerPackageName, res); + user, allUsers, perUserInstalled, installerPackageName, volumeUuid, res); } } private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage, PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user, - int[] allUsers, boolean[] perUserInstalled, - String installerPackageName, PackageInstalledInfo res) { + int[] allUsers, boolean[] perUserInstalled, String installerPackageName, + String volumeUuid, PackageInstalledInfo res) { String pkgName = deletedPackage.packageName; boolean deletedPkg = true; boolean updatedSettings = false; @@ -10285,7 +10821,7 @@ public class PackageManagerService extends IPackageManager.Stub { // If deleted package lived in a container, give users a chance to // relinquish resources before killing. - if (isForwardLocked(deletedPackage) || isExternal(deletedPackage)) { + if (deletedPackage.isForwardLocked() || isExternal(deletedPackage)) { if (DEBUG_INSTALL) { Slog.i(TAG, "upgrading pkg " + deletedPackage + " is ASEC-hosted -> UNAVAILABLE"); } @@ -10295,11 +10831,12 @@ public class PackageManagerService extends IPackageManager.Stub { sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null); } - deleteCodeCacheDirsLI(pkgName); + deleteCodeCacheDirsLI(pkg.volumeUuid, pkgName); try { final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user); - updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res); + updateSettingsLI(newPackage, installerPackageName, volumeUuid, allUsers, + perUserInstalled, res, user); updatedSettings = true; } catch (PackageManagerException e) { res.setError("Package couldn't be installed in " + pkg.codePath, e); @@ -10324,10 +10861,10 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage); File restoreFile = new File(deletedPackage.codePath); // Parse old package - boolean oldOnSd = isExternal(deletedPackage); + boolean oldExternal = isExternal(deletedPackage); int oldParseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | - (isForwardLocked(deletedPackage) ? PackageParser.PARSE_FORWARD_LOCK : 0) | - (oldOnSd ? PackageParser.PARSE_ON_SDCARD : 0); + (deletedPackage.isForwardLocked() ? PackageParser.PARSE_FORWARD_LOCK : 0) | + (oldExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0); int oldScanFlags = SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME; try { scanPackageLI(restoreFile, oldParseFlags, oldScanFlags, origUpdateTime, null); @@ -10351,14 +10888,15 @@ public class PackageManagerService extends IPackageManager.Stub { private void replaceSystemPackageLI(PackageParser.Package deletedPackage, PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user, - int[] allUsers, boolean[] perUserInstalled, - String installerPackageName, PackageInstalledInfo res) { + int[] allUsers, boolean[] perUserInstalled, String installerPackageName, + String volumeUuid, PackageInstalledInfo res) { if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg + ", old=" + deletedPackage); boolean disabledSystem = false; boolean updatedSettings = false; parseFlags |= PackageParser.PARSE_IS_SYSTEM; - if ((deletedPackage.applicationInfo.flags&ApplicationInfo.FLAG_PRIVILEGED) != 0) { + if ((deletedPackage.applicationInfo.privateFlags&ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) + != 0) { parseFlags |= PackageParser.PARSE_IS_PRIVILEGED; } String packageName = deletedPackage.packageName; @@ -10405,7 +10943,7 @@ public class PackageManagerService extends IPackageManager.Stub { } // Successfully disabled the old package. Now proceed with re-installation - deleteCodeCacheDirsLI(packageName); + deleteCodeCacheDirsLI(pkg.volumeUuid, packageName); res.returnCode = PackageManager.INSTALL_SUCCEEDED; pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; @@ -10428,7 +10966,8 @@ public class PackageManagerService extends IPackageManager.Stub { } if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res); + updateSettingsLI(newPackage, installerPackageName, volumeUuid, allUsers, + perUserInstalled, res, user); updatedSettings = true; } @@ -10463,8 +11002,8 @@ public class PackageManagerService extends IPackageManager.Stub { } private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName, - int[] allUsers, boolean[] perUserInstalled, - PackageInstalledInfo res) { + String volumeUuid, int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res, + UserHandle user) { String pkgName = newPackage.packageName; synchronized (mPackages) { //write settings. the installStatus will be incomplete at this stage. @@ -10483,13 +11022,13 @@ public class PackageManagerService extends IPackageManager.Stub { // For system-bundled packages, we assume that installing an upgraded version // of the package implies that the user actually wants to run that new code, // so we enable the package. - if (isSystemApp(newPackage)) { - // NB: implicit assumption that system package upgrades apply to all users - if (DEBUG_INSTALL) { - Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName); - } - PackageSetting ps = mSettings.mPackages.get(pkgName); - if (ps != null) { + PackageSetting ps = mSettings.mPackages.get(pkgName); + if (ps != null) { + if (isSystemApp(newPackage)) { + // NB: implicit assumption that system package upgrades apply to all users + if (DEBUG_INSTALL) { + Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName); + } if (res.origUsers != null) { for (int userHandle : res.origUsers) { ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, @@ -10509,6 +11048,13 @@ public class PackageManagerService extends IPackageManager.Stub { // upcoming call to mSettings.writeLPr(). } } + // It's implied that when a user requests installation, they want the app to be + // installed and enabled. + int userId = user.getIdentifier(); + if (userId != UserHandle.USER_ALL) { + ps.setInstalled(true, userId); + ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); + } } res.name = pkgName; res.uid = newPackage.applicationInfo.uid; @@ -10523,12 +11069,14 @@ public class PackageManagerService extends IPackageManager.Stub { private void installPackageLI(InstallArgs args, PackageInstalledInfo res) { final int installFlags = args.installFlags; - String installerPackageName = args.installerPackageName; - File tmpPackageFile = new File(args.getCodePath()); - boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0); - boolean onSd = ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0); + final String installerPackageName = args.installerPackageName; + final String volumeUuid = args.volumeUuid; + final File tmpPackageFile = new File(args.getCodePath()); + final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0); + final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) + || (args.volumeUuid != null)); boolean replace = false; - final int scanFlags = SCAN_NEW_INSTALL | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE; + int scanFlags = SCAN_NEW_INSTALL | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE; // Result object to be returned res.returnCode = PackageManager.INSTALL_SUCCEEDED; @@ -10536,7 +11084,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Retrieve PackageSettings and parse package final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) - | (onSd ? PackageParser.PARSE_ON_SDCARD : 0); + | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0); PackageParser pp = new PackageParser(); pp.setSeparateProcesses(mSeparateProcesses); pp.setDisplayMetrics(mMetrics); @@ -10688,24 +11236,40 @@ public class PackageManagerService extends IPackageManager.Stub { } - if (systemApp && onSd) { + if (systemApp && onExternal) { // Disable updates to system apps on sdcard res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION, "Cannot install updates to system apps on sdcard"); return; } + // If app directory is not writable, dexopt will be called after the rename + if (!forwardLocked && !pkg.applicationInfo.isExternalAsec()) { + // Enable SCAN_NO_DEX flag to skip dexopt at a later stage + scanFlags |= SCAN_NO_DEX; + // Run dexopt before old package gets removed, to minimize time when app is unavailable + int result = mPackageDexOptimizer + .performDexOpt(pkg, null /* instruction sets */, true /* forceDex */, + false /* defer */, false /* inclDependencies */); + if (result == PackageDexOptimizer.DEX_OPT_FAILED) { + res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath); + return; + } + } + if (!args.doRename(res.returnCode, pkg, oldCodePath)) { res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename"); return; } + startIntentFilterVerifications(args.user.getIdentifier(), pkg); + if (replace) { replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, - installerPackageName, res); + installerPackageName, volumeUuid, res); } else { installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, - args.user, installerPackageName, res); + args.user, installerPackageName, volumeUuid, res); } synchronized (mPackages) { final PackageSetting ps = mSettings.mPackages.get(pkgName); @@ -10715,16 +11279,103 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private static boolean isForwardLocked(PackageParser.Package pkg) { - return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; + private void startIntentFilterVerifications(int userId, PackageParser.Package pkg) { + if (mIntentFilterVerifierComponent == null) { + Slog.d(TAG, "No IntentFilter verification will not be done as " + + "there is no IntentFilterVerifier available!"); + return; + } + + final int verifierUid = getPackageUid( + mIntentFilterVerifierComponent.getPackageName(), + (userId == UserHandle.USER_ALL) ? UserHandle.USER_OWNER : userId); + + mHandler.removeMessages(START_INTENT_FILTER_VERIFICATIONS); + final Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS); + msg.obj = pkg; + msg.arg1 = userId; + msg.arg2 = verifierUid; + + mHandler.sendMessage(msg); } - private static boolean isForwardLocked(ApplicationInfo info) { - return (info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; + private void verifyIntentFiltersIfNeeded(int userId, int verifierUid, + PackageParser.Package pkg) { + int size = pkg.activities.size(); + if (size == 0) { + Slog.d(TAG, "No activity, so no need to verify any IntentFilter!"); + return; + } + + final boolean hasDomainURLs = hasDomainURLs(pkg); + if (!hasDomainURLs) { + Slog.d(TAG, "No domain URLs, so no need to verify any IntentFilter!"); + return; + } + + Slog.d(TAG, "Checking for userId:" + userId + " if any IntentFilter from the " + size + + " Activities needs verification ..."); + + final int verificationId = mIntentFilterVerificationToken++; + int count = 0; + final String packageName = pkg.packageName; + ArrayList<String> allHosts = new ArrayList<>(); + + synchronized (mPackages) { + for (PackageParser.Activity a : pkg.activities) { + for (ActivityIntentInfo filter : a.intents) { + boolean needsFilterVerification = filter.needsVerification(); + if (needsFilterVerification && needsNetworkVerificationLPr(filter)) { + Slog.d(TAG, "Verification needed for IntentFilter:" + filter.toString()); + mIntentFilterVerifier.addOneIntentFilterVerification( + verifierUid, userId, verificationId, filter, packageName); + count++; + } else if (!needsFilterVerification) { + Slog.d(TAG, "No verification needed for IntentFilter:" + + filter.toString()); + if (hasValidDomains(filter)) { + allHosts.addAll(filter.getHostsList()); + } + } else { + Slog.d(TAG, "Verification already done for IntentFilter:" + + filter.toString()); + } + } + } + } + + if (count > 0) { + mIntentFilterVerifier.startVerifications(userId); + Slog.d(TAG, "Started " + count + " IntentFilter verification" + + (count > 1 ? "s" : "") + " for userId:" + userId + "!"); + } else { + Slog.d(TAG, "No need to start any IntentFilter verification!"); + if (allHosts.size() > 0 && mSettings.createIntentFilterVerificationIfNeededLPw( + packageName, allHosts) != null) { + scheduleWriteSettingsLocked(); + } + } } - private boolean isForwardLocked(PackageSetting ps) { - return (ps.pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; + private boolean needsNetworkVerificationLPr(ActivityIntentInfo filter) { + final ComponentName cn = filter.activity.getComponentName(); + final String packageName = cn.getPackageName(); + + IntentFilterVerificationInfo ivi = mSettings.getIntentFilterVerificationLPr( + packageName); + if (ivi == null) { + return true; + } + int status = ivi.getStatus(); + switch (status) { + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: + case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: + return true; + + default: + // Nothing to do + return false; + } } private static boolean isMultiArch(PackageSetting ps) { @@ -10752,11 +11403,11 @@ public class PackageManagerService extends IPackageManager.Stub { } private static boolean isPrivilegedApp(PackageParser.Package pkg) { - return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0; + return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; } - private static boolean isSystemApp(ApplicationInfo info) { - return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + private static boolean hasDomainURLs(PackageParser.Package pkg) { + return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0; } private static boolean isSystemApp(PackageSetting ps) { @@ -10767,20 +11418,14 @@ public class PackageManagerService extends IPackageManager.Stub { return (ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } - private static boolean isUpdatedSystemApp(PackageParser.Package pkg) { - return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; - } - - private static boolean isUpdatedSystemApp(ApplicationInfo info) { - return (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; - } - private int packageFlagsToInstallFlags(PackageSetting ps) { int installFlags = 0; - if (isExternal(ps)) { + if (isExternal(ps) && TextUtils.isEmpty(ps.volumeUuid)) { + // This existing package was an external ASEC install when we have + // the external flag without a UUID installFlags |= PackageManager.INSTALL_EXTERNAL; } - if (isForwardLocked(ps)) { + if (ps.isForwardLocked()) { installFlags |= PackageManager.INSTALL_FORWARD_LOCK; } return installFlags; @@ -11033,7 +11678,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { - removeDataDirsLI(packageName); + removeDataDirsLI(ps.volumeUuid, packageName); schedulePackageCleaning(packageName, UserHandle.USER_ALL, true); } // writer @@ -11044,14 +11689,33 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName); outInfo.removedAppId = mSettings.removePackageLPw(packageName); } - if (deletedPs != null) { - updatePermissionsLPw(deletedPs.name, null, 0); - if (deletedPs.sharedUser != null) { - // remove permissions associated with package - mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids); + updatePermissionsLPw(deletedPs.name, null, 0); + if (deletedPs.sharedUser != null) { + // Remove permissions associated with package. Since runtime + // permissions are per user we have to kill the removed package + // or packages running under the shared user of the removed + // package if revoking the permissions requested only by the removed + // package is successful and this causes a change in gids. + for (int userId : UserManagerService.getInstance().getUserIds()) { + final int userIdToKill = mSettings.updateSharedUserPermsLPw(deletedPs, + userId); + if (userIdToKill == UserHandle.USER_ALL + || userIdToKill >= UserHandle.USER_OWNER) { + // If gids changed for this user, kill all affected packages. + mHandler.post(new Runnable() { + @Override + public void run() { + // This has to happen with no lock held. + killSettingPackagesForUser(deletedPs, userIdToKill, + KILL_APP_REASON_GIDS_CHANGED); + } + }); + break; + } } } clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL); + clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL); } // make sure to preserve per-user disabled state if this removal was just // a downgrade of a system app to the factory package @@ -11280,8 +11944,8 @@ public class PackageManagerService extends IPackageManager.Stub { true, //notLaunched false, //hidden null, null, null, - false // blockUninstall - ); + false, // blockUninstall + INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED); if (!isSystemApp(ps)) { if (ps.isAnyInstalled(sUserManager.getUserIds())) { // Other user still have this package installed, so all @@ -11290,7 +11954,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users"); removeUser = user.getIdentifier(); appId = ps.appId; - mSettings.writePackageRestrictionsLPr(removeUser); + scheduleWritePackageRestrictionsLocked(removeUser); } else { // We need to set it back to 'installed' so the uninstall // broadcasts will be sent correctly. @@ -11305,7 +11969,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app"); removeUser = user.getIdentifier(); appId = ps.appId; - mSettings.writePackageRestrictionsLPr(removeUser); + scheduleWritePackageRestrictionsLocked(removeUser); } } } @@ -11319,9 +11983,14 @@ public class PackageManagerService extends IPackageManager.Stub { outInfo.removedAppId = appId; outInfo.removedUsers = new int[] {removeUser}; } - mInstaller.clearUserData(packageName, removeUser); + mInstaller.clearUserData(ps.volumeUuid, packageName, removeUser); removeKeystoreDataIfNeeded(removeUser, appId); schedulePackageCleaning(packageName, removeUser, false); + synchronized (mPackages) { + if (clearPackagePreferredActivitiesLPw(packageName, removeUser)) { + scheduleWritePackageRestrictionsLocked(removeUser); + } + } return true; } @@ -11483,7 +12152,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Always delete data directories for package, even if we found no other // record of app. This helps users recover from UID mismatches without // resorting to a full data wipe. - int retCode = mInstaller.clearUserData(packageName, userId); + int retCode = mInstaller.clearUserData(pkg.volumeUuid, packageName, userId); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); return false; @@ -11504,7 +12173,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkg != null && pkg.applicationInfo.primaryCpuAbi != null && !VMRuntime.is64BitAbi(pkg.applicationInfo.primaryCpuAbi)) { final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir; - if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) { + if (mInstaller.linkNativeLibraryDirectory(pkg.volumeUuid, pkg.packageName, + nativeLibPath, userId) < 0) { Slog.w(TAG, "Failed linking native library dir"); return false; } @@ -11580,7 +12250,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } - int retCode = mInstaller.deleteCacheFiles(packageName, userId); + int retCode = mInstaller.deleteCacheFiles(p.volumeUuid, packageName, userId); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName + " u" + userId); @@ -11634,7 +12304,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (ps != null) { libDirRoot = ps.legacyNativeLibraryPathString; } - if (p != null && (isExternal(p) || isForwardLocked(p))) { + if (p != null && (isExternal(p) || p.isForwardLocked())) { String secureContainerId = cidFromCodePath(p.applicationInfo.getBaseCodePath()); if (secureContainerId != null) { asecPath = PackageHelper.getSdFilesystem(secureContainerId); @@ -11648,7 +12318,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } - if (isForwardLocked(p)) { + if (p.isForwardLocked()) { publicSrcDir = applicationInfo.getBaseResourcePath(); } } @@ -11658,8 +12328,8 @@ public class PackageManagerService extends IPackageManager.Stub { // TODO(multiArch): Extend getSizeInfo to look at *all* instruction sets, not // just the primary. String[] dexCodeInstructionSets = getDexCodeInstructionSets(getAppDexInstructionSets(ps)); - int res = mInstaller.getSizeInfo(packageName, userHandle, p.baseCodePath, libDirRoot, - publicSrcDir, asecPath, dexCodeInstructionSets, pStats); + int res = mInstaller.getSizeInfo(p.volumeUuid, packageName, userHandle, p.baseCodePath, + libDirRoot, publicSrcDir, asecPath, dexCodeInstructionSets, pStats); if (res < 0) { return false; } @@ -11904,6 +12574,19 @@ public class PackageManagerService extends IPackageManager.Stub { return changed; } + /** This method takes a specific user id as well as UserHandle.USER_ALL. */ + void clearIntentFilterVerificationsLPw(String packageName, int userId) { + if (userId == UserHandle.USER_ALL) { + mSettings.removeIntentFilterVerificationLPw(packageName, sUserManager.getUserIds()); + for (int oneUserId : sUserManager.getUserIds()) { + scheduleWritePackageRestrictionsLocked(oneUserId); + } + } else { + mSettings.removeIntentFilterVerificationLPw(packageName, userId); + scheduleWritePackageRestrictionsLocked(userId); + } + } + @Override public void resetPreferredActivities(int userId) { /* TODO: Actually use userId. Why is it being passed in? */ @@ -12013,13 +12696,90 @@ public class PackageManagerService extends IPackageManager.Stub { } } + /** + * Non-Binder method, support for the backup/restore mechanism: write the + * full set of preferred activities in its canonical XML format. Returns true + * on success; false otherwise. + */ + @Override + public byte[] getPreferredActivityBackup(int userId) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system may call getPreferredActivityBackup()"); + } + + ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); + try { + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(dataStream, "utf-8"); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_PREFERRED_BACKUP); + + synchronized (mPackages) { + mSettings.writePreferredActivitiesLPr(serializer, userId, true); + } + + serializer.endTag(null, TAG_PREFERRED_BACKUP); + serializer.endDocument(); + serializer.flush(); + } catch (Exception e) { + if (DEBUG_BACKUP) { + Slog.e(TAG, "Unable to write preferred activities for backup", e); + } + return null; + } + + return dataStream.toByteArray(); + } + + @Override + public void restorePreferredActivities(byte[] backup, int userId) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only the system may call restorePreferredActivities()"); + } + + try { + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new ByteArrayInputStream(backup), null); + + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + } + if (type != XmlPullParser.START_TAG) { + // oops didn't find a start tag?! + if (DEBUG_BACKUP) { + Slog.e(TAG, "Didn't find start tag during restore"); + } + return; + } + + // this is supposed to be TAG_PREFERRED_BACKUP + if (!TAG_PREFERRED_BACKUP.equals(parser.getName())) { + if (DEBUG_BACKUP) { + Slog.e(TAG, "Found unexpected tag " + parser.getName()); + } + return; + } + + // skip interfering stuff, then we're aligned with the backing implementation + while ((type = parser.next()) == XmlPullParser.TEXT) { } + synchronized (mPackages) { + mSettings.readPreferredActivitiesLPw(parser, userId); + } + } catch (Exception e) { + if (DEBUG_BACKUP) { + Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage()); + } + } + } + @Override public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage, - int ownerUserId, int sourceUserId, int targetUserId, int flags) { + int sourceUserId, int targetUserId, int flags) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); int callingUid = Binder.getCallingUid(); - enforceOwnerRights(ownerPackage, ownerUserId, callingUid); + enforceOwnerRights(ownerPackage, callingUid); enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId); if (intentFilter.countActions() == 0) { Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions"); @@ -12027,7 +12787,7 @@ public class PackageManagerService extends IPackageManager.Stub { } synchronized (mPackages) { CrossProfileIntentFilter newFilter = new CrossProfileIntentFilter(intentFilter, - ownerPackage, UserHandle.getUserId(callingUid), targetUserId, flags); + ownerPackage, targetUserId, flags); CrossProfileIntentResolver resolver = mSettings.editCrossProfileIntentResolverLPw(sourceUserId); ArrayList<CrossProfileIntentFilter> existing = resolver.findFilters(intentFilter); @@ -12046,22 +12806,19 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage, - int ownerUserId) { + public void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); int callingUid = Binder.getCallingUid(); - enforceOwnerRights(ownerPackage, ownerUserId, callingUid); + enforceOwnerRights(ownerPackage, callingUid); enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId); - int callingUserId = UserHandle.getUserId(callingUid); synchronized (mPackages) { CrossProfileIntentResolver resolver = mSettings.editCrossProfileIntentResolverLPw(sourceUserId); ArraySet<CrossProfileIntentFilter> set = new ArraySet<CrossProfileIntentFilter>(resolver.filterSet()); for (CrossProfileIntentFilter filter : set) { - if (filter.getOwnerPackage().equals(ownerPackage) - && filter.getOwnerUserId() == callingUserId) { + if (filter.getOwnerPackage().equals(ownerPackage)) { resolver.removeFilter(filter); } } @@ -12070,17 +12827,12 @@ public class PackageManagerService extends IPackageManager.Stub { } // Enforcing that callingUid is owning pkg on userId - private void enforceOwnerRights(String pkg, int userId, int callingUid) { + private void enforceOwnerRights(String pkg, int callingUid) { // The system owns everything. if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) { return; } int callingUserId = UserHandle.getUserId(callingUid); - if (callingUserId != userId) { - throw new SecurityException("calling uid " + callingUid - + " pretends to own " + pkg + " on user " + userId + " but belongs to user " - + callingUserId); - } PackageInfo pi = getPackageInfo(pkg, 0, callingUserId); if (pi == null) { throw new IllegalArgumentException("Unknown package " + pkg + " on user " @@ -12221,7 +12973,7 @@ public class PackageManagerService extends IPackageManager.Stub { return; } } - mSettings.writePackageRestrictionsLPr(userId); + scheduleWritePackageRestrictionsLocked(userId); components = mPendingBroadcasts.get(userId, packageName); final boolean newPackage = components == null; if (newPackage) { @@ -12382,6 +13134,12 @@ public class PackageManagerService extends IPackageManager.Stub { } mPostSystemReadyMessages = null; } + + // Watch for external volumes that come and go over time + final StorageManager storage = mContext.getSystemService(StorageManager.class); + storage.registerListener(mStorageListener); + + mInstallerService.systemReady(); } @Override @@ -12422,6 +13180,8 @@ public class PackageManagerService extends IPackageManager.Stub { public static final int DUMP_KEYSETS = 1 << 11; public static final int DUMP_VERSION = 1 << 12; public static final int DUMP_INSTALLS = 1 << 13; + public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 14; + public static final int DUMP_DOMAIN_PREFERRED = 1 << 15; public static final int OPTION_SHOW_FILTERS = 1 << 0; @@ -12527,6 +13287,8 @@ public class PackageManagerService extends IPackageManager.Stub { pw.println(" write: write current settings now"); pw.println(" <package.name>: info about given package"); pw.println(" installs: details about install sessions"); + pw.println(" d[omain-preferred-apps]: print domains preferred apps"); + pw.println(" i[ntent-filter-verifiers]|ifv: print intent filter verifier info"); return; } else if ("--checkin".equals(opt)) { checkin = true; @@ -12563,6 +13325,8 @@ public class PackageManagerService extends IPackageManager.Stub { fullPreferred = true; opti++; } + } else if ("d".equals(cmd) || "domain-preferred-apps".equals(cmd)) { + dumpState.setDump(DumpState.DUMP_DOMAIN_PREFERRED); } else if ("p".equals(cmd) || "packages".equals(cmd)) { dumpState.setDump(DumpState.DUMP_PACKAGES); } else if ("s".equals(cmd) || "shared-users".equals(cmd)) { @@ -12573,6 +13337,9 @@ 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 ("i".equals(cmd) || "ifv".equals(cmd) + || "intent-filter-verifiers".equals(cmd)) { + dumpState.setDump(DumpState.DUMP_INTENT_FILTER_VERIFIERS); } else if ("version".equals(cmd)) { dumpState.setDump(DumpState.DUMP_VERSION); } else if ("k".equals(cmd) || "keysets".equals(cmd)) { @@ -12628,6 +13395,29 @@ public class PackageManagerService extends IPackageManager.Stub { } } + if (dumpState.isDumping(DumpState.DUMP_INTENT_FILTER_VERIFIERS) && + packageName == null) { + if (mIntentFilterVerifierComponent != null) { + String verifierPackageName = mIntentFilterVerifierComponent.getPackageName(); + if (!checkin) { + if (dumpState.onTitlePrinted()) + pw.println(); + pw.println("Intent Filter Verifier:"); + pw.print(" Using: "); + pw.print(verifierPackageName); + pw.print(" (uid="); + pw.print(getPackageUid(verifierPackageName, 0)); + pw.println(")"); + } else if (verifierPackageName != null) { + pw.print("ifv,"); pw.print(verifierPackageName); + pw.print(","); pw.println(getPackageUid(verifierPackageName, 0)); + } + } else { + pw.println(); + pw.println("No Intent Filter Verifier available!"); + } + } + if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { boolean printedHeader = false; final Iterator<String> it = mSharedLibraries.keySet().iterator(); @@ -12747,6 +13537,65 @@ public class PackageManagerService extends IPackageManager.Stub { } } + if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) { + pw.println(); + int count = mSettings.mPackages.size(); + if (count == 0) { + pw.println("No domain preferred apps!"); + pw.println(); + } else { + final String prefix = " "; + Collection<PackageSetting> allPackageSettings = mSettings.mPackages.values(); + if (allPackageSettings.size() == 0) { + pw.println("No domain preferred apps!"); + pw.println(); + } else { + pw.println("Domain preferred apps status:"); + pw.println(); + count = 0; + for (PackageSetting ps : allPackageSettings) { + IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo(); + if (ivi == null || ivi.getPackageName() == null) continue; + pw.println(prefix + "Package Name: " + ivi.getPackageName()); + pw.println(prefix + "Domains: " + ivi.getDomainsString()); + pw.println(prefix + "Status: " + ivi.getStatusString()); + pw.println(); + count++; + } + if (count == 0) { + pw.println(prefix + "No domain preferred app status!"); + pw.println(); + } + for (int userId : sUserManager.getUserIds()) { + pw.println("Domain preferred apps for User " + userId + ":"); + pw.println(); + count = 0; + for (PackageSetting ps : allPackageSettings) { + IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo(); + if (ivi == null || ivi.getPackageName() == null) { + continue; + } + final int status = ps.getDomainVerificationStatusForUser(userId); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) { + continue; + } + pw.println(prefix + "Package Name: " + ivi.getPackageName()); + pw.println(prefix + "Domains: " + ivi.getDomainsString()); + String statusStr = IntentFilterVerificationInfo. + getStatusStringFromValue(status); + pw.println(prefix + "Status: " + statusStr); + pw.println(); + count++; + } + if (count == 0) { + pw.println(prefix + "No domain preferred apps!"); + pw.println(); + } + } + } + } + } + if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) { mSettings.dumpPermissionsLPr(pw, packageName, dumpState); if (packageName == null) { @@ -12988,7 +13837,7 @@ public class PackageManagerService extends IPackageManager.Stub { } final AsecInstallArgs args = new AsecInstallArgs(cid, - getAppDexInstructionSets(ps), isForwardLocked(ps)); + getAppDexInstructionSets(ps), ps.isForwardLocked()); // The package status is changed only if the code path // matches between settings and the container id. if (ps.codePathString != null @@ -13029,13 +13878,32 @@ public class PackageManagerService extends IPackageManager.Stub { } private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, + ArrayList<ApplicationInfo> infos, IIntentReceiver finishedReceiver) { + final int size = infos.size(); + final String[] packageNames = new String[size]; + final int[] packageUids = new int[size]; + for (int i = 0; i < size; i++) { + final ApplicationInfo info = infos.get(i); + packageNames[i] = info.packageName; + packageUids[i] = info.uid; + } + sendResourcesChangedBroadcast(mediaStatus, replacing, packageNames, packageUids, + finishedReceiver); + } + + private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) { - int size = pkgList.size(); + sendResourcesChangedBroadcast(mediaStatus, replacing, + pkgList.toArray(new String[pkgList.size()]), uidArr, finishedReceiver); + } + + private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, + String[] pkgList, int uidArr[], IIntentReceiver finishedReceiver) { + int size = pkgList.length; if (size > 0) { // Send broadcasts here Bundle extras = new Bundle(); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList - .toArray(new String[size])); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); if (uidArr != null) { extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr); } @@ -13078,8 +13946,8 @@ public class PackageManagerService extends IPackageManager.Stub { } // Parse package int parseFlags = mDefParseFlags; - if (args.isExternal()) { - parseFlags |= PackageParser.PARSE_ON_SDCARD; + if (args.isExternalAsec()) { + parseFlags |= PackageParser.PARSE_EXTERNAL_STORAGE; } if (args.isFwdLocked()) { parseFlags |= PackageParser.PARSE_FORWARD_LOCK; @@ -13226,75 +14094,164 @@ public class PackageManagerService extends IPackageManager.Stub { } } - /** Binder call */ + private void loadPrivatePackages(VolumeInfo vol) { + final ArrayList<ApplicationInfo> loaded = new ArrayList<>(); + final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE; + synchronized (mPackages) { + final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid); + for (PackageSetting ps : packages) { + synchronized (mInstallLock) { + final PackageParser.Package pkg; + try { + pkg = scanPackageLI(ps.codePath, parseFlags, 0, 0, null); + loaded.add(pkg.applicationInfo); + } catch (PackageManagerException e) { + Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage()); + } + } + } + + // TODO: regrant any permissions that changed based since original install + + mSettings.writeLPr(); + } + + Slog.d(TAG, "Loaded packages " + loaded); + sendResourcesChangedBroadcast(true, false, loaded, null); + } + + private void unloadPrivatePackages(VolumeInfo vol) { + final ArrayList<ApplicationInfo> unloaded = new ArrayList<>(); + synchronized (mPackages) { + final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid); + for (PackageSetting ps : packages) { + if (ps.pkg == null) continue; + synchronized (mInstallLock) { + final ApplicationInfo info = ps.pkg.applicationInfo; + final PackageRemovedInfo outInfo = new PackageRemovedInfo(); + if (deletePackageLI(ps.name, null, false, null, null, + PackageManager.DELETE_KEEP_DATA, outInfo, false)) { + unloaded.add(info); + } else { + Slog.w(TAG, "Failed to unload " + ps.codePath); + } + } + } + + mSettings.writeLPr(); + } + + Slog.d(TAG, "Unloaded packages " + unloaded); + sendResourcesChangedBroadcast(false, false, unloaded, null); + } + @Override - public void movePackage(final String packageName, final IPackageMoveObserver observer, - final int flags) { + public int movePackage(final String packageName, final String volumeUuid) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); - UserHandle user = new UserHandle(UserHandle.getCallingUserId()); - int returnCode = PackageManager.MOVE_SUCCEEDED; - int currInstallFlags = 0; - int newInstallFlags = 0; - File codeFile = null; - String installerPackageName = null; - String packageAbiOverride = null; + final int moveId = mNextMoveId.getAndIncrement(); + try { + movePackageInternal(packageName, volumeUuid, moveId); + } catch (PackageManagerException e) { + Slog.d(TAG, "Failed to move " + packageName, e); + mMoveCallbacks.notifyStatusChanged(moveId, PackageManager.MOVE_FAILED_INTERNAL_ERROR); + } + return moveId; + } + + private void movePackageInternal(final String packageName, final String volumeUuid, + final int moveId) throws PackageManagerException { + final UserHandle user = new UserHandle(UserHandle.getCallingUserId()); + final PackageManager pm = mContext.getPackageManager(); + + final boolean currentAsec; + final String currentVolumeUuid; + final File codeFile; + final String installerPackageName; + final String packageAbiOverride; + final int appId; + final String seinfo; // reader synchronized (mPackages) { final PackageParser.Package pkg = mPackages.get(packageName); final PackageSetting ps = mSettings.mPackages.get(packageName); if (pkg == null || ps == null) { - returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; - } else { - // Disable moving fwd locked apps and system packages - if (pkg.applicationInfo != null && isSystemApp(pkg)) { - Slog.w(TAG, "Cannot move system application"); - returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; - } else if (pkg.mOperationPending) { - Slog.w(TAG, "Attempt to move package which has pending operations"); - returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING; - } else { - // Find install location first - if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 - && (flags & PackageManager.MOVE_INTERNAL) != 0) { - Slog.w(TAG, "Ambigous flags specified for move location."); - returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; - } else { - newInstallFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 - ? PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL; - currInstallFlags = isExternal(pkg) - ? PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL; - - if (newInstallFlags == currInstallFlags) { - Slog.w(TAG, "No move required. Trying to move to same location"); - returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; - } else { - if (isForwardLocked(pkg)) { - currInstallFlags |= PackageManager.INSTALL_FORWARD_LOCK; - newInstallFlags |= PackageManager.INSTALL_FORWARD_LOCK; - } - } - } - if (returnCode == PackageManager.MOVE_SUCCEEDED) { - pkg.mOperationPending = true; - } - } + throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); + } - codeFile = new File(pkg.codePath); - installerPackageName = ps.installerPackageName; - packageAbiOverride = ps.cpuAbiOverrideString; + if (pkg.applicationInfo.isSystemApp()) { + throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE, + "Cannot move system application"); + } else if (pkg.mOperationPending) { + throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING, + "Attempt to move package which has pending operations"); } + + // TODO: yell if already in desired location + + mMoveCallbacks.notifyStarted(moveId, + String.valueOf(pm.getApplicationLabel(pkg.applicationInfo))); + + pkg.mOperationPending = true; + + currentAsec = pkg.applicationInfo.isForwardLocked() + || pkg.applicationInfo.isExternalAsec(); + currentVolumeUuid = ps.volumeUuid; + codeFile = new File(pkg.codePath); + installerPackageName = ps.installerPackageName; + packageAbiOverride = ps.cpuAbiOverrideString; + appId = UserHandle.getAppId(pkg.applicationInfo.uid); + seinfo = pkg.applicationInfo.seinfo; } - if (returnCode != PackageManager.MOVE_SUCCEEDED) { - try { - observer.packageMoved(packageName, returnCode); - } catch (RemoteException ignored) { + int installFlags; + final boolean moveData; + + if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) { + installFlags = INSTALL_INTERNAL; + moveData = !currentAsec; + } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) { + installFlags = INSTALL_EXTERNAL; + moveData = false; + } else { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + final VolumeInfo volume = storage.findVolumeByUuid(volumeUuid); + if (volume == null || volume.getType() != VolumeInfo.TYPE_PRIVATE + || !volume.isMountedWritable()) { + throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, + "Move location not mounted private volume"); } - return; + + Preconditions.checkState(!currentAsec); + + installFlags = INSTALL_INTERNAL; + moveData = true; } + Slog.d(TAG, "Moving " + packageName + " from " + currentVolumeUuid + " to " + volumeUuid); + mMoveCallbacks.notifyStatusChanged(moveId, 10, -1); + + if (moveData) { + synchronized (mInstallLock) { + // TODO: split this into separate copy and delete operations + if (mInstaller.moveUserDataDirs(currentVolumeUuid, volumeUuid, packageName, appId, + seinfo) != 0) { + synchronized (mPackages) { + final PackageParser.Package pkg = mPackages.get(packageName); + if (pkg != null) { + pkg.mOperationPending = false; + } + } + + throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR, + "Failed to move private data"); + } + } + } + + mMoveCallbacks.notifyStatusChanged(moveId, 50); + final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() { @Override public void onUserActionRequired(Intent intent) throws RemoteException { @@ -13320,13 +14277,16 @@ public class PackageManagerService extends IPackageManager.Stub { final int status = PackageManager.installStatusToPublicStatus(returnCode); switch (status) { case PackageInstaller.STATUS_SUCCESS: - observer.packageMoved(packageName, PackageManager.MOVE_SUCCEEDED); + mMoveCallbacks.notifyStatusChanged(moveId, + PackageManager.MOVE_SUCCEEDED); break; case PackageInstaller.STATUS_FAILURE_STORAGE: - observer.packageMoved(packageName, PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE); + mMoveCallbacks.notifyStatusChanged(moveId, + PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE); break; default: - observer.packageMoved(packageName, PackageManager.MOVE_FAILED_INTERNAL_ERROR); + mMoveCallbacks.notifyStatusChanged(moveId, + PackageManager.MOVE_FAILED_INTERNAL_ERROR); break; } } @@ -13334,16 +14294,49 @@ public class PackageManagerService extends IPackageManager.Stub { // Treat a move like reinstalling an existing app, which ensures that we // process everythign uniformly, like unpacking native libraries. - newInstallFlags |= PackageManager.INSTALL_REPLACE_EXISTING; + installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; final Message msg = mHandler.obtainMessage(INIT_COPY); final OriginInfo origin = OriginInfo.fromExistingFile(codeFile); - msg.obj = new InstallParams(origin, installObserver, newInstallFlags, - installerPackageName, null, user, packageAbiOverride); + msg.obj = new InstallParams(origin, installObserver, installFlags, + installerPackageName, volumeUuid, null, user, packageAbiOverride); mHandler.sendMessage(msg); } @Override + public int movePrimaryStorage(String volumeUuid) throws RemoteException { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); + + final int moveId = mNextMoveId.getAndIncrement(); + + // TODO: ask mountservice to take down both, connect over to DCS to + // migrate, and then bring up new storage + + return moveId; + } + + @Override + public int getMoveStatus(int moveId) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + return mMoveCallbacks.mLastStatus.get(moveId); + } + + @Override + public void registerMoveCallback(IPackageMoveObserver callback) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + mMoveCallbacks.register(callback); + } + + @Override + public void unregisterMoveCallback(IPackageMoveObserver callback) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + mMoveCallbacks.unregister(callback); + } + + @Override public boolean setInstallLocation(int loc) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, null); @@ -13375,7 +14368,15 @@ public class PackageManagerService extends IPackageManager.Stub { // Technically, we shouldn't be doing this with the package lock // held. However, this is very rare, and there is already so much // other disk I/O going on, that we'll let it slide for now. - mInstaller.removeUserDataDirs(userHandle); + final StorageManager storage = StorageManager.from(mContext); + final List<VolumeInfo> vols = storage.getVolumes(); + for (VolumeInfo vol : vols) { + if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) { + final String volumeUuid = vol.getFsUuid(); + Slog.d(TAG, "Removing user data on volume " + volumeUuid); + mInstaller.removeUserDataDirs(volumeUuid, userHandle); + } + } } mUserNeedsBadging.delete(userHandle); removeUnusedPackagesLILPw(userManager, userHandle); @@ -13435,6 +14436,11 @@ public class PackageManagerService extends IPackageManager.Stub { } } + void newUserCreatedLILPw(int userHandle) { + // Adding a user requires updating runtime permissions for system apps. + updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL); + } + @Override public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException { mContext.enforceCallingOrSelfPermission( @@ -13653,4 +14659,82 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + + private static class MoveCallbacks extends Handler { + private static final int MSG_STARTED = 1; + private static final int MSG_STATUS_CHANGED = 2; + + private final RemoteCallbackList<IPackageMoveObserver> + mCallbacks = new RemoteCallbackList<>(); + + private final SparseIntArray mLastStatus = new SparseIntArray(); + + public MoveCallbacks(Looper looper) { + super(looper); + } + + public void register(IPackageMoveObserver callback) { + mCallbacks.register(callback); + } + + public void unregister(IPackageMoveObserver callback) { + mCallbacks.unregister(callback); + } + + @Override + public void handleMessage(Message msg) { + final SomeArgs args = (SomeArgs) msg.obj; + final int n = mCallbacks.beginBroadcast(); + for (int i = 0; i < n; i++) { + final IPackageMoveObserver callback = mCallbacks.getBroadcastItem(i); + try { + invokeCallback(callback, msg.what, args); + } catch (RemoteException ignored) { + } + } + mCallbacks.finishBroadcast(); + args.recycle(); + } + + private void invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args) + throws RemoteException { + switch (what) { + case MSG_STARTED: { + callback.onStarted(args.argi1, (String) args.arg2); + break; + } + case MSG_STATUS_CHANGED: { + callback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3); + break; + } + } + } + + private void notifyStarted(int moveId, String title) { + Slog.v(TAG, "Move " + moveId + " started with title " + title); + + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.arg2 = title; + obtainMessage(MSG_STARTED, args).sendToTarget(); + } + + private void notifyStatusChanged(int moveId, int status) { + notifyStatusChanged(moveId, status, -1); + } + + private void notifyStatusChanged(int moveId, int status, long estMillis) { + Slog.v(TAG, "Move " + moveId + " status " + status); + + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.argi2 = status; + args.arg3 = estMillis; + obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget(); + + synchronized (mLastStatus) { + mLastStatus.put(moveId, status); + } + } + } } diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 696aa34..e7c0ef7 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -32,10 +32,10 @@ final class PackageSetting extends PackageSettingBase { PackageSetting(String name, String realName, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, String cpuAbiOverrideString, - int pVersionCode, int pkgFlags) { + int pVersionCode, int pkgFlags, int privateFlags) { super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, - pVersionCode, pkgFlags); + pVersionCode, pkgFlags, privateFlags); } /** @@ -57,11 +57,25 @@ final class PackageSetting extends PackageSettingBase { + " " + name + "/" + appId + "}"; } - public int[] getGids() { - return sharedUser != null ? sharedUser.gids : gids; + public PermissionsState getPermissionsState() { + return (sharedUser != null) + ? sharedUser.getPermissionsState() + : super.getPermissionsState(); } public boolean isPrivileged() { - return (pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0; + return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0; + } + + public boolean isForwardLocked() { + return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0; + } + + public boolean isSystem() { + return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + public boolean isSharedUser() { + return sharedUser != null; } } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 1dcadb4..5429517 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -20,7 +20,11 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import android.content.pm.IntentFilterVerificationInfo; +import android.content.pm.PackageManager; import android.content.pm.PackageUserState; +import android.os.storage.VolumeInfo; +import android.text.TextUtils; import android.util.ArraySet; import android.util.SparseArray; @@ -29,7 +33,7 @@ import java.io.File; /** * Settings base class for pending and resolved classes. */ -class PackageSettingBase extends GrantedPermissions { +abstract class PackageSettingBase extends SettingBase { /** * Indicates the state of installation. Used by PackageManager to figure out * incomplete installations. Say a package is being installed (the state is @@ -92,8 +96,7 @@ class PackageSettingBase extends GrantedPermissions { PackageSignatures signatures = new PackageSignatures(); - boolean permissionsFixed; - boolean haveGids; + boolean installPermissionsFixed; PackageKeySetData keySetData = new PackageKeySetData(); @@ -107,13 +110,18 @@ class PackageSettingBase extends GrantedPermissions { PackageSettingBase origPackage; - /* package name of the app that installed this package */ + /** Package name of the app that installed this package */ String installerPackageName; + /** UUID of {@link VolumeInfo} hosting this app */ + String volumeUuid; + + IntentFilterVerificationInfo verificationInfo; + PackageSettingBase(String name, String realName, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, String cpuAbiOverrideString, - int pVersionCode, int pkgFlags) { - super(pkgFlags); + int pVersionCode, int pkgFlags, int pkgPrivateFlags) { + super(pkgFlags, pkgPrivateFlags); this.name = name; this.realName = realName; init(codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, @@ -146,8 +154,7 @@ class PackageSettingBase extends GrantedPermissions { signatures = new PackageSignatures(base.signatures); - permissionsFixed = base.permissionsFixed; - haveGids = base.haveGids; + installPermissionsFixed = base.installPermissionsFixed; userState.clear(); for (int i=0; i<base.userState.size(); i++) { userState.put(base.userState.keyAt(i), @@ -158,9 +165,9 @@ class PackageSettingBase extends GrantedPermissions { origPackage = base.origPackage; installerPackageName = base.installerPackageName; + volumeUuid = base.volumeUuid; keySetData = new PackageKeySetData(base.keySetData); - } void init(File codePath, File resourcePath, String legacyNativeLibraryPathString, @@ -181,10 +188,18 @@ class PackageSettingBase extends GrantedPermissions { installerPackageName = packageName; } - String getInstallerPackageName() { + public String getInstallerPackageName() { return installerPackageName; } + public void setVolumeUuid(String volumeUuid) { + this.volumeUuid = volumeUuid; + } + + public String getVolumeUuid() { + return volumeUuid; + } + public void setInstallStatus(int newStatus) { installStatus = newStatus; } @@ -201,9 +216,8 @@ class PackageSettingBase extends GrantedPermissions { * Make a shallow copy of this package settings. */ public void copyFrom(PackageSettingBase base) { - grantedPermissions = base.grantedPermissions; - gids = base.gids; - + setPermissionsUpdatedForUserIds(base.getPermissionsUpdatedForUserIds()); + mPermissionsState.copyFrom(base.mPermissionsState); primaryCpuAbiString = base.primaryCpuAbiString; secondaryCpuAbiString = base.secondaryCpuAbiString; cpuAbiOverrideString = base.cpuAbiOverrideString; @@ -211,14 +225,14 @@ class PackageSettingBase extends GrantedPermissions { firstInstallTime = base.firstInstallTime; lastUpdateTime = base.lastUpdateTime; signatures = base.signatures; - permissionsFixed = base.permissionsFixed; - haveGids = base.haveGids; + installPermissionsFixed = base.installPermissionsFixed; userState.clear(); for (int i=0; i<base.userState.size(); i++) { userState.put(base.userState.keyAt(i), base.userState.valueAt(i)); } installStatus = base.installStatus; keySetData = base.keySetData; + verificationInfo = base.verificationInfo; } private PackageUserState modifyUserState(int userId) { @@ -322,7 +336,7 @@ class PackageSettingBase extends GrantedPermissions { void setUserState(int userId, int enabled, boolean installed, boolean stopped, boolean notLaunched, boolean hidden, String lastDisableAppCaller, ArraySet<String> enabledComponents, - ArraySet<String> disabledComponents, boolean blockUninstall) { + ArraySet<String> disabledComponents, boolean blockUninstall, int domainVerifState) { PackageUserState state = modifyUserState(userId); state.enabled = enabled; state.installed = installed; @@ -333,6 +347,7 @@ class PackageSettingBase extends GrantedPermissions { state.enabledComponents = enabledComponents; state.disabledComponents = disabledComponents; state.blockUninstall = blockUninstall; + state.domainVerificationStatus = domainVerifState; } ArraySet<String> getEnabledComponents(int userId) { @@ -420,4 +435,25 @@ class PackageSettingBase extends GrantedPermissions { void removeUser(int userId) { userState.delete(userId); } + + IntentFilterVerificationInfo getIntentFilterVerificationInfo() { + return verificationInfo; + } + + void setIntentFilterVerificationInfo(IntentFilterVerificationInfo info) { + verificationInfo = info; + } + + int getDomainVerificationStatusForUser(int userId) { + return readUserState(userId).domainVerificationStatus; + } + + void setDomainVerificationStatusForUser(int status, int userId) { + modifyUserState(userId).domainVerificationStatus = status; + } + + void clearDomainVerificationStatusForUser(int userId) { + modifyUserState(userId).domainVerificationStatus = + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + } } diff --git a/services/core/java/com/android/server/pm/PendingPackage.java b/services/core/java/com/android/server/pm/PendingPackage.java index 5d30e76..bb0dba1 100644 --- a/services/core/java/com/android/server/pm/PendingPackage.java +++ b/services/core/java/com/android/server/pm/PendingPackage.java @@ -24,10 +24,10 @@ final class PendingPackage extends PackageSettingBase { PendingPackage(String name, String realName, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, String cpuAbiOverrideString, int sharedId, - int pVersionCode, int pkgFlags) { + int pVersionCode, int pkgFlags, int pkgPrivateFlags) { super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, - pVersionCode, pkgFlags); + pVersionCode, pkgFlags, pkgPrivateFlags); this.sharedId = sharedId; } } diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java new file mode 100644 index 0000000..3749957 --- /dev/null +++ b/services/core/java/com/android/server/pm/PermissionsState.java @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2015 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.os.UserHandle; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.util.ArrayUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +/** + * This class encapsulates the permissions for a package or a shared user. + * <p> + * There are two types of permissions: install (granted at installation) + * and runtime (granted at runtime). Install permissions are granted to + * all device users while runtime permissions are granted explicitly to + * specific users. + * </p> + * <p> + * The permissions are kept on a per device user basis. For example, an + * application may have some runtime permissions granted under the device + * owner but not granted under the secondary user. + * <p> + * This class is also responsible for keeping track of the Linux gids per + * user for a package or a shared user. The gids are computed as a set of + * the gids for all granted permissions' gids on a per user basis. + * </p> + */ +public final class PermissionsState { + + /** The permission operation succeeded and no gids changed. */ + public static final int PERMISSION_OPERATION_SUCCESS = 1; + + /** The permission operation succeeded and gids changed. */ + public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 2; + + /** The permission operation failed. */ + public static final int PERMISSION_OPERATION_FAILURE = 3; + + public static final int[] USERS_ALL = {UserHandle.USER_ALL}; + + public static final int[] USERS_NONE = {}; + + private static final int[] NO_GIDS = {}; + + private static final int FLAG_INSTALL_PERMISSIONS = 1 << 0; + private static final int FLAG_RUNTIME_PERMISSIONS = 1 << 1; + + private ArrayMap<String, PermissionData> mPermissions; + + private int[] mGlobalGids = NO_GIDS; + + public PermissionsState() { + /* do nothing */ + } + + public PermissionsState(PermissionsState prototype) { + copyFrom(prototype); + } + + /** + * Sets the global gids, applicable to all users. + * + * @param globalGids The global gids. + */ + public void setGlobalGids(int[] globalGids) { + if (!ArrayUtils.isEmpty(globalGids)) { + mGlobalGids = Arrays.copyOf(globalGids, globalGids.length); + } + } + + /** + * Initialized this instance from another one. + * + * @param other The other instance. + */ + public void copyFrom(PermissionsState other) { + if (other == this) { + return; + } + if (mPermissions != null) { + if (other.mPermissions == null) { + mPermissions = null; + } else { + mPermissions.clear(); + } + } + if (other.mPermissions != null) { + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + final int permissionCount = other.mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String name = other.mPermissions.keyAt(i); + PermissionData permissionData = other.mPermissions.valueAt(i); + mPermissions.put(name, new PermissionData(permissionData)); + } + } + + mGlobalGids = NO_GIDS; + if (other.mGlobalGids != NO_GIDS) { + mGlobalGids = Arrays.copyOf(other.mGlobalGids, + other.mGlobalGids.length); + } + } + + /** + * Grant an install permission. + * + * @param permission The permission to grant. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int grantInstallPermission(BasePermission permission) { + return grantPermission(permission, UserHandle.USER_ALL); + } + + /** + * Revoke an install permission. + * + * @param permission The permission to revoke. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int revokeInstallPermission(BasePermission permission) { + return revokePermission(permission, UserHandle.USER_ALL); + } + + /** + * Grant a runtime permission. + * + * @param permission The permission to grant. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int grantRuntimePermission(BasePermission permission, int userId) { + if (userId == UserHandle.USER_ALL) { + return PERMISSION_OPERATION_FAILURE; + } + return grantPermission(permission, userId); + } + + /** + * Revoke a runtime permission for a given device user. + * + * @param permission The permission to revoke. + * @param userId The device user id. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int revokeRuntimePermission(BasePermission permission, int userId) { + if (userId == UserHandle.USER_ALL) { + return PERMISSION_OPERATION_FAILURE; + } + return revokePermission(permission, userId); + } + + /** + * Gets whether this state has a given permission, regardless if + * it is install time or runtime one. + * + * @param name The permission name. + * @return Whether this state has the permission. + */ + public boolean hasPermission(String name) { + return mPermissions != null && mPermissions.get(name) != null; + } + + /** + * Gets whether this state has a given runtime permission for a + * given device user id. + * + * @param name The permission name. + * @param userId The device user id. + * @return Whether this state has the permission. + */ + public boolean hasRuntimePermission(String name, int userId) { + return !hasInstallPermission(name) && hasPermission(name, userId); + } + + /** + * Gets whether this state has a given install permission. + * + * @param name The permission name. + * @return Whether this state has the permission. + */ + public boolean hasInstallPermission(String name) { + return hasPermission(name, UserHandle.USER_ALL); + } + + /** + * Revokes a permission for all users regardless if it is an install or + * a runtime permission. + * + * @param permission The permission to revoke. + * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS}, + * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link + * #PERMISSION_OPERATION_FAILURE}. + */ + public int revokePermission(BasePermission permission) { + if (!hasPermission(permission.name)) { + return PERMISSION_OPERATION_FAILURE; + } + + int result = PERMISSION_OPERATION_SUCCESS; + + PermissionData permissionData = mPermissions.get(permission.name); + for (int userId : permissionData.getUserIds()) { + if (revokePermission(permission, userId) + == PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { + result = PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; + break; + } + } + + mPermissions.remove(permission.name); + + return result; + } + + /** + * Gets whether the state has a given permission for the specified + * user, regardless if this is an install or a runtime permission. + * + * @param name The permission name. + * @param userId The device user id. + * @return Whether the user has the permission. + */ + public boolean hasPermission(String name, int userId) { + enforceValidUserId(userId); + + if (mPermissions == null) { + return false; + } + + PermissionData permissionData = mPermissions.get(name); + return permissionData != null && permissionData.hasUserId(userId); + } + + /** + * Gets all permissions regardless if they are install or runtime. + * + * @return The permissions or an empty set. + */ + public Set<String> getPermissions() { + if (mPermissions != null) { + return mPermissions.keySet(); + } + + return Collections.emptySet(); + } + + /** + * Gets all permissions for a given device user id regardless if they + * are install time or runtime permissions. + * + * @param userId The device user id. + * @return The permissions or an empty set. + */ + public Set<String> getPermissions(int userId) { + return getPermissionsInternal(FLAG_INSTALL_PERMISSIONS | FLAG_RUNTIME_PERMISSIONS, userId); + } + + /** + * Gets all runtime permissions. + * + * @return The permissions or an empty set. + */ + public Set<String> getRuntimePermissions(int userId) { + return getPermissionsInternal(FLAG_RUNTIME_PERMISSIONS, userId); + } + + /** + * Gets all install permissions. + * + * @return The permissions or an empty set. + */ + public Set<String> getInstallPermissions() { + return getPermissionsInternal(FLAG_INSTALL_PERMISSIONS, UserHandle.USER_ALL); + } + + /** + * Compute the Linux gids for a given device user from the permissions + * granted to this user. Note that these are computed to avoid additional + * state as they are rarely accessed. + * + * @param userId The device user id. + * @return The gids for the device user. + */ + public int[] computeGids(int userId) { + enforceValidUserId(userId); + + int[] gids = mGlobalGids; + + if (mPermissions != null) { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String permission = mPermissions.keyAt(i); + if (!hasPermission(permission, userId)) { + continue; + } + PermissionData permissionData = mPermissions.valueAt(i); + final int[] permGids = permissionData.computeGids(userId); + if (permGids != NO_GIDS) { + gids = appendInts(gids, permGids); + } + } + } + + return gids; + } + + /** + * Compute the Linux gids for all device users from the permissions + * granted to these users. + * + * @return The gids for all device users. + */ + public int[] computeGids() { + int[] gids = mGlobalGids; + + for (int userId : UserManagerService.getInstance().getUserIds()) { + final int[] userGids = computeGids(userId); + gids = appendInts(gids, userGids); + } + + return gids; + } + + /** + * Resets the internal state of this object. + */ + public void reset() { + mGlobalGids = NO_GIDS; + mPermissions = null; + } + + private Set<String> getPermissionsInternal(int flags, int userId) { + enforceValidUserId(userId); + + if (mPermissions == null) { + return Collections.emptySet(); + } + + if (userId == UserHandle.USER_ALL) { + flags = FLAG_INSTALL_PERMISSIONS; + } + + Set<String> permissions = new ArraySet<>(); + + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String permission = mPermissions.keyAt(i); + + if ((flags & FLAG_INSTALL_PERMISSIONS) != 0) { + if (hasInstallPermission(permission)) { + permissions.add(permission); + } + } + if ((flags & FLAG_RUNTIME_PERMISSIONS) != 0) { + if (hasRuntimePermission(permission, userId)) { + permissions.add(permission); + } + } + } + + return permissions; + } + + private int grantPermission(BasePermission permission, int userId) { + if (hasPermission(permission.name, userId)) { + return PERMISSION_OPERATION_FAILURE; + } + + final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId)); + final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; + + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + + PermissionData permissionData = mPermissions.get(permission.name); + if (permissionData == null) { + permissionData = new PermissionData(permission); + mPermissions.put(permission.name, permissionData); + } + + if (!permissionData.addUserId(userId)) { + return PERMISSION_OPERATION_FAILURE; + } + + if (hasGids) { + final int[] newGids = computeGids(userId); + if (oldGids.length != newGids.length) { + return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; + } + } + + return PERMISSION_OPERATION_SUCCESS; + } + + private int revokePermission(BasePermission permission, int userId) { + if (!hasPermission(permission.name, userId)) { + return PERMISSION_OPERATION_FAILURE; + } + + final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId)); + final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; + + PermissionData permissionData = mPermissions.get(permission.name); + + if (!permissionData.removeUserId(userId)) { + return PERMISSION_OPERATION_FAILURE; + } + + if (permissionData.getUserIds() == USERS_NONE) { + mPermissions.remove(permission.name); + } + + if (mPermissions.isEmpty()) { + mPermissions = null; + } + + if (hasGids) { + final int[] newGids = computeGids(userId); + if (oldGids.length != newGids.length) { + return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED; + } + } + + return PERMISSION_OPERATION_SUCCESS; + } + + private static int[] appendInts(int[] current, int[] added) { + if (current != null && added != null) { + for (int guid : added) { + current = ArrayUtils.appendInt(current, guid); + } + } + return current; + } + + private static void enforceValidUserId(int userId) { + if (userId != UserHandle.USER_ALL && userId < 0) { + throw new IllegalArgumentException("Invalid userId:" + userId); + } + } + + private static final class PermissionData { + private final BasePermission mPerm; + private int[] mUserIds = USERS_NONE; + + public PermissionData(BasePermission perm) { + mPerm = perm; + } + + public PermissionData(PermissionData other) { + this(other.mPerm); + + if (other.mUserIds == USERS_ALL || other.mUserIds == USERS_NONE) { + mUserIds = other.mUserIds; + } else { + mUserIds = Arrays.copyOf(other.mUserIds, other.mUserIds.length); + } + } + + public int[] computeGids(int userId) { + return mPerm.computeGids(userId); + } + + public int[] getUserIds() { + return mUserIds; + } + + public boolean hasUserId(int userId) { + if (mUserIds == USERS_ALL) { + return true; + } + + if (userId != UserHandle.USER_ALL) { + return ArrayUtils.contains(mUserIds, userId); + } + + return false; + } + + public boolean addUserId(int userId) { + if (hasUserId(userId)) { + return false; + } + + if (userId == UserHandle.USER_ALL) { + mUserIds = USERS_ALL; + return true; + } + + mUserIds = ArrayUtils.appendInt(mUserIds, userId); + + return true; + } + + public boolean removeUserId(int userId) { + if (!hasUserId(userId)) { + return false; + } + + if (mUserIds == USERS_ALL) { + mUserIds = UserManagerService.getInstance().getUserIds(); + } + + mUserIds = ArrayUtils.removeInt(mUserIds, userId); + + if (mUserIds.length == 0) { + mUserIds = USERS_NONE; + } + + return true; + } + } +} diff --git a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java index 9c8a9bd..ef29cb3 100644 --- a/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java +++ b/services/core/java/com/android/server/pm/PersistentPreferredIntentResolver.java @@ -16,8 +16,6 @@ package com.android.server.pm; -import java.io.PrintWriter; - import com.android.server.IntentResolver; public class PersistentPreferredIntentResolver diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 81302b9..c75a1d3 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -16,15 +16,12 @@ package com.android.server.pm; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; import android.content.pm.Signature; import android.os.Environment; import android.util.Slog; import android.util.Xml; -import com.android.internal.util.XmlUtils; - import libcore.io.IoUtils; import java.io.File; @@ -35,27 +32,36 @@ import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; /** - * Centralized access to SELinux MMAC (middleware MAC) implementation. + * Centralized access to SELinux MMAC (middleware MAC) implementation. This + * class is responsible for loading the appropriate mac_permissions.xml file + * as well as providing an interface for assigning seinfo values to apks. + * * {@hide} */ public final class SELinuxMMAC { - private static final String TAG = "SELinuxMMAC"; + static final String TAG = "SELinuxMMAC"; private static final boolean DEBUG_POLICY = false; private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false; + private static final boolean DEBUG_POLICY_ORDER = DEBUG_POLICY || false; - // Signature seinfo values read from policy. - private static HashMap<Signature, Policy> sSigSeinfo = new HashMap<Signature, Policy>(); - - // Default seinfo read from policy. - private static String sDefaultSeinfo = null; + // All policy stanzas read from mac_permissions.xml. This is also the lock + // to synchronize access during policy load and access attempts. + private static List<Policy> sPolicies = new ArrayList<>(); // Data policy override version file. private static final String DATA_VERSION_FILE = @@ -94,285 +100,272 @@ public final class SELinuxMMAC { private static final String SEAPP_HASH_FILE = Environment.getDataDirectory().toString() + "/system/seapp_hash"; - - // Signature policy stanzas - static class Policy { - private String seinfo; - private final HashMap<String, String> pkgMap; - - Policy() { - seinfo = null; - pkgMap = new HashMap<String, String>(); - } - - void putSeinfo(String seinfoValue) { - seinfo = seinfoValue; - } - - void putPkg(String pkg, String seinfoValue) { - pkgMap.put(pkg, seinfoValue); - } - - // Valid policy stanza means there exists a global - // seinfo value or at least one package policy. - boolean isValid() { - return (seinfo != null) || (!pkgMap.isEmpty()); - } - - String checkPolicy(String pkgName) { - // Check for package name seinfo value first. - String seinfoValue = pkgMap.get(pkgName); - if (seinfoValue != null) { - return seinfoValue; - } - - // Return the global seinfo value. - return seinfo; - } - } - - private static void flushInstallPolicy() { - sSigSeinfo.clear(); - sDefaultSeinfo = null; - } - + /** + * Load the mac_permissions.xml file containing all seinfo assignments used to + * label apps. The loaded mac_permissions.xml file is determined by the + * MAC_PERMISSIONS class variable which is set at class load time which itself + * is based on the USE_OVERRIDE_POLICY class variable. For further guidance on + * the proper structure of a mac_permissions.xml file consult the source code + * located at external/sepolicy/mac_permissions.xml. + * + * @return boolean indicating if policy was correctly loaded. A value of false + * typically indicates a structural problem with the xml or incorrectly + * constructed policy stanzas. A value of true means that all stanzas + * were loaded successfully; no partial loading is possible. + */ public static boolean readInstallPolicy() { - // Temp structures to hold the rules while we parse the xml file. - // We add all the rules together once we know there's no structural problems. - HashMap<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>(); - String defaultSeinfo = null; + // Temp structure to hold the rules while we parse the xml file + List<Policy> policies = new ArrayList<>(); FileReader policyFile = null; + XmlPullParser parser = Xml.newPullParser(); try { policyFile = new FileReader(MAC_PERMISSIONS); Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS); - XmlPullParser parser = Xml.newPullParser(); parser.setInput(policyFile); + parser.nextTag(); + parser.require(XmlPullParser.START_TAG, null, "policy"); - XmlUtils.beginDocument(parser, "policy"); - while (true) { - XmlUtils.nextElement(parser); - if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { - break; + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; } - String tagName = parser.getName(); - if ("signer".equals(tagName)) { - String cert = parser.getAttributeValue(null, "signature"); - if (cert == null) { - Slog.w(TAG, "<signer> without signature at " - + parser.getPositionDescription()); - XmlUtils.skipCurrentTag(parser); - continue; - } - Signature signature; - try { - signature = new Signature(cert); - } catch (IllegalArgumentException e) { - Slog.w(TAG, "<signer> with bad signature at " - + parser.getPositionDescription(), e); - XmlUtils.skipCurrentTag(parser); - continue; - } - Policy policy = readPolicyTags(parser); - if (policy.isValid()) { - sigSeinfo.put(signature, policy); - } - } else if ("default".equals(tagName)) { - // Value is null if default tag is absent or seinfo tag is malformed. - defaultSeinfo = readSeinfoTag(parser); - if (DEBUG_POLICY_INSTALL) - Slog.i(TAG, "<default> tag assigned seinfo=" + defaultSeinfo); - - } else { - XmlUtils.skipCurrentTag(parser); + switch (parser.getName()) { + case "signer": + policies.add(readSignerOrThrow(parser)); + break; + case "default": + policies.add(readDefaultOrThrow(parser)); + break; + default: + skip(parser); } } - } catch (XmlPullParserException xpe) { - Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, xpe); + } catch (IllegalStateException | IllegalArgumentException | + XmlPullParserException ex) { + StringBuilder sb = new StringBuilder("Exception @"); + sb.append(parser.getPositionDescription()); + sb.append(" while parsing "); + sb.append(MAC_PERMISSIONS); + sb.append(":"); + sb.append(ex); + Slog.w(TAG, sb.toString()); return false; } catch (IOException ioe) { - Slog.w(TAG, "Got exception parsing " + MAC_PERMISSIONS, ioe); + Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe); return false; } finally { IoUtils.closeQuietly(policyFile); } - flushInstallPolicy(); - sSigSeinfo = sigSeinfo; - sDefaultSeinfo = defaultSeinfo; + // Now sort the policy stanzas + PolicyComparator policySort = new PolicyComparator(); + Collections.sort(policies, policySort); + if (policySort.foundDuplicate()) { + Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS); + return false; + } + + synchronized (sPolicies) { + sPolicies = policies; + + if (DEBUG_POLICY_ORDER) { + for (Policy policy : sPolicies) { + Slog.d(TAG, "Policy: " + policy.toString()); + } + } + } return true; } - private static Policy readPolicyTags(XmlPullParser parser) throws - IOException, XmlPullParserException { + /** + * Loop over a signer tag looking for seinfo, package and cert tags. A {@link Policy} + * instance will be created and returned in the process. During the pass all other + * tag elements will be skipped. + * + * @param parser an XmlPullParser object representing a signer element. + * @return the constructed {@link Policy} instance + * @throws IOException + * @throws XmlPullParserException + * @throws IllegalArgumentException if any of the validation checks fail while + * parsing tag values. + * @throws IllegalStateException if any of the invariants fail when constructing + * the {@link Policy} instance. + */ + private static Policy readSignerOrThrow(XmlPullParser parser) throws IOException, + XmlPullParserException { + + parser.require(XmlPullParser.START_TAG, null, "signer"); + Policy.PolicyBuilder pb = new Policy.PolicyBuilder(); + + // Check for a cert attached to the signer tag. We allow a signature + // to appear as an attribute as well as those attached to cert tags. + String cert = parser.getAttributeValue(null, "signature"); + if (cert != null) { + pb.addSignature(cert); + } - int type; - int outerDepth = parser.getDepth(); - Policy policy = new Policy(); - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String tagName = parser.getName(); if ("seinfo".equals(tagName)) { - String seinfo = parseSeinfo(parser); - if (seinfo != null) { - policy.putSeinfo(seinfo); - } - XmlUtils.skipCurrentTag(parser); + String seinfo = parser.getAttributeValue(null, "value"); + pb.setGlobalSeinfoOrThrow(seinfo); + readSeinfo(parser); } else if ("package".equals(tagName)) { - String pkg = parser.getAttributeValue(null, "name"); - if (!validatePackageName(pkg)) { - Slog.w(TAG, "<package> without valid name at " - + parser.getPositionDescription()); - XmlUtils.skipCurrentTag(parser); - continue; - } - - String seinfo = readSeinfoTag(parser); - if (seinfo != null) { - policy.putPkg(pkg, seinfo); - } + readPackageOrThrow(parser, pb); + } else if ("cert".equals(tagName)) { + String sig = parser.getAttributeValue(null, "signature"); + pb.addSignature(sig); + readCert(parser); } else { - XmlUtils.skipCurrentTag(parser); + skip(parser); } } - return policy; + + return pb.build(); } - private static String readSeinfoTag(XmlPullParser parser) throws - IOException, XmlPullParserException { + /** + * Loop over a default element looking for seinfo child tags. A {@link Policy} + * instance will be created and returned in the process. All other tags encountered + * will be skipped. + * + * @param parser an XmlPullParser object representing a default element. + * @return the constructed {@link Policy} instance + * @throws IOException + * @throws XmlPullParserException + * @throws IllegalArgumentException if any of the validation checks fail while + * parsing tag values. + * @throws IllegalStateException if any of the invariants fail when constructing + * the {@link Policy} instance. + */ + private static Policy readDefaultOrThrow(XmlPullParser parser) throws IOException, + XmlPullParserException { - int type; - int outerDepth = parser.getDepth(); - String seinfo = null; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { + parser.require(XmlPullParser.START_TAG, null, "default"); + Policy.PolicyBuilder pb = new Policy.PolicyBuilder(); + pb.setAsDefaultPolicy(); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String tagName = parser.getName(); if ("seinfo".equals(tagName)) { - seinfo = parseSeinfo(parser); + String seinfo = parser.getAttributeValue(null, "value"); + pb.setGlobalSeinfoOrThrow(seinfo); + readSeinfo(parser); + } else { + skip(parser); } - XmlUtils.skipCurrentTag(parser); } - return seinfo; - } - private static String parseSeinfo(XmlPullParser parser) { - - String seinfoValue = parser.getAttributeValue(null, "value"); - if (!validateValue(seinfoValue)) { - Slog.w(TAG, "<seinfo> without valid value at " - + parser.getPositionDescription()); - seinfoValue = null; - } - return seinfoValue; + return pb.build(); } /** - * General validation routine for package names. - * Returns a boolean indicating if the passed string - * is a valid android package name. + * Loop over a package element looking for seinfo child tags. If found return the + * value attribute of the seinfo tag, otherwise return null. All other tags encountered + * will be skipped. + * + * @param parser an XmlPullParser object representing a package element. + * @param pb a Policy.PolicyBuilder instance to build + * @throws IOException + * @throws XmlPullParserException + * @throws IllegalArgumentException if any of the validation checks fail while + * parsing tag values. + * @throws IllegalStateException if there is a duplicate seinfo tag for the current + * package tag. */ - private static boolean validatePackageName(String name) { - if (name == null) - return false; + private static void readPackageOrThrow(XmlPullParser parser, Policy.PolicyBuilder pb) throws + IOException, XmlPullParserException { + parser.require(XmlPullParser.START_TAG, null, "package"); + String pkgName = parser.getAttributeValue(null, "name"); - final int N = name.length(); - boolean hasSep = false; - boolean front = true; - for (int i=0; i<N; i++) { - final char c = name.charAt(i); - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - front = false; + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } - if (!front) { - if ((c >= '0' && c <= '9') || c == '_') { - continue; - } - } - if (c == '.') { - hasSep = true; - front = true; - continue; + + String tagName = parser.getName(); + if ("seinfo".equals(tagName)) { + String seinfo = parser.getAttributeValue(null, "value"); + pb.addInnerPackageMapOrThrow(pkgName, seinfo); + readSeinfo(parser); + } else { + skip(parser); } - return false; } - return hasSep; } - /** - * General validation routine for tag values. - * Returns a boolean indicating if the passed string - * contains only letters or underscores. - */ - private static boolean validateValue(String name) { - if (name == null) - return false; + private static void readCert(XmlPullParser parser) throws IOException, + XmlPullParserException { + parser.require(XmlPullParser.START_TAG, null, "cert"); + parser.nextTag(); + } - final int N = name.length(); - if (N == 0) - return false; + private static void readSeinfo(XmlPullParser parser) throws IOException, + XmlPullParserException { + parser.require(XmlPullParser.START_TAG, null, "seinfo"); + parser.nextTag(); + } - for (int i = 0; i < N; i++) { - final char c = name.charAt(i); - if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) { - return false; + private static void skip(XmlPullParser p) throws IOException, XmlPullParserException { + if (p.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (p.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; } } - return true; } /** - * Labels a package based on an seinfo tag from install policy. - * The label is attached to the ApplicationInfo instance of the package. + * Applies a security label to a package based on an seinfo tag taken from a matched + * policy. All signature based policy stanzas are consulted first and, if no match + * is found, the default policy stanza is then consulted. The security label is + * attached to the ApplicationInfo instance of the package in the event that a matching + * policy was found. + * * @param pkg object representing the package to be labeled. - * @return boolean which determines whether a non null seinfo label - * was assigned to the package. A null value simply meaning that - * no policy matched. + * @return boolean which determines whether a non null seinfo label was assigned + * to the package. A null value simply represents that no policy matched. */ public static boolean assignSeinfoValue(PackageParser.Package pkg) { - - // We just want one of the signatures to match. - for (Signature s : pkg.mSignatures) { - if (s == null) - continue; - - Policy policy = sSigSeinfo.get(s); - if (policy != null) { - String seinfo = policy.checkPolicy(pkg.packageName); + synchronized (sPolicies) { + for (Policy policy : sPolicies) { + String seinfo = policy.getMatchedSeinfo(pkg); if (seinfo != null) { pkg.applicationInfo.seinfo = seinfo; - if (DEBUG_POLICY_INSTALL) - Slog.i(TAG, "package (" + pkg.packageName + - ") labeled with seinfo=" + seinfo); - + if (DEBUG_POLICY_INSTALL) { + Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " + + "seinfo=" + seinfo); + } return true; } } } - // If we have a default seinfo value then great, otherwise - // we set a null object and that is what we started with. - pkg.applicationInfo.seinfo = sDefaultSeinfo; - if (DEBUG_POLICY_INSTALL) - Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo=" - + (sDefaultSeinfo == null ? "null" : sDefaultSeinfo)); - - return (sDefaultSeinfo != null); + if (DEBUG_POLICY_INSTALL) { + Slog.i(TAG, "package (" + pkg.packageName + ") doesn't match any policy; " + + "seinfo will remain null"); + } + return false; } /** @@ -477,3 +470,438 @@ public final class SELinuxMMAC { return false; } } + +/** + * Holds valid policy representations of individual stanzas from a mac_permissions.xml + * file. Each instance can further be used to assign seinfo values to apks using the + * {@link Policy#getMatchedSeinfo} method. To create an instance of this use the + * {@link PolicyBuilder} pattern class, where each instance is validated against a set + * of invariants before being built and returned. Each instance can be guaranteed to + * hold one valid policy stanza as outlined in the external/sepolicy/mac_permissions.xml + * file. + * <p> + * The following is an example of how to use {@link Policy.PolicyBuilder} to create a + * signer based Policy instance with only inner package name refinements. + * </p> + * <pre> + * {@code + * Policy policy = new Policy.PolicyBuilder() + * .addSignature("308204a8...") + * .addSignature("483538c8...") + * .addInnerPackageMapOrThrow("com.foo.", "bar") + * .addInnerPackageMapOrThrow("com.foo.other", "bar") + * .build(); + * } + * </pre> + * <p> + * The following is an example of how to use {@link Policy.PolicyBuilder} to create a + * signer based Policy instance with only a global seinfo tag. + * </p> + * <pre> + * {@code + * Policy policy = new Policy.PolicyBuilder() + * .addSignature("308204a8...") + * .addSignature("483538c8...") + * .setGlobalSeinfoOrThrow("paltform") + * .build(); + * } + * </pre> + * <p> + * The following is an example of how to use {@link Policy.PolicyBuilder} to create a + * default based Policy instance. + * </p> + * <pre> + * {@code + * Policy policy = new Policy.PolicyBuilder() + * .setAsDefaultPolicy() + * .setGlobalSeinfoOrThrow("default") + * .build(); + * } + * </pre> + */ +final class Policy { + + private final String mSeinfo; + private final boolean mDefaultStanza; + private final Set<Signature> mCerts; + private final Map<String, String> mPkgMap; + + // Use the PolicyBuilder pattern to instantiate + private Policy(PolicyBuilder builder) { + mSeinfo = builder.mSeinfo; + mDefaultStanza = builder.mDefaultStanza; + mCerts = Collections.unmodifiableSet(builder.mCerts); + mPkgMap = Collections.unmodifiableMap(builder.mPkgMap); + } + + /** + * Return all the certs stored with this policy stanza. + * + * @return A set of Signature objects representing all the certs stored + * with the policy. + */ + public Set<Signature> getSignatures() { + return mCerts; + } + + /** + * Return whether this policy object represents a default stanza. + * + * @return A boolean indicating if this object represents a default policy stanza. + */ + public boolean isDefaultStanza() { + return mDefaultStanza; + } + + /** + * Return whether this policy object contains package name mapping refinements. + * + * @return A boolean indicating if this object has inner package name mappings. + */ + public boolean hasInnerPackages() { + return !mPkgMap.isEmpty(); + } + + /** + * Return the mapping of all package name refinements. + * + * @return A Map object whose keys are the package names and whose values are + * the seinfo assignments. + */ + public Map<String, String> getInnerPackages() { + return mPkgMap; + } + + /** + * Return whether the policy object has a global seinfo tag attached. + * + * @return A boolean indicating if this stanza has a global seinfo tag. + */ + public boolean hasGlobalSeinfo() { + return mSeinfo != null; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (mDefaultStanza) { + sb.append("defaultStanza=true "); + } + + for (Signature cert : mCerts) { + sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... "); + } + + if (mSeinfo != null) { + sb.append("seinfo=" + mSeinfo); + } + + for (String name : mPkgMap.keySet()) { + sb.append(" " + name + "=" + mPkgMap.get(name)); + } + + return sb.toString(); + } + + /** + * <p> + * Determine the seinfo value to assign to an apk. The appropriate seinfo value + * is determined using the following steps: + * </p> + * <ul> + * <li> If this Policy instance is defined as a default stanza: + * <ul><li>Return the global seinfo value</li></ul> + * </li> + * <li> If this Policy instance is defined as a signer stanza: + * <ul> + * <li> All certs used to sign the apk and all certs stored with this policy + * instance are tested for set equality. If this fails then null is returned. + * </li> + * <li> If all certs match then an appropriate inner package stanza is + * searched based on package name alone. If matched, the stored seinfo + * value for that mapping is returned. + * </li> + * <li> If all certs matched and no inner package stanza matches then return + * the global seinfo value. The returned value can be null in this case. + * </li> + * </ul> + * </li> + * </ul> + * <p> + * In all cases, a return value of null should be interpreted as the apk failing + * to match this Policy instance; i.e. failing this policy stanza. + * </p> + * @param pkg the apk to check given as a PackageParser.Package object + * @return A string representing the seinfo matched during policy lookup. + * A value of null can also be returned if no match occured. + */ + public String getMatchedSeinfo(PackageParser.Package pkg) { + if (!mDefaultStanza) { + // Check for exact signature matches across all certs. + Signature[] certs = mCerts.toArray(new Signature[0]); + if (!Signature.areExactMatch(certs, pkg.mSignatures)) { + return null; + } + + // Check for inner package name matches given that the + // signature checks already passed. + String seinfoValue = mPkgMap.get(pkg.packageName); + if (seinfoValue != null) { + return seinfoValue; + } + } + + // Return the global seinfo value (even if it's null). + return mSeinfo; + } + + /** + * A nested builder class to create {@link Policy} instances. A {@link Policy} + * class instance represents one valid policy stanza found in a mac_permissions.xml + * file. A valid policy stanza is defined to be either a signer or default stanza + * which obeys the rules outlined in external/sepolicy/mac_permissions.xml. The + * {@link #build} method ensures a set of invariants are upheld enforcing the correct + * stanza structure before returning a valid Policy object. + */ + public static final class PolicyBuilder { + + private String mSeinfo; + private boolean mDefaultStanza; + private final Set<Signature> mCerts; + private final Map<String, String> mPkgMap; + + public PolicyBuilder() { + mCerts = new HashSet<Signature>(2); + mPkgMap = new HashMap<String, String>(2); + } + + /** + * Sets this stanza as a default stanza. All policy stanzas are assumed to + * be signer stanzas unless this method is explicitly called. Default stanzas + * are treated differently with respect to allowable child tags, ordering and + * when and how policy decisions are enforced. + * + * @return The reference to this PolicyBuilder. + */ + public PolicyBuilder setAsDefaultPolicy() { + mDefaultStanza = true; + return this; + } + + /** + * Adds a signature to the set of certs used for validation checks. The purpose + * being that all contained certs will need to be matched against all certs + * contained with an apk. + * + * @param cert the signature to add given as a String. + * @return The reference to this PolicyBuilder. + * @throws IllegalArgumentException if the cert value fails validation; + * null or is an invalid hex-encoded ASCII string. + */ + public PolicyBuilder addSignature(String cert) { + if (cert == null) { + String err = "Invalid signature value " + cert; + throw new IllegalArgumentException(err); + } + + mCerts.add(new Signature(cert)); + return this; + } + + /** + * Set the global seinfo tag for this policy stanza. The global seinfo tag + * represents the seinfo element that is used in one of two ways depending on + * its context. When attached to a signer tag the global seinfo represents an + * assignment when there isn't a further inner package refinement in policy. + * When used with a default tag, it represents the only allowable assignment + * value. + * + * @param seinfo the seinfo value given as a String. + * @return The reference to this PolicyBuilder. + * @throws IllegalArgumentException if the seinfo value fails validation; + * null, zero length or contains non-valid characters [^a-zA-Z_\._0-9]. + * @throws IllegalStateException if an seinfo value has already been found + */ + public PolicyBuilder setGlobalSeinfoOrThrow(String seinfo) { + if (!validateValue(seinfo)) { + String err = "Invalid seinfo value " + seinfo; + throw new IllegalArgumentException(err); + } + + if (mSeinfo != null && !mSeinfo.equals(seinfo)) { + String err = "Duplicate seinfo tag found"; + throw new IllegalStateException(err); + } + + mSeinfo = seinfo; + return this; + } + + /** + * Create a package name to seinfo value mapping. Each mapping represents + * the seinfo value that will be assigned to the described package name. + * These localized mappings allow the global seinfo to be overriden. This + * mapping provides no value when used in conjunction with a default stanza; + * enforced through the {@link #build} method. + * + * @param pkgName the android package name given to the app + * @param seinfo the seinfo value that will be assigned to the passed pkgName + * @return The reference to this PolicyBuilder. + * @throws IllegalArgumentException if the seinfo value fails validation; + * null, zero length or contains non-valid characters [^a-zA-Z_\.0-9]. + * Or, if the package name isn't a valid android package name. + * @throws IllegalStateException if trying to reset a package mapping with a + * different seinfo value. + */ + public PolicyBuilder addInnerPackageMapOrThrow(String pkgName, String seinfo) { + if (!validateValue(pkgName)) { + String err = "Invalid package name " + pkgName; + throw new IllegalArgumentException(err); + } + if (!validateValue(seinfo)) { + String err = "Invalid seinfo value " + seinfo; + throw new IllegalArgumentException(err); + } + + String pkgValue = mPkgMap.get(pkgName); + if (pkgValue != null && !pkgValue.equals(seinfo)) { + String err = "Conflicting seinfo value found"; + throw new IllegalStateException(err); + } + + mPkgMap.put(pkgName, seinfo); + return this; + } + + /** + * General validation routine for the attribute strings of an element. Checks + * if the string is non-null, positive length and only contains [a-zA-Z_\.0-9]. + * + * @param name the string to validate. + * @return boolean indicating if the string was valid. + */ + private boolean validateValue(String name) { + if (name == null) + return false; + + // Want to match on [0-9a-zA-Z_.] + if (!name.matches("\\A[\\.\\w]+\\z")) { + return false; + } + + return true; + } + + /** + * <p> + * Create a {@link Policy} instance based on the current configuration. This + * method checks for certain policy invariants used to enforce certain guarantees + * about the expected structure of a policy stanza. + * Those invariants are: + * </p> + * <ul> + * <li> If a default stanza + * <ul> + * <li> an attached global seinfo tag must be present </li> + * <li> no signatures and no package names can be present </li> + * </ul> + * </li> + * <li> If a signer stanza + * <ul> + * <li> at least one cert must be found </li> + * <li> either a global seinfo value is present OR at least one + * inner package mapping must be present BUT not both. </li> + * </ul> + * </li> + * </ul> + * + * @return an instance of {@link Policy} with the options set from this builder + * @throws IllegalStateException if an invariant is violated. + */ + public Policy build() { + Policy p = new Policy(this); + + if (p.mDefaultStanza) { + if (p.mSeinfo == null) { + String err = "Missing global seinfo tag with default stanza."; + throw new IllegalStateException(err); + } + if (p.mCerts.size() != 0) { + String err = "Certs not allowed with default stanza."; + throw new IllegalStateException(err); + } + if (!p.mPkgMap.isEmpty()) { + String err = "Inner package mappings not allowed with default stanza."; + throw new IllegalStateException(err); + } + } else { + if (p.mCerts.size() == 0) { + String err = "Missing certs with signer tag. Expecting at least one."; + throw new IllegalStateException(err); + } + if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) { + String err = "Only seinfo tag XOR package tags are allowed within " + + "a signer stanza."; + throw new IllegalStateException(err); + } + } + + return p; + } + } +} + +/** + * Comparision imposing an ordering on Policy objects. It is understood that Policy + * objects can only take one of three forms and ordered according to the following + * set of rules most specific to least. + * <ul> + * <li> signer stanzas with inner package mappings </li> + * <li> signer stanzas with global seinfo tags </li> + * <li> default stanza </li> + * </ul> + * This comparison also checks for duplicate entries on the input selectors. Any + * found duplicates will be flagged and can be checked with {@link #foundDuplicate}. + */ + +final class PolicyComparator implements Comparator<Policy> { + + private boolean duplicateFound = false; + + public boolean foundDuplicate() { + return duplicateFound; + } + + @Override + public int compare(Policy p1, Policy p2) { + + // Give precedence to signature stanzas over default stanzas + if (p1.isDefaultStanza() != p2.isDefaultStanza()) { + return p1.isDefaultStanza() ? 1 : -1; + } + + // Give precedence to stanzas with inner package mappings + if (p1.hasInnerPackages() != p2.hasInnerPackages()) { + return p1.hasInnerPackages() ? -1 : 1; + } + + // Check for duplicate entries + if (p1.getSignatures().equals(p2.getSignatures())) { + // Checks if default stanza or a signer w/o inner package names + if (p1.hasGlobalSeinfo()) { + duplicateFound = true; + Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString()); + } + + // Look for common inner package name mappings + final Map<String, String> p1Packages = p1.getInnerPackages(); + final Map<String, String> p2Packages = p2.getInnerPackages(); + if (!Collections.disjoint(p1Packages.keySet(), p2Packages.keySet())) { + duplicateFound = true; + Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString()); + } + } + + return 0; + } +} diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java new file mode 100644 index 0000000..0c7f79d --- /dev/null +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -0,0 +1,74 @@ +/* + * 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.pm; + +import android.content.pm.ApplicationInfo; + +import java.util.Arrays; + +abstract class SettingBase { + int pkgFlags; + int pkgPrivateFlags; + + protected final PermissionsState mPermissionsState; + private int[] mPermissionsUpdatedForUserIds = PermissionsState.USERS_NONE; + + SettingBase(int pkgFlags, int pkgPrivateFlags) { + setFlags(pkgFlags); + setPrivateFlags(pkgPrivateFlags); + mPermissionsState = new PermissionsState(); + } + + SettingBase(SettingBase base) { + pkgFlags = base.pkgFlags; + pkgPrivateFlags = base.pkgPrivateFlags; + mPermissionsState = new PermissionsState(base.mPermissionsState); + setPermissionsUpdatedForUserIds(base.mPermissionsUpdatedForUserIds); + } + + public PermissionsState getPermissionsState() { + return mPermissionsState; + } + + public int[] getPermissionsUpdatedForUserIds() { + return mPermissionsUpdatedForUserIds; + } + + public void setPermissionsUpdatedForUserIds(int[] userIds) { + if (Arrays.equals(mPermissionsUpdatedForUserIds, userIds)) { + return; + } + + if (userIds == PermissionsState.USERS_NONE || userIds == PermissionsState.USERS_ALL) { + mPermissionsUpdatedForUserIds = userIds; + } else { + mPermissionsUpdatedForUserIds = Arrays.copyOf(userIds, userIds.length); + } + } + + void setFlags(int pkgFlags) { + this.pkgFlags = pkgFlags + & (ApplicationInfo.FLAG_SYSTEM + | ApplicationInfo.FLAG_EXTERNAL_STORAGE); + } + + void setPrivateFlags(int pkgPrivateFlags) { + this.pkgPrivateFlags = pkgPrivateFlags + & (ApplicationInfo.PRIVATE_FLAG_PRIVILEGED + | ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK); + } +} diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 524f638..252c16a 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -22,28 +22,46 @@ 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.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER; +import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; import static android.os.Process.SYSTEM_UID; import static android.os.Process.PACKAGE_INFO_GID; import android.content.IntentFilter; import android.content.pm.ActivityInfo; +import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Environment; import android.os.FileUtils; +import android.os.Handler; +import android.os.Message; import android.os.PatternMatcher; import android.os.Process; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.VolumeInfo; +import android.util.AtomicFile; +import android.text.TextUtils; import android.util.LogPrinter; +import android.util.SparseBooleanArray; +import android.util.SparseLongArray; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; +import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.server.backup.PreferredActivityBackupHelper; import com.android.server.pm.PackageManagerService.DumpState; +import java.io.FileNotFoundException; import java.util.Collection; import org.xmlpull.v1.XmlPullParser; @@ -51,7 +69,6 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; @@ -79,6 +96,7 @@ import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -134,6 +152,8 @@ final class Settings { private static final boolean DEBUG_STOPPED = false; private static final boolean DEBUG_MU = false; + private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml"; + private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage"; private static final String ATTR_ENFORCEMENT = "enforcement"; @@ -142,10 +162,16 @@ final class Settings { private static final String TAG_ENABLED_COMPONENTS = "enabled-components"; private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions"; private static final String TAG_PACKAGE = "pkg"; + private static final String TAG_SHARED_USER = "shared-user"; + private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions"; + private static final String TAG_PERMISSIONS = "perms"; private static final String TAG_PERSISTENT_PREFERRED_ACTIVITIES = "persistent-preferred-activities"; static final String TAG_CROSS_PROFILE_INTENT_FILTERS = "crossProfile-intent-filters"; + public static final String TAG_DOMAIN_VERIFICATION = "domain-verification"; + public static final String TAG_DEFAULT_APPS= "default-apps"; + public static final String TAG_DEFAULT_BROWSER= "default-browser"; private static final String ATTR_NAME = "name"; private static final String ATTR_USER = "user"; @@ -160,6 +186,12 @@ final class Settings { private static final String ATTR_HIDDEN = "hidden"; private static final String ATTR_INSTALLED = "inst"; private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall"; + private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus"; + private static final String ATTR_PACKAGE_NAME= "packageName"; + + private final Object mLock; + + private final RuntimePermissionPersistence mRuntimePermissionsPersistence; private final File mSettingsFilename; private final File mBackupSettingsFilename; @@ -175,6 +207,8 @@ final class Settings { private static int mFirstAvailableUid = 0; + // TODO: store SDK versions and fingerprint for each volume UUID + // 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. @@ -223,6 +257,8 @@ final class Settings { // For reading/writing settings file. private final ArrayList<Signature> mPastSignatures = new ArrayList<Signature>(); + private final ArrayMap<Long, Integer> mKeySetRefs = + new ArrayMap<Long, Integer>(); // Mapping from permission names to info about them. final ArrayMap<String, BasePermission> mPermissions = @@ -241,7 +277,10 @@ final class Settings { // names. The packages appear everwhere else under their original // names. final ArrayMap<String, String> mRenamedPackages = new ArrayMap<String, String>(); - + + // For every user, it is used to find the package name of the default Browser App. + final SparseArray<String> mDefaultBrowserApp = new SparseArray<String>(); + final StringBuilder mReadMessages = new StringBuilder(); /** @@ -257,11 +296,15 @@ final class Settings { public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages); - Settings(Context context) { - this(context, Environment.getDataDirectory()); + Settings(Object lock) { + this(Environment.getDataDirectory(), lock); } - Settings(Context context, File dataDir) { + Settings(File dataDir, Object lock) { + mLock = lock; + + mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock); + mSystemDir = new File(dataDir, "system"); mSystemDir.mkdirs(); FileUtils.setPermissions(mSystemDir.toString(), @@ -281,11 +324,11 @@ final class Settings { PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbi, String secondaryCpuAbi, - int pkgFlags, UserHandle user, boolean add) { + int pkgFlags, int pkgPrivateFlags, UserHandle user, boolean add) { final String name = pkg.packageName; PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbi, secondaryCpuAbi, - pkg.mVersionCode, pkgFlags, user, add, true /* allowInstall */); + pkg.mVersionCode, pkgFlags, pkgPrivateFlags, user, add, true /* allowInstall */); return p; } @@ -302,22 +345,21 @@ final class Settings { } } - void setInstallerPackageName(String pkgName, - String installerPkgName) { + void setInstallerPackageName(String pkgName, String installerPkgName) { PackageSetting p = mPackages.get(pkgName); - if(p != null) { + if (p != null) { p.setInstallerPackageName(installerPkgName); } } SharedUserSetting getSharedUserLPw(String name, - int pkgFlags, boolean create) { + int pkgFlags, int pkgPrivateFlags, boolean create) { SharedUserSetting s = mSharedUsers.get(name); if (s == null) { if (!create) { return null; } - s = new SharedUserSetting(name, pkgFlags); + s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); s.userId = newUserIdLPw(s); Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId); // < 0 means we couldn't assign a userid; fall out and return @@ -373,7 +415,7 @@ final class Settings { PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath, p.legacyNativeLibraryPathString, p.primaryCpuAbiString, p.secondaryCpuAbiString, p.secondaryCpuAbiString, - p.appId, p.versionCode, p.pkgFlags); + p.appId, p.versionCode, p.pkgFlags, p.pkgPrivateFlags); mDisabledSysPackages.remove(name); return ret; } @@ -388,7 +430,7 @@ final class Settings { PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, - String cpuAbiOverrideString, int uid, int vc, int pkgFlags) { + String cpuAbiOverrideString, int uid, int vc, int pkgFlags, int pkgPrivateFlags) { PackageSetting p = mPackages.get(name); if (p != null) { if (p.appId == uid) { @@ -400,7 +442,7 @@ final class Settings { } p = new PackageSetting(name, realName, codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, - cpuAbiOverrideString, vc, pkgFlags); + cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags); p.appId = uid; if (addUserIdLPw(uid, p, name)) { mPackages.put(name, p); @@ -409,7 +451,7 @@ final class Settings { return null; } - SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) { + SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) { SharedUserSetting s = mSharedUsers.get(name); if (s != null) { if (s.userId == uid) { @@ -419,7 +461,7 @@ final class Settings { "Adding duplicate shared user, keeping first: " + name); return null; } - s = new SharedUserSetting(name, pkgFlags); + s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags); s.userId = uid; if (addUserIdLPw(uid, s, name)) { mSharedUsers.put(name, s); @@ -460,7 +502,7 @@ final class Settings { bp.pendingInfo.packageName = newPkg; } bp.uid = 0; - bp.gids = null; + bp.setGids(null, false); } } } @@ -468,9 +510,9 @@ final class Settings { private PackageSetting getPackageLPw(String name, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, - int vc, int pkgFlags, UserHandle installUser, boolean add, - boolean allowInstall) { + String legacyNativeLibraryPathString, String primaryCpuAbiString, + String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags, + UserHandle installUser, boolean add, boolean allowInstall) { PackageSetting p = mPackages.get(name); UserManagerService userManager = UserManagerService.getInstance(); if (p != null) { @@ -511,9 +553,8 @@ final class Settings { // If what we are scanning is a system (and possibly privileged) package, // then make it so, regardless of whether it was previously installed only // in the data partition. - final int sysPrivFlags = pkgFlags - & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PRIVILEGED); - p.pkgFlags |= sysPrivFlags; + p.pkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM; + p.pkgPrivateFlags |= pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } } if (p == null) { @@ -521,7 +562,7 @@ final class Settings { // We are consuming the data from an existing package. p = new PackageSetting(origPackage.name, name, codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, - null /* cpuAbiOverrideString */, vc, pkgFlags); + null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags); if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " + name + " is adopting original package " + origPackage.name); // Note that we will retain the new package's signature so @@ -539,7 +580,7 @@ final class Settings { } else { p = new PackageSetting(name, realName, codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, - null /* cpuAbiOverrideString */, vc, pkgFlags); + null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags); p.setTimeStamp(codePath.lastModified()); p.sharedUser = sharedUser; // If this is not a system app, it starts out stopped. @@ -569,8 +610,8 @@ final class Settings { true, // notLaunched false, // hidden null, null, null, - false // blockUninstall - ); + false, // blockUninstall + INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED); writePackageRestrictionsLPr(user.id); } } @@ -590,7 +631,7 @@ final class Settings { } p.appId = dis.appId; // Clone permissions - p.grantedPermissions = new ArraySet<String>(dis.grantedPermissions); + p.getPermissionsState().copyFrom(dis.getPermissionsState()); // Clone component info List<UserInfo> users = getAllUsers(); if (users != null) { @@ -653,19 +694,26 @@ final class Settings { p.pkg = pkg; // pkg.mSetEnabled = p.getEnabled(userId); // pkg.mSetStopped = p.getStopped(userId); + final String volumeUuid = pkg.applicationInfo.volumeUuid; final String codePath = pkg.applicationInfo.getCodePath(); final String resourcePath = pkg.applicationInfo.getResourcePath(); final String legacyNativeLibraryPath = pkg.applicationInfo.nativeLibraryRootDir; + // Update volume if needed + if (!Objects.equals(volumeUuid, p.volumeUuid)) { + Slog.w(PackageManagerService.TAG, "Volume for " + p.pkg.packageName + + " changing from " + p.volumeUuid + " to " + volumeUuid); + p.volumeUuid = volumeUuid; + } // Update code path if needed if (!Objects.equals(codePath, p.codePathString)) { - Slog.w(PackageManagerService.TAG, "Code path for pkg : " + p.pkg.packageName + + Slog.w(PackageManagerService.TAG, "Code path for " + p.pkg.packageName + " changing from " + p.codePathString + " to " + codePath); p.codePath = new File(codePath); p.codePathString = codePath; } //Update resource path if needed if (!Objects.equals(resourcePath, p.resourcePathString)) { - Slog.w(PackageManagerService.TAG, "Resource path for pkg : " + p.pkg.packageName + + Slog.w(PackageManagerService.TAG, "Resource path for " + p.pkg.packageName + " changing from " + p.resourcePathString + " to " + resourcePath); p.resourcePath = new File(resourcePath); p.resourcePathString = resourcePath; @@ -733,45 +781,60 @@ final class Settings { * not in use by other permissions of packages in the * shared user setting. */ - void updateSharedUserPermsLPw(PackageSetting deletedPs, int[] globalGids) { + int updateSharedUserPermsLPw(PackageSetting deletedPs, int userId) { if ((deletedPs == null) || (deletedPs.pkg == null)) { Slog.i(PackageManagerService.TAG, "Trying to update info for null package. Just ignoring"); - return; + return UserHandle.USER_NULL; } + // No sharedUserId if (deletedPs.sharedUser == null) { - return; + return UserHandle.USER_NULL; } + SharedUserSetting sus = deletedPs.sharedUser; + // Update permissions for (String eachPerm : deletedPs.pkg.requestedPermissions) { - boolean used = false; - if (!sus.grantedPermissions.contains(eachPerm)) { + BasePermission bp = mPermissions.get(eachPerm); + if (bp == null) { continue; } - for (PackageSetting pkg:sus.packages) { - if (pkg.pkg != null && - !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) && - pkg.pkg.requestedPermissions.contains(eachPerm)) { + + // If no user has the permission, nothing to remove. + if (!sus.getPermissionsState().hasPermission(bp.name, userId)) { + continue; + } + + boolean used = false; + + // Check if another package in the shared user needs the permission. + for (PackageSetting pkg : sus.packages) { + if (pkg.pkg != null + && !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) + && pkg.pkg.requestedPermissions.contains(eachPerm)) { used = true; break; } } + if (!used) { - // can safely delete this permission from list - sus.grantedPermissions.remove(eachPerm); - } - } - // Update gids - int newGids[] = globalGids; - for (String eachPerm : sus.grantedPermissions) { - BasePermission bp = mPermissions.get(eachPerm); - if (bp != null) { - newGids = PackageManagerService.appendInts(newGids, bp.gids); + // Try to revoke as an install permission which is for all users. + if (sus.getPermissionsState().revokeInstallPermission(bp) == + PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { + return UserHandle.USER_ALL; + } + + // Try to revoke as an install permission which is per user. + if (sus.getPermissionsState().revokeRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) { + return userId; + } } } - sus.gids = newGids; + + return UserHandle.USER_NULL; } int removePackageLPw(String name) { @@ -829,7 +892,7 @@ final class Settings { if (mOtherUserIds.get(uid) != null) { PackageManagerService.reportSettingsProblem(Log.ERROR, "Adding duplicate shared id: " + uid - + " name=" + name); + + " name=" + name); return false; } mOtherUserIds.put(uid, obj); @@ -895,8 +958,159 @@ final class Settings { return cpir; } + /** + * The following functions suppose that you have a lock for managing access to the + * mIntentFiltersVerifications map. + */ + + /* package protected */ + IntentFilterVerificationInfo getIntentFilterVerificationLPr(String packageName) { + PackageSetting ps = mPackages.get(packageName); + if (ps == null) { + Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName); + return null; + } + return ps.getIntentFilterVerificationInfo(); + } + + /* package protected */ + IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(String packageName, + ArrayList<String> domains) { + PackageSetting ps = mPackages.get(packageName); + if (ps == null) { + Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName); + return null; + } + IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo(); + if (ivi == null) { + ivi = new IntentFilterVerificationInfo(packageName, domains); + ps.setIntentFilterVerificationInfo(ivi); + } + return ivi; + } + + int getIntentFilterVerificationStatusLPr(String packageName, int userId) { + PackageSetting ps = mPackages.get(packageName); + if (ps == null) { + Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName); + return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + } + int status = ps.getDomainVerificationStatusForUser(userId); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) { + if (ps.getIntentFilterVerificationInfo() != null) { + status = ps.getIntentFilterVerificationInfo().getStatus(); + } + } + return status; + } + + boolean updateIntentFilterVerificationStatusLPw(String packageName, int status, int userId) { + // Update the status for the current package + PackageSetting current = mPackages.get(packageName); + if (current == null) { + Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName); + return false; + } + current.setDomainVerificationStatusForUser(status, userId); + + if (current.getIntentFilterVerificationInfo() == null) { + Slog.w(PackageManagerService.TAG, + "No IntentFilterVerificationInfo known for name: " + packageName); + return false; + } + + // Then, if we set a ALWAYS status, then put NEVER status for Apps whose IntentFilter + // domains overlap the domains of the current package + ArraySet<String> currentDomains = current.getIntentFilterVerificationInfo().getDomainsSet(); + if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) { + for (PackageSetting ps : mPackages.values()) { + if (ps == null || ps.pkg.packageName.equals(packageName)) continue; + IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo(); + if (ivi == null) { + continue; + } + ArraySet<String> set = ivi.getDomainsSet(); + set.retainAll(currentDomains); + if (set.size() > 0) { + ps.setDomainVerificationStatusForUser( + INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER, userId); + } + } + } + return true; + } + + /** + * Used for Settings App and PackageManagerService dump. Should be read only. + */ + List<IntentFilterVerificationInfo> getIntentFilterVerificationsLPr( + String packageName) { + if (packageName == null) { + return Collections.<IntentFilterVerificationInfo>emptyList(); + } + ArrayList<IntentFilterVerificationInfo> result = new ArrayList<>(); + for (PackageSetting ps : mPackages.values()) { + IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo(); + if (ivi == null || TextUtils.isEmpty(ivi.getPackageName()) || + !ivi.getPackageName().equalsIgnoreCase(packageName)) { + continue; + } + result.add(ivi); + } + return result; + } + + void removeIntentFilterVerificationLPw(String packageName, int userId) { + PackageSetting ps = mPackages.get(packageName); + if (ps == null) { + Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName); + return; + } + ps.clearDomainVerificationStatusForUser(userId); + } + + void removeIntentFilterVerificationLPw(String packageName, int[] userIds) { + for (int userId : userIds) { + removeIntentFilterVerificationLPw(packageName, userId); + } + } + + boolean setDefaultBrowserPackageNameLPr(String packageName, int userId) { + if (userId == UserHandle.USER_ALL) { + return false; + } + mDefaultBrowserApp.put(userId, packageName); + writePackageRestrictionsLPr(userId); + return true; + } + + String getDefaultBrowserPackageNameLPw(int userId) { + return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.get(userId); + } + private File getUserPackagesStateFile(int userId) { - return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml"); + // TODO: Implement a cleaner solution when adding tests. + // This instead of Environment.getUserSystemDirectory(userId) to support testing. + File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId)); + return new File(userDir, "package-restrictions.xml"); + } + + private File getUserRuntimePermissionsFile(int userId) { + // TODO: Implement a cleaner solution when adding tests. + // This instead of Environment.getUserSystemDirectory(userId) to support testing. + File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId)); + return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME); + } + + boolean isFirstRuntimePermissionsBoot() { + return !getUserRuntimePermissionsFile(UserHandle.USER_OWNER).exists(); + } + + void deleteRuntimePermissionsFiles() { + for (int userId : UserManagerService.getInstance().getUserIds()) { + File file = getUserRuntimePermissionsFile(userId); + file.delete(); + } } private File getUserPackagesStateBackupFile(int userId) { @@ -913,15 +1127,9 @@ final class Settings { } } - void readAllUsersPackageRestrictionsLPr() { - List<UserInfo> users = getAllUsers(); - if (users == null) { - readPackageRestrictionsLPr(0); - return; - } - - for (UserInfo user : users) { - readPackageRestrictionsLPr(user.id); + void writeAllRuntimePermissionsLPr() { + for (int userId : UserManagerService.getInstance().getUserIds()) { + mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId); } } @@ -959,7 +1167,13 @@ final class Settings { mExternalDatabaseVersion = CURRENT_DATABASE_VERSION; } - private void readPreferredActivitiesLPw(XmlPullParser parser, int userId) + /** + * Applies the preferred activity state described by the given XML. This code + * also supports the restore-from-backup code path. + * + * @see PreferredActivityBackupHelper + */ + void readPreferredActivitiesLPw(XmlPullParser parser, int userId) throws XmlPullParserException, IOException { int outerDepth = parser.getDepth(); int type; @@ -1032,6 +1246,35 @@ final class Settings { } } + private void readDomainVerificationLPw(XmlPullParser parser, PackageSettingBase packageSetting) + throws XmlPullParserException, IOException { + IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser); + packageSetting.setIntentFilterVerificationInfo(ivi); + Log.d(TAG, "Read domain verification for package:" + ivi.getPackageName()); + } + + private void readDefaultAppsLPw(XmlPullParser parser, int userId) + 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; + } + String tagName = parser.getName(); + if (tagName.equals(TAG_DEFAULT_BROWSER)) { + String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); + mDefaultBrowserApp.put(userId, packageName); + } else { + String msg = "Unknown element under " + TAG_DEFAULT_APPS + ": " + + parser.getName(); + PackageManagerService.reportSettingsProblem(Log.WARN, msg); + XmlUtils.skipCurrentTag(parser); + } + } + } + void readPackageRestrictionsLPr(int userId) { if (DEBUG_MU) { Log.i(TAG, "Reading package restrictions for user=" + userId); @@ -1076,8 +1319,8 @@ final class Settings { false, // notLaunched false, // hidden null, null, null, - false // blockUninstall - ); + false, // blockUninstall + INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED); } return; } @@ -1146,6 +1389,12 @@ final class Settings { final boolean blockUninstall = blockUninstallStr == null ? false : Boolean.parseBoolean(blockUninstallStr); + final String verifStateStr = + parser.getAttributeValue(null, ATTR_DOMAIN_VERIFICATON_STATE); + final int verifState = (verifStateStr == null) ? + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED : + Integer.parseInt(verifStateStr); + ArraySet<String> enabledComponents = null; ArraySet<String> disabledComponents = null; @@ -1166,13 +1415,16 @@ final class Settings { } ps.setUserState(userId, enabled, installed, stopped, notLaunched, hidden, - enabledCaller, enabledComponents, disabledComponents, blockUninstall); + enabledCaller, enabledComponents, disabledComponents, blockUninstall, + verifState); } else if (tagName.equals("preferred-activities")) { readPreferredActivitiesLPw(parser, userId); } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) { readPersistentPreferredActivitiesLPw(parser, userId); } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) { readCrossProfileIntentFiltersLPw(parser, userId); + } else if (tagName.equals(TAG_DEFAULT_APPS)) { + readDefaultAppsLPw(parser, userId); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: " + parser.getName()); @@ -1224,6 +1476,11 @@ final class Settings { return components; } + /** + * Record the state of preferred activity configuration into XML. This is used both + * for recording packages.xml internally and for supporting backup/restore of the + * preferred activity configuration. + */ void writePreferredActivitiesLPr(XmlSerializer serializer, int userId, boolean full) throws IllegalArgumentException, IllegalStateException, IOException { serializer.startTag(null, "preferred-activities"); @@ -1266,6 +1523,30 @@ final class Settings { serializer.endTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS); } + void writeDomainVerificationsLPr(XmlSerializer serializer, + IntentFilterVerificationInfo verificationInfo) + throws IllegalArgumentException, IllegalStateException, IOException { + if (verificationInfo != null && verificationInfo.getPackageName() != null) { + serializer.startTag(null, TAG_DOMAIN_VERIFICATION); + verificationInfo.writeToXml(serializer); + Log.d(TAG, "Wrote domain verification for package: " + + verificationInfo.getPackageName()); + serializer.endTag(null, TAG_DOMAIN_VERIFICATION); + } + } + + void writeDefaultAppsLPr(XmlSerializer serializer, int userId) + throws IllegalArgumentException, IllegalStateException, IOException { + serializer.startTag(null, TAG_DEFAULT_APPS); + String packageName = mDefaultBrowserApp.get(userId); + if (!TextUtils.isEmpty(packageName)) { + serializer.startTag(null, TAG_DEFAULT_BROWSER); + serializer.attribute(null, ATTR_PACKAGE_NAME, packageName); + serializer.endTag(null, TAG_DEFAULT_BROWSER); + } + serializer.endTag(null, TAG_DEFAULT_APPS); + } + void writePackageRestrictionsLPr(int userId) { if (DEBUG_MU) { Log.i(TAG, "Writing package restrictions for user=" + userId); @@ -1313,7 +1594,9 @@ final class Settings { && ustate.enabledComponents.size() > 0) || (ustate.disabledComponents != null && ustate.disabledComponents.size() > 0) - || ustate.blockUninstall) { + || ustate.blockUninstall + || (ustate.domainVerificationStatus != + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED)) { serializer.startTag(null, TAG_PACKAGE); serializer.attribute(null, ATTR_NAME, pkg.name); if (DEBUG_MU) Log.i(TAG, " pkg=" + pkg.name + ", state=" + ustate.enabled); @@ -1341,6 +1624,11 @@ final class Settings { ustate.lastDisableAppCaller); } } + if (ustate.domainVerificationStatus != + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) { + serializer.attribute(null, ATTR_DOMAIN_VERIFICATON_STATE, + Integer.toString(ustate.domainVerificationStatus)); + } if (ustate.enabledComponents != null && ustate.enabledComponents.size() > 0) { serializer.startTag(null, TAG_ENABLED_COMPONENTS); @@ -1361,15 +1649,15 @@ final class Settings { } serializer.endTag(null, TAG_DISABLED_COMPONENTS); } + serializer.endTag(null, TAG_PACKAGE); } } writePreferredActivitiesLPr(serializer, userId, true); - writePersistentPreferredActivitiesLPr(serializer, userId); - writeCrossProfileIntentFiltersLPr(serializer, userId); + writeDefaultAppsLPr(serializer, userId); serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS); @@ -1404,6 +1692,58 @@ final class Settings { } } + void readInstallPermissionsLPr(XmlPullParser parser, + PermissionsState permissionsState) throws IOException, XmlPullParserException { + 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; + } + String tagName = parser.getName(); + if (tagName.equals(TAG_ITEM)) { + String name = parser.getAttributeValue(null, ATTR_NAME); + + BasePermission bp = mPermissions.get(name); + if (bp == null) { + Slog.w(PackageManagerService.TAG, "Unknown permission: " + name); + XmlUtils.skipCurrentTag(parser); + continue; + } + + if (permissionsState.grantInstallPermission(bp) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + Slog.w(PackageManagerService.TAG, "Permission already added: " + name); + XmlUtils.skipCurrentTag(parser); + } + } else { + Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } + + void writePermissionsLPr(XmlSerializer serializer, Set<String> permissions) + throws IOException { + if (permissions.isEmpty()) { + return; + } + + serializer.startTag(null, TAG_PERMISSIONS); + + for (String permission : permissions) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, permission); + serializer.endTag(null, TAG_ITEM); + } + + serializer.endTag(null, TAG_PERMISSIONS); + } + // Note: assumed "stopped" field is already cleared in all packages. // Legacy reader, used to read in the old file format after an upgrade. Not used after that. void readStoppedLPw() { @@ -1595,13 +1935,7 @@ final class Settings { serializer.attribute(null, "userId", Integer.toString(usr.userId)); usr.signatures.writeXml(serializer, "sigs", mPastSignatures); - serializer.startTag(null, "perms"); - for (String name : usr.grantedPermissions) { - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, name); - serializer.endTag(null, TAG_ITEM); - } - serializer.endTag(null, "perms"); + writePermissionsLPr(serializer, usr.getPermissionsState().getInstallPermissions()); serializer.endTag(null, "shared-user"); } @@ -1615,7 +1949,7 @@ final class Settings { serializer.endTag(null, "cleaning-package"); } } - + if (mRenamedPackages.size() > 0) { for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) { serializer.startTag(null, "renamed-package"); @@ -1624,7 +1958,7 @@ final class Settings { serializer.endTag(null, "renamed-package"); } } - + mKeySetManagerService.writeKeySetManagerServiceLPr(serializer); serializer.endTag(null, "packages"); @@ -1663,7 +1997,7 @@ final class Settings { 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(); + final int[] gids = pkg.getPermissionsState().computeGids(); // Avoid any application that has a space in its path. if (dataPath.indexOf(" ") >= 0) @@ -1682,6 +2016,7 @@ final class Settings { // // 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/logd/LogStatistics.cpp // system/core/run-as/run-as.c // system/core/sdcard/sdcard.c // external/libselinux/src/android.c:package_info_init() @@ -1718,6 +2053,8 @@ final class Settings { } writeAllUsersPackageRestrictionsLPr(); + + writeAllRuntimePermissionsLPr(); return; } catch(XmlPullParserException e) { @@ -1770,26 +2107,12 @@ final class Settings { } else { serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId)); } - serializer.startTag(null, "perms"); + + // If this is a shared user, the permissions will be written there. if (pkg.sharedUser == null) { - // If this is a shared user, the permissions will - // be written there. We still need to write an - // empty permissions list so permissionsFixed will - // be set. - for (final String name : pkg.grantedPermissions) { - BasePermission bp = mPermissions.get(name); - if (bp != null) { - // We only need to write signature or system permissions but - // this wont - // match the semantics of grantedPermissions. So write all - // permissions. - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, name); - serializer.endTag(null, TAG_ITEM); - } - } + writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions()); } - serializer.endTag(null, "perms"); + serializer.endTag(null, "updated-package"); } @@ -1818,7 +2141,8 @@ final class Settings { serializer.attribute(null, "cpuAbiOverride", pkg.cpuAbiOverrideString); } - serializer.attribute(null, "flags", Integer.toString(pkg.pkgFlags)); + serializer.attribute(null, "publicFlags", Integer.toString(pkg.pkgFlags)); + serializer.attribute(null, "privateFlags", Integer.toString(pkg.pkgPrivateFlags)); serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); @@ -1837,50 +2161,33 @@ final class Settings { if (pkg.installerPackageName != null) { serializer.attribute(null, "installer", pkg.installerPackageName); } + if (pkg.volumeUuid != null) { + serializer.attribute(null, "volumeUuid", pkg.volumeUuid); + } pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { - serializer.startTag(null, "perms"); - if (pkg.sharedUser == null) { - // If this is a shared user, the permissions will - // be written there. We still need to write an - // empty permissions list so permissionsFixed will - // be set. - for (final String name : pkg.grantedPermissions) { - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, name); - serializer.endTag(null, TAG_ITEM); - } - } - serializer.endTag(null, "perms"); + writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions()); } - writeSigningKeySetsLPr(serializer, pkg.keySetData); + writeSigningKeySetLPr(serializer, pkg.keySetData); writeUpgradeKeySetsLPr(serializer, pkg.keySetData); writeKeySetAliasesLPr(serializer, pkg.keySetData); + writeDomainVerificationsLPr(serializer, pkg.verificationInfo); serializer.endTag(null, "package"); } - void writeSigningKeySetsLPr(XmlSerializer serializer, + void writeSigningKeySetLPr(XmlSerializer serializer, PackageKeySetData data) throws IOException { - if (data.getSigningKeySets() != null) { - // Keep track of the original signing-keyset. - // Must be recorded first, since it will be read first and wipe the - // current signing-keysets for the package when set. - long properSigningKeySet = data.getProperSigningKeySet(); - serializer.startTag(null, "proper-signing-keyset"); - serializer.attribute(null, "identifier", Long.toString(properSigningKeySet)); - serializer.endTag(null, "proper-signing-keyset"); - for (long id : data.getSigningKeySets()) { - serializer.startTag(null, "signing-keyset"); - serializer.attribute(null, "identifier", Long.toString(id)); - serializer.endTag(null, "signing-keyset"); - } - } + serializer.startTag(null, "proper-signing-keyset"); + serializer.attribute(null, "identifier", + Long.toString(data.getProperSigningKeySet())); + serializer.endTag(null, "proper-signing-keyset"); } void writeUpgradeKeySetsLPr(XmlSerializer serializer, PackageKeySetData data) throws IOException { + long properSigning = data.getProperSigningKeySet(); if (data.isUsingUpgradeKeySets()) { for (long id : data.getUpgradeKeySets()) { serializer.startTag(null, "upgrade-keyset"); @@ -1972,6 +2279,7 @@ final class Settings { mPendingPackages.clear(); mPastSignatures.clear(); + mKeySetRefs.clear(); try { if (str == null) { @@ -2033,6 +2341,8 @@ final class Settings { // TODO: check whether this is okay! as it is very // similar to how preferred-activities are treated readCrossProfileIntentFiltersLPw(parser, 0); + } else if (tagName.equals(TAG_DEFAULT_BROWSER)) { + readDefaultAppsLPw(parser, 0); } else if (tagName.equals("updated-package")) { readDisabledSysPackageLPw(parser); } else if (tagName.equals("cleaning-package")) { @@ -2098,7 +2408,7 @@ final class Settings { final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT); mReadExternalStorageEnforced = "1".equals(enforcement); } else if (tagName.equals("keyset-settings")) { - mKeySetManagerService.readKeySetsLPw(parser); + mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: " + parser.getName()); @@ -2120,6 +2430,7 @@ final class Settings { } final int N = mPendingPackages.size(); + for (int i = 0; i < N; i++) { final PendingPackage pp = mPendingPackages.get(i); Object idObj = getUserIdLPr(pp.sharedId); @@ -2127,8 +2438,8 @@ final class Settings { PackageSetting p = getPackageLPw(pp.name, null, pp.realName, (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, pp.legacyNativeLibraryPathString, pp.primaryCpuAbiString, - pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, null, - true /* add */, false /* allowInstall */); + pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, pp.pkgPrivateFlags, + null, true /* add */, false /* allowInstall */); if (p == null) { PackageManagerService.reportSettingsProblem(Log.WARN, "Unable to create application package for " + pp.name); @@ -2160,9 +2471,11 @@ final class Settings { } else { if (users == null) { readPackageRestrictionsLPr(0); + mRuntimePermissionsPersistence.readStateForUserSyncLPr(UserHandle.USER_OWNER); } else { for (UserInfo user : users) { readPackageRestrictionsLPr(user.id); + mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id); } } } @@ -2536,7 +2849,7 @@ final class Settings { final String ptype = parser.getAttributeValue(null, "type"); if (name != null && sourcePackage != null) { final boolean dynamic = "dynamic".equals(ptype); - final BasePermission bp = new BasePermission(name, sourcePackage, + final BasePermission bp = new BasePermission(name.intern(), sourcePackage, dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL); bp.protectionLevel = readInt(parser, null, "protection", PermissionInfo.PROTECTION_NORMAL); @@ -2596,14 +2909,15 @@ final class Settings { } int pkgFlags = 0; + int pkgPrivateFlags = 0; pkgFlags |= ApplicationInfo.FLAG_SYSTEM; final File codePathFile = new File(codePathStr); if (PackageManagerService.locationIsPrivileged(codePathFile)) { - pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED; + pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } PackageSetting ps = new PackageSetting(name, realName, codePathFile, new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr, - secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags); + secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags); String timeStampStr = parser.getAttributeValue(null, "ft"); if (timeStampStr != null) { try { @@ -2641,6 +2955,7 @@ final class Settings { String sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); ps.appId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; } + int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -2649,9 +2964,8 @@ final class Settings { continue; } - String tagName = parser.getName(); - if (tagName.equals("perms")) { - readGrantedPermissionsLPw(parser, ps.grantedPermissions); + if (parser.getName().equals(TAG_PERMISSIONS)) { + readInstallPermissionsLPr(parser, ps.getPermissionsState()); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Unknown element under <updated-package>: " + parser.getName()); @@ -2659,9 +2973,26 @@ final class Settings { } } + // We keep track for which users we granted permissions to be able + // to grant runtime permissions to system apps for newly appeared + // users or newly appeared system apps. If we supported runtime + // permissions during the previous boot, then we already granted + // permissions for all device users. In such a case we set the users + // for which we granted permissions to avoid clobbering of runtime + // permissions we granted to system apps but the user revoked later. + if (!isFirstRuntimePermissionsBoot()) { + final int[] userIds = UserManagerService.getInstance().getUserIds(); + ps.setPermissionsUpdatedForUserIds(userIds); + } + mDisabledSysPackages.put(name, ps); } + private static int PRE_M_APP_INFO_FLAG_HIDDEN = 1<<27; + private static int PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE = 1<<28; + private static int PRE_M_APP_INFO_FLAG_FORWARD_LOCK = 1<<29; + private static int PRE_M_APP_INFO_FLAG_PRIVILEGED = 1<<30; + private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException { String name = null; String realName = null; @@ -2676,8 +3007,10 @@ final class Settings { String cpuAbiOverrideString = null; String systemStr = null; String installerPackageName = null; + String volumeUuid = null; String uidError = null; int pkgFlags = 0; + int pkgPrivateFlags = 0; long timeStamp = 0; long firstInstallTime = 0; long lastUpdateTime = 0; @@ -2703,7 +3036,7 @@ final class Settings { if (primaryCpuAbiString == null && legacyCpuAbiString != null) { primaryCpuAbiString = legacyCpuAbiString; } -; + version = parser.getAttributeValue(null, "version"); if (version != null) { try { @@ -2712,23 +3045,56 @@ final class Settings { } } installerPackageName = parser.getAttributeValue(null, "installer"); + volumeUuid = parser.getAttributeValue(null, "volumeUuid"); - systemStr = parser.getAttributeValue(null, "flags"); + systemStr = parser.getAttributeValue(null, "publicFlags"); if (systemStr != null) { try { pkgFlags = Integer.parseInt(systemStr); } catch (NumberFormatException e) { } + systemStr = parser.getAttributeValue(null, "privateFlags"); + if (systemStr != null) { + try { + pkgPrivateFlags = Integer.parseInt(systemStr); + } catch (NumberFormatException e) { + } + } } else { - // For backward compatibility - systemStr = parser.getAttributeValue(null, "system"); + // Pre-M -- both public and private flags were stored in one "flags" field. + systemStr = parser.getAttributeValue(null, "flags"); if (systemStr != null) { - pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM - : 0; + try { + pkgFlags = Integer.parseInt(systemStr); + } catch (NumberFormatException e) { + } + if ((pkgFlags & PRE_M_APP_INFO_FLAG_HIDDEN) != 0) { + pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN; + } + if ((pkgFlags & PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE) != 0) { + pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE; + } + if ((pkgFlags & PRE_M_APP_INFO_FLAG_FORWARD_LOCK) != 0) { + pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK; + } + if ((pkgFlags & PRE_M_APP_INFO_FLAG_PRIVILEGED) != 0) { + pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; + } + pkgFlags &= ~(PRE_M_APP_INFO_FLAG_HIDDEN + | PRE_M_APP_INFO_FLAG_CANT_SAVE_STATE + | PRE_M_APP_INFO_FLAG_FORWARD_LOCK + | PRE_M_APP_INFO_FLAG_PRIVILEGED); } else { - // Old settings that don't specify system... just treat - // them as system, good enough. - pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + // For backward compatibility + systemStr = parser.getAttributeValue(null, "system"); + if (systemStr != null) { + pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM + : 0; + } else { + // Old settings that don't specify system... just treat + // them as system, good enough. + pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + } } } String timeStampStr = parser.getAttributeValue(null, "ft"); @@ -2781,7 +3147,8 @@ final class Settings { } else if (userId > 0) { packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString, - secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags); + secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags, + pkgPrivateFlags); if (PackageManagerService.DEBUG_SETTINGS) Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId=" + userId + " pkg=" + packageSetting); @@ -2800,7 +3167,7 @@ final class Settings { packageSetting = new PendingPackage(name.intern(), realName, new File( codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, - userId, versionCode, pkgFlags); + userId, versionCode, pkgFlags, pkgPrivateFlags); packageSetting.setTimeStamp(timeStamp); packageSetting.firstInstallTime = firstInstallTime; packageSetting.lastUpdateTime = lastUpdateTime; @@ -2827,6 +3194,7 @@ final class Settings { if (packageSetting != null) { packageSetting.uidError = "true".equals(uidError); packageSetting.installerPackageName = installerPackageName; + packageSetting.volumeUuid = volumeUuid; packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr; packageSetting.primaryCpuAbiString = primaryCpuAbiString; packageSetting.secondaryCpuAbiString = secondaryCpuAbiString; @@ -2861,7 +3229,6 @@ final class Settings { packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_COMPLETE; } } - int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -2878,22 +3245,36 @@ final class Settings { readEnabledComponentsLPw(packageSetting, parser, 0); } else if (tagName.equals("sigs")) { packageSetting.signatures.readXml(parser, mPastSignatures); - } else if (tagName.equals("perms")) { - readGrantedPermissionsLPw(parser, packageSetting.grantedPermissions); - packageSetting.permissionsFixed = true; + } else if (tagName.equals(TAG_PERMISSIONS)) { + readInstallPermissionsLPr(parser, + packageSetting.getPermissionsState()); + packageSetting.installPermissionsFixed = true; } else if (tagName.equals("proper-signing-keyset")) { long id = Long.parseLong(parser.getAttributeValue(null, "identifier")); + Integer refCt = mKeySetRefs.get(id); + if (refCt != null) { + mKeySetRefs.put(id, refCt + 1); + } else { + mKeySetRefs.put(id, 1); + } packageSetting.keySetData.setProperSigningKeySet(id); } else if (tagName.equals("signing-keyset")) { - long id = Long.parseLong(parser.getAttributeValue(null, "identifier")); - packageSetting.keySetData.addSigningKeySet(id); + // from v1 of keysetmanagerservice - no longer used } else if (tagName.equals("upgrade-keyset")) { long id = Long.parseLong(parser.getAttributeValue(null, "identifier")); packageSetting.keySetData.addUpgradeKeySetById(id); } else if (tagName.equals("defined-keyset")) { long id = Long.parseLong(parser.getAttributeValue(null, "identifier")); String alias = parser.getAttributeValue(null, "alias"); + Integer refCt = mKeySetRefs.get(id); + if (refCt != null) { + mKeySetRefs.put(id, refCt + 1); + } else { + mKeySetRefs.put(id, 1); + } packageSetting.keySetData.addDefinedKeySet(id, alias); + } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) { + readDomainVerificationLPw(parser, packageSetting); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Unknown element under <package>: " + parser.getName()); @@ -2901,7 +3282,17 @@ final class Settings { } } - + // We keep track for which users we granted permissions to be able + // to grant runtime permissions to system apps for newly appeared + // users or newly appeared system apps. If we supported runtime + // permissions during the previous boot, then we already granted + // permissions for all device users. In such a case we set the users + // for which we granted permissions to avoid clobbering of runtime + // permissions we granted to system apps but the user revoked later. + if (!isFirstRuntimePermissionsBoot()) { + final int[] userIds = UserManagerService.getInstance().getUserIds(); + packageSetting.setPermissionsUpdatedForUserIds(userIds); + } } else { XmlUtils.skipCurrentTag(parser); } @@ -2967,6 +3358,7 @@ final class Settings { String name = null; String idStr = null; int pkgFlags = 0; + int pkgPrivateFlags = 0; SharedUserSetting su = null; try { name = parser.getAttributeValue(null, ATTR_NAME); @@ -2985,7 +3377,8 @@ final class Settings { + " has bad userId " + idStr + " at " + parser.getPositionDescription()); } else { - if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags)) == null) { + if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags)) + == null) { PackageManagerService .reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at " + parser.getPositionDescription()); @@ -2996,7 +3389,6 @@ final class Settings { "Error in package manager settings: package " + name + " has bad userId " + idStr + " at " + parser.getPositionDescription()); } - ; if (su != null) { int outerDepth = parser.getDepth(); @@ -3011,7 +3403,7 @@ final class Settings { if (tagName.equals("sigs")) { su.signatures.readXml(parser, mPastSignatures); } else if (tagName.equals("perms")) { - readGrantedPermissionsLPw(parser, su.grantedPermissions); + readInstallPermissionsLPr(parser, su.getPermissionsState()); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Unknown element under <shared-user>: " + parser.getName()); @@ -3019,35 +3411,18 @@ final class Settings { } } - } else { - XmlUtils.skipCurrentTag(parser); - } - } - - private void readGrantedPermissionsLPw(XmlPullParser parser, ArraySet<String> outPerms) - throws IOException, XmlPullParserException { - 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; - } - - String tagName = parser.getName(); - if (tagName.equals(TAG_ITEM)) { - String name = parser.getAttributeValue(null, ATTR_NAME); - if (name != null) { - outPerms.add(name.intern()); - } else { - PackageManagerService.reportSettingsProblem(Log.WARN, - "Error in package manager settings: <perms> has" + " no name at " - + parser.getPositionDescription()); - } - } else { - PackageManagerService.reportSettingsProblem(Log.WARN, - "Unknown element under <perms>: " + parser.getName()); + // We keep track for which users we granted permissions to be able + // to grant runtime permissions to system apps for newly appeared + // users or newly appeared system apps. If we supported runtime + // permissions during the previous boot, then we already granted + // permissions for all device users. In such a case we set the users + // for which we granted permissions to avoid clobbering of runtime + // permissions we granted to system apps but the user revoked later. + if (!isFirstRuntimePermissionsBoot()) { + final int[] userIds = UserManagerService.getInstance().getUserIds(); + su.setPermissionsUpdatedForUserIds(userIds); } + } else { XmlUtils.skipCurrentTag(parser); } } @@ -3064,7 +3439,7 @@ final class Settings { // Only system apps are initially installed. ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle); // Need to create a data directory for all apps under this user. - installer.createUserData(ps.name, + installer.createUserData(ps.volumeUuid, ps.name, UserHandle.getUid(userHandle, ps.appId), userHandle, ps.pkg.applicationInfo.seinfo); } @@ -3083,6 +3458,8 @@ final class Settings { file = getUserPackagesStateBackupFile(userId); file.delete(); removeCrossProfileIntentFiltersLPw(userId); + + mRuntimePermissionsPersistence.onUserRemoved(userId); } void removeCrossProfileIntentFiltersLPw(int userId) { @@ -3262,7 +3639,7 @@ final class Settings { return false; } - private List<UserInfo> getAllUsers() { + List<UserInfo> getAllUsers() { long id = Binder.clearCallingIdentity(); try { return UserManagerService.getInstance().getUsers(false); @@ -3274,7 +3651,23 @@ final class Settings { return null; } - static final void printFlags(PrintWriter pw, int val, Object[] spec) { + /** + * Return all {@link PackageSetting} that are actively installed on the + * given {@link VolumeInfo#fsUuid}. + */ + List<PackageSetting> getVolumePackagesLPr(String volumeUuid) { + Preconditions.checkNotNull(volumeUuid); + ArrayList<PackageSetting> res = new ArrayList<>(); + for (int i = 0; i < mPackages.size(); i++) { + final PackageSetting setting = mPackages.valueAt(i); + if (Objects.equals(volumeUuid, setting.volumeUuid)) { + res.add(setting); + } + } + return res; + } + + static void printFlags(PrintWriter pw, int val, Object[] spec) { pw.print("[ "); for (int i=0; i<spec.length; i+=2) { int mask = (Integer)spec[i]; @@ -3302,9 +3695,12 @@ 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", + }; + + static final Object[] PRIVATE_FLAG_DUMP_SPEC = new Object[] { + ApplicationInfo.PRIVATE_FLAG_PRIVILEGED, "PRIVILEGED", + ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK, "FORWARD_LOCK", + ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE", }; void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag, PackageSetting ps, @@ -3368,8 +3764,8 @@ final class Settings { pw.println(ps.name); } - pw.print(prefix); pw.print(" userId="); pw.print(ps.appId); - pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids)); + pw.print(prefix); pw.print(" userId="); pw.println(ps.appId); + if (ps.sharedUser != null) { pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.sharedUser); } @@ -3391,6 +3787,8 @@ final class Settings { pw.println(ps.pkg.applicationInfo.toString()); pw.print(prefix); pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println(); + pw.print(prefix); pw.print(" priavateFlags="); printFlags(pw, + ps.pkg.applicationInfo.privateFlags, PRIVATE_FLAG_DUMP_SPEC); pw.println(); pw.print(prefix); pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); if (ps.pkg.mOperationPending) { pw.print(prefix); pw.println(" mOperationPending=true"); @@ -3475,12 +3873,22 @@ final class Settings { pw.print(prefix); pw.print(" installerPackageName="); pw.println(ps.installerPackageName); } + if (ps.volumeUuid != null) { + pw.print(prefix); pw.print(" volumeUuid="); + pw.println(ps.volumeUuid); + } pw.print(prefix); pw.print(" signatures="); pw.println(ps.signatures); - pw.print(prefix); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); - pw.print(" haveGids="); pw.print(ps.haveGids); + pw.print(prefix); pw.print(" installPermissionsFixed="); + pw.print(ps.installPermissionsFixed); pw.print(" installStatus="); pw.println(ps.installStatus); pw.print(prefix); pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC); pw.println(); + + if (ps.sharedUser == null) { + PermissionsState permissionsState = ps.getPermissionsState(); + dumpInstallPermissionsLPr(pw, prefix + " ", permissionsState); + } + for (UserInfo user : users) { pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": "); pw.print(" installed="); @@ -3498,6 +3906,14 @@ final class Settings { pw.print(prefix); pw.print(" lastDisabledCaller: "); pw.println(lastDisabledAppCaller); } + + if (ps.sharedUser == null) { + PermissionsState permissionsState = ps.getPermissionsState(); + dumpGidsLPr(pw, prefix + " ", permissionsState.computeGids(user.id)); + dumpRuntimePermissionsLPr(pw, prefix + " ", permissionsState + .getRuntimePermissions(user.id)); + } + ArraySet<String> cmp = ps.getDisabledComponents(user.id); if (cmp != null && cmp.size() > 0) { pw.print(prefix); pw.println(" disabledComponents:"); @@ -3513,12 +3929,6 @@ final class Settings { } } } - if (ps.grantedPermissions.size() > 0) { - pw.print(prefix); pw.println(" grantedPermissions:"); - for (String s : ps.grantedPermissions) { - pw.print(prefix); pw.print(" "); pw.println(s); - } - } } void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState, boolean checkin) { @@ -3604,7 +4014,8 @@ final class Settings { pw.println("):"); pw.print(" sourcePackage="); pw.println(p.sourcePackage); pw.print(" uid="); pw.print(p.uid); - pw.print(" gids="); pw.print(PackageManagerService.arrayToString(p.gids)); + pw.print(" gids="); pw.print(Arrays.toString( + p.computeGids(UserHandle.USER_OWNER))); pw.print(" type="); pw.print(p.type); pw.print(" prot="); pw.println(PermissionInfo.protectionToString(p.protectionLevel)); @@ -3640,14 +4051,21 @@ final class Settings { pw.print("] ("); pw.print(Integer.toHexString(System.identityHashCode(su))); pw.println("):"); - pw.print(" userId="); - pw.print(su.userId); - pw.print(" gids="); - pw.println(PackageManagerService.arrayToString(su.gids)); - pw.println(" grantedPermissions:"); - for (String s : su.grantedPermissions) { - pw.print(" "); - pw.println(s); + + String prefix = " "; + pw.print(prefix); pw.print("userId="); pw.println(su.userId); + + PermissionsState permissionsState = su.getPermissionsState(); + dumpInstallPermissionsLPr(pw, prefix, permissionsState); + + for (int userId : UserManagerService.getInstance().getUserIds()) { + final int[] gids = permissionsState.computeGids(userId); + Set<String> permissions = permissionsState.getRuntimePermissions(userId); + if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) { + pw.print(prefix); pw.print("User "); pw.print(userId); pw.println(": "); + dumpGidsLPr(pw, prefix + " ", gids); + dumpRuntimePermissionsLPr(pw, prefix + " ", permissions); + } } } else { pw.print("suid,"); pw.print(su.userId); pw.print(","); pw.println(su.name); @@ -3682,4 +4100,326 @@ final class Settings { pw.print("]"); } } + + void dumpGidsLPr(PrintWriter pw, String prefix, int[] gids) { + if (!ArrayUtils.isEmpty(gids)) { + pw.print(prefix); + pw.print("gids="); pw.println( + PackageManagerService.arrayToString(gids)); + } + } + + void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, Set<String> permissions) { + if (!permissions.isEmpty()) { + pw.print(prefix); pw.println("runtime permissions:"); + for (String permission : permissions) { + pw.print(prefix); pw.print(" "); pw.println(permission); + } + } + } + + void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, + PermissionsState permissionsState) { + Set<String> permissions = permissionsState.getInstallPermissions(); + if (!permissions.isEmpty()) { + pw.print(prefix); pw.println("install permissions:"); + for (String permission : permissions) { + pw.print(prefix); pw.print(" "); pw.println(permission); + } + } + } + + public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) { + if (sync) { + mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId); + } else { + mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId); + } + } + + private final class RuntimePermissionPersistence { + private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200; + + private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000; + + private final Handler mHandler = new MyHandler(); + + private final Object mLock; + + @GuardedBy("mLock") + private SparseBooleanArray mWriteScheduled = new SparseBooleanArray(); + + @GuardedBy("mLock") + private SparseLongArray mLastNotWrittenMutationTimesMillis = new SparseLongArray(); + + public RuntimePermissionPersistence(Object lock) { + mLock = lock; + } + + public void writePermissionsForUserSyncLPr(int userId) { + if (!PackageManagerService.RUNTIME_PERMISSIONS_ENABLED) { + return; + } + + mHandler.removeMessages(userId); + writePermissionsSync(userId); + } + + public void writePermissionsForUserAsyncLPr(int userId) { + if (!PackageManagerService.RUNTIME_PERMISSIONS_ENABLED) { + return; + } + + final long currentTimeMillis = SystemClock.uptimeMillis(); + + if (mWriteScheduled.get(userId)) { + mHandler.removeMessages(userId); + + // If enough time passed, write without holding off anymore. + final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis + .get(userId); + final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis + - lastNotWrittenMutationTimeMillis; + if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) { + mHandler.obtainMessage(userId).sendToTarget(); + return; + } + + // Hold off a bit more as settings are frequently changing. + final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis + + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0); + final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS, + maxDelayMillis); + + Message message = mHandler.obtainMessage(userId); + mHandler.sendMessageDelayed(message, writeDelayMillis); + } else { + mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis); + Message message = mHandler.obtainMessage(userId); + mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS); + mWriteScheduled.put(userId, true); + } + } + + private void writePermissionsSync(int userId) { + AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId)); + + ArrayMap<String, Set<String>> permissionsForPackage = new ArrayMap<>(); + ArrayMap<String, Set<String>> permissionsForSharedUser = new ArrayMap<>(); + + synchronized (mLock) { + mWriteScheduled.delete(userId); + + final int packageCount = mPackages.size(); + for (int i = 0; i < packageCount; i++) { + String packageName = mPackages.keyAt(i); + PackageSetting packageSetting = mPackages.valueAt(i); + if (packageSetting.sharedUser == null) { + PermissionsState permissionsState = packageSetting.getPermissionsState(); + Set<String> permissions = permissionsState.getRuntimePermissions(userId); + if (!permissions.isEmpty()) { + permissionsForPackage.put(packageName, permissions); + } + } + } + + final int sharedUserCount = mSharedUsers.size(); + for (int i = 0; i < sharedUserCount; i++) { + String sharedUserName = mSharedUsers.keyAt(i); + SharedUserSetting sharedUser = mSharedUsers.valueAt(i); + PermissionsState permissionsState = sharedUser.getPermissionsState(); + Set<String> permissions = permissionsState.getRuntimePermissions(userId); + if (!permissions.isEmpty()) { + permissionsForSharedUser.put(sharedUserName, permissions); + } + } + } + + FileOutputStream out = null; + try { + out = destination.startWrite(); + + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + serializer.startTag(null, TAG_RUNTIME_PERMISSIONS); + + final int packageCount = permissionsForPackage.size(); + for (int i = 0; i < packageCount; i++) { + String packageName = permissionsForPackage.keyAt(i); + Set<String> permissions = permissionsForPackage.valueAt(i); + serializer.startTag(null, TAG_PACKAGE); + serializer.attribute(null, ATTR_NAME, packageName); + writePermissions(serializer, permissions); + serializer.endTag(null, TAG_PACKAGE); + } + + final int sharedUserCount = permissionsForSharedUser.size(); + for (int i = 0; i < sharedUserCount; i++) { + String packageName = permissionsForSharedUser.keyAt(i); + Set<String> permissions = permissionsForSharedUser.valueAt(i); + serializer.startTag(null, TAG_SHARED_USER); + serializer.attribute(null, ATTR_NAME, packageName); + writePermissions(serializer, permissions); + serializer.endTag(null, TAG_SHARED_USER); + } + + serializer.endTag(null, TAG_RUNTIME_PERMISSIONS); + serializer.endDocument(); + destination.finishWrite(out); + + // Any error while writing is fatal. + } catch (Throwable t) { + Slog.wtf(PackageManagerService.TAG, + "Failed to write settings, restoring backup", t); + destination.failWrite(out); + throw new IllegalStateException("Failed to write runtime permissions," + + " restoring backup", t); + } finally { + IoUtils.closeQuietly(out); + } + } + + private void onUserRemoved(int userId) { + // Make sure we do not + mHandler.removeMessages(userId); + + for (SettingBase sb : mPackages.values()) { + revokeRuntimePermissions(sb, userId); + } + + for (SettingBase sb : mSharedUsers.values()) { + revokeRuntimePermissions(sb, userId); + } + } + + private void revokeRuntimePermissions(SettingBase sb, int userId) { + PermissionsState permissionsState = sb.getPermissionsState(); + for (String permission : permissionsState.getRuntimePermissions(userId)) { + BasePermission bp = mPermissions.get(permission); + if (bp != null) { + permissionsState.revokeRuntimePermission(bp, userId); + } + } + } + + public void readStateForUserSyncLPr(int userId) { + File permissionsFile = getUserRuntimePermissionsFile(userId); + if (!permissionsFile.exists()) { + return; + } + + FileInputStream in; + try { + in = new FileInputStream(permissionsFile); + } catch (FileNotFoundException fnfe) { + Slog.i(PackageManagerService.TAG, "No permissions state"); + return; + } + + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseRuntimePermissionsLPr(parser, userId); + + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed parsing permissions file: " + + permissionsFile , e); + } finally { + IoUtils.closeQuietly(in); + } + } + + private void parseRuntimePermissionsLPr(XmlPullParser parser, int userId) + throws IOException, XmlPullParserException { + final 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; + } + + switch (parser.getName()) { + case TAG_PACKAGE: { + String name = parser.getAttributeValue(null, ATTR_NAME); + PackageSetting ps = mPackages.get(name); + if (ps == null) { + Slog.w(PackageManagerService.TAG, "Unknown package:" + name); + XmlUtils.skipCurrentTag(parser); + continue; + } + parsePermissionsLPr(parser, ps.getPermissionsState(), userId); + } break; + + case TAG_SHARED_USER: { + String name = parser.getAttributeValue(null, ATTR_NAME); + SharedUserSetting sus = mSharedUsers.get(name); + if (sus == null) { + Slog.w(PackageManagerService.TAG, "Unknown shared user:" + name); + XmlUtils.skipCurrentTag(parser); + continue; + } + parsePermissionsLPr(parser, sus.getPermissionsState(), userId); + } break; + } + } + } + + private void parsePermissionsLPr(XmlPullParser parser, PermissionsState permissionsState, + int userId) throws IOException, XmlPullParserException { + final 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; + } + + switch (parser.getName()) { + case TAG_ITEM: { + String name = parser.getAttributeValue(null, ATTR_NAME); + BasePermission bp = mPermissions.get(name); + if (bp == null) { + Slog.w(PackageManagerService.TAG, "Unknown permission:" + name); + XmlUtils.skipCurrentTag(parser); + continue; + } + + if (permissionsState.grantRuntimePermission(bp, userId) == + PermissionsState.PERMISSION_OPERATION_FAILURE) { + Slog.w(PackageManagerService.TAG, "Duplicate permission:" + name); + } + } break; + } + } + } + + private void writePermissions(XmlSerializer serializer, Set<String> permissions) + throws IOException { + for (String permission : permissions) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, permission); + serializer.endTag(null, TAG_ITEM); + } + } + + private final class MyHandler extends Handler { + public MyHandler() { + super(BackgroundThread.getHandler().getLooper()); + } + + @Override + public void handleMessage(Message message) { + final int userId = message.what; + Runnable callback = (Runnable) message.obj; + writePermissionsSync(userId); + if (callback != null) { + callback.run(); + } + } + } + } } diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 2b406f7..06e020a 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -21,21 +21,23 @@ import android.util.ArraySet; /** * Settings data for a particular shared user ID we know about. */ -final class SharedUserSetting extends GrantedPermissions { +final class SharedUserSetting extends SettingBase { final String name; int userId; // flags that are associated with this uid, regardless of any package flags int uidFlags; + int uidPrivateFlags; final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>(); final PackageSignatures signatures = new PackageSignatures(); - SharedUserSetting(String _name, int _pkgFlags) { - super(_pkgFlags); + SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) { + super(_pkgFlags, _pkgPrivateFlags); uidFlags = _pkgFlags; + uidPrivateFlags = _pkgPrivateFlags; name = _name; } @@ -55,12 +57,20 @@ final class SharedUserSetting extends GrantedPermissions { } setFlags(aggregatedFlags); } + if ((this.pkgPrivateFlags & packageSetting.pkgPrivateFlags) != 0) { + int aggregatedPrivateFlags = uidPrivateFlags; + for (PackageSetting ps : packages) { + aggregatedPrivateFlags |= ps.pkgPrivateFlags; + } + setPrivateFlags(aggregatedPrivateFlags); + } } } void addPackage(PackageSetting packageSetting) { if (packages.add(packageSetting)) { setFlags(this.pkgFlags | packageSetting.pkgFlags); + setPrivateFlags(this.pkgPrivateFlags | packageSetting.pkgPrivateFlags); } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d484b8f..e79a206 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -16,8 +16,6 @@ package com.android.server.pm; -import static android.text.format.DateUtils.MINUTE_IN_MILLIS; - import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -30,14 +28,15 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.os.Binder; import android.os.Bundle; -import android.os.Debug; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.IUserManager; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -51,9 +50,11 @@ import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.util.Xml; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsService; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -73,6 +74,8 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; +import libcore.io.IoUtils; + public class UserManagerService extends IUserManager.Stub { private static final String LOG_TAG = "UserManagerService"; @@ -109,10 +112,13 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_TYPE_STRING = "s"; private static final String ATTR_TYPE_BOOLEAN = "b"; private static final String ATTR_TYPE_INTEGER = "i"; + private static final String ATTR_TYPE_BUNDLE = "B"; + private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA"; private static final String USER_INFO_DIR = "system" + File.separator + "users"; private static final String USER_LIST_FILENAME = "userlist.xml"; private static final String USER_PHOTO_FILENAME = "photo.png"; + private static final String USER_PHOTO_FILENAME_TMP = USER_PHOTO_FILENAME + ".tmp"; private static final String RESTRICTIONS_FILE_PREFIX = "res_"; private static final String XML_SUFFIX = ".xml"; @@ -134,6 +140,8 @@ public class UserManagerService extends IUserManager.Stub { // BACKOFF_INC_INTERVAL times. private static final int[] BACKOFF_TIMES = { 0, 30*1000, 60*1000, 5*60*1000, 30*60*1000 }; + static final int WRITE_USER_MSG = 1; + static final int WRITE_USER_DELAY = 2*1000; // 2 seconds private final Context mContext; private final PackageManagerService mPm; @@ -210,7 +218,7 @@ public class UserManagerService extends IUserManager.Stub { mPm = pm; mInstallLock = installLock; mPackagesLock = packagesLock; - mHandler = new Handler(); + mHandler = new MainHandler(); synchronized (mInstallLock) { synchronized (mPackagesLock) { mUsersDir = new File(dataDir, USER_INFO_DIR); @@ -319,16 +327,20 @@ public class UserManagerService extends IUserManager.Stub { public UserInfo getProfileParent(int userHandle) { checkManageUsersPermission("get the profile parent"); synchronized (mPackagesLock) { - UserInfo profile = getUserInfoLocked(userHandle); - if (profile == null) { - return null; - } - int parentUserId = profile.profileGroupId; - if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) { - return null; - } else { - return getUserInfoLocked(parentUserId); - } + return getProfileParentLocked(userHandle); + } + } + + private UserInfo getProfileParentLocked(int userHandle) { + UserInfo profile = getUserInfoLocked(userHandle); + if (profile == null) { + return null; + } + int parentUserId = profile.profileGroupId; + if (parentUserId == UserInfo.NO_PROFILE_GROUP_ID) { + return null; + } else { + return getUserInfoLocked(parentUserId); } } @@ -433,7 +445,8 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public Bitmap getUserIcon(int userId) { + public ParcelFileDescriptor getUserIcon(int userId) { + String iconPath; synchronized (mPackagesLock) { UserInfo info = mUsers.get(userId); if (info == null || info.partial) { @@ -448,8 +461,16 @@ public class UserManagerService extends IUserManager.Stub { if (info.iconPath == null) { return null; } - return BitmapFactory.decodeFile(info.iconPath); + iconPath = info.iconPath; + } + + try { + return ParcelFileDescriptor.open( + new File(iconPath), ParcelFileDescriptor.MODE_READ_ONLY); + } catch (FileNotFoundException e) { + Log.e(LOG_TAG, "Couldn't find icon file", e); } + return null; } public void makeInitialized(int userId) { @@ -461,7 +482,7 @@ public class UserManagerService extends IUserManager.Stub { } if ((info.flags&UserInfo.FLAG_INITIALIZED) == 0) { info.flags |= UserInfo.FLAG_INITIALIZED; - writeUserLocked(info); + scheduleWriteUserLocked(info); } } } @@ -529,7 +550,7 @@ public class UserManagerService extends IUserManager.Stub { } finally { Binder.restoreCallingIdentity(token); } - writeUserLocked(mUsers.get(userId)); + scheduleWriteUserLocked(mUsers.get(userId)); } } @@ -572,6 +593,7 @@ public class UserManagerService extends IUserManager.Stub { try { File dir = new File(mUsersDir, Integer.toString(info.id)); File file = new File(dir, USER_PHOTO_FILENAME); + File tmp = new File(dir, USER_PHOTO_FILENAME_TMP); if (!dir.exists()) { dir.mkdir(); FileUtils.setPermissions( @@ -580,7 +602,8 @@ public class UserManagerService extends IUserManager.Stub { -1, -1); } FileOutputStream os; - if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, os = new FileOutputStream(file))) { + if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, os = new FileOutputStream(tmp)) + && tmp.renameTo(file)) { info.iconPath = file.getAbsolutePath(); } try { @@ -588,6 +611,7 @@ public class UserManagerService extends IUserManager.Stub { } catch (IOException ioe) { // What the ... ! } + tmp.delete(); } catch (FileNotFoundException e) { Slog.w(LOG_TAG, "Error setting photo for user ", e); } @@ -695,7 +719,7 @@ public class UserManagerService extends IUserManager.Stub { UserInfo user = mUsers.get(UserHandle.USER_OWNER); if ("Primary".equals(user.name)) { user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name); - writeUserLocked(user); + scheduleWriteUserLocked(user); } userVersion = 1; } @@ -705,7 +729,7 @@ public class UserManagerService extends IUserManager.Stub { UserInfo user = mUsers.get(UserHandle.USER_OWNER); if ((user.flags & UserInfo.FLAG_INITIALIZED) == 0) { user.flags |= UserInfo.FLAG_INITIALIZED; - writeUserLocked(user); + scheduleWriteUserLocked(user); } userVersion = 2; } @@ -748,6 +772,13 @@ public class UserManagerService extends IUserManager.Stub { writeUserLocked(primary); } + private void scheduleWriteUserLocked(UserInfo userInfo) { + if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) { + Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userInfo); + mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY); + } + } + /* * Writes the user file in this format: * @@ -897,6 +928,8 @@ public class UserManagerService extends IUserManager.Stub { writeBoolean(serializer, restrictions, UserManager.DISALLOW_CREATE_WINDOWS); writeBoolean(serializer, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); writeBoolean(serializer, restrictions, UserManager.DISALLOW_OUTGOING_BEAM); + writeBoolean(serializer, restrictions, UserManager.DISALLOW_WALLPAPER); + writeBoolean(serializer, restrictions, UserManager.DISALLOW_SAFE_BOOT); serializer.endTag(null, TAG_RESTRICTIONS); } @@ -951,12 +984,6 @@ public class UserManagerService extends IUserManager.Stub { lastAttemptTime = readLongAttribute(parser, ATTR_LAST_RETRY_MS, 0L); profileGroupId = readIntAttribute(parser, ATTR_PROFILE_GROUP_ID, UserInfo.NO_PROFILE_GROUP_ID); - if (profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) { - // This attribute was added and renamed during development of L. - // TODO Remove upgrade path by 1st May 2014 - profileGroupId = readIntAttribute(parser, "relatedGroupId", - UserInfo.NO_PROFILE_GROUP_ID); - } String valueString = parser.getAttributeValue(null, ATTR_PARTIAL); if ("true".equals(valueString)) { partial = true; @@ -1049,6 +1076,8 @@ public class UserManagerService extends IUserManager.Stub { readBoolean(parser, restrictions, UserManager.DISALLOW_CREATE_WINDOWS); readBoolean(parser, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); readBoolean(parser, restrictions, UserManager.DISALLOW_OUTGOING_BEAM); + readBoolean(parser, restrictions, UserManager.DISALLOW_WALLPAPER); + readBoolean(parser, restrictions, UserManager.DISALLOW_SAFE_BOOT); } private void readBoolean(XmlPullParser parser, Bundle restrictions, @@ -1191,17 +1220,17 @@ public class UserManagerService extends IUserManager.Stub { if (parent != null) { if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) { parent.profileGroupId = parent.id; - writeUserLocked(parent); + scheduleWriteUserLocked(parent); } userInfo.profileGroupId = parent.profileGroupId; } - writeUserLocked(userInfo); mPm.createNewUserLILPw(userId, userPath); userInfo.partial = false; - writeUserLocked(userInfo); + scheduleWriteUserLocked(userInfo); updateUserIdsLocked(); Bundle restrictions = new Bundle(); mUserRestrictions.append(userId, restrictions); + mPm.newUserCreatedLILPw(userId); } } if (userInfo != null) { @@ -1523,12 +1552,12 @@ public class UserManagerService extends IUserManager.Stub { } if (passwordToHash(pin, pinState.salt).equals(pinState.pinHash)) { pinState.failedAttempts = 0; - writeUserLocked(mUsers.get(userId)); + scheduleWriteUserLocked(mUsers.get(userId)); return UserManager.PIN_VERIFICATION_SUCCESS; } else { pinState.failedAttempts++; pinState.lastAttemptTime = System.currentTimeMillis(); - writeUserLocked(mUsers.get(userId)); + scheduleWriteUserLocked(mUsers.get(userId)); return waitTime; } } @@ -1592,7 +1621,8 @@ public class UserManagerService extends IUserManager.Stub { try { for (ApplicationInfo appInfo : apps) { if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0 - && (appInfo.flags & ApplicationInfo.FLAG_HIDDEN) != 0) { + && (appInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HIDDEN) + != 0) { mPm.setApplicationHiddenSettingAsUser(appInfo.packageName, false, userHandle); } @@ -1653,124 +1683,171 @@ public class UserManagerService extends IUserManager.Stub { private Bundle readApplicationRestrictionsLocked(String packageName, int userId) { + AtomicFile restrictionsFile = + new AtomicFile(new File(Environment.getUserSystemDirectory(userId), + packageToRestrictionsFileName(packageName))); + return readApplicationRestrictionsLocked(restrictionsFile); + } + + @VisibleForTesting + static Bundle readApplicationRestrictionsLocked(AtomicFile restrictionsFile) { final Bundle restrictions = new Bundle(); - final ArrayList<String> values = new ArrayList<String>(); + final ArrayList<String> values = new ArrayList<>(); + if (!restrictionsFile.getBaseFile().exists()) { + return restrictions; + } FileInputStream fis = null; try { - AtomicFile restrictionsFile = - new AtomicFile(new File(Environment.getUserSystemDirectory(userId), - packageToRestrictionsFileName(packageName))); fis = restrictionsFile.openRead(); XmlPullParser parser = Xml.newPullParser(); parser.setInput(fis, null); - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { + XmlUtils.nextElement(parser); + if (parser.getEventType() != XmlPullParser.START_TAG) { Slog.e(LOG_TAG, "Unable to read restrictions file " + restrictionsFile.getBaseFile()); return restrictions; } + while (parser.next() != XmlPullParser.END_DOCUMENT) { + readEntry(restrictions, values, parser); + } + } catch (IOException|XmlPullParserException e) { + Log.w(LOG_TAG, "Error parsing " + restrictionsFile.getBaseFile(), e); + } finally { + IoUtils.closeQuietly(fis); + } + return restrictions; + } - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) { - String key = parser.getAttributeValue(null, ATTR_KEY); - String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE); - String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE); - if (multiple != null) { - values.clear(); - int count = Integer.parseInt(multiple); - while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (type == XmlPullParser.START_TAG - && parser.getName().equals(TAG_VALUE)) { - values.add(parser.nextText().trim()); - count--; - } - } - String [] valueStrings = new String[values.size()]; - values.toArray(valueStrings); - restrictions.putStringArray(key, valueStrings); - } else { - String value = parser.nextText().trim(); - if (ATTR_TYPE_BOOLEAN.equals(valType)) { - restrictions.putBoolean(key, Boolean.parseBoolean(value)); - } else if (ATTR_TYPE_INTEGER.equals(valType)) { - restrictions.putInt(key, Integer.parseInt(value)); - } else { - restrictions.putString(key, value); - } + private static void readEntry(Bundle restrictions, ArrayList<String> values, + XmlPullParser parser) throws XmlPullParserException, IOException { + int type = parser.getEventType(); + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) { + String key = parser.getAttributeValue(null, ATTR_KEY); + String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE); + String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE); + if (multiple != null) { + values.clear(); + int count = Integer.parseInt(multiple); + while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG + && parser.getName().equals(TAG_VALUE)) { + values.add(parser.nextText().trim()); + count--; } } - } - } catch (IOException ioe) { - } catch (XmlPullParserException pe) { - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException e) { + String [] valueStrings = new String[values.size()]; + values.toArray(valueStrings); + restrictions.putStringArray(key, valueStrings); + } else if (ATTR_TYPE_BUNDLE.equals(valType)) { + restrictions.putBundle(key, readBundleEntry(parser, values)); + } else if (ATTR_TYPE_BUNDLE_ARRAY.equals(valType)) { + final int outerDepth = parser.getDepth(); + ArrayList<Bundle> bundleList = new ArrayList<>(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + Bundle childBundle = readBundleEntry(parser, values); + bundleList.add(childBundle); + } + restrictions.putParcelableArray(key, + bundleList.toArray(new Bundle[bundleList.size()])); + } else { + String value = parser.nextText().trim(); + if (ATTR_TYPE_BOOLEAN.equals(valType)) { + restrictions.putBoolean(key, Boolean.parseBoolean(value)); + } else if (ATTR_TYPE_INTEGER.equals(valType)) { + restrictions.putInt(key, Integer.parseInt(value)); + } else { + restrictions.putString(key, value); } } } - return restrictions; + } + + private static Bundle readBundleEntry(XmlPullParser parser, ArrayList<String> values) + throws IOException, XmlPullParserException { + Bundle childBundle = new Bundle(); + final int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + readEntry(childBundle, values, parser); + } + return childBundle; } private void writeApplicationRestrictionsLocked(String packageName, Bundle restrictions, int userId) { - FileOutputStream fos = null; AtomicFile restrictionsFile = new AtomicFile( new File(Environment.getUserSystemDirectory(userId), packageToRestrictionsFileName(packageName))); + writeApplicationRestrictionsLocked(restrictions, restrictionsFile); + } + + @VisibleForTesting + static void writeApplicationRestrictionsLocked(Bundle restrictions, + AtomicFile restrictionsFile) { + FileOutputStream fos = null; try { fos = restrictionsFile.startWrite(); final BufferedOutputStream bos = new BufferedOutputStream(fos); - // XmlSerializer serializer = XmlUtils.serializerInstance(); final XmlSerializer serializer = new FastXmlSerializer(); serializer.setOutput(bos, "utf-8"); serializer.startDocument(null, true); serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, TAG_RESTRICTIONS); - - for (String key : restrictions.keySet()) { - Object value = restrictions.get(key); - serializer.startTag(null, TAG_ENTRY); - serializer.attribute(null, ATTR_KEY, key); - - if (value instanceof Boolean) { - serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN); - serializer.text(value.toString()); - } else if (value instanceof Integer) { - serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER); - serializer.text(value.toString()); - } else if (value == null || value instanceof String) { - serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING); - serializer.text(value != null ? (String) value : ""); - } else { - serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY); - String[] values = (String[]) value; - serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length)); - for (String choice : values) { - serializer.startTag(null, TAG_VALUE); - serializer.text(choice != null ? choice : ""); - serializer.endTag(null, TAG_VALUE); - } - } - serializer.endTag(null, TAG_ENTRY); - } - + writeBundle(restrictions, serializer); serializer.endTag(null, TAG_RESTRICTIONS); serializer.endDocument(); restrictionsFile.finishWrite(fos); } catch (Exception e) { restrictionsFile.failWrite(fos); - Slog.e(LOG_TAG, "Error writing application restrictions list"); + Slog.e(LOG_TAG, "Error writing application restrictions list", e); + } + } + + private static void writeBundle(Bundle restrictions, XmlSerializer serializer) + throws IOException { + for (String key : restrictions.keySet()) { + Object value = restrictions.get(key); + serializer.startTag(null, TAG_ENTRY); + serializer.attribute(null, ATTR_KEY, key); + + if (value instanceof Boolean) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN); + serializer.text(value.toString()); + } else if (value instanceof Integer) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER); + serializer.text(value.toString()); + } else if (value == null || value instanceof String) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING); + serializer.text(value != null ? (String) value : ""); + } else if (value instanceof Bundle) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE); + writeBundle((Bundle) value, serializer); + } else if (value instanceof Parcelable[]) { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE_ARRAY); + Parcelable[] array = (Parcelable[]) value; + for (Parcelable parcelable : array) { + if (!(parcelable instanceof Bundle)) { + throw new IllegalArgumentException("bundle-array can only hold Bundles"); + } + serializer.startTag(null, TAG_ENTRY); + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE); + writeBundle((Bundle) parcelable, serializer); + serializer.endTag(null, TAG_ENTRY); + } + } else { + serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY); + String[] values = (String[]) value; + serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length)); + for (String choice : values) { + serializer.startTag(null, TAG_VALUE); + serializer.text(choice != null ? choice : ""); + serializer.endTag(null, TAG_VALUE); + } + } + serializer.endTag(null, TAG_ENTRY); } } @@ -1786,13 +1863,35 @@ public class UserManagerService extends IUserManager.Stub { public int getUserHandle(int userSerialNumber) { synchronized (mPackagesLock) { for (int userId : mUserIds) { - if (getUserInfoLocked(userId).serialNumber == userSerialNumber) return userId; + UserInfo info = getUserInfoLocked(userId); + if (info != null && info.serialNumber == userSerialNumber) return userId; } // Not found return -1; } } + @Override + public long getUserCreationTime(int userHandle) { + int callingUserId = UserHandle.getCallingUserId(); + UserInfo userInfo = null; + synchronized (mPackagesLock) { + if (callingUserId == userHandle) { + userInfo = getUserInfoLocked(userHandle); + } else { + UserInfo parent = getProfileParentLocked(userHandle); + if (parent != null && parent.id == callingUserId) { + userInfo = getUserInfoLocked(userHandle); + } + } + } + if (userInfo == null) { + throw new SecurityException("userHandle can only be the calling user or a managed " + + "profile associated with this user"); + } + return userInfo.creationTime; + } + /** * Caches the list of user ids in an array, adjusting the array size when necessary. */ @@ -1827,7 +1926,7 @@ public class UserManagerService extends IUserManager.Stub { } if (now > EPOCH_PLUS_30_YEARS) { user.lastLoggedInTime = now; - writeUserLocked(user); + scheduleWriteUserLocked(user); } } } @@ -1904,4 +2003,22 @@ public class UserManagerService extends IUserManager.Stub { } } } + + final class MainHandler extends Handler { + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case WRITE_USER_MSG: + removeMessages(WRITE_USER_MSG, msg.obj); + synchronized (mPackagesLock) { + int userId = ((UserInfo) msg.obj).id; + UserInfo userInfo = mUsers.get(userId); + if (userInfo != null) { + writeUserLocked(userInfo); + } + } + } + } + } } diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java new file mode 100644 index 0000000..e972ec7 --- /dev/null +++ b/services/core/java/com/android/server/policy/BarController.java @@ -0,0 +1,284 @@ +/* + * 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.policy; + +import android.app.StatusBarManager; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.Slog; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManagerPolicy.WindowState; + +import com.android.internal.statusbar.IStatusBarService; + +import java.io.PrintWriter; + +/** + * Controls state/behavior specific to a system bar window. + */ +public class BarController { + private static final boolean DEBUG = false; + + private static final int TRANSIENT_BAR_NONE = 0; + private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1; + private static final int TRANSIENT_BAR_SHOWING = 2; + private static final int TRANSIENT_BAR_HIDING = 3; + + private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000; + + protected final String mTag; + private final int mTransientFlag; + private final int mUnhideFlag; + private final int mTranslucentFlag; + private final int mStatusBarManagerId; + private final int mTranslucentWmFlag; + protected final Handler mHandler; + private final Object mServiceAquireLock = new Object(); + protected IStatusBarService mStatusBarService; + + private WindowState mWin; + private int mState = StatusBarManager.WINDOW_STATE_SHOWING; + private int mTransientBarState; + private boolean mPendingShow; + private long mLastTranslucent; + + public BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag, + int statusBarManagerId, int translucentWmFlag) { + mTag = "BarController." + tag; + mTransientFlag = transientFlag; + mUnhideFlag = unhideFlag; + mTranslucentFlag = translucentFlag; + mStatusBarManagerId = statusBarManagerId; + mTranslucentWmFlag = translucentWmFlag; + mHandler = new Handler(); + } + + public void setWindow(WindowState win) { + mWin = win; + } + + public void showTransient() { + if (mWin != null) { + setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED); + } + } + + public boolean isTransientShowing() { + return mTransientBarState == TRANSIENT_BAR_SHOWING; + } + + public boolean isTransientShowRequested() { + return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED; + } + + public boolean wasRecentlyTranslucent() { + return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS; + } + + public void adjustSystemUiVisibilityLw(int oldVis, int vis) { + if (mWin != null && mTransientBarState == TRANSIENT_BAR_SHOWING && + (vis & mTransientFlag) == 0) { + // sysui requests hide + setTransientBarState(TRANSIENT_BAR_HIDING); + setBarShowingLw(false); + } else if (mWin != null && (oldVis & mUnhideFlag) != 0 && (vis & mUnhideFlag) == 0) { + // sysui ready to unhide + setBarShowingLw(true); + } + } + + public int applyTranslucentFlagLw(WindowState win, int vis, int oldVis) { + if (mWin != null) { + if (win != null && (win.getAttrs().privateFlags + & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) == 0) { + int fl = PolicyControl.getWindowFlags(win, null); + if ((fl & mTranslucentWmFlag) != 0) { + vis |= mTranslucentFlag; + } else { + vis &= ~mTranslucentFlag; + } + if ((fl & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) { + vis |= View.SYSTEM_UI_TRANSPARENT; + } else { + vis &= ~View.SYSTEM_UI_TRANSPARENT; + } + } else { + vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag); + vis = (vis & ~View.SYSTEM_UI_TRANSPARENT) | (oldVis & View.SYSTEM_UI_TRANSPARENT); + } + } + return vis; + } + + public boolean setBarShowingLw(final boolean show) { + if (mWin == null) return false; + if (show && mTransientBarState == TRANSIENT_BAR_HIDING) { + mPendingShow = true; + return false; + } + final boolean wasVis = mWin.isVisibleLw(); + final boolean wasAnim = mWin.isAnimatingLw(); + final boolean change = show ? mWin.showLw(true) : mWin.hideLw(true); + final int state = computeStateLw(wasVis, wasAnim, mWin, change); + final boolean stateChanged = updateStateLw(state); + return change || stateChanged; + } + + private int computeStateLw(boolean wasVis, boolean wasAnim, WindowState win, boolean change) { + if (win.hasDrawnLw()) { + final boolean vis = win.isVisibleLw(); + final boolean anim = win.isAnimatingLw(); + if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) { + return StatusBarManager.WINDOW_STATE_HIDDEN; + } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) { + return StatusBarManager.WINDOW_STATE_SHOWING; + } else if (change) { + if (wasVis && vis && !wasAnim && anim) { + return StatusBarManager.WINDOW_STATE_HIDING; + } else { + return StatusBarManager.WINDOW_STATE_SHOWING; + } + } + } + return mState; + } + + private boolean updateStateLw(final int state) { + if (state != mState) { + mState = state; + if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state)); + mHandler.post(new Runnable() { + @Override + public void run() { + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.setWindowState(mStatusBarManagerId, state); + } + } catch (RemoteException e) { + if (DEBUG) Slog.w(mTag, "Error posting window state", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + }); + return true; + } + return false; + } + + public boolean checkHiddenLw() { + if (mWin != null && mWin.hasDrawnLw()) { + if (!mWin.isVisibleLw() && !mWin.isAnimatingLw()) { + updateStateLw(StatusBarManager.WINDOW_STATE_HIDDEN); + } + if (mTransientBarState == TRANSIENT_BAR_HIDING && !mWin.isVisibleLw()) { + // Finished animating out, clean up and reset style + setTransientBarState(TRANSIENT_BAR_NONE); + if (mPendingShow) { + setBarShowingLw(true); + mPendingShow = false; + } + return true; + } + } + return false; + } + + public boolean checkShowTransientBarLw() { + if (mTransientBarState == TRANSIENT_BAR_SHOWING) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown"); + return false; + } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested"); + return false; + } else if (mWin == null) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist"); + return false; + } else if (mWin.isDisplayedLw()) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar already visible"); + return false; + } else { + return true; + } + } + + public int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) { + if (mWin == null) return vis; + if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested + if (transientAllowed) { + vis |= mTransientFlag; + if ((oldVis & mTransientFlag) == 0) { + vis |= mUnhideFlag; // tell sysui we're ready to unhide + } + setTransientBarState(TRANSIENT_BAR_SHOWING); // request accepted + } else { + setTransientBarState(TRANSIENT_BAR_NONE); // request denied + } + } + if (mTransientBarState != TRANSIENT_BAR_NONE) { + vis |= mTransientFlag; // ignore clear requests until transition completes + vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; // never show transient bars in low profile + } + if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0 || + ((vis | oldVis) & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) { + mLastTranslucent = SystemClock.uptimeMillis(); + } + return vis; + } + + private void setTransientBarState(int state) { + if (mWin != null && state != mTransientBarState) { + if (mTransientBarState == TRANSIENT_BAR_SHOWING || state == TRANSIENT_BAR_SHOWING) { + mLastTranslucent = SystemClock.uptimeMillis(); + } + mTransientBarState = state; + if (DEBUG) Slog.d(mTag, "mTransientBarState: " + transientBarStateToString(state)); + } + } + + protected IStatusBarService getStatusBarService() { + synchronized (mServiceAquireLock) { + if (mStatusBarService == null) { + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService("statusbar")); + } + return mStatusBarService; + } + } + + private static String transientBarStateToString(int state) { + if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING"; + if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING"; + if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED"; + if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE"; + throw new IllegalArgumentException("Unknown state " + state); + } + + public void dump(PrintWriter pw, String prefix) { + if (mWin != null) { + pw.print(prefix); pw.println(mTag); + pw.print(prefix); pw.print(" "); pw.print("mState"); pw.print('='); + pw.println(StatusBarManager.windowStateToString(mState)); + pw.print(prefix); pw.print(" "); pw.print("mTransientBar"); pw.print('='); + pw.println(transientBarStateToString(mTransientBarState)); + } + } +} diff --git a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java new file mode 100644 index 0000000..fef1e57 --- /dev/null +++ b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2015 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.policy; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.os.SystemClock; +import android.util.Slog; +import android.view.Display; +import android.view.animation.LinearInterpolator; + +import com.android.server.LocalServices; + +import java.io.PrintWriter; +import java.util.concurrent.TimeUnit; + +public class BurnInProtectionHelper implements DisplayManager.DisplayListener, + Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { + private static final String TAG = "BurnInProtection"; + + // Default value when max burnin radius is not set. + public static final int BURN_IN_MAX_RADIUS_DEFAULT = -1; + + private static final long BURNIN_PROTECTION_WAKEUP_INTERVAL_MS = TimeUnit.MINUTES.toMillis(1); + private static final long BURNIN_PROTECTION_MINIMAL_INTERVAL_MS = TimeUnit.SECONDS.toMillis(10); + + private static final boolean DEBUG = false; + + private static final String ACTION_BURN_IN_PROTECTION = + "android.internal.policy.action.BURN_IN_PROTECTION"; + + private static final int BURN_IN_SHIFT_STEP = 2; + private static final long CENTERING_ANIMATION_DURATION_MS = 100; + private final ValueAnimator mCenteringAnimator; + + private boolean mBurnInProtectionActive; + private boolean mFirstUpdate; + + private final int mMinHorizontalBurnInOffset; + private final int mMaxHorizontalBurnInOffset; + private final int mMinVerticalBurnInOffset; + private final int mMaxVerticalBurnInOffset; + + private final int mBurnInRadiusMaxSquared; + + private int mLastBurnInXOffset = 0; + /* 1 means increasing, -1 means decreasing */ + private int mXOffsetDirection = 1; + private int mLastBurnInYOffset = 0; + /* 1 means increasing, -1 means decreasing */ + private int mYOffsetDirection = 1; + + private final AlarmManager mAlarmManager; + private final PendingIntent mBurnInProtectionIntent; + private final DisplayManagerInternal mDisplayManagerInternal; + private final Display mDisplay; + + private BroadcastReceiver mBurnInProtectionReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "onReceive " + intent); + } + updateBurnInProtection(); + } + }; + + public BurnInProtectionHelper(Context context, int minHorizontalOffset, + int maxHorizontalOffset, int minVerticalOffset, int maxVerticalOffset, + int maxOffsetRadius) { + mMinHorizontalBurnInOffset = minHorizontalOffset; + mMaxHorizontalBurnInOffset = maxHorizontalOffset; + mMinVerticalBurnInOffset = minVerticalOffset; + mMaxVerticalBurnInOffset = maxVerticalOffset; + if (maxOffsetRadius != BURN_IN_MAX_RADIUS_DEFAULT) { + mBurnInRadiusMaxSquared = maxOffsetRadius * maxOffsetRadius; + } else { + mBurnInRadiusMaxSquared = BURN_IN_MAX_RADIUS_DEFAULT; + } + + mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + context.registerReceiver(mBurnInProtectionReceiver, + new IntentFilter(ACTION_BURN_IN_PROTECTION)); + Intent intent = new Intent(ACTION_BURN_IN_PROTECTION); + intent.setPackage(context.getPackageName()); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mBurnInProtectionIntent = PendingIntent.getBroadcast(context, 0, + intent, PendingIntent.FLAG_UPDATE_CURRENT); + DisplayManager displayManager = + (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + mDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + displayManager.registerDisplayListener(this, null /* handler */); + + mCenteringAnimator = ValueAnimator.ofFloat(1f, 0f); + mCenteringAnimator.setDuration(CENTERING_ANIMATION_DURATION_MS); + mCenteringAnimator.setInterpolator(new LinearInterpolator()); + mCenteringAnimator.addListener(this); + mCenteringAnimator.addUpdateListener(this); + } + + public void startBurnInProtection() { + if (!mBurnInProtectionActive) { + mBurnInProtectionActive = true; + mFirstUpdate = true; + mCenteringAnimator.cancel(); + updateBurnInProtection(); + } + } + + private void updateBurnInProtection() { + if (mBurnInProtectionActive) { + // We don't want to adjust offsets immediately after the device goes into ambient mode. + // Instead, we want to wait until it's more likely that the user is not observing the + // screen anymore. + if (mFirstUpdate) { + mFirstUpdate = false; + } else { + adjustOffsets(); + mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), + mLastBurnInXOffset, mLastBurnInYOffset); + } + // We use currentTimeMillis to compute the next wakeup time since we want to wake up at + // the same time as we wake up to update ambient mode to minimize power consumption. + // However, we use elapsedRealtime to schedule the alarm so that setting the time can't + // disable burn-in protection for extended periods. + final long nowWall = System.currentTimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + // Next adjustment at least ten seconds in the future. + long nextWall = nowWall + BURNIN_PROTECTION_MINIMAL_INTERVAL_MS; + // And aligned to the minute. + nextWall = nextWall - nextWall % BURNIN_PROTECTION_WAKEUP_INTERVAL_MS + + BURNIN_PROTECTION_WAKEUP_INTERVAL_MS; + // Use elapsed real time that is adjusted to full minute on wall clock. + final long nextElapsed = nowElapsed + (nextWall - nowWall); + if (DEBUG) { + Slog.d(TAG, "scheduling next wake-up, now wall time " + nowWall + + ", next wall: " + nextWall + ", now elapsed: " + nowElapsed + + ", next elapsed: " + nextElapsed); + } + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, nextElapsed, + mBurnInProtectionIntent); + } else { + mAlarmManager.cancel(mBurnInProtectionIntent); + mCenteringAnimator.start(); + } + } + + public void cancelBurnInProtection() { + if (mBurnInProtectionActive) { + mBurnInProtectionActive = false; + updateBurnInProtection(); + } + } + + /** + * Gently shifts current burn-in offsets, minimizing the change for the user. + * + * Shifts are applied in following fashion: + * 1) shift horizontally from minimum to the maximum; + * 2) shift vertically by one from minimum to the maximum; + * 3) shift horizontally from maximum to the minimum; + * 4) shift vertically by one from minimum to the maximum. + * 5) if you reach the maximum vertically, start shifting back by one from maximum to minimum. + * + * On top of that, stay within specified radius. If the shift distance from the center is + * higher than the radius, skip these values and go the next position that is within the radius. + */ + private void adjustOffsets() { + do { + // By default, let's just shift the X offset. + final int xChange = mXOffsetDirection * BURN_IN_SHIFT_STEP; + mLastBurnInXOffset += xChange; + if (mLastBurnInXOffset > mMaxHorizontalBurnInOffset + || mLastBurnInXOffset < mMinHorizontalBurnInOffset) { + // Whoops, we went too far horizontally. Let's retract.. + mLastBurnInXOffset -= xChange; + // change horizontal direction.. + mXOffsetDirection *= -1; + // and let's shift the Y offset. + final int yChange = mYOffsetDirection * BURN_IN_SHIFT_STEP; + mLastBurnInYOffset += yChange; + if (mLastBurnInYOffset > mMaxVerticalBurnInOffset + || mLastBurnInYOffset < mMinVerticalBurnInOffset) { + // Whoops, we went to far vertically. Let's retract.. + mLastBurnInYOffset -= yChange; + // and change vertical direction. + mYOffsetDirection *= -1; + } + } + // If we are outside of the radius, let's try again. + } while (mBurnInRadiusMaxSquared != BURN_IN_MAX_RADIUS_DEFAULT + && mLastBurnInXOffset * mLastBurnInXOffset + mLastBurnInYOffset * mLastBurnInYOffset + > mBurnInRadiusMaxSquared); + } + + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + TAG); + prefix += " "; + pw.println(prefix + "mBurnInProtectionActive=" + mBurnInProtectionActive); + pw.println(prefix + "mHorizontalBurnInOffsetsBounds=(" + mMinHorizontalBurnInOffset + ", " + + mMaxHorizontalBurnInOffset + ")"); + pw.println(prefix + "mVerticalBurnInOffsetsBounds=(" + mMinVerticalBurnInOffset + ", " + + mMaxVerticalBurnInOffset + ")"); + pw.println(prefix + "mBurnInRadiusMaxSquared=" + mBurnInRadiusMaxSquared); + pw.println(prefix + "mLastBurnInOffset=(" + mLastBurnInXOffset + ", " + + mLastBurnInYOffset + ")"); + pw.println(prefix + "mOfsetChangeDirections=(" + mXOffsetDirection + ", " + + mYOffsetDirection + ")"); + } + + @Override + public void onDisplayAdded(int i) { + } + + @Override + public void onDisplayRemoved(int i) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == mDisplay.getDisplayId()) { + if (mDisplay.getState() == Display.STATE_DOZE + || mDisplay.getState() == Display.STATE_DOZE_SUSPEND) { + startBurnInProtection(); + } else { + cancelBurnInProtection(); + } + } + } + + @Override + public void onAnimationStart(Animator animator) { + } + + @Override + public void onAnimationEnd(Animator animator) { + if (animator == mCenteringAnimator && !mBurnInProtectionActive) { + // No matter how the animation finishes, we want to zero the offsets. + mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), 0, 0); + } + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + if (!mBurnInProtectionActive) { + final float value = (Float) valueAnimator.getAnimatedValue(); + mDisplayManagerInternal.setDisplayOffsets(mDisplay.getDisplayId(), + (int) (mLastBurnInXOffset * value), (int) (mLastBurnInYOffset * value)); + } + } +} diff --git a/services/core/java/com/android/server/policy/EnableAccessibilityController.java b/services/core/java/com/android/server/policy/EnableAccessibilityController.java new file mode 100644 index 0000000..da9c001 --- /dev/null +++ b/services/core/java/com/android/server/policy/EnableAccessibilityController.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2012 Google Inc. + * + * 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.policy; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ServiceInfo; +import android.media.AudioManager; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserManager; +import android.provider.Settings; +import android.speech.tts.TextToSpeech; +import android.util.MathUtils; +import android.view.IWindowManager; +import android.view.MotionEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class EnableAccessibilityController { + + private static final int SPEAK_WARNING_DELAY_MILLIS = 2000; + private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000; + + public static final int MESSAGE_SPEAK_WARNING = 1; + public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2; + public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_SPEAK_WARNING: { + String text = mContext.getString(R.string.continue_to_enable_accessibility); + mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null); + } break; + case MESSAGE_SPEAK_ENABLE_CANCELED: { + String text = mContext.getString(R.string.enable_accessibility_canceled); + mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null); + } break; + case MESSAGE_ENABLE_ACCESSIBILITY: { + enableAccessibility(); + mTone.play(); + mTts.speak(mContext.getString(R.string.accessibility_enabled), + TextToSpeech.QUEUE_FLUSH, null); + } break; + } + } + }; + + private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService("window")); + + private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager + .Stub.asInterface(ServiceManager.getService("accessibility")); + + + private final Context mContext; + private final Runnable mOnAccessibilityEnabledCallback; + private final UserManager mUserManager; + private final TextToSpeech mTts; + private final Ringtone mTone; + + private final float mTouchSlop; + + private boolean mDestroyed; + private boolean mCanceled; + + private float mFirstPointerDownX; + private float mFirstPointerDownY; + private float mSecondPointerDownX; + private float mSecondPointerDownY; + + public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) { + mContext = context; + mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback; + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() { + @Override + public void onInit(int status) { + if (mDestroyed) { + mTts.shutdown(); + } + } + }); + mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI); + mTone.setStreamType(AudioManager.STREAM_MUSIC); + mTouchSlop = context.getResources().getDimensionPixelSize( + R.dimen.accessibility_touch_slop); + } + + public static boolean canEnableAccessibilityViaGesture(Context context) { + AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context); + // Accessibility is enabled and there is an enabled speaking + // accessibility service, then we have nothing to do. + if (accessibilityManager.isEnabled() + && !accessibilityManager.getEnabledAccessibilityServiceList( + AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) { + return false; + } + // If the global gesture is enabled and there is a speaking service + // installed we are good to go, otherwise there is nothing to do. + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1 + && !getInstalledSpeakingAccessibilityServices(context).isEmpty(); + } + + private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices( + Context context) { + List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>(); + services.addAll(AccessibilityManager.getInstance(context) + .getInstalledAccessibilityServiceList()); + Iterator<AccessibilityServiceInfo> iterator = services.iterator(); + while (iterator.hasNext()) { + AccessibilityServiceInfo service = iterator.next(); + if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) { + iterator.remove(); + } + } + return services; + } + + public void onDestroy() { + mDestroyed = true; + } + + public boolean onInterceptTouchEvent(MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN + && event.getPointerCount() == 2) { + mFirstPointerDownX = event.getX(0); + mFirstPointerDownY = event.getY(0); + mSecondPointerDownX = event.getX(1); + mSecondPointerDownY = event.getY(1); + mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING, + SPEAK_WARNING_DELAY_MILLIS); + mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY, + ENABLE_ACCESSIBILITY_DELAY_MILLIS); + return true; + } + return false; + } + + public boolean onTouchEvent(MotionEvent event) { + final int pointerCount = event.getPointerCount(); + final int action = event.getActionMasked(); + if (mCanceled) { + if (action == MotionEvent.ACTION_UP) { + mCanceled = false; + } + return true; + } + switch (action) { + case MotionEvent.ACTION_POINTER_DOWN: { + if (pointerCount > 2) { + cancel(); + } + } break; + case MotionEvent.ACTION_MOVE: { + final float firstPointerMove = MathUtils.dist(event.getX(0), + event.getY(0), mFirstPointerDownX, mFirstPointerDownY); + if (Math.abs(firstPointerMove) > mTouchSlop) { + cancel(); + } + final float secondPointerMove = MathUtils.dist(event.getX(1), + event.getY(1), mSecondPointerDownX, mSecondPointerDownY); + if (Math.abs(secondPointerMove) > mTouchSlop) { + cancel(); + } + } break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: { + cancel(); + } break; + } + return true; + } + + private void cancel() { + mCanceled = true; + if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) { + mHandler.removeMessages(MESSAGE_SPEAK_WARNING); + } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) { + mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED); + } + mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY); + } + + private void enableAccessibility() { + List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices( + mContext); + if (services.isEmpty()) { + return; + } + boolean keyguardLocked = false; + try { + keyguardLocked = mWindowManager.isKeyguardLocked(); + } catch (RemoteException re) { + /* ignore */ + } + + final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1; + + AccessibilityServiceInfo service = services.get(0); + boolean enableTouchExploration = (service.flags + & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; + // Try to find a service supporting explore by touch. + if (!enableTouchExploration) { + final int serviceCount = services.size(); + for (int i = 1; i < serviceCount; i++) { + AccessibilityServiceInfo candidate = services.get(i); + if ((candidate.flags & AccessibilityServiceInfo + .FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) { + enableTouchExploration = true; + service = candidate; + break; + } + } + } + + ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; + ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name); + if (!keyguardLocked || !hasMoreThanOneUser) { + final int userId = ActivityManager.getCurrentUser(); + String enabledServiceString = componentName.flattenToString(); + ContentResolver resolver = mContext.getContentResolver(); + // Enable one speaking accessibility service. + Settings.Secure.putStringForUser(resolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + enabledServiceString, userId); + // Allow the services we just enabled to toggle touch exploration. + Settings.Secure.putStringForUser(resolver, + Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + enabledServiceString, userId); + // Enable touch exploration. + if (enableTouchExploration) { + Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED, + 1, userId); + } + // Enable accessibility script injection (AndroidVox) for web content. + Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, + 1, userId); + // Turn on accessibility mode last. + Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED, + 1, userId); + } else if (keyguardLocked) { + try { + mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved( + componentName, enableTouchExploration); + } catch (RemoteException re) { + /* ignore */ + } + } + + mOnAccessibilityEnabledCallback.run(); + } +} diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java new file mode 100644 index 0000000..b431b33 --- /dev/null +++ b/services/core/java/com/android/server/policy/GlobalActions.java @@ -0,0 +1,1267 @@ +/* + * 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.policy; + +import com.android.internal.app.AlertController; +import com.android.internal.app.AlertController.AlertParams; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.TelephonyProperties; +import com.android.internal.R; +import com.android.internal.widget.LockPatternUtils; + +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import android.util.TypedValue; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerPolicy.WindowManagerFuncs; +import android.view.accessibility.AccessibilityEvent; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper to show the global actions dialog. Each item is an {@link Action} that + * may show depending on whether the keyguard is showing, and whether the device + * is provisioned. + */ +class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { + + private static final String TAG = "GlobalActions"; + + private static final boolean SHOW_SILENT_TOGGLE = true; + + /* Valid settings for global actions keys. + * see config.xml config_globalActionList */ + private static final String GLOBAL_ACTION_KEY_POWER = "power"; + private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; + private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; + private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; + private static final String GLOBAL_ACTION_KEY_USERS = "users"; + private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; + private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; + private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; + private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; + + private final Context mContext; + private final WindowManagerFuncs mWindowManagerFuncs; + private final AudioManager mAudioManager; + private final IDreamManager mDreamManager; + + private ArrayList<Action> mItems; + private GlobalActionsDialog mDialog; + + private Action mSilentModeAction; + private ToggleAction mAirplaneModeOn; + + private MyAdapter mAdapter; + + private boolean mKeyguardShowing = false; + private boolean mDeviceProvisioned = false; + private ToggleAction.State mAirplaneState = ToggleAction.State.Off; + private boolean mIsWaitingForEcmExit = false; + private boolean mHasTelephony; + private boolean mHasVibrator; + private final boolean mShowSilentToggle; + + /** + * @param context everything needs a context :( + */ + public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) { + mContext = context; + mWindowManagerFuncs = windowManagerFuncs; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mDreamManager = IDreamManager.Stub.asInterface( + ServiceManager.getService(DreamService.DREAM_SERVICE)); + + // receive broadcasts + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); + context.registerReceiver(mBroadcastReceiver, filter); + + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + + // get notified of phone state changes + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, + mAirplaneModeObserver); + Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + mHasVibrator = vibrator != null && vibrator.hasVibrator(); + + mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean( + com.android.internal.R.bool.config_useFixedVolume); + } + + /** + * Show the global actions dialog (creating if necessary) + * @param keyguardShowing True if keyguard is showing + */ + public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { + mKeyguardShowing = keyguardShowing; + mDeviceProvisioned = isDeviceProvisioned; + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + // Show delayed, so that the dismiss of the previous dialog completes + mHandler.sendEmptyMessage(MESSAGE_SHOW); + } else { + handleShow(); + } + } + + private void awakenIfNecessary() { + if (mDreamManager != null) { + try { + if (mDreamManager.isDreaming()) { + mDreamManager.awaken(); + } + } catch (RemoteException e) { + // we tried + } + } + } + + private void handleShow() { + awakenIfNecessary(); + mDialog = createDialog(); + prepareDialog(); + + // If we only have 1 item and it's a simple press action, just do this action. + if (mAdapter.getCount() == 1 + && mAdapter.getItem(0) instanceof SinglePressAction + && !(mAdapter.getItem(0) instanceof LongPressAction)) { + ((SinglePressAction) mAdapter.getItem(0)).onPress(); + } else { + WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); + attrs.setTitle("GlobalActions"); + mDialog.getWindow().setAttributes(attrs); + mDialog.show(); + mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND); + } + } + + /** + * Create the global actions dialog. + * @return A new dialog. + */ + private GlobalActionsDialog createDialog() { + // Simple toggle style if there's no vibrator, otherwise use a tri-state + if (!mHasVibrator) { + mSilentModeAction = new SilentModeToggleAction(); + } else { + mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler); + } + mAirplaneModeOn = new ToggleAction( + R.drawable.ic_lock_airplane_mode, + R.drawable.ic_lock_airplane_mode_off, + R.string.global_actions_toggle_airplane_mode, + R.string.global_actions_airplane_mode_on_status, + R.string.global_actions_airplane_mode_off_status) { + + void onToggle(boolean on) { + if (mHasTelephony && Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { + mIsWaitingForEcmExit = true; + // Launch ECM exit dialog + Intent ecmDialogIntent = + new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); + ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(ecmDialogIntent); + } else { + changeAirplaneModeSystemSetting(on); + } + } + + @Override + protected void changeStateFromPress(boolean buttonOn) { + if (!mHasTelephony) return; + + // In ECM mode airplane state cannot be changed + if (!(Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { + mState = buttonOn ? State.TurningOn : State.TurningOff; + mAirplaneState = mState; + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + onAirplaneModeChanged(); + + mItems = new ArrayList<Action>(); + String[] defaultActions = mContext.getResources().getStringArray( + com.android.internal.R.array.config_globalActionsList); + + ArraySet<String> addedKeys = new ArraySet<String>(); + for (int i = 0; i < defaultActions.length; i++) { + String actionKey = defaultActions[i]; + if (addedKeys.contains(actionKey)) { + // If we already have added this, don't add it again. + continue; + } + if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { + mItems.add(new PowerAction()); + } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { + mItems.add(mAirplaneModeOn); + } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { + if (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) { + mItems.add(getBugReportAction()); + } + } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { + if (mShowSilentToggle) { + mItems.add(mSilentModeAction); + } + } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { + if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { + addUsersToMenu(mItems); + } + } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { + mItems.add(getSettingsAction()); + } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { + mItems.add(getLockdownAction()); + } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { + mItems.add(getVoiceAssistAction()); + } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { + mItems.add(getAssistAction()); + } else { + Log.e(TAG, "Invalid global action key " + actionKey); + } + // Add here so we don't add more than one. + addedKeys.add(actionKey); + } + + mAdapter = new MyAdapter(); + + AlertParams params = new AlertParams(mContext); + params.mAdapter = mAdapter; + params.mOnClickListener = this; + params.mForceInverseBackground = true; + + GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params); + dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. + + dialog.getListView().setItemsCanFocus(true); + dialog.getListView().setLongClickable(true); + dialog.getListView().setOnItemLongClickListener( + new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, + long id) { + final Action action = mAdapter.getItem(position); + if (action instanceof LongPressAction) { + return ((LongPressAction) action).onLongPress(); + } + return false; + } + }); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + + dialog.setOnDismissListener(this); + + return dialog; + } + + private final class PowerAction extends SinglePressAction implements LongPressAction { + private PowerAction() { + super(com.android.internal.R.drawable.ic_lock_power_off, + R.string.global_action_power_off); + } + + @Override + public boolean onLongPress() { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + mWindowManagerFuncs.rebootSafeMode(true); + return true; + } + return false; + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + // shutdown by making sure radio and power are handled accordingly. + mWindowManagerFuncs.shutdown(false /* confirm */); + } + } + + private Action getBugReportAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport, + R.string.bugreport_title) { + + public void onPress() { + AlertDialog.Builder builder = new AlertDialog.Builder(mContext); + builder.setTitle(com.android.internal.R.string.bugreport_title); + builder.setMessage(com.android.internal.R.string.bugreport_message); + builder.setNegativeButton(com.android.internal.R.string.cancel, null); + builder.setPositiveButton(com.android.internal.R.string.report, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // don't actually trigger the bugreport if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return; + } + // Add a little delay before executing, to give the + // dialog a chance to go away before it takes a + // screenshot. + mHandler.postDelayed(new Runnable() { + @Override public void run() { + try { + ActivityManagerNative.getDefault() + .requestBugReport(); + } catch (RemoteException e) { + } + } + }, 500); + } + }); + AlertDialog dialog = builder.create(); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + dialog.show(); + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + + @Override + public String getStatus() { + return mContext.getString( + com.android.internal.R.string.bugreport_status, + Build.VERSION.RELEASE, + Build.ID); + } + }; + } + + private Action getSettingsAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_settings, + R.string.global_action_settings) { + + @Override + public void onPress() { + Intent intent = new Intent(Settings.ACTION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getAssistAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused, + R.string.global_action_assist) { + @Override + public void onPress() { + Intent intent = new Intent(Intent.ACTION_ASSIST); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getVoiceAssistAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search, + R.string.global_action_voice_assist) { + @Override + public void onPress() { + Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getLockdownAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock, + R.string.global_action_lockdown) { + + @Override + public void onPress() { + new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL); + try { + WindowManagerGlobal.getWindowManagerService().lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "Error while trying to lock device.", e); + } + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + }; + } + + private UserInfo getCurrentUser() { + try { + return ActivityManagerNative.getDefault().getCurrentUser(); + } catch (RemoteException re) { + return null; + } + } + + private boolean isCurrentUserOwner() { + UserInfo currentUser = getCurrentUser(); + return currentUser == null || currentUser.isPrimary(); + } + + private void addUsersToMenu(ArrayList<Action> items) { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (um.isUserSwitcherEnabled()) { + List<UserInfo> users = um.getUsers(); + UserInfo currentUser = getCurrentUser(); + for (final UserInfo user : users) { + if (user.supportsSwitchTo()) { + boolean isCurrentUser = currentUser == null + ? user.id == 0 : (currentUser.id == user.id); + Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) + : null; + SinglePressAction switchToUser = new SinglePressAction( + com.android.internal.R.drawable.ic_menu_cc, icon, + (user.name != null ? user.name : "Primary") + + (isCurrentUser ? " \u2714" : "")) { + public void onPress() { + try { + ActivityManagerNative.getDefault().switchUser(user.id); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't switch user " + re); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + items.add(switchToUser); + } + } + } + } + + private void prepareDialog() { + refreshSilentMode(); + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + if (mShowSilentToggle) { + IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + mContext.registerReceiver(mRingerModeReceiver, filter); + } + } + + private void refreshSilentMode() { + if (!mHasVibrator) { + final boolean silentModeOn = + mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + ((ToggleAction)mSilentModeAction).updateState( + silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); + } + } + + /** {@inheritDoc} */ + public void onDismiss(DialogInterface dialog) { + if (mShowSilentToggle) { + try { + mContext.unregisterReceiver(mRingerModeReceiver); + } catch (IllegalArgumentException ie) { + // ignore this + Log.w(TAG, ie); + } + } + } + + /** {@inheritDoc} */ + public void onClick(DialogInterface dialog, int which) { + if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) { + dialog.dismiss(); + } + mAdapter.getItem(which).onPress(); + } + + /** + * The adapter used for the list within the global actions dialog, taking + * into account whether the keyguard is showing via + * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned + * via {@link GlobalActions#mDeviceProvisioned}. + */ + private class MyAdapter extends BaseAdapter { + + public int getCount() { + int count = 0; + + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + + if (mKeyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + count++; + } + return count; + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + public Action getItem(int position) { + + int filteredPos = 0; + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + if (mKeyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + if (filteredPos == position) { + return action; + } + filteredPos++; + } + + throw new IllegalArgumentException("position " + position + + " out of range of showable actions" + + ", filtered count=" + getCount() + + ", keyguardshowing=" + mKeyguardShowing + + ", provisioned=" + mDeviceProvisioned); + } + + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); + } + } + + // note: the scheme below made more sense when we were planning on having + // 8 different things in the global actions dialog. seems overkill with + // only 3 items now, but may as well keep this flexible approach so it will + // be easy should someone decide at the last minute to include something + // else, such as 'enable wifi', or 'enable bluetooth' + + /** + * What each item in the global actions dialog must be able to support. + */ + private interface Action { + /** + * @return Text that will be announced when dialog is created. null + * for none. + */ + CharSequence getLabelForAccessibility(Context context); + + View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); + + void onPress(); + + /** + * @return whether this action should appear in the dialog when the keygaurd + * is showing. + */ + boolean showDuringKeyguard(); + + /** + * @return whether this action should appear in the dialog before the + * device is provisioned. + */ + boolean showBeforeProvisioning(); + + boolean isEnabled(); + } + + /** + * An action that also supports long press. + */ + private interface LongPressAction extends Action { + boolean onLongPress(); + } + + /** + * A single press action maintains no state, just responds to a press + * and takes an action. + */ + private static abstract class SinglePressAction implements Action { + private final int mIconResId; + private final Drawable mIcon; + private final int mMessageResId; + private final CharSequence mMessage; + + protected SinglePressAction(int iconResId, int messageResId) { + mIconResId = iconResId; + mMessageResId = messageResId; + mMessage = null; + mIcon = null; + } + + protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { + mIconResId = iconResId; + mMessageResId = 0; + mMessage = message; + mIcon = icon; + } + + protected SinglePressAction(int iconResId, CharSequence message) { + mIconResId = iconResId; + mMessageResId = 0; + mMessage = message; + mIcon = null; + } + + public boolean isEnabled() { + return true; + } + + public String getStatus() { + return null; + } + + abstract public void onPress(); + + public CharSequence getLabelForAccessibility(Context context) { + if (mMessage != null) { + return mMessage; + } else { + return context.getString(mMessageResId); + } + } + + public View create( + Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { + View v = inflater.inflate(R.layout.global_actions_item, parent, false); + + ImageView icon = (ImageView) v.findViewById(R.id.icon); + TextView messageView = (TextView) v.findViewById(R.id.message); + + TextView statusView = (TextView) v.findViewById(R.id.status); + final String status = getStatus(); + if (!TextUtils.isEmpty(status)) { + statusView.setText(status); + } else { + statusView.setVisibility(View.GONE); + } + if (mIcon != null) { + icon.setImageDrawable(mIcon); + icon.setScaleType(ScaleType.CENTER_CROP); + } else if (mIconResId != 0) { + icon.setImageDrawable(context.getDrawable(mIconResId)); + } + if (mMessage != null) { + messageView.setText(mMessage); + } else { + messageView.setText(mMessageResId); + } + + return v; + } + } + + /** + * A toggle action knows whether it is on or off, and displays an icon + * and status message accordingly. + */ + private static abstract class ToggleAction implements Action { + + enum State { + Off(false), + TurningOn(true), + TurningOff(true), + On(false); + + private final boolean inTransition; + + State(boolean intermediate) { + inTransition = intermediate; + } + + public boolean inTransition() { + return inTransition; + } + } + + protected State mState = State.Off; + + // prefs + protected int mEnabledIconResId; + protected int mDisabledIconResid; + protected int mMessageResId; + protected int mEnabledStatusMessageResId; + protected int mDisabledStatusMessageResId; + + /** + * @param enabledIconResId The icon for when this action is on. + * @param disabledIconResid The icon for when this action is off. + * @param essage The general information message, e.g 'Silent Mode' + * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' + * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' + */ + public ToggleAction(int enabledIconResId, + int disabledIconResid, + int message, + int enabledStatusMessageResId, + int disabledStatusMessageResId) { + mEnabledIconResId = enabledIconResId; + mDisabledIconResid = disabledIconResid; + mMessageResId = message; + mEnabledStatusMessageResId = enabledStatusMessageResId; + mDisabledStatusMessageResId = disabledStatusMessageResId; + } + + /** + * Override to make changes to resource IDs just before creating the + * View. + */ + void willCreate() { + + } + + @Override + public CharSequence getLabelForAccessibility(Context context) { + return context.getString(mMessageResId); + } + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + willCreate(); + + View v = inflater.inflate(R + .layout.global_actions_item, parent, false); + + ImageView icon = (ImageView) v.findViewById(R.id.icon); + TextView messageView = (TextView) v.findViewById(R.id.message); + TextView statusView = (TextView) v.findViewById(R.id.status); + final boolean enabled = isEnabled(); + + if (messageView != null) { + messageView.setText(mMessageResId); + messageView.setEnabled(enabled); + } + + boolean on = ((mState == State.On) || (mState == State.TurningOn)); + if (icon != null) { + icon.setImageDrawable(context.getDrawable( + (on ? mEnabledIconResId : mDisabledIconResid))); + icon.setEnabled(enabled); + } + + if (statusView != null) { + statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); + statusView.setVisibility(View.VISIBLE); + statusView.setEnabled(enabled); + } + v.setEnabled(enabled); + + return v; + } + + public final void onPress() { + if (mState.inTransition()) { + Log.w(TAG, "shouldn't be able to toggle when in transition"); + return; + } + + final boolean nowOn = !(mState == State.On); + onToggle(nowOn); + changeStateFromPress(nowOn); + } + + public boolean isEnabled() { + return !mState.inTransition(); + } + + /** + * Implementations may override this if their state can be in on of the intermediate + * states until some notification is received (e.g airplane mode is 'turning off' until + * we know the wireless connections are back online + * @param buttonOn Whether the button was turned on or off + */ + protected void changeStateFromPress(boolean buttonOn) { + mState = buttonOn ? State.On : State.Off; + } + + abstract void onToggle(boolean on); + + public void updateState(State state) { + mState = state; + } + } + + private class SilentModeToggleAction extends ToggleAction { + public SilentModeToggleAction() { + super(R.drawable.ic_audio_vol_mute, + R.drawable.ic_audio_vol, + R.string.global_action_toggle_silent_mode, + R.string.global_action_silent_mode_on_status, + R.string.global_action_silent_mode_off_status); + } + + void onToggle(boolean on) { + if (on) { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + } + + private static class SilentModeTriStateAction implements Action, View.OnClickListener { + + private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 }; + + private final AudioManager mAudioManager; + private final Handler mHandler; + private final Context mContext; + + SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) { + mAudioManager = audioManager; + mHandler = handler; + mContext = context; + } + + private int ringerModeToIndex(int ringerMode) { + // They just happen to coincide + return ringerMode; + } + + private int indexToRingerMode(int index) { + // They just happen to coincide + return index; + } + + @Override + public CharSequence getLabelForAccessibility(Context context) { + return null; + } + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); + + int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); + for (int i = 0; i < 3; i++) { + View itemView = v.findViewById(ITEM_IDS[i]); + itemView.setSelected(selectedIndex == i); + // Set up click handler + itemView.setTag(i); + itemView.setOnClickListener(this); + } + return v; + } + + public void onPress() { + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + + public boolean isEnabled() { + return true; + } + + void willCreate() { + } + + public void onClick(View v) { + if (!(v.getTag() instanceof Integer)) return; + + int index = (Integer) v.getTag(); + mAudioManager.setRingerMode(indexToRingerMode(index)); + mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); + } + } + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_SCREEN_OFF.equals(action)) { + String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); + if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { + mHandler.sendEmptyMessage(MESSAGE_DISMISS); + } + } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { + // Airplane mode can be changed after ECM exits if airplane toggle button + // is pressed during ECM mode + if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && + mIsWaitingForEcmExit) { + mIsWaitingForEcmExit = false; + changeAirplaneModeSystemSetting(true); + } + } + } + }; + + PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + if (!mHasTelephony) return; + final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; + mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + } + }; + + private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { + mHandler.sendEmptyMessage(MESSAGE_REFRESH); + } + } + }; + + private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + onAirplaneModeChanged(); + } + }; + + private static final int MESSAGE_DISMISS = 0; + private static final int MESSAGE_REFRESH = 1; + private static final int MESSAGE_SHOW = 2; + private static final int DIALOG_DISMISS_DELAY = 300; // ms + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_DISMISS: + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + break; + case MESSAGE_REFRESH: + refreshSilentMode(); + mAdapter.notifyDataSetChanged(); + break; + case MESSAGE_SHOW: + handleShow(); + break; + } + } + }; + + private void onAirplaneModeChanged() { + // Let the service state callbacks handle the state. + if (mHasTelephony) return; + + boolean airplaneModeOn = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, + 0) == 1; + mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneModeOn.updateState(mAirplaneState); + } + + /** + * Change the airplane mode system setting + */ + private void changeAirplaneModeSystemSetting(boolean on) { + Settings.Global.putInt( + mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, + on ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("state", on); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + if (!mHasTelephony) { + mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off; + } + } + + private static final class GlobalActionsDialog extends Dialog implements DialogInterface { + private final Context mContext; + private final int mWindowTouchSlop; + private final AlertController mAlert; + private final MyAdapter mAdapter; + + private EnableAccessibilityController mEnableAccessibilityController; + + private boolean mIntercepted; + private boolean mCancelOnUp; + + public GlobalActionsDialog(Context context, AlertParams params) { + super(context, getDialogTheme(context)); + mContext = context; + mAlert = new AlertController(mContext, this, getWindow()); + mAdapter = (MyAdapter) params.mAdapter; + mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop(); + params.apply(mAlert); + } + + private static int getDialogTheme(Context context) { + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme, + outValue, true); + return outValue.resourceId; + } + + @Override + protected void onStart() { + // If global accessibility gesture can be performed, we will take care + // of dismissing the dialog on touch outside. This is because the dialog + // is dismissed on the first down while the global gesture is a long press + // with two fingers anywhere on the screen. + if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) { + mEnableAccessibilityController = new EnableAccessibilityController(mContext, + new Runnable() { + @Override + public void run() { + dismiss(); + } + }); + super.setCanceledOnTouchOutside(false); + } else { + mEnableAccessibilityController = null; + super.setCanceledOnTouchOutside(true); + } + + super.onStart(); + } + + @Override + protected void onStop() { + if (mEnableAccessibilityController != null) { + mEnableAccessibilityController.onDestroy(); + } + super.onStop(); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (mEnableAccessibilityController != null) { + final int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + View decor = getWindow().getDecorView(); + final int eventX = (int) event.getX(); + final int eventY = (int) event.getY(); + if (eventX < -mWindowTouchSlop + || eventY < -mWindowTouchSlop + || eventX >= decor.getWidth() + mWindowTouchSlop + || eventY >= decor.getHeight() + mWindowTouchSlop) { + mCancelOnUp = true; + } + } + try { + if (!mIntercepted) { + mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event); + if (mIntercepted) { + final long now = SystemClock.uptimeMillis(); + event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); + event.setSource(InputDevice.SOURCE_TOUCHSCREEN); + mCancelOnUp = true; + } + } else { + return mEnableAccessibilityController.onTouchEvent(event); + } + } finally { + if (action == MotionEvent.ACTION_UP) { + if (mCancelOnUp) { + cancel(); + } + mCancelOnUp = false; + mIntercepted = false; + } + } + } + return super.dispatchTouchEvent(event); + } + + public ListView getListView() { + return mAlert.getListView(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mAlert.installContent(); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + for (int i = 0; i < mAdapter.getCount(); ++i) { + CharSequence label = + mAdapter.getItem(i).getLabelForAccessibility(getContext()); + if (label != null) { + event.getText().add(label); + } + } + } + return super.dispatchPopulateAccessibilityEvent(event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mAlert.onKeyDown(keyCode, event)) { + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (mAlert.onKeyUp(keyCode, event)) { + return true; + } + return super.onKeyUp(keyCode, event); + } + } +} diff --git a/services/core/java/com/android/server/policy/GlobalKeyManager.java b/services/core/java/com/android/server/policy/GlobalKeyManager.java new file mode 100644 index 0000000..e08c004 --- /dev/null +++ b/services/core/java/com/android/server/policy/GlobalKeyManager.java @@ -0,0 +1,145 @@ +/* + * + * 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.policy; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.os.UserHandle; +import android.util.Log; +import android.util.SparseArray; +import android.view.KeyEvent; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Stores a mapping of global keys. + * <p> + * A global key will NOT go to the foreground application and instead only ever be sent via targeted + * broadcast to the specified component. The action of the intent will be + * {@link Intent#ACTION_GLOBAL_BUTTON} and the KeyEvent will be included in the intent with + * {@link Intent#EXTRA_KEY_EVENT}. + */ +final class GlobalKeyManager { + + private static final String TAG = "GlobalKeyManager"; + + private static final String TAG_GLOBAL_KEYS = "global_keys"; + private static final String ATTR_VERSION = "version"; + private static final String TAG_KEY = "key"; + private static final String ATTR_KEY_CODE = "keyCode"; + private static final String ATTR_COMPONENT = "component"; + + private static final int GLOBAL_KEY_FILE_VERSION = 1; + + private SparseArray<ComponentName> mKeyMapping; + + public GlobalKeyManager(Context context) { + mKeyMapping = new SparseArray<ComponentName>(); + loadGlobalKeys(context); + } + + /** + * Broadcasts an intent if the keycode is part of the global key mapping. + * + * @param context context used to broadcast the event + * @param keyCode keyCode which triggered this function + * @param event keyEvent which trigged this function + * @return {@code true} if this was handled + */ + boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) { + if (mKeyMapping.size() > 0) { + ComponentName component = mKeyMapping.get(keyCode); + if (component != null) { + Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON) + .setComponent(component) + .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .putExtra(Intent.EXTRA_KEY_EVENT, event); + context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null); + return true; + } + } + return false; + } + + /** + * Returns {@code true} if the key will be handled globally. + */ + boolean shouldHandleGlobalKey(int keyCode, KeyEvent event) { + return mKeyMapping.get(keyCode) != null; + } + + private void loadGlobalKeys(Context context) { + XmlResourceParser parser = null; + try { + parser = context.getResources().getXml(com.android.internal.R.xml.global_keys); + XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS); + int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0); + if (GLOBAL_KEY_FILE_VERSION == version) { + while (true) { + XmlUtils.nextElement(parser); + String element = parser.getName(); + if (element == null) { + break; + } + if (TAG_KEY.equals(element)) { + String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE); + String componentName = parser.getAttributeValue(null, ATTR_COMPONENT); + int keyCode = KeyEvent.keyCodeFromString(keyCodeName); + if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { + mKeyMapping.put(keyCode, ComponentName.unflattenFromString( + componentName)); + } + } + } + } + } catch (Resources.NotFoundException e) { + Log.w(TAG, "global keys file not found", e); + } catch (XmlPullParserException e) { + Log.w(TAG, "XML parser exception reading global keys file", e); + } catch (IOException e) { + Log.w(TAG, "I/O exception reading global keys file", e); + } finally { + if (parser != null) { + parser.close(); + } + } + } + + public void dump(String prefix, PrintWriter pw) { + final int numKeys = mKeyMapping.size(); + if (numKeys == 0) { + pw.print(prefix); pw.println("mKeyMapping.size=0"); + return; + } + pw.print(prefix); pw.println("mKeyMapping={"); + for (int i = 0; i < numKeys; ++i) { + pw.print(" "); + pw.print(prefix); + pw.print(KeyEvent.keyCodeToString(mKeyMapping.keyAt(i))); + pw.print("="); + pw.println(mKeyMapping.valueAt(i).flattenToString()); + } + pw.print(prefix); pw.println("}"); + } +} diff --git a/services/core/java/com/android/server/policy/IconUtilities.java b/services/core/java/com/android/server/policy/IconUtilities.java new file mode 100644 index 0000000..4658344 --- /dev/null +++ b/services/core/java/com/android/server/policy/IconUtilities.java @@ -0,0 +1,190 @@ +/* + * 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.policy; + +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PaintDrawable; +import android.graphics.drawable.StateListDrawable; +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.TableMaskFilter; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.content.res.Resources; +import android.content.Context; + +/** + * Various utilities shared amongst the Launcher's classes. + */ +final class IconUtilities { + private static final String TAG = "IconUtilities"; + + private static final int sColors[] = { 0xffff0000, 0xff00ff00, 0xff0000ff }; + + private int mIconWidth = -1; + private int mIconHeight = -1; + private int mIconTextureWidth = -1; + private int mIconTextureHeight = -1; + + private final Paint mPaint = new Paint(); + private final Paint mBlurPaint = new Paint(); + private final Paint mGlowColorPressedPaint = new Paint(); + private final Paint mGlowColorFocusedPaint = new Paint(); + private final Rect mOldBounds = new Rect(); + private final Canvas mCanvas = new Canvas(); + private final DisplayMetrics mDisplayMetrics; + + private int mColorIndex = 0; + + public IconUtilities(Context context) { + final Resources resources = context.getResources(); + DisplayMetrics metrics = mDisplayMetrics = resources.getDisplayMetrics(); + final float density = metrics.density; + final float blurPx = 5 * density; + + mIconWidth = mIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size); + mIconTextureWidth = mIconTextureHeight = mIconWidth + (int)(blurPx*2); + + mBlurPaint.setMaskFilter(new BlurMaskFilter(blurPx, BlurMaskFilter.Blur.NORMAL)); + + TypedValue value = new TypedValue(); + mGlowColorPressedPaint.setColor(context.getTheme().resolveAttribute( + android.R.attr.colorPressedHighlight, value, true) ? value.data : 0xffffc300); + mGlowColorPressedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); + mGlowColorFocusedPaint.setColor(context.getTheme().resolveAttribute( + android.R.attr.colorFocusedHighlight, value, true) ? value.data : 0xffff8e00); + mGlowColorFocusedPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); + + ColorMatrix cm = new ColorMatrix(); + cm.setSaturation(0.2f); + + mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, + Paint.FILTER_BITMAP_FLAG)); + } + + public Drawable createIconDrawable(Drawable src) { + Bitmap scaled = createIconBitmap(src); + + StateListDrawable result = new StateListDrawable(); + + result.addState(new int[] { android.R.attr.state_focused }, + new BitmapDrawable(createSelectedBitmap(scaled, false))); + result.addState(new int[] { android.R.attr.state_pressed }, + new BitmapDrawable(createSelectedBitmap(scaled, true))); + result.addState(new int[0], new BitmapDrawable(scaled)); + + result.setBounds(0, 0, mIconTextureWidth, mIconTextureHeight); + return result; + } + + /** + * Returns a bitmap suitable for the all apps view. The bitmap will be a power + * of two sized ARGB_8888 bitmap that can be used as a gl texture. + */ + private Bitmap createIconBitmap(Drawable icon) { + int width = mIconWidth; + int height = mIconHeight; + + if (icon instanceof PaintDrawable) { + PaintDrawable painter = (PaintDrawable) icon; + painter.setIntrinsicWidth(width); + painter.setIntrinsicHeight(height); + } else if (icon instanceof BitmapDrawable) { + // Ensure the bitmap has a density. + BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; + Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap.getDensity() == Bitmap.DENSITY_NONE) { + bitmapDrawable.setTargetDensity(mDisplayMetrics); + } + } + int sourceWidth = icon.getIntrinsicWidth(); + int sourceHeight = icon.getIntrinsicHeight(); + + if (sourceWidth > 0 && sourceHeight > 0) { + // There are intrinsic sizes. + if (width < sourceWidth || height < sourceHeight) { + // It's too big, scale it down. + final float ratio = (float) sourceWidth / sourceHeight; + if (sourceWidth > sourceHeight) { + height = (int) (width / ratio); + } else if (sourceHeight > sourceWidth) { + width = (int) (height * ratio); + } + } else if (sourceWidth < width && sourceHeight < height) { + // It's small, use the size they gave us. + width = sourceWidth; + height = sourceHeight; + } + } + + // no intrinsic size --> use default size + int textureWidth = mIconTextureWidth; + int textureHeight = mIconTextureHeight; + + final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight, + Bitmap.Config.ARGB_8888); + final Canvas canvas = mCanvas; + canvas.setBitmap(bitmap); + + final int left = (textureWidth-width) / 2; + final int top = (textureHeight-height) / 2; + + if (false) { + // draw a big box for the icon for debugging + canvas.drawColor(sColors[mColorIndex]); + if (++mColorIndex >= sColors.length) mColorIndex = 0; + Paint debugPaint = new Paint(); + debugPaint.setColor(0xffcccc00); + canvas.drawRect(left, top, left+width, top+height, debugPaint); + } + + mOldBounds.set(icon.getBounds()); + icon.setBounds(left, top, left+width, top+height); + icon.draw(canvas); + icon.setBounds(mOldBounds); + + return bitmap; + } + + private Bitmap createSelectedBitmap(Bitmap src, boolean pressed) { + final Bitmap result = Bitmap.createBitmap(mIconTextureWidth, mIconTextureHeight, + Bitmap.Config.ARGB_8888); + final Canvas dest = new Canvas(result); + + dest.drawColor(0, PorterDuff.Mode.CLEAR); + + int[] xy = new int[2]; + Bitmap mask = src.extractAlpha(mBlurPaint, xy); + + dest.drawBitmap(mask, xy[0], xy[1], + pressed ? mGlowColorPressedPaint : mGlowColorFocusedPaint); + + mask.recycle(); + + dest.drawBitmap(src, 0, 0, mPaint); + dest.setBitmap(null); + + return result; + } +} diff --git a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java new file mode 100644 index 0000000..e720f3e --- /dev/null +++ b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java @@ -0,0 +1,349 @@ +/* + * 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.policy; + +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.PixelFormat; +import android.graphics.drawable.ColorDrawable; +import android.os.Handler; +import android.os.Message; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.util.SparseBooleanArray; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.Button; +import android.widget.FrameLayout; + +import com.android.internal.R; + +/** + * Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden + * entering immersive mode. + */ +public class ImmersiveModeConfirmation { + private static final String TAG = "ImmersiveModeConfirmation"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution + private static final String CONFIRMED = "confirmed"; + + private final Context mContext; + private final H mHandler; + private final long mShowDelayMs; + private final long mPanicThresholdMs; + private final SparseBooleanArray mUserPanicResets = new SparseBooleanArray(); + + private boolean mConfirmed; + private ClingWindowView mClingWindow; + private long mPanicTime; + private WindowManager mWindowManager; + private int mCurrentUserId; + + public ImmersiveModeConfirmation(Context context) { + mContext = context; + mHandler = new H(); + mShowDelayMs = getNavBarExitDuration() * 3; + mPanicThresholdMs = context.getResources() + .getInteger(R.integer.config_immersive_mode_confirmation_panic); + mWindowManager = (WindowManager) + mContext.getSystemService(Context.WINDOW_SERVICE); + } + + private long getNavBarExitDuration() { + Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit); + return exit != null ? exit.getDuration() : 0; + } + + public void loadSetting(int currentUserId) { + mConfirmed = false; + mCurrentUserId = currentUserId; + if (DEBUG) Slog.d(TAG, String.format("loadSetting() mCurrentUserId=%d resetForPanic=%s", + mCurrentUserId, mUserPanicResets.get(mCurrentUserId, false))); + String value = null; + try { + value = Settings.Secure.getStringForUser(mContext.getContentResolver(), + Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, + UserHandle.USER_CURRENT); + mConfirmed = CONFIRMED.equals(value); + if (DEBUG) Slog.d(TAG, "Loaded mConfirmed=" + mConfirmed); + } catch (Throwable t) { + Slog.w(TAG, "Error loading confirmations, value=" + value, t); + } + } + + private void saveSetting() { + if (DEBUG) Slog.d(TAG, "saveSetting()"); + try { + final String value = mConfirmed ? CONFIRMED : null; + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, + value, + UserHandle.USER_CURRENT); + if (DEBUG) Slog.d(TAG, "Saved value=" + value); + } catch (Throwable t) { + Slog.w(TAG, "Error saving confirmations, mConfirmed=" + mConfirmed, t); + } + } + + public void immersiveModeChanged(String pkg, boolean isImmersiveMode, + boolean userSetupComplete) { + mHandler.removeMessages(H.SHOW); + if (isImmersiveMode) { + final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg); + if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s mConfirmed=%s", + disabled, mConfirmed)); + if (!disabled && (DEBUG_SHOW_EVERY_TIME || !mConfirmed) && userSetupComplete) { + mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs); + } + } else { + mHandler.sendEmptyMessage(H.HIDE); + } + } + + public boolean onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode) { + if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) { + // turning the screen back on within the panic threshold + mHandler.sendEmptyMessage(H.PANIC); + return mClingWindow == null; + } + if (isScreenOn && inImmersiveMode) { + // turning the screen off, remember if we were in immersive mode + mPanicTime = time; + } else { + mPanicTime = 0; + } + return false; + } + + public void confirmCurrentPrompt() { + if (mClingWindow != null) { + if (DEBUG) Slog.d(TAG, "confirmCurrentPrompt()"); + mHandler.post(mConfirm); + } + } + + private void handlePanic() { + if (DEBUG) Slog.d(TAG, "handlePanic()"); + if (mUserPanicResets.get(mCurrentUserId, false)) return; // already reset for panic + mUserPanicResets.put(mCurrentUserId, true); + mConfirmed = false; + saveSetting(); + } + + private void handleHide() { + if (mClingWindow != null) { + if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation"); + mWindowManager.removeView(mClingWindow); + mClingWindow = null; + } + } + + public WindowManager.LayoutParams getClingWindowLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, + 0 + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + , + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + lp.setTitle("ImmersiveModeConfirmation"); + lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation; + return lp; + } + + public FrameLayout.LayoutParams getBubbleLayoutParams() { + return new FrameLayout.LayoutParams( + mContext.getResources().getDimensionPixelSize( + R.dimen.immersive_mode_cling_width), + ViewGroup.LayoutParams.WRAP_CONTENT, + Gravity.CENTER_HORIZONTAL | Gravity.TOP); + } + + private class ClingWindowView extends FrameLayout { + private static final int BGCOLOR = 0x80000000; + private static final int OFFSET_DP = 96; + private static final int ANIMATION_DURATION = 250; + + private final Runnable mConfirm; + private final ColorDrawable mColor = new ColorDrawable(0); + private final Interpolator mInterpolator; + private ValueAnimator mColorAnim; + private ViewGroup mClingLayout; + + private Runnable mUpdateLayoutRunnable = new Runnable() { + @Override + public void run() { + if (mClingLayout != null && mClingLayout.getParent() != null) { + mClingLayout.setLayoutParams(getBubbleLayoutParams()); + } + } + }; + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) { + post(mUpdateLayoutRunnable); + } + } + }; + + public ClingWindowView(Context context, Runnable confirm) { + super(context); + mConfirm = confirm; + setBackground(mColor); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + mInterpolator = AnimationUtils + .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + DisplayMetrics metrics = new DisplayMetrics(); + mWindowManager.getDefaultDisplay().getMetrics(metrics); + float density = metrics.density; + + // create the confirmation cling + mClingLayout = (ViewGroup) + View.inflate(getContext(), R.layout.immersive_mode_cling, null); + + final Button ok = (Button) mClingLayout.findViewById(R.id.ok); + ok.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mConfirm.run(); + } + }); + addView(mClingLayout, getBubbleLayoutParams()); + + if (ActivityManager.isHighEndGfx()) { + final View cling = mClingLayout; + cling.setAlpha(0f); + cling.setTranslationY(-OFFSET_DP * density); + + postOnAnimation(new Runnable() { + @Override + public void run() { + cling.animate() + .alpha(1f) + .translationY(0) + .setDuration(ANIMATION_DURATION) + .setInterpolator(mInterpolator) + .withLayer() + .start(); + + mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR); + mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final int c = (Integer) animation.getAnimatedValue(); + mColor.setColor(c); + } + }); + mColorAnim.setDuration(ANIMATION_DURATION); + mColorAnim.setInterpolator(mInterpolator); + mColorAnim.start(); + } + }); + } else { + mColor.setColor(BGCOLOR); + } + + mContext.registerReceiver(mReceiver, + new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); + } + + @Override + public void onDetachedFromWindow() { + mContext.unregisterReceiver(mReceiver); + } + + @Override + public boolean onTouchEvent(MotionEvent motion) { + return true; + } + } + + private void handleShow() { + if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation"); + + mClingWindow = new ClingWindowView(mContext, mConfirm); + + // we will be hiding the nav bar, so layout as if it's already hidden + mClingWindow.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + + // show the confirmation + WindowManager.LayoutParams lp = getClingWindowLayoutParams(); + mWindowManager.addView(mClingWindow, lp); + } + + private final Runnable mConfirm = new Runnable() { + @Override + public void run() { + if (DEBUG) Slog.d(TAG, "mConfirm.run()"); + if (!mConfirmed) { + mConfirmed = true; + saveSetting(); + } + handleHide(); + } + }; + + private final class H extends Handler { + private static final int SHOW = 1; + private static final int HIDE = 2; + private static final int PANIC = 3; + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case SHOW: + handleShow(); + break; + case HIDE: + handleHide(); + break; + case PANIC: + handlePanic(); + break; + } + } + } +} diff --git a/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java b/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java new file mode 100644 index 0000000..ed5dc6f --- /dev/null +++ b/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 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.policy; + +import android.view.animation.Interpolator; + +public class LogDecelerateInterpolator implements Interpolator { + + private int mBase; + private int mDrift; + private final float mLogScale; + + public LogDecelerateInterpolator(int base, int drift) { + mBase = base; + mDrift = drift; + + mLogScale = 1f / computeLog(1, mBase, mDrift); + } + + private static float computeLog(float t, int base, int drift) { + return (float) -Math.pow(base, -t) + 1 + (drift * t); + } + + @Override + public float getInterpolation(float t) { + return computeLog(t, mBase, mDrift) * mLogScale; + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java new file mode 100644 index 0000000..25857c5 --- /dev/null +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -0,0 +1,6602 @@ +/* + * 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.policy; + +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.ActivityManagerInternal.SleepToken; +import android.app.ActivityManagerNative; +import android.app.AppOpsManager; +import android.app.IUiModeManager; +import android.app.ProgressDialog; +import android.app.SearchManager; +import android.app.StatusBarManager; +import android.app.UiModeManager; +import android.content.ActivityNotFoundException; +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.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.ContentObserver; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.IAudioService; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.media.session.MediaSessionLegacyHelper; +import android.os.Build; +import android.os.Bundle; +import android.os.Debug; +import android.os.FactoryTest; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UEventObserver; +import android.os.UserHandle; +import android.os.Vibrator; +import android.provider.MediaStore; +import android.provider.Settings; +import android.service.dreams.DreamManagerInternal; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; +import android.speech.RecognizerIntent; +import android.telecom.TelecomManager; +import android.util.DisplayMetrics; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.view.Display; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.IApplicationToken; +import android.view.IWindowManager; +import android.view.InputChannel; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.KeyCharacterMap; +import android.view.KeyCharacterMap.FallbackAction; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.PhoneWindow; +import android.view.Surface; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewRootImpl; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerInternal; +import android.view.WindowManagerPolicy; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.AnimationUtils; +import com.android.internal.R; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.widget.PointerLocationView; +import com.android.server.LocalServices; +import com.android.server.policy.keyguard.KeyguardServiceDelegate; +import com.android.server.policy.keyguard.KeyguardServiceDelegate.ShowListener; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.List; + +import static android.view.WindowManager.LayoutParams.*; +import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; +import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; +import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED; +import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; +import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED; +import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; + +/** + * WindowManagerPolicy implementation for the Android phone UI. This + * introduces a new method suffix, Lp, for an internal lock of the + * PhoneWindowManager. This is used to protect some internal state, and + * can be acquired with either the Lw and Li lock held, so has the restrictions + * of both of those when held. + */ +public class PhoneWindowManager implements WindowManagerPolicy { + static final String TAG = "WindowManager"; + static final boolean DEBUG = false; + static final boolean localLOGV = false; + static final boolean DEBUG_INPUT = false; + static final boolean DEBUG_KEYGUARD = false; + static final boolean DEBUG_LAYOUT = false; + static final boolean DEBUG_STARTING_WINDOW = false; + static final boolean DEBUG_WAKEUP = false; + static final boolean SHOW_STARTING_ANIMATIONS = true; + static final boolean SHOW_PROCESSES_ON_ALT_MENU = false; + + // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. + // No longer recommended for desk docks; still useful in car docks. + static final boolean ENABLE_CAR_DOCK_HOME_CAPTURE = true; + static final boolean ENABLE_DESK_DOCK_HOME_CAPTURE = false; + + static final int SHORT_PRESS_POWER_NOTHING = 0; + static final int SHORT_PRESS_POWER_GO_TO_SLEEP = 1; + static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP = 2; + static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME = 3; + static final int SHORT_PRESS_POWER_GO_HOME = 4; + + static final int LONG_PRESS_POWER_NOTHING = 0; + static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; + static final int LONG_PRESS_POWER_SHUT_OFF = 2; + static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3; + + static final int MULTI_PRESS_POWER_NOTHING = 0; + static final int MULTI_PRESS_POWER_THEATER_MODE = 1; + static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2; + + // These need to match the documentation/constant in + // core/res/res/values/config.xml + static final int LONG_PRESS_HOME_NOTHING = 0; + static final int LONG_PRESS_HOME_RECENT_SYSTEM_UI = 1; + static final int LONG_PRESS_HOME_ASSIST = 2; + + static final int DOUBLE_TAP_HOME_NOTHING = 0; + static final int DOUBLE_TAP_HOME_RECENT_SYSTEM_UI = 1; + + static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP = 0; + static final int SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME = 1; + + static final int APPLICATION_MEDIA_SUBLAYER = -2; + static final int APPLICATION_MEDIA_OVERLAY_SUBLAYER = -1; + static final int APPLICATION_PANEL_SUBLAYER = 1; + static final int APPLICATION_SUB_PANEL_SUBLAYER = 2; + static final int APPLICATION_ABOVE_SUB_PANEL_SUBLAYER = 3; + + static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; + static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; + static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; + static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; + static public final String SYSTEM_DIALOG_REASON_ASSIST = "assist"; + + /** + * These are the system UI flags that, when changing, can cause the layout + * of the screen to change. + */ + static final int SYSTEM_UI_CHANGING_LAYOUT = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.STATUS_BAR_TRANSLUCENT + | View.NAVIGATION_BAR_TRANSLUCENT + | View.SYSTEM_UI_TRANSPARENT; + + private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .build(); + + /** + * Keyguard stuff + */ + private WindowState mKeyguardScrim; + private boolean mKeyguardHidden; + private boolean mKeyguardDrawnOnce; + + /* Table of Application Launch keys. Maps from key codes to intent categories. + * + * These are special keys that are used to launch particular kinds of applications, + * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C) + * usage page. We don't support quite that many yet... + */ + static SparseArray<String> sApplicationLaunchKeyCategories; + static { + sApplicationLaunchKeyCategories = new SparseArray<String>(); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_EXPLORER, Intent.CATEGORY_APP_BROWSER); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC); + sApplicationLaunchKeyCategories.append( + KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR); + } + + /** Amount of time (in milliseconds) to wait for windows drawn before powering on. */ + static final int WAITING_FOR_DRAWN_TIMEOUT = 1000; + + /** + * Lock protecting internal state. Must not call out into window + * manager with lock held. (This lock will be acquired in places + * where the window manager is calling in with its own lock held.) + */ + private final Object mLock = new Object(); + + Context mContext; + IWindowManager mWindowManager; + WindowManagerFuncs mWindowManagerFuncs; + WindowManagerInternal mWindowManagerInternal; + PowerManager mPowerManager; + ActivityManagerInternal mActivityManagerInternal; + DreamManagerInternal mDreamManagerInternal; + IStatusBarService mStatusBarService; + boolean mPreloadedRecentApps; + final Object mServiceAquireLock = new Object(); + Vibrator mVibrator; // Vibrator for giving feedback of orientation changes + SearchManager mSearchManager; + AccessibilityManager mAccessibilityManager; + BurnInProtectionHelper mBurnInProtectionHelper; + + // Vibrator pattern for haptic feedback of a long press. + long[] mLongPressVibePattern; + + // Vibrator pattern for haptic feedback of virtual key press. + long[] mVirtualKeyVibePattern; + + // Vibrator pattern for a short vibration. + long[] mKeyboardTapVibePattern; + + // Vibrator pattern for a short vibration when tapping on an hour/minute tick of a Clock. + long[] mClockTickVibePattern; + + // Vibrator pattern for a short vibration when tapping on a day/month/year date of a Calendar. + long[] mCalendarDateVibePattern; + + // Vibrator pattern for haptic feedback during boot when safe mode is disabled. + long[] mSafeModeDisabledVibePattern; + + // Vibrator pattern for haptic feedback during boot when safe mode is enabled. + long[] mSafeModeEnabledVibePattern; + + /** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */ + boolean mEnableShiftMenuBugReports = false; + + boolean mSafeMode; + WindowState mStatusBar = null; + int mStatusBarHeight; + WindowState mNavigationBar = null; + boolean mHasNavigationBar = false; + boolean mCanHideNavigationBar = false; + boolean mNavigationBarCanMove = false; // can the navigation bar ever move to the side? + boolean mNavigationBarOnBottom = true; // is the navigation bar on the bottom *right now*? + int[] mNavigationBarHeightForRotation = new int[4]; + int[] mNavigationBarWidthForRotation = new int[4]; + + boolean mBootMessageNeedsHiding; + KeyguardServiceDelegate mKeyguardDelegate; + final Runnable mWindowManagerDrawCallback = new Runnable() { + @Override + public void run() { + if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display!"); + mHandler.sendEmptyMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE); + } + }; + final ShowListener mKeyguardDelegateCallback = new ShowListener() { + @Override + public void onShown(IBinder windowToken) { + if (DEBUG_WAKEUP) Slog.d(TAG, "mKeyguardDelegate.ShowListener.onShown."); + mHandler.sendEmptyMessage(MSG_KEYGUARD_DRAWN_COMPLETE); + } + }; + + GlobalActions mGlobalActions; + Handler mHandler; + WindowState mLastInputMethodWindow = null; + WindowState mLastInputMethodTargetWindow = null; + + // FIXME This state is shared between the input reader and handler thread. + // Technically it's broken and buggy but it has been like this for many years + // and we have not yet seen any problems. Someday we'll rewrite this logic + // so that only one thread is involved in handling input policy. Unfortunately + // it's on a critical path for power management so we can't just post the work to the + // handler thread. We'll need to resolve this someday by teaching the input dispatcher + // to hold wakelocks during dispatch and eliminating the critical path. + volatile boolean mPowerKeyHandled; + volatile boolean mBeganFromNonInteractive; + volatile int mPowerKeyPressCounter; + volatile boolean mEndCallKeyHandled; + + boolean mRecentsVisible; + int mRecentAppsHeldModifiers; + boolean mLanguageSwitchKeyPressed; + + int mLidState = LID_ABSENT; + int mCameraLensCoverState = CAMERA_LENS_COVER_ABSENT; + boolean mHaveBuiltInKeyboard; + + boolean mSystemReady; + boolean mSystemBooted; + boolean mHdmiPlugged; + IUiModeManager mUiModeManager; + int mUiMode; + int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED; + int mLidOpenRotation; + int mCarDockRotation; + int mDeskDockRotation; + int mUndockedHdmiRotation; + int mDemoHdmiRotation; + boolean mDemoHdmiRotationLock; + int mDemoRotation; + boolean mDemoRotationLock; + + boolean mWakeGestureEnabledSetting; + MyWakeGestureListener mWakeGestureListener; + + // Default display does not rotate, apps that require non-default orientation will have to + // have the orientation emulated. + private boolean mForceDefaultOrientation = false; + + int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE; + int mUserRotation = Surface.ROTATION_0; + boolean mAccelerometerDefault; + + boolean mSupportAutoRotation; + int mAllowAllRotations = -1; + boolean mCarDockEnablesAccelerometer; + boolean mDeskDockEnablesAccelerometer; + int mLidKeyboardAccessibility; + int mLidNavigationAccessibility; + boolean mLidControlsSleep; + int mShortPressOnPowerBehavior; + int mLongPressOnPowerBehavior; + int mDoublePressOnPowerBehavior; + int mTriplePressOnPowerBehavior; + int mShortPressOnSleepBehavior; + boolean mAwake; + boolean mScreenOnEarly; + boolean mScreenOnFully; + ScreenOnListener mScreenOnListener; + boolean mKeyguardDrawComplete; + boolean mWindowManagerDrawComplete; + boolean mOrientationSensorEnabled = false; + int mCurrentAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + boolean mHasSoftInput = false; + boolean mTranslucentDecorEnabled = true; + boolean mUseTvRouting; + + int mPointerLocationMode = 0; // guarded by mLock + + // The last window we were told about in focusChanged. + WindowState mFocusedWindow; + IApplicationToken mFocusedApp; + + PointerLocationView mPointerLocationView; + + // The current size of the screen; really; extends into the overscan area of + // the screen and doesn't account for any system elements like the status bar. + int mOverscanScreenLeft, mOverscanScreenTop; + int mOverscanScreenWidth, mOverscanScreenHeight; + // The current visible size of the screen; really; (ir)regardless of whether the status + // bar can be hidden but not extending into the overscan area. + int mUnrestrictedScreenLeft, mUnrestrictedScreenTop; + int mUnrestrictedScreenWidth, mUnrestrictedScreenHeight; + // Like mOverscanScreen*, but allowed to move into the overscan region where appropriate. + int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop; + int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight; + // The current size of the screen; these may be different than (0,0)-(dw,dh) + // if the status bar can't be hidden; in that case it effectively carves out + // that area of the display from all other windows. + int mRestrictedScreenLeft, mRestrictedScreenTop; + int mRestrictedScreenWidth, mRestrictedScreenHeight; + // During layout, the current screen borders accounting for any currently + // visible system UI elements. + int mSystemLeft, mSystemTop, mSystemRight, mSystemBottom; + // For applications requesting stable content insets, these are them. + int mStableLeft, mStableTop, mStableRight, mStableBottom; + // For applications requesting stable content insets but have also set the + // fullscreen window flag, these are the stable dimensions without the status bar. + int mStableFullscreenLeft, mStableFullscreenTop; + int mStableFullscreenRight, mStableFullscreenBottom; + // During layout, the current screen borders with all outer decoration + // (status bar, input method dock) accounted for. + int mCurLeft, mCurTop, mCurRight, mCurBottom; + // During layout, the frame in which content should be displayed + // to the user, accounting for all screen decoration except for any + // space they deem as available for other content. This is usually + // the same as mCur*, but may be larger if the screen decor has supplied + // content insets. + int mContentLeft, mContentTop, mContentRight, mContentBottom; + // During layout, the frame in which voice content should be displayed + // to the user, accounting for all screen decoration except for any + // space they deem as available for other content. + int mVoiceContentLeft, mVoiceContentTop, mVoiceContentRight, mVoiceContentBottom; + // During layout, the current screen borders along which input method + // windows are placed. + int mDockLeft, mDockTop, mDockRight, mDockBottom; + // During layout, the layer at which the doc window is placed. + int mDockLayer; + // During layout, this is the layer of the status bar. + int mStatusBarLayer; + int mLastSystemUiFlags; + // Bits that we are in the process of clearing, so we want to prevent + // them from being set by applications until everything has been updated + // to have them clear. + int mResettingSystemUiFlags = 0; + // Bits that we are currently always keeping cleared. + int mForceClearedSystemUiFlags = 0; + // What we last reported to system UI about whether the compatibility + // menu needs to be displayed. + boolean mLastFocusNeedsMenu = false; + + FakeWindow mHideNavFakeWindow = null; + + static final Rect mTmpParentFrame = new Rect(); + static final Rect mTmpDisplayFrame = new Rect(); + static final Rect mTmpOverscanFrame = new Rect(); + static final Rect mTmpContentFrame = new Rect(); + static final Rect mTmpVisibleFrame = new Rect(); + static final Rect mTmpDecorFrame = new Rect(); + static final Rect mTmpStableFrame = new Rect(); + static final Rect mTmpNavigationFrame = new Rect(); + + WindowState mTopFullscreenOpaqueWindowState; + WindowState mTopFullscreenOpaqueOrDimmingWindowState; + HashSet<IApplicationToken> mAppsToBeHidden = new HashSet<IApplicationToken>(); + HashSet<IApplicationToken> mAppsThatDismissKeyguard = new HashSet<IApplicationToken>(); + boolean mTopIsFullscreen; + boolean mForceStatusBar; + boolean mForceStatusBarFromKeyguard; + boolean mHideLockScreen; + boolean mForcingShowNavBar; + int mForcingShowNavBarLayer; + + // States of keyguard dismiss. + private static final int DISMISS_KEYGUARD_NONE = 0; // Keyguard not being dismissed. + private static final int DISMISS_KEYGUARD_START = 1; // Keyguard needs to be dismissed. + private static final int DISMISS_KEYGUARD_CONTINUE = 2; // Keyguard has been dismissed. + int mDismissKeyguard = DISMISS_KEYGUARD_NONE; + + /** The window that is currently dismissing the keyguard. Dismissing the keyguard must only + * be done once per window. */ + private WindowState mWinDismissingKeyguard; + + /** The window that is currently showing "over" the keyguard. If there is an app window + * belonging to another app on top of this the keyguard shows. If there is a fullscreen + * app window under this, still dismiss the keyguard but don't show the app underneath. Show + * the wallpaper. */ + private WindowState mWinShowWhenLocked; + + boolean mShowingLockscreen; + boolean mShowingDream; + boolean mDreamingLockscreen; + boolean mDreamingSleepTokenNeeded; + SleepToken mDreamingSleepToken; + boolean mKeyguardSecure; + boolean mKeyguardSecureIncludingHidden; + volatile boolean mKeyguardOccluded; + boolean mHomePressed; + boolean mHomeConsumed; + boolean mHomeDoubleTapPending; + Intent mHomeIntent; + Intent mCarDockIntent; + Intent mDeskDockIntent; + boolean mSearchKeyShortcutPending; + boolean mConsumeSearchKeyUp; + boolean mAssistKeyLongPressed; + boolean mPendingMetaAction; + + // support for activating the lock screen while the screen is on + boolean mAllowLockscreenWhenOn; + int mLockScreenTimeout; + boolean mLockScreenTimerActive; + + // Behavior of ENDCALL Button. (See Settings.System.END_BUTTON_BEHAVIOR.) + int mEndcallBehavior; + + // Behavior of POWER button while in-call and screen on. + // (See Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR.) + int mIncallPowerBehavior; + + Display mDisplay; + + int mLandscapeRotation = 0; // default landscape rotation + int mSeascapeRotation = 0; // "other" landscape rotation, 180 degrees from mLandscapeRotation + int mPortraitRotation = 0; // default portrait rotation + int mUpsideDownRotation = 0; // "other" portrait rotation + + int mOverscanLeft = 0; + int mOverscanTop = 0; + int mOverscanRight = 0; + int mOverscanBottom = 0; + + // What we do when the user long presses on home + private int mLongPressOnHomeBehavior; + + // What we do when the user double-taps on home + private int mDoubleTapOnHomeBehavior; + + // Allowed theater mode wake actions + private boolean mAllowTheaterModeWakeFromKey; + private boolean mAllowTheaterModeWakeFromPowerKey; + private boolean mAllowTheaterModeWakeFromMotion; + private boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming; + private boolean mAllowTheaterModeWakeFromCameraLens; + private boolean mAllowTheaterModeWakeFromLidSwitch; + private boolean mAllowTheaterModeWakeFromWakeGesture; + + // Whether to support long press from power button in non-interactive mode + private boolean mSupportLongPressPowerWhenNonInteractive; + + // Whether to go to sleep entering theater mode from power button + private boolean mGoToSleepOnButtonPressTheaterMode; + + // Screenshot trigger states + // Time to volume and power must be pressed within this interval of each other. + private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150; + // Increase the chord delay when taking a screenshot from the keyguard + private static final float KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER = 2.5f; + private boolean mScreenshotChordEnabled; + private boolean mScreenshotChordVolumeDownKeyTriggered; + private long mScreenshotChordVolumeDownKeyTime; + private boolean mScreenshotChordVolumeDownKeyConsumed; + private boolean mScreenshotChordVolumeUpKeyTriggered; + private boolean mScreenshotChordPowerKeyTriggered; + private long mScreenshotChordPowerKeyTime; + + /* The number of steps between min and max brightness */ + private static final int BRIGHTNESS_STEPS = 10; + + SettingsObserver mSettingsObserver; + ShortcutManager mShortcutManager; + PowerManager.WakeLock mBroadcastWakeLock; + PowerManager.WakeLock mPowerKeyWakeLock; + boolean mHavePendingMediaKeyRepeatWithWakeLock; + + private int mCurrentUserId; + + // Maps global key codes to the components that will handle them. + private GlobalKeyManager mGlobalKeyManager; + + // Fallback actions by key code. + private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions = + new SparseArray<KeyCharacterMap.FallbackAction>(); + + private final LogDecelerateInterpolator mLogDecelerateInterpolator + = new LogDecelerateInterpolator(100, 0); + + private static final int MSG_ENABLE_POINTER_LOCATION = 1; + private static final int MSG_DISABLE_POINTER_LOCATION = 2; + private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; + private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4; + private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5; + private static final int MSG_KEYGUARD_DRAWN_TIMEOUT = 6; + private static final int MSG_WINDOW_MANAGER_DRAWN_COMPLETE = 7; + private static final int MSG_DISPATCH_SHOW_RECENTS = 9; + private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10; + private static final int MSG_HIDE_BOOT_MESSAGE = 11; + private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12; + private static final int MSG_POWER_DELAYED_PRESS = 13; + private static final int MSG_POWER_LONG_PRESS = 14; + private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 15; + + private class PolicyHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ENABLE_POINTER_LOCATION: + enablePointerLocation(); + break; + case MSG_DISABLE_POINTER_LOCATION: + disablePointerLocation(); + break; + case MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK: + dispatchMediaKeyWithWakeLock((KeyEvent)msg.obj); + break; + case MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK: + dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj); + break; + case MSG_DISPATCH_SHOW_RECENTS: + showRecentApps(false); + break; + case MSG_DISPATCH_SHOW_GLOBAL_ACTIONS: + showGlobalActionsInternal(); + break; + case MSG_KEYGUARD_DRAWN_COMPLETE: + if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mKeyguardDrawComplete"); + finishKeyguardDrawn(); + break; + case MSG_KEYGUARD_DRAWN_TIMEOUT: + Slog.w(TAG, "Keyguard drawn timeout. Setting mKeyguardDrawComplete"); + finishKeyguardDrawn(); + break; + case MSG_WINDOW_MANAGER_DRAWN_COMPLETE: + if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete"); + finishWindowsDrawn(); + break; + case MSG_HIDE_BOOT_MESSAGE: + handleHideBootMessage(); + break; + case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK: + launchVoiceAssistWithWakeLock(msg.arg1 != 0); + break; + case MSG_POWER_DELAYED_PRESS: + powerPress((Long)msg.obj, msg.arg1 != 0, msg.arg2); + finishPowerKeyPress(); + break; + case MSG_POWER_LONG_PRESS: + powerLongPress(); + break; + case MSG_UPDATE_DREAMING_SLEEP_TOKEN: + updateDreamingSleepToken(msg.arg1 != 0); + break; + } + } + } + + private UEventObserver mHDMIObserver = new UEventObserver() { + @Override + public void onUEvent(UEventObserver.UEvent event) { + setHdmiPlugged("1".equals(event.get("SWITCH_STATE"))); + } + }; + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + void observe() { + // Observe all users' changes + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.END_BUTTON_BEHAVIOR), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.WAKE_GESTURE_ENABLED), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.ACCELEROMETER_ROTATION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.USER_ROTATION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_OFF_TIMEOUT), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.POINTER_LOCATION), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.DEFAULT_INPUT_METHOD), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.POLICY_CONTROL), false, this, + UserHandle.USER_ALL); + updateSettings(); + } + + @Override public void onChange(boolean selfChange) { + updateSettings(); + updateRotation(false); + } + } + + class MyWakeGestureListener extends WakeGestureListener { + MyWakeGestureListener(Context context, Handler handler) { + super(context, handler); + } + + @Override + public void onWakeUp() { + synchronized (mLock) { + if (shouldEnableWakeGestureLp()) { + performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false); + wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture); + } + } + } + } + + class MyOrientationListener extends WindowOrientationListener { + MyOrientationListener(Context context, Handler handler) { + super(context, handler); + } + + @Override + public void onProposedRotationChanged(int rotation) { + if (localLOGV) Slog.v(TAG, "onProposedRotationChanged, rotation=" + rotation); + updateRotation(false); + } + } + MyOrientationListener mOrientationListener; + + private final StatusBarController mStatusBarController = new StatusBarController(); + + private final BarController mNavigationBarController = new BarController("NavigationBar", + View.NAVIGATION_BAR_TRANSIENT, + View.NAVIGATION_BAR_UNHIDE, + View.NAVIGATION_BAR_TRANSLUCENT, + StatusBarManager.WINDOW_NAVIGATION_BAR, + WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + + private ImmersiveModeConfirmation mImmersiveModeConfirmation; + + private SystemGesturesPointerEventListener mSystemGestures; + + IStatusBarService getStatusBarService() { + synchronized (mServiceAquireLock) { + if (mStatusBarService == null) { + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService("statusbar")); + } + return mStatusBarService; + } + } + + /* + * We always let the sensor be switched on by default except when + * the user has explicitly disabled sensor based rotation or when the + * screen is switched off. + */ + boolean needSensorRunningLp() { + if (mSupportAutoRotation) { + if (mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR + || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR + || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT + || mCurrentAppOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + // If the application has explicitly requested to follow the + // orientation, then we need to turn the sensor on. + return true; + } + } + if ((mCarDockEnablesAccelerometer && mDockMode == Intent.EXTRA_DOCK_STATE_CAR) || + (mDeskDockEnablesAccelerometer && (mDockMode == Intent.EXTRA_DOCK_STATE_DESK + || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK + || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK))) { + // enable accelerometer if we are docked in a dock that enables accelerometer + // orientation management, + return true; + } + if (mUserRotationMode == USER_ROTATION_LOCKED) { + // If the setting for using the sensor by default is enabled, then + // we will always leave it on. Note that the user could go to + // a window that forces an orientation that does not use the + // sensor and in theory we could turn it off... however, when next + // turning it on we won't have a good value for the current + // orientation for a little bit, which can cause orientation + // changes to lag, so we'd like to keep it always on. (It will + // still be turned off when the screen is off.) + return false; + } + return mSupportAutoRotation; + } + + /* + * Various use cases for invoking this function + * screen turning off, should always disable listeners if already enabled + * screen turned on and current app has sensor based orientation, enable listeners + * if not already enabled + * screen turned on and current app does not have sensor orientation, disable listeners if + * already enabled + * screen turning on and current app has sensor based orientation, enable listeners if needed + * screen turning on and current app has nosensor based orientation, do nothing + */ + void updateOrientationListenerLp() { + if (!mOrientationListener.canDetectOrientation()) { + // If sensor is turned off or nonexistent for some reason + return; + } + //Could have been invoked due to screen turning on or off or + //change of the currently visible window's orientation + if (localLOGV) Slog.v(TAG, "mScreenOnEarly=" + mScreenOnEarly + + ", mAwake=" + mAwake + ", mCurrentAppOrientation=" + mCurrentAppOrientation + + ", mOrientationSensorEnabled=" + mOrientationSensorEnabled); + boolean disable = true; + if (mScreenOnEarly && mAwake) { + if (needSensorRunningLp()) { + disable = false; + //enable listener if not already enabled + if (!mOrientationSensorEnabled) { + mOrientationListener.enable(); + if(localLOGV) Slog.v(TAG, "Enabling listeners"); + mOrientationSensorEnabled = true; + } + } + } + //check if sensors need to be disabled + if (disable && mOrientationSensorEnabled) { + mOrientationListener.disable(); + if(localLOGV) Slog.v(TAG, "Disabling listeners"); + mOrientationSensorEnabled = false; + } + } + + private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { + // Hold a wake lock until the power key is released. + if (!mPowerKeyWakeLock.isHeld()) { + mPowerKeyWakeLock.acquire(); + } + + // Cancel multi-press detection timeout. + if (mPowerKeyPressCounter != 0) { + mHandler.removeMessages(MSG_POWER_DELAYED_PRESS); + } + + // Detect user pressing the power button in panic when an application has + // taken over the whole screen. + boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive, + SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags)); + if (panic) { + mHandler.post(mRequestTransientNav); + } + + // Latch power key state to detect screenshot chord. + if (interactive && !mScreenshotChordPowerKeyTriggered + && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { + mScreenshotChordPowerKeyTriggered = true; + mScreenshotChordPowerKeyTime = event.getDownTime(); + interceptScreenshotChord(); + } + + // Stop ringing or end call if configured to do so when power is pressed. + TelecomManager telecomManager = getTelecommService(); + boolean hungUp = false; + if (telecomManager != null) { + if (telecomManager.isRinging()) { + // Pressing Power while there's a ringing incoming + // call should silence the ringer. + telecomManager.silenceRinger(); + } else if ((mIncallPowerBehavior + & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 + && telecomManager.isInCall() && interactive) { + // Otherwise, if "Power button ends call" is enabled, + // the Power button will hang up any current active call. + hungUp = telecomManager.endCall(); + } + } + + // If the power key has still not yet been handled, then detect short + // press, long press, or multi press and decide what to do. + mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered + || mScreenshotChordVolumeUpKeyTriggered; + if (!mPowerKeyHandled) { + if (interactive) { + // When interactive, we're already awake. + // Wait for a long press or for the button to be released to decide what to do. + if (hasLongPressOnPowerBehavior()) { + Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, + ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + } + } else { + wakeUpFromPowerKey(event.getDownTime()); + + if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { + Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, + ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + mBeganFromNonInteractive = true; + } else { + final int maxCount = getMaxMultiPressPowerCount(); + + if (maxCount <= 1) { + mPowerKeyHandled = true; + } else { + mBeganFromNonInteractive = true; + } + } + } + } + } + + private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) { + final boolean handled = canceled || mPowerKeyHandled; + mScreenshotChordPowerKeyTriggered = false; + cancelPendingScreenshotChordAction(); + cancelPendingPowerKeyAction(); + + if (!handled) { + // Figure out how to handle the key now that it has been released. + mPowerKeyPressCounter += 1; + + final int maxCount = getMaxMultiPressPowerCount(); + final long eventTime = event.getDownTime(); + if (mPowerKeyPressCounter < maxCount) { + // This could be a multi-press. Wait a little bit longer to confirm. + // Continue holding the wake lock. + Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS, + interactive ? 1 : 0, mPowerKeyPressCounter, eventTime); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, ViewConfiguration.getDoubleTapTimeout()); + return; + } + + // No other actions. Handle it immediately. + powerPress(eventTime, interactive, mPowerKeyPressCounter); + } + + // Done. Reset our state. + finishPowerKeyPress(); + } + + private void finishPowerKeyPress() { + mBeganFromNonInteractive = false; + mPowerKeyPressCounter = 0; + if (mPowerKeyWakeLock.isHeld()) { + mPowerKeyWakeLock.release(); + } + } + + private void cancelPendingPowerKeyAction() { + if (!mPowerKeyHandled) { + mPowerKeyHandled = true; + mHandler.removeMessages(MSG_POWER_LONG_PRESS); + } + } + + private void powerPress(long eventTime, boolean interactive, int count) { + if (mScreenOnEarly && !mScreenOnFully) { + Slog.i(TAG, "Suppressed redundant power key press while " + + "already in the process of turning the screen on."); + return; + } + + if (count == 2) { + powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior); + } else if (count == 3) { + powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior); + } else if (interactive && !mBeganFromNonInteractive) { + switch (mShortPressOnPowerBehavior) { + case SHORT_PRESS_POWER_NOTHING: + break; + case SHORT_PRESS_POWER_GO_TO_SLEEP: + mPowerManager.goToSleep(eventTime, + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); + break; + case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP: + mPowerManager.goToSleep(eventTime, + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); + break; + case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME: + mPowerManager.goToSleep(eventTime, + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); + launchHomeFromHotKey(); + break; + case SHORT_PRESS_POWER_GO_HOME: + launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/); + break; + } + } + } + + private void powerMultiPressAction(long eventTime, boolean interactive, int behavior) { + switch (behavior) { + case MULTI_PRESS_POWER_NOTHING: + break; + case MULTI_PRESS_POWER_THEATER_MODE: + if (isTheaterModeEnabled()) { + Slog.i(TAG, "Toggling theater mode off."); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0); + if (!interactive) { + wakeUpFromPowerKey(eventTime); + } + } else { + Slog.i(TAG, "Toggling theater mode on."); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.THEATER_MODE_ON, 1); + + if (mGoToSleepOnButtonPressTheaterMode && interactive) { + mPowerManager.goToSleep(eventTime, + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); + } + } + break; + case MULTI_PRESS_POWER_BRIGHTNESS_BOOST: + Slog.i(TAG, "Starting brightness boost."); + if (!interactive) { + wakeUpFromPowerKey(eventTime); + } + mPowerManager.boostScreenBrightness(eventTime); + break; + } + } + + private int getMaxMultiPressPowerCount() { + if (mTriplePressOnPowerBehavior != MULTI_PRESS_POWER_NOTHING) { + return 3; + } + if (mDoublePressOnPowerBehavior != MULTI_PRESS_POWER_NOTHING) { + return 2; + } + return 1; + } + + private void powerLongPress() { + final int behavior = getResolvedLongPressOnPowerBehavior(); + switch (behavior) { + case LONG_PRESS_POWER_NOTHING: + break; + case LONG_PRESS_POWER_GLOBAL_ACTIONS: + mPowerKeyHandled = true; + if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) { + performAuditoryFeedbackForAccessibilityIfNeed(); + } + showGlobalActionsInternal(); + break; + case LONG_PRESS_POWER_SHUT_OFF: + case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: + mPowerKeyHandled = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); + mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF); + break; + } + } + + private void sleepPress(KeyEvent event) { + switch (mShortPressOnSleepBehavior) { + case SHORT_PRESS_SLEEP_GO_TO_SLEEP: + mPowerManager.goToSleep(event.getEventTime(), + PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); + break; + case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME: + launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/); + mPowerManager.goToSleep(event.getEventTime(), + PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0); + break; + } + } + + private int getResolvedLongPressOnPowerBehavior() { + if (FactoryTest.isLongPressOnPowerOffEnabled()) { + return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM; + } + return mLongPressOnPowerBehavior; + } + + private boolean hasLongPressOnPowerBehavior() { + return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING; + } + + private void interceptScreenshotChord() { + if (mScreenshotChordEnabled + && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered + && !mScreenshotChordVolumeUpKeyTriggered) { + final long now = SystemClock.uptimeMillis(); + if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS + && now <= mScreenshotChordPowerKeyTime + + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) { + mScreenshotChordVolumeDownKeyConsumed = true; + cancelPendingPowerKeyAction(); + + mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); + } + } + } + + private long getScreenshotChordLongPressDelay() { + if (mKeyguardDelegate.isShowing()) { + // Double the time it takes to take a screenshot from the keyguard + return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER * + ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + } + return ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout(); + } + + private void cancelPendingScreenshotChordAction() { + mHandler.removeCallbacks(mScreenshotRunnable); + } + + private final Runnable mEndCallLongPress = new Runnable() { + @Override + public void run() { + mEndCallKeyHandled = true; + if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) { + performAuditoryFeedbackForAccessibilityIfNeed(); + } + showGlobalActionsInternal(); + } + }; + + private final Runnable mScreenshotRunnable = new Runnable() { + @Override + public void run() { + takeScreenshot(); + } + }; + + @Override + public void showGlobalActions() { + mHandler.removeMessages(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS); + mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_GLOBAL_ACTIONS); + } + + void showGlobalActionsInternal() { + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); + if (mGlobalActions == null) { + mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs); + } + final boolean keyguardShowing = isKeyguardShowingAndNotOccluded(); + mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); + if (keyguardShowing) { + // since it took two seconds of long press to bring this up, + // poke the wake lock so they have some time to see the dialog. + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + } + } + + boolean isDeviceProvisioned() { + return Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; + } + + boolean isUserSetupComplete() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; + } + + private void handleShortPressOnHome() { + // If there's a dream running then use home to escape the dream + // but don't actually go home. + if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) { + mDreamManagerInternal.stopDream(false /*immediate*/); + return; + } + + // Go home! + launchHomeFromHotKey(); + } + + private void handleLongPressOnHome() { + if (mLongPressOnHomeBehavior != LONG_PRESS_HOME_NOTHING) { + mHomeConsumed = true; + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + + if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI) { + toggleRecentApps(); + } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_ASSIST) { + launchAssistAction(); + } + } + } + + private void handleDoubleTapOnHome() { + if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { + mHomeConsumed = true; + toggleRecentApps(); + } + } + + private final Runnable mHomeDoubleTapTimeoutRunnable = new Runnable() { + @Override + public void run() { + if (mHomeDoubleTapPending) { + mHomeDoubleTapPending = false; + handleShortPressOnHome(); + } + } + }; + + private boolean isRoundWindow() { + return mContext.getResources().getBoolean(com.android.internal.R.bool.config_windowIsRound) + || (Build.HARDWARE.contains("goldfish") + && SystemProperties.getBoolean(ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false)); + } + + /** {@inheritDoc} */ + @Override + public void init(Context context, IWindowManager windowManager, + WindowManagerFuncs windowManagerFuncs) { + mContext = context; + mWindowManager = windowManager; + mWindowManagerFuncs = windowManagerFuncs; + mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class); + + // Init display burn-in protection + boolean burnInProtectionEnabled = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableBurnInProtection); + // Allow a system property to override this. Used by developer settings. + boolean burnInProtectionDevMode = + SystemProperties.getBoolean("persist.debug.force_burn_in", false); + if (burnInProtectionEnabled || burnInProtectionDevMode) { + final int minHorizontal; + final int maxHorizontal; + final int minVertical; + final int maxVertical; + final int maxRadius; + if (burnInProtectionDevMode) { + minHorizontal = -8; + maxHorizontal = 8; + minVertical = -8; + maxVertical = -4; + maxRadius = (isRoundWindow()) ? 6 : -1; + } else { + Resources resources = context.getResources(); + minHorizontal = resources.getInteger( + com.android.internal.R.integer.config_burnInProtectionMinHorizontalOffset); + maxHorizontal = resources.getInteger( + com.android.internal.R.integer.config_burnInProtectionMaxHorizontalOffset); + minVertical = resources.getInteger( + com.android.internal.R.integer.config_burnInProtectionMinVerticalOffset); + maxVertical = resources.getInteger( + com.android.internal.R.integer.config_burnInProtectionMaxVerticalOffset); + maxRadius = resources.getInteger( + com.android.internal.R.integer.config_burnInProtectionMaxRadius); + } + mBurnInProtectionHelper = new BurnInProtectionHelper( + context, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius); + } + + mHandler = new PolicyHandler(); + mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); + mOrientationListener = new MyOrientationListener(mContext, mHandler); + try { + mOrientationListener.setCurrentRotation(windowManager.getRotation()); + } catch (RemoteException ex) { } + mSettingsObserver = new SettingsObserver(mHandler); + mSettingsObserver.observe(); + mShortcutManager = new ShortcutManager(context); + mUiMode = context.getResources().getInteger( + com.android.internal.R.integer.config_defaultUiModeType); + mHomeIntent = new Intent(Intent.ACTION_MAIN, null); + mHomeIntent.addCategory(Intent.CATEGORY_HOME); + mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + mCarDockIntent = new Intent(Intent.ACTION_MAIN, null); + mCarDockIntent.addCategory(Intent.CATEGORY_CAR_DOCK); + mCarDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + mDeskDockIntent = new Intent(Intent.ACTION_MAIN, null); + mDeskDockIntent.addCategory(Intent.CATEGORY_DESK_DOCK); + mDeskDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + + mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mBroadcastWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "PhoneWindowManager.mBroadcastWakeLock"); + mPowerKeyWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "PhoneWindowManager.mPowerKeyWakeLock"); + mEnableShiftMenuBugReports = "1".equals(SystemProperties.get("ro.debuggable")); + mSupportAutoRotation = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_supportAutoRotation); + mLidOpenRotation = readRotation( + com.android.internal.R.integer.config_lidOpenRotation); + mCarDockRotation = readRotation( + com.android.internal.R.integer.config_carDockRotation); + mDeskDockRotation = readRotation( + com.android.internal.R.integer.config_deskDockRotation); + mUndockedHdmiRotation = readRotation( + com.android.internal.R.integer.config_undockedHdmiRotation); + mCarDockEnablesAccelerometer = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_carDockEnablesAccelerometer); + mDeskDockEnablesAccelerometer = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_deskDockEnablesAccelerometer); + mLidKeyboardAccessibility = mContext.getResources().getInteger( + com.android.internal.R.integer.config_lidKeyboardAccessibility); + mLidNavigationAccessibility = mContext.getResources().getInteger( + com.android.internal.R.integer.config_lidNavigationAccessibility); + mLidControlsSleep = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_lidControlsSleep); + mTranslucentDecorEnabled = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableTranslucentDecor); + + mAllowTheaterModeWakeFromKey = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromKey); + mAllowTheaterModeWakeFromPowerKey = mAllowTheaterModeWakeFromKey + || mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey); + mAllowTheaterModeWakeFromMotion = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion); + mAllowTheaterModeWakeFromMotionWhenNotDreaming = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming); + mAllowTheaterModeWakeFromCameraLens = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens); + mAllowTheaterModeWakeFromLidSwitch = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch); + mAllowTheaterModeWakeFromWakeGesture = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture); + + mGoToSleepOnButtonPressTheaterMode = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_goToSleepOnButtonPressTheaterMode); + + mSupportLongPressPowerWhenNonInteractive = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_supportLongPressPowerWhenNonInteractive); + + mShortPressOnPowerBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shortPressOnPowerBehavior); + mLongPressOnPowerBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnPowerBehavior); + mDoublePressOnPowerBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_doublePressOnPowerBehavior); + mTriplePressOnPowerBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_triplePressOnPowerBehavior); + mShortPressOnSleepBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shortPressOnSleepBehavior); + + mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION; + + readConfigurationDependentBehaviors(); + + mAccessibilityManager = (AccessibilityManager) context.getSystemService( + Context.ACCESSIBILITY_SERVICE); + + // register for dock events + IntentFilter filter = new IntentFilter(); + filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE); + filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE); + filter.addAction(UiModeManager.ACTION_ENTER_DESK_MODE); + filter.addAction(UiModeManager.ACTION_EXIT_DESK_MODE); + filter.addAction(Intent.ACTION_DOCK_EVENT); + Intent intent = context.registerReceiver(mDockReceiver, filter); + if (intent != null) { + // Retrieve current sticky dock event broadcast. + mDockMode = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + } + + // register for dream-related broadcasts + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DREAMING_STARTED); + filter.addAction(Intent.ACTION_DREAMING_STOPPED); + context.registerReceiver(mDreamReceiver, filter); + + // register for multiuser-relevant broadcasts + filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); + context.registerReceiver(mMultiuserReceiver, filter); + + // monitor for system gestures + mSystemGestures = new SystemGesturesPointerEventListener(context, + new SystemGesturesPointerEventListener.Callbacks() { + @Override + public void onSwipeFromTop() { + if (mStatusBar != null) { + requestTransientBars(mStatusBar); + } + } + @Override + public void onSwipeFromBottom() { + if (mNavigationBar != null && mNavigationBarOnBottom) { + requestTransientBars(mNavigationBar); + } + } + @Override + public void onSwipeFromRight() { + if (mNavigationBar != null && !mNavigationBarOnBottom) { + requestTransientBars(mNavigationBar); + } + } + @Override + public void onDebug() { + // no-op + } + @Override + public void onDown() { + mOrientationListener.onTouchStart(); + } + @Override + public void onUpOrCancel() { + mOrientationListener.onTouchEnd(); + } + }); + mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext); + mWindowManagerFuncs.registerPointerEventListener(mSystemGestures); + + mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); + mLongPressVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_longPressVibePattern); + mVirtualKeyVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_virtualKeyVibePattern); + mKeyboardTapVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_keyboardTapVibePattern); + mClockTickVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_clockTickVibePattern); + mCalendarDateVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_calendarDateVibePattern); + mSafeModeDisabledVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_safeModeDisabledVibePattern); + mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(), + com.android.internal.R.array.config_safeModeEnabledVibePattern); + + mScreenshotChordEnabled = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableScreenshotChord); + + mGlobalKeyManager = new GlobalKeyManager(mContext); + + // Controls rotation and the like. + initializeHdmiState(); + + // Match current screen state. + if (!mPowerManager.isInteractive()) { + goingToSleep(WindowManagerPolicy.OFF_BECAUSE_OF_USER); + } + + mWindowManagerInternal.registerAppTransitionListener( + mStatusBarController.getAppTransitionListener()); + } + + /** + * Read values from config.xml that may be overridden depending on + * the configuration of the device. + * eg. Disable long press on home goes to recents on sw600dp. + */ + private void readConfigurationDependentBehaviors() { + mLongPressOnHomeBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_longPressOnHomeBehavior); + if (mLongPressOnHomeBehavior < LONG_PRESS_HOME_NOTHING || + mLongPressOnHomeBehavior > LONG_PRESS_HOME_ASSIST) { + mLongPressOnHomeBehavior = LONG_PRESS_HOME_NOTHING; + } + + mDoubleTapOnHomeBehavior = mContext.getResources().getInteger( + com.android.internal.R.integer.config_doubleTapOnHomeBehavior); + if (mDoubleTapOnHomeBehavior < DOUBLE_TAP_HOME_NOTHING || + mDoubleTapOnHomeBehavior > DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { + mDoubleTapOnHomeBehavior = LONG_PRESS_HOME_NOTHING; + } + } + + @Override + public void setInitialDisplaySize(Display display, int width, int height, int density) { + // This method might be called before the policy has been fully initialized + // or for other displays we don't care about. + if (mContext == null || display.getDisplayId() != Display.DEFAULT_DISPLAY) { + return; + } + mDisplay = display; + + final Resources res = mContext.getResources(); + int shortSize, longSize; + if (width > height) { + shortSize = height; + longSize = width; + mLandscapeRotation = Surface.ROTATION_0; + mSeascapeRotation = Surface.ROTATION_180; + if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) { + mPortraitRotation = Surface.ROTATION_90; + mUpsideDownRotation = Surface.ROTATION_270; + } else { + mPortraitRotation = Surface.ROTATION_270; + mUpsideDownRotation = Surface.ROTATION_90; + } + } else { + shortSize = width; + longSize = height; + mPortraitRotation = Surface.ROTATION_0; + mUpsideDownRotation = Surface.ROTATION_180; + if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) { + mLandscapeRotation = Surface.ROTATION_270; + mSeascapeRotation = Surface.ROTATION_90; + } else { + mLandscapeRotation = Surface.ROTATION_90; + mSeascapeRotation = Surface.ROTATION_270; + } + } + + mStatusBarHeight = + res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + + // Height of the navigation bar when presented horizontally at bottom + mNavigationBarHeightForRotation[mPortraitRotation] = + mNavigationBarHeightForRotation[mUpsideDownRotation] = + res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height); + mNavigationBarHeightForRotation[mLandscapeRotation] = + mNavigationBarHeightForRotation[mSeascapeRotation] = res.getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_height_landscape); + + // Width of the navigation bar when presented vertically along one side + mNavigationBarWidthForRotation[mPortraitRotation] = + mNavigationBarWidthForRotation[mUpsideDownRotation] = + mNavigationBarWidthForRotation[mLandscapeRotation] = + mNavigationBarWidthForRotation[mSeascapeRotation] = + res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width); + + // SystemUI (status bar) layout policy + int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density; + int longSizeDp = longSize * DisplayMetrics.DENSITY_DEFAULT / density; + + // Allow the navigation bar to move on non-square small devices (phones). + mNavigationBarCanMove = width != height && shortSizeDp < 600; + + mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); + // Allow a system property to override this. Used by the emulator. + // See also hasNavigationBar(). + String navBarOverride = SystemProperties.get("qemu.hw.mainkeys"); + if ("1".equals(navBarOverride)) { + mHasNavigationBar = false; + } else if ("0".equals(navBarOverride)) { + mHasNavigationBar = true; + } + + // For demo purposes, allow the rotation of the HDMI display to be controlled. + // By default, HDMI locks rotation to landscape. + if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) { + mDemoHdmiRotation = mPortraitRotation; + } else { + mDemoHdmiRotation = mLandscapeRotation; + } + mDemoHdmiRotationLock = SystemProperties.getBoolean("persist.demo.hdmirotationlock", false); + + // For demo purposes, allow the rotation of the remote display to be controlled. + // By default, remote display locks rotation to landscape. + if ("portrait".equals(SystemProperties.get("persist.demo.remoterotation"))) { + mDemoRotation = mPortraitRotation; + } else { + mDemoRotation = mLandscapeRotation; + } + mDemoRotationLock = SystemProperties.getBoolean( + "persist.demo.rotationlock", false); + + // Only force the default orientation if the screen is xlarge, at least 960dp x 720dp, per + // http://developer.android.com/guide/practices/screens_support.html#range + mForceDefaultOrientation = longSizeDp >= 960 && shortSizeDp >= 720 && + res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) && + // For debug purposes the next line turns this feature off with: + // $ adb shell setprop config.override_forced_orient true + // $ adb shell wm size reset + !"true".equals(SystemProperties.get("config.override_forced_orient")); + } + + /** + * @return whether the navigation bar can be hidden, e.g. the device has a + * navigation bar and touch exploration is not enabled + */ + private boolean canHideNavigationBar() { + return mHasNavigationBar + && !mAccessibilityManager.isTouchExplorationEnabled(); + } + + @Override + public boolean isDefaultOrientationForced() { + return mForceDefaultOrientation; + } + + @Override + public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) { + if (display.getDisplayId() == Display.DEFAULT_DISPLAY) { + mOverscanLeft = left; + mOverscanTop = top; + mOverscanRight = right; + mOverscanBottom = bottom; + } + } + + public void updateSettings() { + ContentResolver resolver = mContext.getContentResolver(); + boolean updateRotation = false; + synchronized (mLock) { + mEndcallBehavior = Settings.System.getIntForUser(resolver, + Settings.System.END_BUTTON_BEHAVIOR, + Settings.System.END_BUTTON_BEHAVIOR_DEFAULT, + UserHandle.USER_CURRENT); + mIncallPowerBehavior = Settings.Secure.getIntForUser(resolver, + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT, + UserHandle.USER_CURRENT); + + // Configure wake gesture. + boolean wakeGestureEnabledSetting = Settings.Secure.getIntForUser(resolver, + Settings.Secure.WAKE_GESTURE_ENABLED, 0, + UserHandle.USER_CURRENT) != 0; + if (mWakeGestureEnabledSetting != wakeGestureEnabledSetting) { + mWakeGestureEnabledSetting = wakeGestureEnabledSetting; + updateWakeGestureListenerLp(); + } + + // Configure rotation lock. + int userRotation = Settings.System.getIntForUser(resolver, + Settings.System.USER_ROTATION, Surface.ROTATION_0, + UserHandle.USER_CURRENT); + if (mUserRotation != userRotation) { + mUserRotation = userRotation; + updateRotation = true; + } + int userRotationMode = Settings.System.getIntForUser(resolver, + Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) != 0 ? + WindowManagerPolicy.USER_ROTATION_FREE : + WindowManagerPolicy.USER_ROTATION_LOCKED; + if (mUserRotationMode != userRotationMode) { + mUserRotationMode = userRotationMode; + updateRotation = true; + updateOrientationListenerLp(); + } + + if (mSystemReady) { + int pointerLocation = Settings.System.getIntForUser(resolver, + Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT); + if (mPointerLocationMode != pointerLocation) { + mPointerLocationMode = pointerLocation; + mHandler.sendEmptyMessage(pointerLocation != 0 ? + MSG_ENABLE_POINTER_LOCATION : MSG_DISABLE_POINTER_LOCATION); + } + } + // use screen off timeout setting as the timeout for the lockscreen + mLockScreenTimeout = Settings.System.getIntForUser(resolver, + Settings.System.SCREEN_OFF_TIMEOUT, 0, UserHandle.USER_CURRENT); + String imId = Settings.Secure.getStringForUser(resolver, + Settings.Secure.DEFAULT_INPUT_METHOD, UserHandle.USER_CURRENT); + boolean hasSoftInput = imId != null && imId.length() > 0; + if (mHasSoftInput != hasSoftInput) { + mHasSoftInput = hasSoftInput; + updateRotation = true; + } + if (mImmersiveModeConfirmation != null) { + mImmersiveModeConfirmation.loadSetting(mCurrentUserId); + } + PolicyControl.reloadFromSetting(mContext); + } + if (updateRotation) { + updateRotation(true); + } + } + + private void updateWakeGestureListenerLp() { + if (shouldEnableWakeGestureLp()) { + mWakeGestureListener.requestWakeUpTrigger(); + } else { + mWakeGestureListener.cancelWakeUpTrigger(); + } + } + + private boolean shouldEnableWakeGestureLp() { + return mWakeGestureEnabledSetting && !mAwake + && (!mLidControlsSleep || mLidState != LID_CLOSED) + && mWakeGestureListener.isSupported(); + } + + private void enablePointerLocation() { + if (mPointerLocationView == null) { + mPointerLocationView = new PointerLocationView(mContext); + mPointerLocationView.setPrintCoords(false); + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT); + lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; + lp.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + if (ActivityManager.isHighEndGfx()) { + lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + lp.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; + } + lp.format = PixelFormat.TRANSLUCENT; + lp.setTitle("PointerLocation"); + WindowManager wm = (WindowManager) + mContext.getSystemService(Context.WINDOW_SERVICE); + lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; + wm.addView(mPointerLocationView, lp); + mWindowManagerFuncs.registerPointerEventListener(mPointerLocationView); + } + } + + private void disablePointerLocation() { + if (mPointerLocationView != null) { + mWindowManagerFuncs.unregisterPointerEventListener(mPointerLocationView); + WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + wm.removeView(mPointerLocationView); + mPointerLocationView = null; + } + } + + private int readRotation(int resID) { + try { + int rotation = mContext.getResources().getInteger(resID); + switch (rotation) { + case 0: + return Surface.ROTATION_0; + case 90: + return Surface.ROTATION_90; + case 180: + return Surface.ROTATION_180; + case 270: + return Surface.ROTATION_270; + } + } catch (Resources.NotFoundException e) { + // fall through + } + return -1; + } + + /** {@inheritDoc} */ + @Override + public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) { + int type = attrs.type; + + outAppOp[0] = AppOpsManager.OP_NONE; + + if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) + || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) + || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) { + return WindowManagerGlobal.ADD_INVALID_TYPE; + } + + if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) { + // Window manager will make sure these are okay. + return WindowManagerGlobal.ADD_OKAY; + } + String permission = null; + switch (type) { + case TYPE_TOAST: + // XXX right now the app process has complete control over + // this... should introduce a token to let the system + // monitor/control what they are doing. + outAppOp[0] = AppOpsManager.OP_TOAST_WINDOW; + break; + case TYPE_DREAM: + case TYPE_INPUT_METHOD: + case TYPE_WALLPAPER: + case TYPE_PRIVATE_PRESENTATION: + case TYPE_VOICE_INTERACTION: + case TYPE_ACCESSIBILITY_OVERLAY: + // The window manager will check these. + break; + case TYPE_PHONE: + case TYPE_PRIORITY_PHONE: + case TYPE_SYSTEM_ALERT: + case TYPE_SYSTEM_ERROR: + case TYPE_SYSTEM_OVERLAY: + permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW; + outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW; + break; + default: + permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; + } + if (permission != null) { + if (mContext.checkCallingOrSelfPermission(permission) + != PackageManager.PERMISSION_GRANTED) { + return WindowManagerGlobal.ADD_PERMISSION_DENIED; + } + } + return WindowManagerGlobal.ADD_OKAY; + } + + @Override + public boolean checkShowToOwnerOnly(WindowManager.LayoutParams attrs) { + + // If this switch statement is modified, modify the comment in the declarations of + // the type in {@link WindowManager.LayoutParams} as well. + switch (attrs.type) { + default: + // These are the windows that by default are shown only to the user that created + // them. If this needs to be overridden, set + // {@link WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS} in + // {@link WindowManager.LayoutParams}. Note that permission + // {@link android.Manifest.permission.INTERNAL_SYSTEM_WINDOW} is required as well. + if ((attrs.privateFlags & PRIVATE_FLAG_SHOW_FOR_ALL_USERS) == 0) { + return true; + } + break; + + // These are the windows that by default are shown to all users. However, to + // protect against spoofing, check permissions below. + case TYPE_APPLICATION_STARTING: + case TYPE_BOOT_PROGRESS: + case TYPE_DISPLAY_OVERLAY: + case TYPE_HIDDEN_NAV_CONSUMER: + case TYPE_KEYGUARD_SCRIM: + case TYPE_KEYGUARD_DIALOG: + case TYPE_MAGNIFICATION_OVERLAY: + case TYPE_NAVIGATION_BAR: + case TYPE_NAVIGATION_BAR_PANEL: + case TYPE_PHONE: + case TYPE_POINTER: + case TYPE_PRIORITY_PHONE: + case TYPE_SEARCH_BAR: + case TYPE_STATUS_BAR: + case TYPE_STATUS_BAR_PANEL: + case TYPE_STATUS_BAR_SUB_PANEL: + case TYPE_SYSTEM_DIALOG: + case TYPE_VOLUME_OVERLAY: + case TYPE_PRIVATE_PRESENTATION: + break; + } + + // Check if third party app has set window to system window type. + return mContext.checkCallingOrSelfPermission( + android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) + != PackageManager.PERMISSION_GRANTED; + } + + @Override + public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) { + switch (attrs.type) { + case TYPE_SYSTEM_OVERLAY: + case TYPE_SECURE_SYSTEM_OVERLAY: + // These types of windows can't receive input events. + attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; + break; + case TYPE_STATUS_BAR: + + // If the Keyguard is in a hidden state (occluded by another window), we force to + // remove the wallpaper and keyguard flag so that any change in-flight after setting + // the keyguard as occluded wouldn't set these flags again. + // See {@link #processKeyguardSetHiddenResultLw}. + if (mKeyguardHidden) { + attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; + } + break; + } + + if (attrs.type != TYPE_STATUS_BAR) { + // The status bar is the only window allowed to exhibit keyguard behavior. + attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; + } + + if (ActivityManager.isHighEndGfx() + && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) { + attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + } + } + + void readLidState() { + mLidState = mWindowManagerFuncs.getLidState(); + } + + private void readCameraLensCoverState() { + mCameraLensCoverState = mWindowManagerFuncs.getCameraLensCoverState(); + } + + private boolean isHidden(int accessibilityMode) { + switch (accessibilityMode) { + case 1: + return mLidState == LID_CLOSED; + case 2: + return mLidState == LID_OPEN; + default: + return false; + } + } + + /** {@inheritDoc} */ + @Override + public void adjustConfigurationLw(Configuration config, int keyboardPresence, + int navigationPresence) { + mHaveBuiltInKeyboard = (keyboardPresence & PRESENCE_INTERNAL) != 0; + + readConfigurationDependentBehaviors(); + readLidState(); + applyLidSwitchState(); + + if (config.keyboard == Configuration.KEYBOARD_NOKEYS + || (keyboardPresence == PRESENCE_INTERNAL + && isHidden(mLidKeyboardAccessibility))) { + config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES; + if (!mHasSoftInput) { + config.keyboardHidden = Configuration.KEYBOARDHIDDEN_YES; + } + } + + if (config.navigation == Configuration.NAVIGATION_NONAV + || (navigationPresence == PRESENCE_INTERNAL + && isHidden(mLidNavigationAccessibility))) { + config.navigationHidden = Configuration.NAVIGATIONHIDDEN_YES; + } + } + + /** {@inheritDoc} */ + @Override + public int windowTypeToLayerLw(int type) { + if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { + return 2; + } + switch (type) { + case TYPE_PRIVATE_PRESENTATION: + return 2; + case TYPE_WALLPAPER: + // wallpaper is at the bottom, though the window manager may move it. + return 2; + case TYPE_PHONE: + return 3; + case TYPE_SEARCH_BAR: + case TYPE_VOICE_INTERACTION_STARTING: + return 4; + case TYPE_VOICE_INTERACTION: + // voice interaction layer is almost immediately above apps. + return 5; + case TYPE_SYSTEM_DIALOG: + return 6; + case TYPE_TOAST: + // toasts and the plugged-in battery thing + return 7; + case TYPE_PRIORITY_PHONE: + // SIM errors and unlock. Not sure if this really should be in a high layer. + return 8; + case TYPE_DREAM: + // used for Dreams (screensavers with TYPE_DREAM windows) + return 9; + case TYPE_SYSTEM_ALERT: + // like the ANR / app crashed dialogs + return 10; + case TYPE_INPUT_METHOD: + // on-screen keyboards and other such input method user interfaces go here. + return 11; + case TYPE_INPUT_METHOD_DIALOG: + // on-screen keyboards and other such input method user interfaces go here. + return 12; + case TYPE_KEYGUARD_SCRIM: + // the safety window that shows behind keyguard while keyguard is starting + return 13; + case TYPE_STATUS_BAR_SUB_PANEL: + return 14; + case TYPE_STATUS_BAR: + return 15; + case TYPE_STATUS_BAR_PANEL: + return 16; + case TYPE_KEYGUARD_DIALOG: + return 17; + case TYPE_VOLUME_OVERLAY: + // the on-screen volume indicator and controller shown when the user + // changes the device volume + return 18; + case TYPE_SYSTEM_OVERLAY: + // the on-screen volume indicator and controller shown when the user + // changes the device volume + return 19; + case TYPE_NAVIGATION_BAR: + // the navigation bar, if available, shows atop most things + return 20; + case TYPE_NAVIGATION_BAR_PANEL: + // some panels (e.g. search) need to show on top of the navigation bar + return 21; + case TYPE_SYSTEM_ERROR: + // system-level error dialogs + return 22; + case TYPE_MAGNIFICATION_OVERLAY: + // used to highlight the magnified portion of a display + return 23; + case TYPE_DISPLAY_OVERLAY: + // used to simulate secondary display devices + return 24; + case TYPE_DRAG: + // the drag layer: input for drag-and-drop is associated with this window, + // which sits above all other focusable windows + return 25; + case TYPE_ACCESSIBILITY_OVERLAY: + // overlay put by accessibility services to intercept user interaction + return 26; + case TYPE_SECURE_SYSTEM_OVERLAY: + return 27; + case TYPE_BOOT_PROGRESS: + return 28; + case TYPE_POINTER: + // the (mouse) pointer layer + return 29; + case TYPE_HIDDEN_NAV_CONSUMER: + return 30; + } + Log.e(TAG, "Unknown window type: " + type); + return 2; + } + + /** {@inheritDoc} */ + @Override + public int subWindowTypeToLayerLw(int type) { + switch (type) { + case TYPE_APPLICATION_PANEL: + case TYPE_APPLICATION_ATTACHED_DIALOG: + return APPLICATION_PANEL_SUBLAYER; + case TYPE_APPLICATION_MEDIA: + return APPLICATION_MEDIA_SUBLAYER; + case TYPE_APPLICATION_MEDIA_OVERLAY: + return APPLICATION_MEDIA_OVERLAY_SUBLAYER; + case TYPE_APPLICATION_SUB_PANEL: + return APPLICATION_SUB_PANEL_SUBLAYER; + case TYPE_APPLICATION_ABOVE_SUB_PANEL: + return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER; + } + Log.e(TAG, "Unknown sub-window type: " + type); + return 0; + } + + @Override + public int getMaxWallpaperLayer() { + return windowTypeToLayerLw(TYPE_STATUS_BAR); + } + + @Override + public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation) { + if (mHasNavigationBar) { + // For a basic navigation bar, when we are in landscape mode we place + // the navigation bar to the side. + if (mNavigationBarCanMove && fullWidth > fullHeight) { + return fullWidth - mNavigationBarWidthForRotation[rotation]; + } + } + return fullWidth; + } + + @Override + public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation) { + if (mHasNavigationBar) { + // For a basic navigation bar, when we are in portrait mode we place + // the navigation bar to the bottom. + if (!mNavigationBarCanMove || fullWidth < fullHeight) { + return fullHeight - mNavigationBarHeightForRotation[rotation]; + } + } + return fullHeight; + } + + @Override + public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation) { + return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation); + } + + @Override + public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation) { + // There is a separate status bar at the top of the display. We don't count that as part + // of the fixed decor, since it can hide; however, for purposes of configurations, + // we do want to exclude it since applications can't generally use that part + // of the screen. + return getNonDecorDisplayHeight(fullWidth, fullHeight, rotation) - mStatusBarHeight; + } + + @Override + public boolean isForceHiding(WindowManager.LayoutParams attrs) { + return (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 || + (isKeyguardHostWindow(attrs) && + (mKeyguardDelegate != null && mKeyguardDelegate.isShowing())) || + (attrs.type == TYPE_KEYGUARD_SCRIM); + } + + @Override + public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs) { + return attrs.type == TYPE_STATUS_BAR; + } + + @Override + public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs) { + switch (attrs.type) { + case TYPE_STATUS_BAR: + case TYPE_NAVIGATION_BAR: + case TYPE_WALLPAPER: + case TYPE_DREAM: + case TYPE_KEYGUARD_SCRIM: + return false; + default: + return true; + } + } + + @Override + public WindowState getWinShowWhenLockedLw() { + return mWinShowWhenLocked; + } + + /** {@inheritDoc} */ + @Override + public View addStartingWindow(IBinder appToken, String packageName, int theme, + CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, + int icon, int logo, int windowFlags) { + if (!SHOW_STARTING_ANIMATIONS) { + return null; + } + if (packageName == null) { + return null; + } + + WindowManager wm = null; + View view = null; + + try { + Context context = mContext; + if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "addStartingWindow " + packageName + + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme=" + + Integer.toHexString(theme)); + if (theme != context.getThemeResId() || labelRes != 0) { + try { + context = context.createPackageContext(packageName, 0); + context.setTheme(theme); + } catch (PackageManager.NameNotFoundException e) { + // Ignore + } + } + + Window win = new PhoneWindow(context); + final TypedArray ta = win.getWindowStyle(); + if (ta.getBoolean( + com.android.internal.R.styleable.Window_windowDisablePreview, false) + || ta.getBoolean( + com.android.internal.R.styleable.Window_windowShowWallpaper,false)) { + return null; + } + + Resources r = context.getResources(); + win.setTitle(r.getText(labelRes, nonLocalizedLabel)); + + win.setType( + WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); + // Force the window flags: this is a fake window, so it is not really + // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM + // flag because we do know that the next window will take input + // focus, so we want to get the IME window up on top of us right away. + win.setFlags( + windowFlags| + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + windowFlags| + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + + win.setDefaultIcon(icon); + win.setDefaultLogo(logo); + + win.setLayout(WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT); + + final WindowManager.LayoutParams params = win.getAttributes(); + params.token = appToken; + params.packageName = packageName; + params.windowAnimations = win.getWindowStyle().getResourceId( + com.android.internal.R.styleable.Window_windowAnimationStyle, 0); + params.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED; + params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + + if (!compatInfo.supportsScreen()) { + params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; + } + + params.setTitle("Starting " + packageName); + + wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); + view = win.getDecorView(); + + if (win.isFloating()) { + // Whoops, there is no way to display an animation/preview + // of such a thing! After all that work... let's skip it. + // (Note that we must do this here because it is in + // getDecorView() where the theme is evaluated... maybe + // we should peek the floating attribute from the theme + // earlier.) + return null; + } + + if (DEBUG_STARTING_WINDOW) Slog.d( + TAG, "Adding starting window for " + packageName + + " / " + appToken + ": " + + (view.getParent() != null ? view : null)); + + wm.addView(view, params); + + // Only return the view if it was successfully added to the + // window manager... which we can tell by it having a parent. + return view.getParent() != null ? view : null; + } catch (WindowManager.BadTokenException e) { + // ignore + Log.w(TAG, appToken + " already running, starting window not displayed. " + + e.getMessage()); + } catch (RuntimeException e) { + // don't crash if something else bad happens, for example a + // failure loading resources because we are loading from an app + // on external storage that has been unmounted. + Log.w(TAG, appToken + " failed creating starting window", e); + } finally { + if (view != null && view.getParent() == null) { + Log.w(TAG, "view not successfully added to wm, removing view"); + wm.removeViewImmediate(view); + } + } + + return null; + } + + /** {@inheritDoc} */ + @Override + public void removeStartingWindow(IBinder appToken, View window) { + if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Removing starting window for " + appToken + ": " + + window + " Callers=" + Debug.getCallers(4)); + + if (window != null) { + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + wm.removeView(window); + } + } + + /** + * Preflight adding a window to the system. + * + * Currently enforces that three window types are singletons: + * <ul> + * <li>STATUS_BAR_TYPE</li> + * <li>KEYGUARD_TYPE</li> + * </ul> + * + * @param win The window to be added + * @param attrs Information about the window to be added + * + * @return If ok, WindowManagerImpl.ADD_OKAY. If too many singletons, + * WindowManagerImpl.ADD_MULTIPLE_SINGLETON + */ + @Override + public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) { + switch (attrs.type) { + case TYPE_STATUS_BAR: + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.STATUS_BAR_SERVICE, + "PhoneWindowManager"); + if (mStatusBar != null) { + if (mStatusBar.isAlive()) { + return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON; + } + } + mStatusBar = win; + mStatusBarController.setWindow(win); + break; + case TYPE_NAVIGATION_BAR: + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.STATUS_BAR_SERVICE, + "PhoneWindowManager"); + if (mNavigationBar != null) { + if (mNavigationBar.isAlive()) { + return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON; + } + } + mNavigationBar = win; + mNavigationBarController.setWindow(win); + if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar); + break; + case TYPE_NAVIGATION_BAR_PANEL: + case TYPE_STATUS_BAR_PANEL: + case TYPE_STATUS_BAR_SUB_PANEL: + case TYPE_VOICE_INTERACTION_STARTING: + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.STATUS_BAR_SERVICE, + "PhoneWindowManager"); + break; + case TYPE_KEYGUARD_SCRIM: + if (mKeyguardScrim != null) { + return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON; + } + mKeyguardScrim = win; + break; + } + return WindowManagerGlobal.ADD_OKAY; + } + + /** {@inheritDoc} */ + @Override + public void removeWindowLw(WindowState win) { + if (mStatusBar == win) { + mStatusBar = null; + mStatusBarController.setWindow(null); + mKeyguardDelegate.showScrim(); + } else if (mKeyguardScrim == win) { + Log.v(TAG, "Removing keyguard scrim"); + mKeyguardScrim = null; + } if (mNavigationBar == win) { + mNavigationBar = null; + mNavigationBarController.setWindow(null); + } + } + + static final boolean PRINT_ANIM = false; + + /** {@inheritDoc} */ + @Override + public int selectAnimationLw(WindowState win, int transit) { + if (PRINT_ANIM) Log.i(TAG, "selectAnimation in " + win + + ": transit=" + transit); + if (win == mStatusBar) { + boolean isKeyguard = (win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0; + if (transit == TRANSIT_EXIT + || transit == TRANSIT_HIDE) { + return isKeyguard ? -1 : R.anim.dock_top_exit; + } else if (transit == TRANSIT_ENTER + || transit == TRANSIT_SHOW) { + return isKeyguard ? -1 : R.anim.dock_top_enter; + } + } else if (win == mNavigationBar) { + // This can be on either the bottom or the right. + if (mNavigationBarOnBottom) { + if (transit == TRANSIT_EXIT + || transit == TRANSIT_HIDE) { + return R.anim.dock_bottom_exit; + } else if (transit == TRANSIT_ENTER + || transit == TRANSIT_SHOW) { + return R.anim.dock_bottom_enter; + } + } else { + if (transit == TRANSIT_EXIT + || transit == TRANSIT_HIDE) { + return R.anim.dock_right_exit; + } else if (transit == TRANSIT_ENTER + || transit == TRANSIT_SHOW) { + return R.anim.dock_right_enter; + } + } + } + + if (transit == TRANSIT_PREVIEW_DONE) { + if (win.hasAppShownWindows()) { + if (PRINT_ANIM) Log.i(TAG, "**** STARTING EXIT"); + return com.android.internal.R.anim.app_starting_exit; + } + } else if (win.getAttrs().type == TYPE_DREAM && mDreamingLockscreen + && transit == TRANSIT_ENTER) { + // Special case: we are animating in a dream, while the keyguard + // is shown. We don't want an animation on the dream, because + // we need it shown immediately with the keyguard animating away + // to reveal it. + return -1; + } + + return 0; + } + + @Override + public void selectRotationAnimationLw(int anim[]) { + if (PRINT_ANIM) Slog.i(TAG, "selectRotationAnimation mTopFullscreen=" + + mTopFullscreenOpaqueWindowState + " rotationAnimation=" + + (mTopFullscreenOpaqueWindowState == null ? + "0" : mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation)); + if (mTopFullscreenOpaqueWindowState != null && mTopIsFullscreen) { + switch (mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation) { + case ROTATION_ANIMATION_CROSSFADE: + anim[0] = R.anim.rotation_animation_xfade_exit; + anim[1] = R.anim.rotation_animation_enter; + break; + case ROTATION_ANIMATION_JUMPCUT: + anim[0] = R.anim.rotation_animation_jump_exit; + anim[1] = R.anim.rotation_animation_enter; + break; + case ROTATION_ANIMATION_ROTATE: + default: + anim[0] = anim[1] = 0; + break; + } + } else { + anim[0] = anim[1] = 0; + } + } + + @Override + public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId, + boolean forceDefault) { + switch (exitAnimId) { + case R.anim.rotation_animation_xfade_exit: + case R.anim.rotation_animation_jump_exit: + // These are the only cases that matter. + if (forceDefault) { + return false; + } + int anim[] = new int[2]; + selectRotationAnimationLw(anim); + return (exitAnimId == anim[0] && enterAnimId == anim[1]); + default: + return true; + } + } + + @Override + public Animation createForceHideEnterAnimation(boolean onWallpaper, + boolean goingToNotificationShade) { + if (goingToNotificationShade) { + return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_behind_enter_fade_in); + } + + AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(mContext, onWallpaper ? + R.anim.lock_screen_behind_enter_wallpaper : + R.anim.lock_screen_behind_enter); + + // TODO: Use XML interpolators when we have log interpolators available in XML. + final List<Animation> animations = set.getAnimations(); + for (int i = animations.size() - 1; i >= 0; --i) { + animations.get(i).setInterpolator(mLogDecelerateInterpolator); + } + + return set; + } + + + @Override + public Animation createForceHideWallpaperExitAnimation(boolean goingToNotificationShade) { + if (goingToNotificationShade) { + return null; + } else { + return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_wallpaper_exit); + } + } + + private static void awakenDreams() { + IDreamManager dreamManager = getDreamManager(); + if (dreamManager != null) { + try { + dreamManager.awaken(); + } catch (RemoteException e) { + // fine, stay asleep then + } + } + } + + static IDreamManager getDreamManager() { + return IDreamManager.Stub.asInterface( + ServiceManager.checkService(DreamService.DREAM_SERVICE)); + } + + TelecomManager getTelecommService() { + return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); + } + + static IAudioService getAudioService() { + IAudioService audioService = IAudioService.Stub.asInterface( + ServiceManager.checkService(Context.AUDIO_SERVICE)); + if (audioService == null) { + Log.w(TAG, "Unable to find IAudioService interface."); + } + return audioService; + } + + boolean keyguardOn() { + return isKeyguardShowingAndNotOccluded() || inKeyguardRestrictedKeyInputMode(); + } + + private static final int[] WINDOW_TYPES_WHERE_HOME_DOESNT_WORK = { + WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, + WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, + }; + + /** {@inheritDoc} */ + @Override + public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) { + final boolean keyguardOn = keyguardOn(); + final int keyCode = event.getKeyCode(); + final int repeatCount = event.getRepeatCount(); + final int metaState = event.getMetaState(); + final int flags = event.getFlags(); + final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + final boolean canceled = event.isCanceled(); + + if (DEBUG_INPUT) { + Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount=" + + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed + + " canceled=" + canceled); + } + + // If we think we might have a volume down & power key chord on the way + // but we're not sure, then tell the dispatcher to wait a little while and + // try again later before dispatching. + if (mScreenshotChordEnabled && (flags & KeyEvent.FLAG_FALLBACK) == 0) { + if (mScreenshotChordVolumeDownKeyTriggered && !mScreenshotChordPowerKeyTriggered) { + final long now = SystemClock.uptimeMillis(); + final long timeoutTime = mScreenshotChordVolumeDownKeyTime + + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS; + if (now < timeoutTime) { + return timeoutTime - now; + } + } + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + && mScreenshotChordVolumeDownKeyConsumed) { + if (!down) { + mScreenshotChordVolumeDownKeyConsumed = false; + } + return -1; + } + } + + // Cancel any pending meta actions if we see any other keys being pressed between the down + // of the meta key and its corresponding up. + if (mPendingMetaAction && !KeyEvent.isMetaKey(keyCode)) { + mPendingMetaAction = false; + } + + // First we always handle the home key here, so applications + // can never break it, although if keyguard is on, we do let + // it handle it, because that gives us the correct 5 second + // timeout. + if (keyCode == KeyEvent.KEYCODE_HOME) { + + // If we have released the home key, and didn't do anything else + // while it was pressed, then it is time to go home! + if (!down) { + cancelPreloadRecentApps(); + + mHomePressed = false; + if (mHomeConsumed) { + mHomeConsumed = false; + return -1; + } + + if (canceled) { + Log.i(TAG, "Ignoring HOME; event canceled."); + return -1; + } + + // If an incoming call is ringing, HOME is totally disabled. + // (The user is already on the InCallUI at this point, + // and his ONLY options are to answer or reject the call.) + TelecomManager telecomManager = getTelecommService(); + if (telecomManager != null && telecomManager.isRinging()) { + Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); + return -1; + } + + // Delay handling home if a double-tap is possible. + if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) { + mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case + mHomeDoubleTapPending = true; + mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable, + ViewConfiguration.getDoubleTapTimeout()); + return -1; + } + + handleShortPressOnHome(); + return -1; + } + + // If a system window has focus, then it doesn't make sense + // right now to interact with applications. + WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null; + if (attrs != null) { + final int type = attrs.type; + if (type == WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM + || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG + || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { + // the "app" is keyguard, so give it the key + return 0; + } + final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length; + for (int i=0; i<typeCount; i++) { + if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) { + // don't do anything, but also don't pass it to the app + return -1; + } + } + } + + // Remember that home is pressed and handle special actions. + if (repeatCount == 0) { + mHomePressed = true; + if (mHomeDoubleTapPending) { + mHomeDoubleTapPending = false; + mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); + handleDoubleTapOnHome(); + } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI + || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) { + preloadRecentApps(); + } + } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { + if (!keyguardOn) { + handleLongPressOnHome(); + } + } + return -1; + } else if (keyCode == KeyEvent.KEYCODE_MENU) { + // Hijack modified menu keys for debugging features + final int chordBug = KeyEvent.META_SHIFT_ON; + + if (down && repeatCount == 0) { + if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, + null, null, null, 0, null, null); + return -1; + } else if (SHOW_PROCESSES_ON_ALT_MENU && + (metaState & KeyEvent.META_ALT_ON) == KeyEvent.META_ALT_ON) { + Intent service = new Intent(); + service.setClassName(mContext, "com.android.server.LoadAverageService"); + ContentResolver res = mContext.getContentResolver(); + boolean shown = Settings.Global.getInt( + res, Settings.Global.SHOW_PROCESSES, 0) != 0; + if (!shown) { + mContext.startService(service); + } else { + mContext.stopService(service); + } + Settings.Global.putInt( + res, Settings.Global.SHOW_PROCESSES, shown ? 0 : 1); + return -1; + } + } + } else if (keyCode == KeyEvent.KEYCODE_SEARCH) { + if (down) { + if (repeatCount == 0) { + mSearchKeyShortcutPending = true; + mConsumeSearchKeyUp = false; + } + } else { + mSearchKeyShortcutPending = false; + if (mConsumeSearchKeyUp) { + mConsumeSearchKeyUp = false; + return -1; + } + } + return 0; + } else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) { + if (!keyguardOn) { + if (down && repeatCount == 0) { + preloadRecentApps(); + } else if (!down) { + toggleRecentApps(); + } + } + return -1; + } else if (keyCode == KeyEvent.KEYCODE_ASSIST) { + if (down) { + if (repeatCount == 0) { + mAssistKeyLongPressed = false; + } else if (repeatCount == 1) { + mAssistKeyLongPressed = true; + if (!keyguardOn) { + launchAssistLongPressAction(); + } + } + } else { + if (mAssistKeyLongPressed) { + mAssistKeyLongPressed = false; + } else { + if (!keyguardOn) { + launchAssistAction(); + } + } + } + return -1; + } else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) { + if (!down) { + Intent voiceIntent; + if (!keyguardOn) { + voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + } else { + voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true); + } + startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF); + } + } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { + if (down && repeatCount == 0) { + mHandler.post(mScreenshotRunnable); + } + return -1; + } else if (keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP + || keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN) { + if (down) { + int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1; + + // Disable autobrightness if it's on + int auto = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, + UserHandle.USER_CURRENT_OR_SELF); + if (auto != 0) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, + UserHandle.USER_CURRENT_OR_SELF); + } + + int min = mPowerManager.getMinimumScreenBrightnessSetting(); + int max = mPowerManager.getMaximumScreenBrightnessSetting(); + int step = (max - min + BRIGHTNESS_STEPS - 1) / BRIGHTNESS_STEPS * direction; + int brightness = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, + mPowerManager.getDefaultScreenBrightnessSetting(), + UserHandle.USER_CURRENT_OR_SELF); + brightness += step; + // Make sure we don't go beyond the limits. + brightness = Math.min(max, brightness); + brightness = Math.max(min, brightness); + + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, brightness, + UserHandle.USER_CURRENT_OR_SELF); + startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), + UserHandle.CURRENT_OR_SELF); + } + return -1; + } else if (KeyEvent.isMetaKey(keyCode)) { + if (down) { + mPendingMetaAction = true; + } else if (mPendingMetaAction) { + launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD); + } + return -1; + } + + // Shortcuts are invoked through Search+key, so intercept those here + // Any printing key that is chorded with Search should be consumed + // even if no shortcut was invoked. This prevents text from being + // inadvertently inserted when using a keyboard that has built-in macro + // shortcut keys (that emit Search+x) and some of them are not registered. + if (mSearchKeyShortcutPending) { + final KeyCharacterMap kcm = event.getKeyCharacterMap(); + if (kcm.isPrintingKey(keyCode)) { + mConsumeSearchKeyUp = true; + mSearchKeyShortcutPending = false; + if (down && repeatCount == 0 && !keyguardOn) { + Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, metaState); + if (shortcutIntent != null) { + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + startActivityAsUser(shortcutIntent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping shortcut key combination because " + + "the activity to which it is registered was not found: " + + "SEARCH+" + KeyEvent.keyCodeToString(keyCode), ex); + } + } else { + Slog.i(TAG, "Dropping unregistered shortcut key combination: " + + "SEARCH+" + KeyEvent.keyCodeToString(keyCode)); + } + } + return -1; + } + } + + // Invoke shortcuts using Meta. + if (down && repeatCount == 0 && !keyguardOn + && (metaState & KeyEvent.META_META_ON) != 0) { + final KeyCharacterMap kcm = event.getKeyCharacterMap(); + if (kcm.isPrintingKey(keyCode)) { + Intent shortcutIntent = mShortcutManager.getIntent(kcm, keyCode, + metaState & ~(KeyEvent.META_META_ON + | KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_RIGHT_ON)); + if (shortcutIntent != null) { + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + startActivityAsUser(shortcutIntent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping shortcut key combination because " + + "the activity to which it is registered was not found: " + + "META+" + KeyEvent.keyCodeToString(keyCode), ex); + } + return -1; + } + } + } + + // Handle application launch keys. + if (down && repeatCount == 0 && !keyguardOn) { + String category = sApplicationLaunchKeyCategories.get(keyCode); + if (category != null) { + Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + startActivityAsUser(intent, UserHandle.CURRENT); + } catch (ActivityNotFoundException ex) { + Slog.w(TAG, "Dropping application launch key because " + + "the activity to which it is registered was not found: " + + "keyCode=" + keyCode + ", category=" + category, ex); + } + return -1; + } + } + + // Display task switcher for ALT-TAB. + if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) { + if (mRecentAppsHeldModifiers == 0 && !keyguardOn) { + final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; + if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) { + mRecentAppsHeldModifiers = shiftlessModifiers; + showRecentApps(true); + return -1; + } + } + } else if (!down && mRecentAppsHeldModifiers != 0 + && (metaState & mRecentAppsHeldModifiers) == 0) { + mRecentAppsHeldModifiers = 0; + hideRecentApps(true, false); + } + + // Handle keyboard language switching. + if (down && repeatCount == 0 + && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH + || (keyCode == KeyEvent.KEYCODE_SPACE + && (metaState & KeyEvent.META_CTRL_MASK) != 0))) { + int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; + mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); + return -1; + } + if (mLanguageSwitchKeyPressed && !down + && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH + || keyCode == KeyEvent.KEYCODE_SPACE)) { + mLanguageSwitchKeyPressed = false; + return -1; + } + + if (isValidGlobalKey(keyCode) + && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { + return -1; + } + + // Reserve all the META modifier combos for system behavior + if ((metaState & KeyEvent.META_META_ON) != 0) { + return -1; + } + + // Let the application handle the key. + return 0; + } + + /** {@inheritDoc} */ + @Override + public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) { + // Note: This method is only called if the initial down was unhandled. + if (DEBUG_INPUT) { + Slog.d(TAG, "Unhandled key: win=" + win + ", action=" + event.getAction() + + ", flags=" + event.getFlags() + + ", keyCode=" + event.getKeyCode() + + ", scanCode=" + event.getScanCode() + + ", metaState=" + event.getMetaState() + + ", repeatCount=" + event.getRepeatCount() + + ", policyFlags=" + policyFlags); + } + + KeyEvent fallbackEvent = null; + if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { + final KeyCharacterMap kcm = event.getKeyCharacterMap(); + final int keyCode = event.getKeyCode(); + final int metaState = event.getMetaState(); + final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0; + + // Check for fallback actions specified by the key character map. + final FallbackAction fallbackAction; + if (initialDown) { + fallbackAction = kcm.getFallbackAction(keyCode, metaState); + } else { + fallbackAction = mFallbackActions.get(keyCode); + } + + if (fallbackAction != null) { + if (DEBUG_INPUT) { + Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode + + " metaState=" + Integer.toHexString(fallbackAction.metaState)); + } + + final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK; + fallbackEvent = KeyEvent.obtain( + event.getDownTime(), event.getEventTime(), + event.getAction(), fallbackAction.keyCode, + event.getRepeatCount(), fallbackAction.metaState, + event.getDeviceId(), event.getScanCode(), + flags, event.getSource(), null); + + if (!interceptFallback(win, fallbackEvent, policyFlags)) { + fallbackEvent.recycle(); + fallbackEvent = null; + } + + if (initialDown) { + mFallbackActions.put(keyCode, fallbackAction); + } else if (event.getAction() == KeyEvent.ACTION_UP) { + mFallbackActions.remove(keyCode); + fallbackAction.recycle(); + } + } + } + + if (DEBUG_INPUT) { + if (fallbackEvent == null) { + Slog.d(TAG, "No fallback."); + } else { + Slog.d(TAG, "Performing fallback: " + fallbackEvent); + } + } + return fallbackEvent; + } + + private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) { + int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags); + if ((actions & ACTION_PASS_TO_USER) != 0) { + long delayMillis = interceptKeyBeforeDispatching( + win, fallbackEvent, policyFlags); + if (delayMillis == 0) { + return true; + } + } + return false; + } + + private void launchAssistLongPressAction() { + performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST); + + // launch the search activity + Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + // TODO: This only stops the factory-installed search manager. + // Need to formalize an API to handle others + SearchManager searchManager = getSearchManager(); + if (searchManager != null) { + searchManager.stopSearch(); + } + startActivityAsUser(intent, UserHandle.CURRENT); + } catch (ActivityNotFoundException e) { + Slog.w(TAG, "No activity to handle assist long press action.", e); + } + } + + private void launchAssistAction() { + launchAssistAction(null); + } + + private void launchAssistAction(String hint) { + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST); + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); + if (intent != null) { + if (hint != null) { + intent.putExtra(hint, true); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + try { + startActivityAsUser(intent, UserHandle.CURRENT); + } catch (ActivityNotFoundException e) { + Slog.w(TAG, "No activity to handle assist action.", e); + } + } + } + + private void startActivityAsUser(Intent intent, UserHandle handle) { + if (isUserSetupComplete()) { + mContext.startActivityAsUser(intent, handle); + } else { + Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent); + } + } + + private SearchManager getSearchManager() { + if (mSearchManager == null) { + mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + } + return mSearchManager; + } + + private void preloadRecentApps() { + mPreloadedRecentApps = true; + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.preloadRecentApps(); + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when preloading recent apps", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + + private void cancelPreloadRecentApps() { + if (mPreloadedRecentApps) { + mPreloadedRecentApps = false; + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.cancelPreloadRecentApps(); + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when cancelling recent apps preload", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + } + + private void toggleRecentApps() { + mPreloadedRecentApps = false; // preloading no longer needs to be canceled + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.toggleRecentApps(); + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when toggling recent apps", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + + @Override + public void showRecentApps() { + mHandler.removeMessages(MSG_DISPATCH_SHOW_RECENTS); + mHandler.sendEmptyMessage(MSG_DISPATCH_SHOW_RECENTS); + } + + private void showRecentApps(boolean triggeredFromAltTab) { + mPreloadedRecentApps = false; // preloading no longer needs to be canceled + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.showRecentApps(triggeredFromAltTab); + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when showing recent apps", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + + private void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHome) { + mPreloadedRecentApps = false; // preloading no longer needs to be canceled + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.hideRecentApps(triggeredFromAltTab, triggeredFromHome); + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when closing recent apps", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + + void launchHomeFromHotKey() { + launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/); + } + + /** + * A home key -> launch home action was detected. Take the appropriate action + * given the situation with the keyguard. + */ + void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) { + if (respectKeyguard) { + if (isKeyguardShowingAndNotOccluded()) { + // don't launch home if keyguard showing + return; + } + + if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) { + // when in keyguard restricted mode, must first verify unlock + // before launching home + mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() { + @Override + public void onKeyguardExitResult(boolean success) { + if (success) { + try { + ActivityManagerNative.getDefault().stopAppSwitches(); + } catch (RemoteException e) { + } + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); + startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); + } + } + }); + return; + } + } + + // no keyguard stuff to worry about, just launch home! + try { + ActivityManagerNative.getDefault().stopAppSwitches(); + } catch (RemoteException e) { + } + if (mRecentsVisible) { + // Hide Recents and notify it to launch Home + if (awakenFromDreams) { + awakenDreams(); + } + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); + hideRecentApps(false, true); + } else { + // Otherwise, just launch Home + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); + startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); + } + } + + private final Runnable mClearHideNavigationFlag = new Runnable() { + @Override + public void run() { + synchronized (mWindowManagerFuncs.getWindowManagerLock()) { + // Clear flags. + mForceClearedSystemUiFlags &= + ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + } + mWindowManagerFuncs.reevaluateStatusBarVisibility(); + } + }; + + /** + * Input handler used while nav bar is hidden. Captures any touch on the screen, + * to determine when the nav bar should be shown and prevent applications from + * receiving those touches. + */ + final class HideNavInputEventReceiver extends InputEventReceiver { + public HideNavInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + + @Override + public void onInputEvent(InputEvent event) { + boolean handled = false; + try { + if (event instanceof MotionEvent + && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + final MotionEvent motionEvent = (MotionEvent)event; + if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { + // When the user taps down, we re-show the nav bar. + boolean changed = false; + synchronized (mWindowManagerFuncs.getWindowManagerLock()) { + // Any user activity always causes us to show the + // navigation controls, if they had been hidden. + // We also clear the low profile and only content + // flags so that tapping on the screen will atomically + // restore all currently hidden screen decorations. + int newVal = mResettingSystemUiFlags | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LOW_PROFILE | + View.SYSTEM_UI_FLAG_FULLSCREEN; + if (mResettingSystemUiFlags != newVal) { + mResettingSystemUiFlags = newVal; + changed = true; + } + // We don't allow the system's nav bar to be hidden + // again for 1 second, to prevent applications from + // spamming us and keeping it from being shown. + newVal = mForceClearedSystemUiFlags | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + if (mForceClearedSystemUiFlags != newVal) { + mForceClearedSystemUiFlags = newVal; + changed = true; + mHandler.postDelayed(mClearHideNavigationFlag, 1000); + } + } + if (changed) { + mWindowManagerFuncs.reevaluateStatusBarVisibility(); + } + } + } + } finally { + finishInputEvent(event, handled); + } + } + } + final InputEventReceiver.Factory mHideNavInputEventReceiverFactory = + new InputEventReceiver.Factory() { + @Override + public InputEventReceiver createInputEventReceiver( + InputChannel inputChannel, Looper looper) { + return new HideNavInputEventReceiver(inputChannel, looper); + } + }; + + @Override + public int adjustSystemUiVisibilityLw(int visibility) { + mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); + mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility); + mRecentsVisible = (visibility & View.RECENT_APPS_VISIBLE) > 0; + + // Reset any bits in mForceClearingStatusBarVisibility that + // are now clear. + mResettingSystemUiFlags &= visibility; + // Clear any bits in the new visibility that are currently being + // force cleared, before reporting it. + return visibility & ~mResettingSystemUiFlags + & ~mForceClearedSystemUiFlags; + } + + @Override + public void getInsetHintLw(WindowManager.LayoutParams attrs, Rect outContentInsets, + Rect outStableInsets) { + final int fl = PolicyControl.getWindowFlags(null, attrs); + final int sysuiVis = PolicyControl.getSystemUiVisibility(null, attrs); + final int systemUiVisibility = (sysuiVis | attrs.subtreeSystemUiVisibility); + + if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) + == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { + int availRight, availBottom; + if (canHideNavigationBar() && + (systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) { + availRight = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; + availBottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + } else { + availRight = mRestrictedScreenLeft + mRestrictedScreenWidth; + availBottom = mRestrictedScreenTop + mRestrictedScreenHeight; + } + if ((systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + if ((fl & FLAG_FULLSCREEN) != 0) { + outContentInsets.set(mStableFullscreenLeft, mStableFullscreenTop, + availRight - mStableFullscreenRight, + availBottom - mStableFullscreenBottom); + } else { + outContentInsets.set(mStableLeft, mStableTop, + availRight - mStableRight, availBottom - mStableBottom); + } + } else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) { + outContentInsets.setEmpty(); + } else if ((systemUiVisibility & (View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) == 0) { + outContentInsets.set(mCurLeft, mCurTop, + availRight - mCurRight, availBottom - mCurBottom); + } else { + outContentInsets.set(mCurLeft, mCurTop, + availRight - mCurRight, availBottom - mCurBottom); + } + + outStableInsets.set(mStableLeft, mStableTop, + availRight - mStableRight, availBottom - mStableBottom); + return; + } + outContentInsets.setEmpty(); + outStableInsets.setEmpty(); + } + + /** {@inheritDoc} */ + @Override + public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight, + int displayRotation) { + final int overscanLeft, overscanTop, overscanRight, overscanBottom; + if (isDefaultDisplay) { + switch (displayRotation) { + case Surface.ROTATION_90: + overscanLeft = mOverscanTop; + overscanTop = mOverscanRight; + overscanRight = mOverscanBottom; + overscanBottom = mOverscanLeft; + break; + case Surface.ROTATION_180: + overscanLeft = mOverscanRight; + overscanTop = mOverscanBottom; + overscanRight = mOverscanLeft; + overscanBottom = mOverscanTop; + break; + case Surface.ROTATION_270: + overscanLeft = mOverscanBottom; + overscanTop = mOverscanLeft; + overscanRight = mOverscanTop; + overscanBottom = mOverscanRight; + break; + default: + overscanLeft = mOverscanLeft; + overscanTop = mOverscanTop; + overscanRight = mOverscanRight; + overscanBottom = mOverscanBottom; + break; + } + } else { + overscanLeft = 0; + overscanTop = 0; + overscanRight = 0; + overscanBottom = 0; + } + mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0; + mOverscanScreenTop = mRestrictedOverscanScreenTop = 0; + mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth; + mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight; + mSystemLeft = 0; + mSystemTop = 0; + mSystemRight = displayWidth; + mSystemBottom = displayHeight; + mUnrestrictedScreenLeft = overscanLeft; + mUnrestrictedScreenTop = overscanTop; + mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight; + mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom; + mRestrictedScreenLeft = mUnrestrictedScreenLeft; + mRestrictedScreenTop = mUnrestrictedScreenTop; + mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth; + mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight; + mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft + = mCurLeft = mUnrestrictedScreenLeft; + mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop + = mCurTop = mUnrestrictedScreenTop; + mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight + = mCurRight = displayWidth - overscanRight; + mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom + = mCurBottom = displayHeight - overscanBottom; + mDockLayer = 0x10000000; + mStatusBarLayer = -1; + + // start with the current dock rect, which will be (0,0,displayWidth,displayHeight) + final Rect pf = mTmpParentFrame; + final Rect df = mTmpDisplayFrame; + final Rect of = mTmpOverscanFrame; + final Rect vf = mTmpVisibleFrame; + final Rect dcf = mTmpDecorFrame; + pf.left = df.left = of.left = vf.left = mDockLeft; + pf.top = df.top = of.top = vf.top = mDockTop; + pf.right = df.right = of.right = vf.right = mDockRight; + pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom; + dcf.setEmpty(); // Decor frame N/A for system bars. + + if (isDefaultDisplay) { + // For purposes of putting out fake window up to steal focus, we will + // drive nav being hidden only by whether it is requested. + final int sysui = mLastSystemUiFlags; + boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; + boolean navTranslucent = (sysui + & (View.NAVIGATION_BAR_TRANSLUCENT | View.SYSTEM_UI_TRANSPARENT)) != 0; + boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0; + boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0; + boolean navAllowedHidden = immersive || immersiveSticky; + navTranslucent &= !immersiveSticky; // transient trumps translucent + boolean isKeyguardShowing = isStatusBarKeyguard() && !mHideLockScreen; + if (!isKeyguardShowing) { + navTranslucent &= areTranslucentBarsAllowed(); + } + + // When the navigation bar isn't visible, we put up a fake + // input window to catch all touch events. This way we can + // detect when the user presses anywhere to bring back the nav + // bar and ensure the application doesn't see the event. + if (navVisible || navAllowedHidden) { + if (mHideNavFakeWindow != null) { + mHideNavFakeWindow.dismiss(); + mHideNavFakeWindow = null; + } + } else if (mHideNavFakeWindow == null) { + mHideNavFakeWindow = mWindowManagerFuncs.addFakeWindow( + mHandler.getLooper(), mHideNavInputEventReceiverFactory, + "hidden nav", WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER, 0, + 0, false, false, true); + } + + // For purposes of positioning and showing the nav bar, if we have + // decided that it can't be hidden (because of the screen aspect ratio), + // then take that into account. + navVisible |= !canHideNavigationBar(); + + boolean updateSysUiVisibility = false; + if (mNavigationBar != null) { + boolean transientNavBarShowing = mNavigationBarController.isTransientShowing(); + // Force the navigation bar to its appropriate place and + // size. We need to do this directly, instead of relying on + // it to bubble up from the nav bar, because this needs to + // change atomically with screen rotations. + mNavigationBarOnBottom = (!mNavigationBarCanMove || displayWidth < displayHeight); + if (mNavigationBarOnBottom) { + // It's a system nav bar or a portrait screen; nav bar goes on bottom. + int top = displayHeight - overscanBottom + - mNavigationBarHeightForRotation[displayRotation]; + mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom); + mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top; + if (transientNavBarShowing) { + mNavigationBarController.setBarShowingLw(true); + } else if (navVisible) { + mNavigationBarController.setBarShowingLw(true); + mDockBottom = mTmpNavigationFrame.top; + mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop; + mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop; + } else { + // We currently want to hide the navigation UI. + mNavigationBarController.setBarShowingLw(false); + } + if (navVisible && !navTranslucent && !navAllowedHidden + && !mNavigationBar.isAnimatingLw() + && !mNavigationBarController.wasRecentlyTranslucent()) { + // If the opaque nav bar is currently requested to be visible, + // and not in the process of animating on or off, then + // we can tell the app that it is covered by it. + mSystemBottom = mTmpNavigationFrame.top; + } + } else { + // Landscape screen; nav bar goes to the right. + int left = displayWidth - overscanRight + - mNavigationBarWidthForRotation[displayRotation]; + mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight); + mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left; + if (transientNavBarShowing) { + mNavigationBarController.setBarShowingLw(true); + } else if (navVisible) { + mNavigationBarController.setBarShowingLw(true); + mDockRight = mTmpNavigationFrame.left; + mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft; + mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft; + } else { + // We currently want to hide the navigation UI. + mNavigationBarController.setBarShowingLw(false); + } + if (navVisible && !navTranslucent && !mNavigationBar.isAnimatingLw() + && !mNavigationBarController.wasRecentlyTranslucent()) { + // If the nav bar is currently requested to be visible, + // and not in the process of animating on or off, then + // we can tell the app that it is covered by it. + mSystemRight = mTmpNavigationFrame.left; + } + } + // Make sure the content and current rectangles are updated to + // account for the restrictions from the navigation bar. + mContentTop = mVoiceContentTop = mCurTop = mDockTop; + mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom; + mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft; + mContentRight = mVoiceContentRight = mCurRight = mDockRight; + mStatusBarLayer = mNavigationBar.getSurfaceLayer(); + // And compute the final frame. + mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame, + mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf, + mTmpNavigationFrame); + if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame); + if (mNavigationBarController.checkHiddenLw()) { + updateSysUiVisibility = true; + } + } + if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)", + mDockLeft, mDockTop, mDockRight, mDockBottom)); + + // decide where the status bar goes ahead of time + if (mStatusBar != null) { + // apply any navigation bar insets + pf.left = df.left = of.left = mUnrestrictedScreenLeft; + pf.top = df.top = of.top = mUnrestrictedScreenTop; + pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft; + pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight + + mUnrestrictedScreenTop; + vf.left = mStableLeft; + vf.top = mStableTop; + vf.right = mStableRight; + vf.bottom = mStableBottom; + + mStatusBarLayer = mStatusBar.getSurfaceLayer(); + + // Let the status bar determine its size. + mStatusBar.computeFrameLw(pf, df, vf, vf, vf, dcf, vf); + + // For layout, the status bar is always at the top with our fixed height. + mStableTop = mUnrestrictedScreenTop + mStatusBarHeight; + + boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0; + boolean statusBarTranslucent = (sysui + & (View.STATUS_BAR_TRANSLUCENT | View.SYSTEM_UI_TRANSPARENT)) != 0; + if (!isKeyguardShowing) { + statusBarTranslucent &= areTranslucentBarsAllowed(); + } + + // If the status bar is hidden, we don't want to cause + // windows behind it to scroll. + if (mStatusBar.isVisibleLw() && !statusBarTransient) { + // Status bar may go away, so the screen area it occupies + // is available to apps but just covering them when the + // status bar is visible. + mDockTop = mUnrestrictedScreenTop + mStatusBarHeight; + + mContentTop = mVoiceContentTop = mCurTop = mDockTop; + mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom; + mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft; + mContentRight = mVoiceContentRight = mCurRight = mDockRight; + + if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + + String.format( + "dock=[%d,%d][%d,%d] content=[%d,%d][%d,%d] cur=[%d,%d][%d,%d]", + mDockLeft, mDockTop, mDockRight, mDockBottom, + mContentLeft, mContentTop, mContentRight, mContentBottom, + mCurLeft, mCurTop, mCurRight, mCurBottom)); + } + if (mStatusBar.isVisibleLw() && !mStatusBar.isAnimatingLw() + && !statusBarTransient && !statusBarTranslucent + && !mStatusBarController.wasRecentlyTranslucent()) { + // If the opaque status bar is currently requested to be visible, + // and not in the process of animating on or off, then + // we can tell the app that it is covered by it. + mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight; + } + if (mStatusBarController.checkHiddenLw()) { + updateSysUiVisibility = true; + } + } + if (updateSysUiVisibility) { + updateSystemUiVisibilityLw(); + } + } + } + + /** {@inheritDoc} */ + @Override + public int getSystemDecorLayerLw() { + if (mStatusBar != null && mStatusBar.isVisibleLw()) { + return mStatusBar.getSurfaceLayer(); + } + + if (mNavigationBar != null && mNavigationBar.isVisibleLw()) { + return mNavigationBar.getSurfaceLayer(); + } + + return 0; + } + + @Override + public void getContentRectLw(Rect r) { + r.set(mContentLeft, mContentTop, mContentRight, mContentBottom); + } + + void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached, + boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf) { + if (win.getSurfaceLayer() > mDockLayer && attached.getSurfaceLayer() < mDockLayer) { + // Here's a special case: if this attached window is a panel that is + // above the dock window, and the window it is attached to is below + // the dock window, then the frames we computed for the window it is + // attached to can not be used because the dock is effectively part + // of the underlying window and the attached window is floating on top + // of the whole thing. So, we ignore the attached window and explicitly + // compute the frames that would be appropriate without the dock. + df.left = of.left = cf.left = vf.left = mDockLeft; + df.top = of.top = cf.top = vf.top = mDockTop; + df.right = of.right = cf.right = vf.right = mDockRight; + df.bottom = of.bottom = cf.bottom = vf.bottom = mDockBottom; + } else { + // The effective display frame of the attached window depends on + // whether it is taking care of insetting its content. If not, + // we need to use the parent's content frame so that the entire + // window is positioned within that content. Otherwise we can use + // the overscan frame and let the attached window take care of + // positioning its content appropriately. + if (adjust != SOFT_INPUT_ADJUST_RESIZE) { + // Set the content frame of the attached window to the parent's decor frame + // (same as content frame when IME isn't present) if specifically requested by + // setting {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR} flag. + // Otherwise, use the overscan frame. + cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0 + ? attached.getContentFrameLw() : attached.getOverscanFrameLw()); + } else { + // If the window is resizing, then we want to base the content + // frame on our attached content frame to resize... however, + // things can be tricky if the attached window is NOT in resize + // mode, in which case its content frame will be larger. + // Ungh. So to deal with that, make sure the content frame + // we end up using is not covering the IM dock. + cf.set(attached.getContentFrameLw()); + if (attached.isVoiceInteraction()) { + if (cf.left < mVoiceContentLeft) cf.left = mVoiceContentLeft; + if (cf.top < mVoiceContentTop) cf.top = mVoiceContentTop; + if (cf.right > mVoiceContentRight) cf.right = mVoiceContentRight; + if (cf.bottom > mVoiceContentBottom) cf.bottom = mVoiceContentBottom; + } else if (attached.getSurfaceLayer() < mDockLayer) { + if (cf.left < mContentLeft) cf.left = mContentLeft; + if (cf.top < mContentTop) cf.top = mContentTop; + if (cf.right > mContentRight) cf.right = mContentRight; + if (cf.bottom > mContentBottom) cf.bottom = mContentBottom; + } + } + df.set(insetDecors ? attached.getDisplayFrameLw() : cf); + of.set(insetDecors ? attached.getOverscanFrameLw() : cf); + vf.set(attached.getVisibleFrameLw()); + } + // The LAYOUT_IN_SCREEN flag is used to determine whether the attached + // window should be positioned relative to its parent or the entire + // screen. + pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 + ? attached.getFrameLw() : df); + } + + private void applyStableConstraints(int sysui, int fl, Rect r) { + if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + // If app is requesting a stable layout, don't let the + // content insets go below the stable values. + if ((fl & FLAG_FULLSCREEN) != 0) { + if (r.left < mStableFullscreenLeft) r.left = mStableFullscreenLeft; + if (r.top < mStableFullscreenTop) r.top = mStableFullscreenTop; + if (r.right > mStableFullscreenRight) r.right = mStableFullscreenRight; + if (r.bottom > mStableFullscreenBottom) r.bottom = mStableFullscreenBottom; + } else { + if (r.left < mStableLeft) r.left = mStableLeft; + if (r.top < mStableTop) r.top = mStableTop; + if (r.right > mStableRight) r.right = mStableRight; + if (r.bottom > mStableBottom) r.bottom = mStableBottom; + } + } + } + + /** {@inheritDoc} */ + @Override + public void layoutWindowLw(WindowState win, WindowState attached) { + // we've already done the status bar + final WindowManager.LayoutParams attrs = win.getAttrs(); + if ((win == mStatusBar && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) == 0) || + win == mNavigationBar) { + return; + } + final boolean isDefaultDisplay = win.isDefaultDisplay(); + final boolean needsToOffsetInputMethodTarget = isDefaultDisplay && + (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null); + if (needsToOffsetInputMethodTarget) { + if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state"); + offsetInputMethodWindowLw(mLastInputMethodWindow); + } + + final int fl = PolicyControl.getWindowFlags(win, attrs); + final int sim = attrs.softInputMode; + final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null); + + final Rect pf = mTmpParentFrame; + final Rect df = mTmpDisplayFrame; + final Rect of = mTmpOverscanFrame; + final Rect cf = mTmpContentFrame; + final Rect vf = mTmpVisibleFrame; + final Rect dcf = mTmpDecorFrame; + final Rect sf = mTmpStableFrame; + dcf.setEmpty(); + + final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar + && mNavigationBar != null && mNavigationBar.isVisibleLw()); + + final int adjust = sim & SOFT_INPUT_MASK_ADJUST; + + if (isDefaultDisplay) { + sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom); + } else { + sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom); + } + + if (!isDefaultDisplay) { + if (attached != null) { + // If this window is attached to another, our display + // frame is the same as the one we are attached to. + setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf); + } else { + // Give the window full screen. + pf.left = df.left = of.left = cf.left = mOverscanScreenLeft; + pf.top = df.top = of.top = cf.top = mOverscanScreenTop; + pf.right = df.right = of.right = cf.right + = mOverscanScreenLeft + mOverscanScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom + = mOverscanScreenTop + mOverscanScreenHeight; + } + } else if (attrs.type == TYPE_INPUT_METHOD) { + pf.left = df.left = of.left = cf.left = vf.left = mDockLeft; + pf.top = df.top = of.top = cf.top = vf.top = mDockTop; + pf.right = df.right = of.right = cf.right = vf.right = mDockRight; + // IM dock windows layout below the nav bar... + pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + // ...with content insets above the nav bar + cf.bottom = vf.bottom = mStableBottom; + // IM dock windows always go to the bottom of the screen. + attrs.gravity = Gravity.BOTTOM; + mDockLayer = win.getSurfaceLayer(); + } else if (attrs.type == TYPE_VOICE_INTERACTION) { + pf.left = df.left = of.left = cf.left = vf.left = mUnrestrictedScreenLeft; + pf.top = df.top = of.top = mUnrestrictedScreenTop; + pf.right = df.right = of.right = cf.right = vf.right = mUnrestrictedScreenLeft + + mUnrestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop + + mUnrestrictedScreenHeight; + cf.bottom = vf.bottom = mStableBottom; + cf.top = vf.top = mStableTop; + } else if (win == mStatusBar && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { + pf.left = df.left = of.left = mUnrestrictedScreenLeft; + pf.top = df.top = of.top = mUnrestrictedScreenTop; + pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft; + pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight + mUnrestrictedScreenTop; + cf.left = vf.left = mStableLeft; + cf.top = vf.top = mStableTop; + cf.right = vf.right = mStableRight; + vf.bottom = mStableBottom; + cf.bottom = mContentBottom; + } else { + + // Default policy decor for the default display + dcf.left = mSystemLeft; + dcf.top = mSystemTop; + dcf.right = mSystemRight; + dcf.bottom = mSystemBottom; + final boolean inheritTranslucentDecor = (attrs.privateFlags + & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0; + final boolean isAppWindow = + attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW && + attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; + final boolean topAtRest = + win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw(); + if (isAppWindow && !inheritTranslucentDecor && !topAtRest) { + if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 + && (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0 + && (fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) == 0 + && (fl & WindowManager.LayoutParams. + FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) { + // Ensure policy decor includes status bar + dcf.top = mStableTop; + } + if ((fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0 + && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0 + && (fl & WindowManager.LayoutParams. + FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) { + // Ensure policy decor includes navigation bar + dcf.bottom = mStableBottom; + dcf.right = mStableRight; + } + } + + if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) + == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { + if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + + "): IN_SCREEN, INSET_DECOR"); + // This is the case for a normal activity window: we want it + // to cover all of the screen space, and it can take care of + // moving its contents to account for screen decorations that + // intrude into that space. + if (attached != null) { + // If this window is attached to another, our display + // frame is the same as the one we are attached to. + setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf); + } else { + if (attrs.type == TYPE_STATUS_BAR_PANEL + || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) { + // Status bar panels are the only windows who can go on top of + // the status bar. They are protected by the STATUS_BAR_SERVICE + // permission, so they have the same privileges as the status + // bar itself. + // + // However, they should still dodge the navigation bar if it exists. + + pf.left = df.left = of.left = hasNavBar + ? mDockLeft : mUnrestrictedScreenLeft; + pf.top = df.top = of.top = mUnrestrictedScreenTop; + pf.right = df.right = of.right = hasNavBar + ? mRestrictedScreenLeft+mRestrictedScreenWidth + : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = hasNavBar + ? mRestrictedScreenTop+mRestrictedScreenHeight + : mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + + if (DEBUG_LAYOUT) Slog.v(TAG, String.format( + "Laying out status bar window: (%d,%d - %d,%d)", + pf.left, pf.top, pf.right, pf.bottom)); + } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0 + && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + // Asking to layout into the overscan region, so give it that pure + // unrestricted area. + pf.left = df.left = of.left = mOverscanScreenLeft; + pf.top = df.top = of.top = mOverscanScreenTop; + pf.right = df.right = of.right = mOverscanScreenLeft + mOverscanScreenWidth; + pf.bottom = df.bottom = of.bottom = mOverscanScreenTop + + mOverscanScreenHeight; + } else if (canHideNavigationBar() + && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0 + && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + // Asking for layout as if the nav bar is hidden, lets the + // application extend into the unrestricted overscan screen area. We + // only do this for application windows to ensure no window that + // can be above the nav bar can do this. + pf.left = df.left = mOverscanScreenLeft; + pf.top = df.top = mOverscanScreenTop; + pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth; + pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight; + // We need to tell the app about where the frame inside the overscan + // is, so it can inset its content by that amount -- it didn't ask + // to actually extend itself into the overscan region. + of.left = mUnrestrictedScreenLeft; + of.top = mUnrestrictedScreenTop; + of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; + of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + } else { + pf.left = df.left = mRestrictedOverscanScreenLeft; + pf.top = df.top = mRestrictedOverscanScreenTop; + pf.right = df.right = mRestrictedOverscanScreenLeft + + mRestrictedOverscanScreenWidth; + pf.bottom = df.bottom = mRestrictedOverscanScreenTop + + mRestrictedOverscanScreenHeight; + // We need to tell the app about where the frame inside the overscan + // is, so it can inset its content by that amount -- it didn't ask + // to actually extend itself into the overscan region. + of.left = mUnrestrictedScreenLeft; + of.top = mUnrestrictedScreenTop; + of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; + of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + } + + if ((fl & FLAG_FULLSCREEN) == 0) { + if (win.isVoiceInteraction()) { + cf.left = mVoiceContentLeft; + cf.top = mVoiceContentTop; + cf.right = mVoiceContentRight; + cf.bottom = mVoiceContentBottom; + } else { + if (adjust != SOFT_INPUT_ADJUST_RESIZE) { + cf.left = mDockLeft; + cf.top = mDockTop; + cf.right = mDockRight; + cf.bottom = mDockBottom; + } else { + cf.left = mContentLeft; + cf.top = mContentTop; + cf.right = mContentRight; + cf.bottom = mContentBottom; + } + } + } else { + // Full screen windows are always given a layout that is as if the + // status bar and other transient decors are gone. This is to avoid + // bad states when moving from a window that is not hding the + // status bar to one that is. + cf.left = mRestrictedScreenLeft; + cf.top = mRestrictedScreenTop; + cf.right = mRestrictedScreenLeft + mRestrictedScreenWidth; + cf.bottom = mRestrictedScreenTop + mRestrictedScreenHeight; + } + applyStableConstraints(sysUiFl, fl, cf); + if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + vf.left = mCurLeft; + vf.top = mCurTop; + vf.right = mCurRight; + vf.bottom = mCurBottom; + } else { + vf.set(cf); + } + } + } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl + & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) { + if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + + "): IN_SCREEN"); + // A window that has requested to fill the entire screen just + // gets everything, period. + if (attrs.type == TYPE_STATUS_BAR_PANEL + || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) { + pf.left = df.left = of.left = cf.left = hasNavBar + ? mDockLeft : mUnrestrictedScreenLeft; + pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop; + pf.right = df.right = of.right = cf.right = hasNavBar + ? mRestrictedScreenLeft+mRestrictedScreenWidth + : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom = hasNavBar + ? mRestrictedScreenTop+mRestrictedScreenHeight + : mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + if (DEBUG_LAYOUT) Slog.v(TAG, String.format( + "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)", + pf.left, pf.top, pf.right, pf.bottom)); + } else if (attrs.type == TYPE_NAVIGATION_BAR + || attrs.type == TYPE_NAVIGATION_BAR_PANEL) { + // The navigation bar has Real Ultimate Power. + pf.left = df.left = of.left = mUnrestrictedScreenLeft; + pf.top = df.top = of.top = mUnrestrictedScreenTop; + pf.right = df.right = of.right = mUnrestrictedScreenLeft + + mUnrestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + + mUnrestrictedScreenHeight; + if (DEBUG_LAYOUT) Slog.v(TAG, String.format( + "Laying out navigation bar window: (%d,%d - %d,%d)", + pf.left, pf.top, pf.right, pf.bottom)); + } else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY + || attrs.type == TYPE_BOOT_PROGRESS) + && ((fl & FLAG_FULLSCREEN) != 0)) { + // Fullscreen secure system overlays get what they ask for. + pf.left = df.left = of.left = cf.left = mOverscanScreenLeft; + pf.top = df.top = of.top = cf.top = mOverscanScreenTop; + pf.right = df.right = of.right = cf.right = mOverscanScreenLeft + + mOverscanScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop + + mOverscanScreenHeight; + } else if (attrs.type == TYPE_BOOT_PROGRESS) { + // Boot progress screen always covers entire display. + pf.left = df.left = of.left = cf.left = mOverscanScreenLeft; + pf.top = df.top = of.top = cf.top = mOverscanScreenTop; + pf.right = df.right = of.right = cf.right = mOverscanScreenLeft + + mOverscanScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop + + mOverscanScreenHeight; + } else if (attrs.type == TYPE_WALLPAPER) { + // The wallpaper also has Real Ultimate Power, but we want to tell + // it about the overscan area. + pf.left = df.left = mOverscanScreenLeft; + pf.top = df.top = mOverscanScreenTop; + pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth; + pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight; + of.left = cf.left = mUnrestrictedScreenLeft; + of.top = cf.top = mUnrestrictedScreenTop; + of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; + of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0 + && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + // Asking to layout into the overscan region, so give it that pure + // unrestricted area. + pf.left = df.left = of.left = cf.left = mOverscanScreenLeft; + pf.top = df.top = of.top = cf.top = mOverscanScreenTop; + pf.right = df.right = of.right = cf.right + = mOverscanScreenLeft + mOverscanScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom + = mOverscanScreenTop + mOverscanScreenHeight; + } else if (canHideNavigationBar() + && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0 + && (attrs.type == TYPE_STATUS_BAR + || attrs.type == TYPE_TOAST + || attrs.type == TYPE_VOICE_INTERACTION_STARTING + || (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) { + // Asking for layout as if the nav bar is hidden, lets the + // application extend into the unrestricted screen area. We + // only do this for application windows (or toasts) to ensure no window that + // can be above the nav bar can do this. + // XXX This assumes that an app asking for this will also + // ask for layout in only content. We can't currently figure out + // what the screen would be if only laying out to hide the nav bar. + pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft; + pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop; + pf.right = df.right = of.right = cf.right = mUnrestrictedScreenLeft + + mUnrestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop + + mUnrestrictedScreenHeight; + } else { + pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft; + pf.top = df.top = of.top = cf.top = mRestrictedScreenTop; + pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft + + mRestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop + + mRestrictedScreenHeight; + } + + applyStableConstraints(sysUiFl, fl, cf); + + if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + vf.left = mCurLeft; + vf.top = mCurTop; + vf.right = mCurRight; + vf.bottom = mCurBottom; + } else { + vf.set(cf); + } + } else if (attached != null) { + if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + + "): attached to " + attached); + // A child window should be placed inside of the same visible + // frame that its parent had. + setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf); + } else { + if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + + "): normal window"); + // Otherwise, a normal window must be placed inside the content + // of all screen decorations. + if (attrs.type == TYPE_STATUS_BAR_PANEL) { + // Status bar panels are the only windows who can go on top of + // the status bar. They are protected by the STATUS_BAR_SERVICE + // permission, so they have the same privileges as the status + // bar itself. + pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft; + pf.top = df.top = of.top = cf.top = mRestrictedScreenTop; + pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft + + mRestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop + + mRestrictedScreenHeight; + } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT + || attrs.type == TYPE_VOLUME_OVERLAY) { + // These dialogs are stable to interim decor changes. + pf.left = df.left = of.left = cf.left = mStableLeft; + pf.top = df.top = of.top = cf.top = mStableTop; + pf.right = df.right = of.right = cf.right = mStableRight; + pf.bottom = df.bottom = of.bottom = cf.bottom = mStableBottom; + } else { + pf.left = mContentLeft; + pf.top = mContentTop; + pf.right = mContentRight; + pf.bottom = mContentBottom; + if (win.isVoiceInteraction()) { + df.left = of.left = cf.left = mVoiceContentLeft; + df.top = of.top = cf.top = mVoiceContentTop; + df.right = of.right = cf.right = mVoiceContentRight; + df.bottom = of.bottom = cf.bottom = mVoiceContentBottom; + } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) { + df.left = of.left = cf.left = mDockLeft; + df.top = of.top = cf.top = mDockTop; + df.right = of.right = cf.right = mDockRight; + df.bottom = of.bottom = cf.bottom = mDockBottom; + } else { + df.left = of.left = cf.left = mContentLeft; + df.top = of.top = cf.top = mContentTop; + df.right = of.right = cf.right = mContentRight; + df.bottom = of.bottom = cf.bottom = mContentBottom; + } + if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + vf.left = mCurLeft; + vf.top = mCurTop; + vf.right = mCurRight; + vf.bottom = mCurBottom; + } else { + vf.set(cf); + } + } + } + } + + // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it. + if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR) { + df.left = df.top = -10000; + df.right = df.bottom = 10000; + if (attrs.type != TYPE_WALLPAPER) { + of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000; + of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000; + } + } + + if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle() + + ": sim=#" + Integer.toHexString(sim) + + " attach=" + attached + " type=" + attrs.type + + String.format(" flags=0x%08x", fl) + + " pf=" + pf.toShortString() + " df=" + df.toShortString() + + " of=" + of.toShortString() + + " cf=" + cf.toShortString() + " vf=" + vf.toShortString() + + " dcf=" + dcf.toShortString() + + " sf=" + sf.toShortString()); + + win.computeFrameLw(pf, df, of, cf, vf, dcf, sf); + + // Dock windows carve out the bottom of the screen, so normal windows + // can't appear underneath them. + if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleOrBehindKeyguardLw() + && !win.getGivenInsetsPendingLw()) { + setLastInputMethodWindowLw(null, null); + offsetInputMethodWindowLw(win); + } + if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleOrBehindKeyguardLw() + && !win.getGivenInsetsPendingLw()) { + offsetVoiceInputWindowLw(win); + } + } + + private void offsetInputMethodWindowLw(WindowState win) { + int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top); + top += win.getGivenContentInsetsLw().top; + if (mContentBottom > top) { + mContentBottom = top; + } + if (mVoiceContentBottom > top) { + mVoiceContentBottom = top; + } + top = win.getVisibleFrameLw().top; + top += win.getGivenVisibleInsetsLw().top; + if (mCurBottom > top) { + mCurBottom = top; + } + if (DEBUG_LAYOUT) Slog.v(TAG, "Input method: mDockBottom=" + + mDockBottom + " mContentBottom=" + + mContentBottom + " mCurBottom=" + mCurBottom); + } + + private void offsetVoiceInputWindowLw(WindowState win) { + int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top); + top += win.getGivenContentInsetsLw().top; + if (mVoiceContentBottom > top) { + mVoiceContentBottom = top; + } + } + + /** {@inheritDoc} */ + @Override + public void finishLayoutLw() { + return; + } + + /** {@inheritDoc} */ + @Override + public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) { + mTopFullscreenOpaqueWindowState = null; + mTopFullscreenOpaqueOrDimmingWindowState = null; + mAppsToBeHidden.clear(); + mAppsThatDismissKeyguard.clear(); + mForceStatusBar = false; + mForceStatusBarFromKeyguard = false; + mForcingShowNavBar = false; + mForcingShowNavBarLayer = -1; + + mHideLockScreen = false; + mAllowLockscreenWhenOn = false; + mDismissKeyguard = DISMISS_KEYGUARD_NONE; + mShowingLockscreen = false; + mShowingDream = false; + mWinShowWhenLocked = null; + mKeyguardSecure = isKeyguardSecure(); + mKeyguardSecureIncludingHidden = mKeyguardSecure + && (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()); + } + + /** {@inheritDoc} */ + @Override + public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs) { + if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisibleOrBehindKeyguardLw=" + + win.isVisibleOrBehindKeyguardLw()); + final int fl = PolicyControl.getWindowFlags(win, attrs); + if (mTopFullscreenOpaqueWindowState == null + && win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD) { + mForcingShowNavBar = true; + mForcingShowNavBarLayer = win.getSurfaceLayer(); + } + if (attrs.type == TYPE_STATUS_BAR && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { + mForceStatusBarFromKeyguard = true; + } + if (mTopFullscreenOpaqueWindowState == null && + win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()) { + if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) { + if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { + mForceStatusBarFromKeyguard = true; + } else { + mForceStatusBar = true; + } + } + if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { + mShowingLockscreen = true; + } + boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW + && attrs.type < FIRST_SYSTEM_WINDOW; + if (attrs.type == TYPE_DREAM) { + // If the lockscreen was showing when the dream started then wait + // for the dream to draw before hiding the lockscreen. + if (!mDreamingLockscreen + || (win.isVisibleLw() && win.hasDrawnLw())) { + mShowingDream = true; + appWindow = true; + } + } + + final boolean showWhenLocked = (fl & FLAG_SHOW_WHEN_LOCKED) != 0; + final boolean dismissKeyguard = (fl & FLAG_DISMISS_KEYGUARD) != 0; + if (appWindow) { + final IApplicationToken appToken = win.getAppToken(); + if (showWhenLocked) { + // Remove any previous windows with the same appToken. + mAppsToBeHidden.remove(appToken); + mAppsThatDismissKeyguard.remove(appToken); + if (mAppsToBeHidden.isEmpty()) { + if (dismissKeyguard && !mKeyguardSecure) { + mAppsThatDismissKeyguard.add(appToken); + } else { + mWinShowWhenLocked = win; + mHideLockScreen = true; + mForceStatusBarFromKeyguard = false; + } + } + } else if (dismissKeyguard) { + if (mKeyguardSecure) { + mAppsToBeHidden.add(appToken); + } else { + mAppsToBeHidden.remove(appToken); + } + mAppsThatDismissKeyguard.add(appToken); + } else { + mAppsToBeHidden.add(appToken); + } + if (attrs.x == 0 && attrs.y == 0 + && attrs.width == WindowManager.LayoutParams.MATCH_PARENT + && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) { + if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win); + mTopFullscreenOpaqueWindowState = win; + if (mTopFullscreenOpaqueOrDimmingWindowState == null) { + mTopFullscreenOpaqueOrDimmingWindowState = win; + } + if (!mAppsThatDismissKeyguard.isEmpty() && + mDismissKeyguard == DISMISS_KEYGUARD_NONE) { + if (DEBUG_LAYOUT) Slog.v(TAG, + "Setting mDismissKeyguard true by win " + win); + mDismissKeyguard = mWinDismissingKeyguard == win ? + DISMISS_KEYGUARD_CONTINUE : DISMISS_KEYGUARD_START; + mWinDismissingKeyguard = win; + mForceStatusBarFromKeyguard = mShowingLockscreen && mKeyguardSecure; + } else if (mAppsToBeHidden.isEmpty() && showWhenLocked) { + if (DEBUG_LAYOUT) Slog.v(TAG, + "Setting mHideLockScreen to true by win " + win); + mHideLockScreen = true; + mForceStatusBarFromKeyguard = false; + } + if ((fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) { + mAllowLockscreenWhenOn = true; + } + } + + if (mWinShowWhenLocked != null && + mWinShowWhenLocked.getAppToken() != win.getAppToken()) { + win.hideLw(false); + } + } + } + if (mTopFullscreenOpaqueOrDimmingWindowState == null + && win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw() + && win.isDimming()) { + mTopFullscreenOpaqueOrDimmingWindowState = win; + } + } + + /** {@inheritDoc} */ + @Override + public int finishPostLayoutPolicyLw() { + if (mWinShowWhenLocked != null && mTopFullscreenOpaqueWindowState != null && + mWinShowWhenLocked.getAppToken() != mTopFullscreenOpaqueWindowState.getAppToken() + && isKeyguardLocked()) { + // A dialog is dismissing the keyguard. Put the wallpaper behind it and hide the + // fullscreen window. + // TODO: Make sure FLAG_SHOW_WALLPAPER is restored when dialog is dismissed. Or not. + mWinShowWhenLocked.getAttrs().flags |= FLAG_SHOW_WALLPAPER; + mTopFullscreenOpaqueWindowState.hideLw(false); + mTopFullscreenOpaqueWindowState = mWinShowWhenLocked; + } + + int changes = 0; + boolean topIsFullscreen = false; + + final WindowManager.LayoutParams lp = (mTopFullscreenOpaqueWindowState != null) + ? mTopFullscreenOpaqueWindowState.getAttrs() + : null; + + // If we are not currently showing a dream then remember the current + // lockscreen state. We will use this to determine whether the dream + // started while the lockscreen was showing and remember this state + // while the dream is showing. + if (!mShowingDream) { + mDreamingLockscreen = mShowingLockscreen; + if (mDreamingSleepTokenNeeded) { + mDreamingSleepTokenNeeded = false; + mHandler.obtainMessage(MSG_UPDATE_DREAMING_SLEEP_TOKEN, 0, 1).sendToTarget(); + } + } else { + if (!mDreamingSleepTokenNeeded) { + mDreamingSleepTokenNeeded = true; + mHandler.obtainMessage(MSG_UPDATE_DREAMING_SLEEP_TOKEN, 1, 1).sendToTarget(); + } + } + + if (mStatusBar != null) { + if (DEBUG_LAYOUT) Slog.i(TAG, "force=" + mForceStatusBar + + " forcefkg=" + mForceStatusBarFromKeyguard + + " top=" + mTopFullscreenOpaqueWindowState); + if (mForceStatusBar || mForceStatusBarFromKeyguard) { + if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced"); + if (mStatusBarController.setBarShowingLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT; + } + // Maintain fullscreen layout until incoming animation is complete. + topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw(); + // Transient status bar on the lockscreen is not allowed + if (mForceStatusBarFromKeyguard && mStatusBarController.isTransientShowing()) { + mStatusBarController.updateVisibilityLw(false /*transientAllowed*/, + mLastSystemUiFlags, mLastSystemUiFlags); + } + } else if (mTopFullscreenOpaqueWindowState != null) { + final int fl = PolicyControl.getWindowFlags(null, lp); + if (localLOGV) { + Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() + + " shown frame: " + mTopFullscreenOpaqueWindowState.getShownFrameLw()); + Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs() + + " lp.flags=0x" + Integer.toHexString(fl)); + } + topIsFullscreen = (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0 + || (mLastSystemUiFlags & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; + // The subtle difference between the window for mTopFullscreenOpaqueWindowState + // and mTopIsFullscreen is that mTopIsFullscreen is set only if the window + // has the FLAG_FULLSCREEN set. Not sure if there is another way that to be the + // case though. + if (mStatusBarController.isTransientShowing()) { + if (mStatusBarController.setBarShowingLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT; + } + } else if (topIsFullscreen) { + if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar"); + if (mStatusBarController.setBarShowingLw(false)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT; + } else { + if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar already hiding"); + } + } else { + if (DEBUG_LAYOUT) Slog.v(TAG, "** SHOWING status bar: top is not fullscreen"); + if (mStatusBarController.setBarShowingLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT; + } + } + } + } + + if (mTopIsFullscreen != topIsFullscreen) { + if (!topIsFullscreen) { + // Force another layout when status bar becomes fully shown. + changes |= FINISH_LAYOUT_REDO_LAYOUT; + } + mTopIsFullscreen = topIsFullscreen; + } + + // Hide the key guard if a visible window explicitly specifies that it wants to be + // displayed when the screen is locked. + if (mKeyguardDelegate != null && mStatusBar != null) { + if (localLOGV) Slog.v(TAG, "finishPostLayoutPolicyLw: mHideKeyguard=" + + mHideLockScreen); + if (mDismissKeyguard != DISMISS_KEYGUARD_NONE && !mKeyguardSecure) { + mKeyguardHidden = true; + if (setKeyguardOccludedLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT + | FINISH_LAYOUT_REDO_CONFIG + | FINISH_LAYOUT_REDO_WALLPAPER; + } + if (mKeyguardDelegate.isShowing()) { + mHandler.post(new Runnable() { + @Override + public void run() { + mKeyguardDelegate.keyguardDone(false, false); + } + }); + } + } else if (mHideLockScreen) { + mKeyguardHidden = true; + if (setKeyguardOccludedLw(true)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT + | FINISH_LAYOUT_REDO_CONFIG + | FINISH_LAYOUT_REDO_WALLPAPER; + } + } else if (mDismissKeyguard != DISMISS_KEYGUARD_NONE) { + // This is the case of keyguard isSecure() and not mHideLockScreen. + if (mDismissKeyguard == DISMISS_KEYGUARD_START) { + // Only launch the next keyguard unlock window once per window. + mKeyguardHidden = false; + if (setKeyguardOccludedLw(false)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT + | FINISH_LAYOUT_REDO_CONFIG + | FINISH_LAYOUT_REDO_WALLPAPER; + } + mHandler.post(new Runnable() { + @Override + public void run() { + mKeyguardDelegate.dismiss(); + } + }); + } + } else { + mWinDismissingKeyguard = null; + mKeyguardHidden = false; + if (setKeyguardOccludedLw(false)) { + changes |= FINISH_LAYOUT_REDO_LAYOUT + | FINISH_LAYOUT_REDO_CONFIG + | FINISH_LAYOUT_REDO_WALLPAPER; + } + } + } + + if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) { + // If the navigation bar has been hidden or shown, we need to do another + // layout pass to update that window. + changes |= FINISH_LAYOUT_REDO_LAYOUT; + } + + // update since mAllowLockscreenWhenOn might have changed + updateLockScreenTimeout(); + return changes; + } + + /** + * Updates the occluded state of the Keyguard. + * + * @return Whether the flags have changed and we have to redo the layout. + */ + private boolean setKeyguardOccludedLw(boolean isOccluded) { + boolean wasOccluded = mKeyguardOccluded; + boolean showing = mKeyguardDelegate.isShowing(); + if (wasOccluded && !isOccluded && showing) { + mKeyguardOccluded = false; + mKeyguardDelegate.setOccluded(false); + mStatusBar.getAttrs().privateFlags |= PRIVATE_FLAG_KEYGUARD; + mStatusBar.getAttrs().flags |= FLAG_SHOW_WALLPAPER; + return true; + } else if (!wasOccluded && isOccluded && showing) { + mKeyguardOccluded = true; + mKeyguardDelegate.setOccluded(true); + mStatusBar.getAttrs().privateFlags &= ~PRIVATE_FLAG_KEYGUARD; + mStatusBar.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER; + return true; + } else { + return false; + } + } + + private boolean isStatusBarKeyguard() { + return mStatusBar != null + && (mStatusBar.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0; + } + + @Override + public boolean allowAppAnimationsLw() { + if (isStatusBarKeyguard() || mShowingDream) { + // If keyguard or dreams is currently visible, no reason to animate behind it. + return false; + } + return true; + } + + @Override + public int focusChangedLw(WindowState lastFocus, WindowState newFocus) { + mFocusedWindow = newFocus; + if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) { + // If the navigation bar has been hidden or shown, we need to do another + // layout pass to update that window. + return FINISH_LAYOUT_REDO_LAYOUT; + } + return 0; + } + + /** {@inheritDoc} */ + @Override + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + // lid changed state + final int newLidState = lidOpen ? LID_OPEN : LID_CLOSED; + if (newLidState == mLidState) { + return; + } + + mLidState = newLidState; + applyLidSwitchState(); + updateRotation(true); + + if (lidOpen) { + wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromLidSwitch); + } else if (!mLidControlsSleep) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + } + } + + @Override + public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) { + int lensCoverState = lensCovered ? CAMERA_LENS_COVERED : CAMERA_LENS_UNCOVERED; + if (mCameraLensCoverState == lensCoverState) { + return; + } + if (mCameraLensCoverState == CAMERA_LENS_COVERED && + lensCoverState == CAMERA_LENS_UNCOVERED) { + Intent intent; + final boolean keyguardActive = mKeyguardDelegate == null ? false : + mKeyguardDelegate.isShowing(); + if (keyguardActive) { + intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE); + } else { + intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); + } + wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromCameraLens); + startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF); + } + mCameraLensCoverState = lensCoverState; + } + + void setHdmiPlugged(boolean plugged) { + if (mHdmiPlugged != plugged) { + mHdmiPlugged = plugged; + updateRotation(true, true); + Intent intent = new Intent(ACTION_HDMI_PLUGGED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.putExtra(EXTRA_HDMI_PLUGGED_STATE, plugged); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + } + + void initializeHdmiState() { + boolean plugged = false; + // watch for HDMI plug messages if the hdmi switch exists + if (new File("/sys/devices/virtual/switch/hdmi/state").exists()) { + mHDMIObserver.startObserving("DEVPATH=/devices/virtual/switch/hdmi"); + + final String filename = "/sys/class/switch/hdmi/state"; + FileReader reader = null; + try { + reader = new FileReader(filename); + char[] buf = new char[15]; + int n = reader.read(buf); + if (n > 1) { + plugged = 0 != Integer.parseInt(new String(buf, 0, n-1)); + } + } catch (IOException ex) { + Slog.w(TAG, "Couldn't read hdmi state from " + filename + ": " + ex); + } catch (NumberFormatException ex) { + Slog.w(TAG, "Couldn't read hdmi state from " + filename + ": " + ex); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException ex) { + } + } + } + } + // This dance forces the code in setHdmiPlugged to run. + // Always do this so the sticky intent is stuck (to false) if there is no hdmi. + mHdmiPlugged = !plugged; + setHdmiPlugged(!mHdmiPlugged); + } + + final Object mScreenshotLock = new Object(); + ServiceConnection mScreenshotConnection = null; + + final Runnable mScreenshotTimeout = new Runnable() { + @Override public void run() { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + } + } + } + }; + + // Assume this is called from the Handler thread. + private void takeScreenshot() { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != null) { + return; + } + ComponentName cn = new ComponentName("com.android.systemui", + "com.android.systemui.screenshot.TakeScreenshotService"); + Intent intent = new Intent(); + intent.setComponent(cn); + ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection != this) { + return; + } + Messenger messenger = new Messenger(service); + Message msg = Message.obtain(null, 1); + final ServiceConnection myConn = this; + Handler h = new Handler(mHandler.getLooper()) { + @Override + public void handleMessage(Message msg) { + synchronized (mScreenshotLock) { + if (mScreenshotConnection == myConn) { + mContext.unbindService(mScreenshotConnection); + mScreenshotConnection = null; + mHandler.removeCallbacks(mScreenshotTimeout); + } + } + } + }; + msg.replyTo = new Messenger(h); + msg.arg1 = msg.arg2 = 0; + if (mStatusBar != null && mStatusBar.isVisibleLw()) + msg.arg1 = 1; + if (mNavigationBar != null && mNavigationBar.isVisibleLw()) + msg.arg2 = 1; + try { + messenger.send(msg); + } catch (RemoteException e) { + } + } + } + @Override + public void onServiceDisconnected(ComponentName name) {} + }; + if (mContext.bindServiceAsUser( + intent, conn, Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { + mScreenshotConnection = conn; + mHandler.postDelayed(mScreenshotTimeout, 10000); + } + } + } + + /** {@inheritDoc} */ + @Override + public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { + if (!mSystemBooted) { + // If we have not yet booted, don't let key events do anything. + return 0; + } + + final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0; + final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + final boolean canceled = event.isCanceled(); + final int keyCode = event.getKeyCode(); + + final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0; + + // If screen is off then we treat the case where the keyguard is open but hidden + // the same as if it were open and in front. + // This will prevent any keys other than the power button from waking the screen + // when the keyguard is hidden by another activity. + final boolean keyguardActive = (mKeyguardDelegate == null ? false : + (interactive ? + isKeyguardShowingAndNotOccluded() : + mKeyguardDelegate.isShowing())); + + if (DEBUG_INPUT) { + Log.d(TAG, "interceptKeyTq keycode=" + keyCode + + " interactive=" + interactive + " keyguardActive=" + keyguardActive + + " policyFlags=" + Integer.toHexString(policyFlags)); + } + + // Basic policy based on interactive state. + int result; + boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0 + || event.isWakeKey(); + if (interactive || (isInjected && !isWakeKey)) { + // When the device is interactive or the key is injected pass the + // key to the application. + result = ACTION_PASS_TO_USER; + isWakeKey = false; + } else if (!interactive && shouldDispatchInputWhenNonInteractive()) { + // If we're currently dozing with the screen on and the keyguard showing, pass the key + // to the application but preserve its wake key status to make sure we still move + // from dozing to fully interactive if we would normally go from off to fully + // interactive. + result = ACTION_PASS_TO_USER; + } else { + // When the screen is off and the key is not injected, determine whether + // to wake the device but don't pass the key to the application. + result = 0; + if (isWakeKey && (!down || !isWakeKeyWhenScreenOff(keyCode))) { + isWakeKey = false; + } + } + + // If the key would be handled globally, just return the result, don't worry about special + // key processing. + if (isValidGlobalKey(keyCode) + && mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) { + if (isWakeKey) { + wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey); + } + return result; + } + + boolean useHapticFeedback = down + && (policyFlags & WindowManagerPolicy.FLAG_VIRTUAL) != 0 + && event.getRepeatCount() == 0; + + // Handle special keys. + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_MUTE: { + if (mUseTvRouting) { + // On TVs volume keys never go to the foreground app + result &= ~ACTION_PASS_TO_USER; + } + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + if (down) { + if (interactive && !mScreenshotChordVolumeDownKeyTriggered + && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { + mScreenshotChordVolumeDownKeyTriggered = true; + mScreenshotChordVolumeDownKeyTime = event.getDownTime(); + mScreenshotChordVolumeDownKeyConsumed = false; + cancelPendingPowerKeyAction(); + interceptScreenshotChord(); + } + } else { + mScreenshotChordVolumeDownKeyTriggered = false; + cancelPendingScreenshotChordAction(); + } + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + if (down) { + if (interactive && !mScreenshotChordVolumeUpKeyTriggered + && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { + mScreenshotChordVolumeUpKeyTriggered = true; + cancelPendingPowerKeyAction(); + cancelPendingScreenshotChordAction(); + } + } else { + mScreenshotChordVolumeUpKeyTriggered = false; + cancelPendingScreenshotChordAction(); + } + } + if (down) { + TelecomManager telecomManager = getTelecommService(); + if (telecomManager != null) { + if (telecomManager.isRinging()) { + // If an incoming call is ringing, either VOLUME key means + // "silence ringer". We handle these keys here, rather than + // in the InCallScreen, to make sure we'll respond to them + // even if the InCallScreen hasn't come to the foreground yet. + // Look for the DOWN event here, to agree with the "fallback" + // behavior in the InCallScreen. + Log.i(TAG, "interceptKeyBeforeQueueing:" + + " VOLUME key-down while ringing: Silence ringer!"); + + // Silence the ringer. (It's safe to call this + // even if the ringer has already been silenced.) + telecomManager.silenceRinger(); + + // And *don't* pass this key thru to the current activity + // (which is probably the InCallScreen.) + result &= ~ACTION_PASS_TO_USER; + break; + } + if (telecomManager.isInCall() + && (result & ACTION_PASS_TO_USER) == 0) { + // If we are in call but we decided not to pass the key to + // the application, just pass it to the session service. + + MediaSessionLegacyHelper.getHelper(mContext) + .sendVolumeKeyEvent(event, false); + break; + } + } + + if ((result & ACTION_PASS_TO_USER) == 0) { + if (mUseTvRouting) { + dispatchDirectAudioEvent(event); + } else { + // If we aren't passing to the user and no one else + // handled it send it to the session manager to + // figure out. + MediaSessionLegacyHelper.getHelper(mContext) + .sendVolumeKeyEvent(event, true); + } + break; + } + } + break; + } + + case KeyEvent.KEYCODE_ENDCALL: { + result &= ~ACTION_PASS_TO_USER; + if (down) { + TelecomManager telecomManager = getTelecommService(); + boolean hungUp = false; + if (telecomManager != null) { + hungUp = telecomManager.endCall(); + } + if (interactive && !hungUp) { + mEndCallKeyHandled = false; + mHandler.postDelayed(mEndCallLongPress, + ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); + } else { + mEndCallKeyHandled = true; + } + } else { + if (!mEndCallKeyHandled) { + mHandler.removeCallbacks(mEndCallLongPress); + if (!canceled) { + if ((mEndcallBehavior + & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0) { + if (goHome()) { + break; + } + } + if ((mEndcallBehavior + & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) { + mPowerManager.goToSleep(event.getEventTime(), + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); + isWakeKey = false; + } + } + } + } + break; + } + + case KeyEvent.KEYCODE_POWER: { + result &= ~ACTION_PASS_TO_USER; + isWakeKey = false; // wake-up will be handled separately + if (down) { + interceptPowerKeyDown(event, interactive); + } else { + interceptPowerKeyUp(event, interactive, canceled); + } + break; + } + + case KeyEvent.KEYCODE_SLEEP: { + result &= ~ACTION_PASS_TO_USER; + isWakeKey = false; + if (!mPowerManager.isInteractive()) { + useHapticFeedback = false; // suppress feedback if already non-interactive + } + sleepPress(event); + break; + } + + case KeyEvent.KEYCODE_WAKEUP: { + result &= ~ACTION_PASS_TO_USER; + isWakeKey = true; + break; + } + + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { + if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) { + // If the global session is active pass all media keys to it + // instead of the active window. + result &= ~ACTION_PASS_TO_USER; + } + if ((result & ACTION_PASS_TO_USER) == 0) { + // Only do this if we would otherwise not pass it to the user. In that + // case, the PhoneWindow class will do the same thing, except it will + // only do it if the showing app doesn't process the key on its own. + // Note that we need to make a copy of the key event here because the + // original key event will be recycled when we return. + mBroadcastWakeLock.acquire(); + Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK, + new KeyEvent(event)); + msg.setAsynchronous(true); + msg.sendToTarget(); + } + break; + } + + case KeyEvent.KEYCODE_CALL: { + if (down) { + TelecomManager telecomManager = getTelecommService(); + if (telecomManager != null) { + if (telecomManager.isRinging()) { + Log.i(TAG, "interceptKeyBeforeQueueing:" + + " CALL key-down while ringing: Answer the call!"); + telecomManager.acceptRingingCall(); + + // And *don't* pass this key thru to the current activity + // (which is presumably the InCallScreen.) + result &= ~ACTION_PASS_TO_USER; + } + } + } + break; + } + case KeyEvent.KEYCODE_VOICE_ASSIST: { + // Only do this if we would otherwise not pass it to the user. In that case, + // interceptKeyBeforeDispatching would apply a similar but different policy in + // order to invoke voice assist actions. Note that we need to make a copy of the + // key event here because the original key event will be recycled when we return. + if ((result & ACTION_PASS_TO_USER) == 0 && !down) { + mBroadcastWakeLock.acquire(); + Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK, + keyguardActive ? 1 : 0, 0); + msg.setAsynchronous(true); + msg.sendToTarget(); + } + } + } + + if (useHapticFeedback) { + performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false); + } + + if (isWakeKey) { + wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey); + } + + return result; + } + + /** + * Returns true if the key can have global actions attached to it. + * We reserve all power management keys for the system since they require + * very careful handling. + */ + private static boolean isValidGlobalKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_POWER: + case KeyEvent.KEYCODE_WAKEUP: + case KeyEvent.KEYCODE_SLEEP: + return false; + default: + return true; + } + } + + /** + * When the screen is off we ignore some keys that might otherwise typically + * be considered wake keys. We filter them out here. + * + * {@link KeyEvent#KEYCODE_POWER} is notably absent from this list because it + * is always considered a wake key. + */ + private boolean isWakeKeyWhenScreenOff(int keyCode) { + switch (keyCode) { + // ignore volume keys unless docked + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_MUTE: + return mDockMode != Intent.EXTRA_DOCK_STATE_UNDOCKED; + + // ignore media and camera keys + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: + case KeyEvent.KEYCODE_CAMERA: + return false; + } + return true; + } + + + /** {@inheritDoc} */ + @Override + public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags) { + if ((policyFlags & FLAG_WAKE) != 0) { + if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion)) { + return 0; + } + } + + if (shouldDispatchInputWhenNonInteractive()) { + return ACTION_PASS_TO_USER; + } + + // If we have not passed the action up and we are in theater mode without dreaming, + // there will be no dream to intercept the touch and wake into ambient. The device should + // wake up in this case. + if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) { + wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming); + } + + return 0; + } + + private boolean shouldDispatchInputWhenNonInteractive() { + // Send events to keyguard while the screen is on. + if (isKeyguardShowingAndNotOccluded() && mDisplay != null + && mDisplay.getState() != Display.STATE_OFF) { + return true; + } + + // Send events to a dozing dream even if the screen is off since the dream + // is in control of the state of the screen. + IDreamManager dreamManager = getDreamManager(); + + try { + if (dreamManager != null && dreamManager.isDreaming()) { + return true; + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when checking if dreaming", e); + } + + // Otherwise, consume events since the user can't see what is being + // interacted with. + return false; + } + + private void dispatchDirectAudioEvent(KeyEvent event) { + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return; + } + int keyCode = event.getKeyCode(); + int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND + | AudioManager.FLAG_FROM_KEY; + String pkgName = mContext.getOpPackageName(); + switch (keyCode) { + case KeyEvent.KEYCODE_VOLUME_UP: + try { + getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE, + AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e); + } + break; + case KeyEvent.KEYCODE_VOLUME_DOWN: + try { + getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER, + AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG); + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e); + } + break; + case KeyEvent.KEYCODE_VOLUME_MUTE: + try { + if (event.getRepeatCount() == 0) { + getAudioService().adjustSuggestedStreamVolume( + AudioManager.ADJUST_TOGGLE_MUTE, + AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG); + } + } catch (RemoteException e) { + Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e); + } + break; + } + } + + void dispatchMediaKeyWithWakeLock(KeyEvent event) { + if (DEBUG_INPUT) { + Slog.d(TAG, "dispatchMediaKeyWithWakeLock: " + event); + } + + if (mHavePendingMediaKeyRepeatWithWakeLock) { + if (DEBUG_INPUT) { + Slog.d(TAG, "dispatchMediaKeyWithWakeLock: canceled repeat"); + } + + mHandler.removeMessages(MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK); + mHavePendingMediaKeyRepeatWithWakeLock = false; + mBroadcastWakeLock.release(); // pending repeat was holding onto the wake lock + } + + dispatchMediaKeyWithWakeLockToAudioService(event); + + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + mHavePendingMediaKeyRepeatWithWakeLock = true; + + Message msg = mHandler.obtainMessage( + MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK, event); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, ViewConfiguration.getKeyRepeatTimeout()); + } else { + mBroadcastWakeLock.release(); + } + } + + void dispatchMediaKeyRepeatWithWakeLock(KeyEvent event) { + mHavePendingMediaKeyRepeatWithWakeLock = false; + + KeyEvent repeatEvent = KeyEvent.changeTimeRepeat(event, + SystemClock.uptimeMillis(), 1, event.getFlags() | KeyEvent.FLAG_LONG_PRESS); + if (DEBUG_INPUT) { + Slog.d(TAG, "dispatchMediaKeyRepeatWithWakeLock: " + repeatEvent); + } + + dispatchMediaKeyWithWakeLockToAudioService(repeatEvent); + mBroadcastWakeLock.release(); + } + + void dispatchMediaKeyWithWakeLockToAudioService(KeyEvent event) { + if (ActivityManagerNative.isSystemReady()) { + MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(event, true); + } + } + + void launchVoiceAssistWithWakeLock(boolean keyguardActive) { + Intent voiceIntent = + new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); + voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, keyguardActive); + startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF); + mBroadcastWakeLock.release(); + } + + BroadcastReceiver mDockReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) { + mDockMode = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + } else { + try { + IUiModeManager uiModeService = IUiModeManager.Stub.asInterface( + ServiceManager.getService(Context.UI_MODE_SERVICE)); + mUiMode = uiModeService.getCurrentModeType(); + } catch (RemoteException e) { + } + } + updateRotation(true); + synchronized (mLock) { + updateOrientationListenerLp(); + } + } + }; + + BroadcastReceiver mDreamReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_DREAMING_STARTED.equals(intent.getAction())) { + if (mKeyguardDelegate != null) { + mKeyguardDelegate.onDreamingStarted(); + } + } else if (Intent.ACTION_DREAMING_STOPPED.equals(intent.getAction())) { + if (mKeyguardDelegate != null) { + mKeyguardDelegate.onDreamingStopped(); + } + } + } + }; + + BroadcastReceiver mMultiuserReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { + // tickle the settings observer: this first ensures that we're + // observing the relevant settings for the newly-active user, + // and then updates our own bookkeeping based on the now- + // current user. + mSettingsObserver.onChange(false); + + // force a re-application of focused window sysui visibility. + // the window may never have been shown for this user + // e.g. the keyguard when going through the new-user setup flow + synchronized (mWindowManagerFuncs.getWindowManagerLock()) { + mLastSystemUiFlags = 0; + updateSystemUiVisibilityLw(); + } + } + } + }; + + private final Runnable mRequestTransientNav = new Runnable() { + @Override + public void run() { + requestTransientBars(mNavigationBar); + } + }; + + private void requestTransientBars(WindowState swipeTarget) { + synchronized (mWindowManagerFuncs.getWindowManagerLock()) { + if (!isUserSetupComplete()) { + // Swipe-up for navigation bar is disabled during setup + return; + } + boolean sb = mStatusBarController.checkShowTransientBarLw(); + boolean nb = mNavigationBarController.checkShowTransientBarLw(); + if (sb || nb) { + // Don't show status bar when swiping on already visible navigation bar + if (!nb && swipeTarget == mNavigationBar) { + if (DEBUG) Slog.d(TAG, "Not showing transient bar, wrong swipe target"); + return; + } + if (sb) mStatusBarController.showTransient(); + if (nb) mNavigationBarController.showTransient(); + mImmersiveModeConfirmation.confirmCurrentPrompt(); + updateSystemUiVisibilityLw(); + } + } + } + + // Called on the PowerManager's Notifier thread. + @Override + public void goingToSleep(int why) { + EventLog.writeEvent(70000, 0); + if (DEBUG_WAKEUP) Slog.i(TAG, "Going to sleep..."); + + // We must get this work done here because the power manager will drop + // the wake lock and let the system suspend once this function returns. + synchronized (mLock) { + mAwake = false; + mKeyguardDrawComplete = false; + updateWakeGestureListenerLp(); + updateOrientationListenerLp(); + updateLockScreenTimeout(); + } + + if (mKeyguardDelegate != null) { + mKeyguardDelegate.onScreenTurnedOff(why); + } + } + + private void wakeUpFromPowerKey(long eventTime) { + wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey); + } + + private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode) { + if (!wakeInTheaterMode && isTheaterModeEnabled()) { + return false; + } + + mPowerManager.wakeUp(wakeTime); + return true; + } + + // Called on the PowerManager's Notifier thread. + @Override + public void wakingUp() { + EventLog.writeEvent(70000, 1); + if (DEBUG_WAKEUP) Slog.i(TAG, "Waking up..."); + + // Since goToSleep performs these functions synchronously, we must + // do the same here. We cannot post this work to a handler because + // that might cause it to become reordered with respect to what + // may happen in a future call to goToSleep. + synchronized (mLock) { + mAwake = true; + mKeyguardDrawComplete = false; + if (mKeyguardDelegate != null) { + mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT); + mHandler.sendEmptyMessageDelayed(MSG_KEYGUARD_DRAWN_TIMEOUT, 1000); + } + + updateWakeGestureListenerLp(); + updateOrientationListenerLp(); + updateLockScreenTimeout(); + } + + if (mKeyguardDelegate != null) { + mKeyguardDelegate.onScreenTurnedOn(mKeyguardDelegateCallback); + // ... eventually calls finishKeyguardDrawn + } else { + if (DEBUG_WAKEUP) Slog.d(TAG, "null mKeyguardDelegate: setting mKeyguardDrawComplete."); + finishKeyguardDrawn(); + } + } + + private void finishKeyguardDrawn() { + synchronized (mLock) { + if (!mAwake || mKeyguardDrawComplete) { + return; // spurious + } + + mKeyguardDrawComplete = true; + if (mKeyguardDelegate != null) { + mHandler.removeMessages(MSG_KEYGUARD_DRAWN_TIMEOUT); + } + } + + finishScreenTurningOn(); + } + + // Called on the DisplayManager's DisplayPowerController thread. + @Override + public void screenTurnedOff() { + if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turned off..."); + + synchronized (mLock) { + mScreenOnEarly = false; + mScreenOnFully = false; + mWindowManagerDrawComplete = false; + mScreenOnListener = null; + updateOrientationListenerLp(); + } + } + + // Called on the DisplayManager's DisplayPowerController thread. + @Override + public void screenTurningOn(final ScreenOnListener screenOnListener) { + if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on..."); + + synchronized (mLock) { + mScreenOnEarly = true; + mScreenOnFully = false; + mWindowManagerDrawComplete = false; + mScreenOnListener = screenOnListener; + updateOrientationListenerLp(); + } + + mWindowManagerInternal.waitForAllWindowsDrawn(mWindowManagerDrawCallback, + WAITING_FOR_DRAWN_TIMEOUT); + // ... eventually calls finishWindowsDrawn + } + + private void finishWindowsDrawn() { + synchronized (mLock) { + if (!mScreenOnEarly || mWindowManagerDrawComplete) { + return; // spurious + } + + mWindowManagerDrawComplete = true; + } + + finishScreenTurningOn(); + } + + private void finishScreenTurningOn() { + final ScreenOnListener listener; + final boolean enableScreen; + synchronized (mLock) { + if (DEBUG_WAKEUP) Slog.d(TAG, + "finishScreenTurningOn: mAwake=" + mAwake + + ", mScreenOnEarly=" + mScreenOnEarly + + ", mScreenOnFully=" + mScreenOnFully + + ", mKeyguardDrawComplete=" + mKeyguardDrawComplete + + ", mWindowManagerDrawComplete=" + mWindowManagerDrawComplete); + + if (mScreenOnFully || !mScreenOnEarly || !mWindowManagerDrawComplete + || (mAwake && !mKeyguardDrawComplete)) { + return; // spurious or not ready yet + } + + if (DEBUG_WAKEUP) Slog.i(TAG, "Finished screen turning on..."); + listener = mScreenOnListener; + mScreenOnListener = null; + mScreenOnFully = true; + + // Remember the first time we draw the keyguard so we know when we're done with + // the main part of booting and can enable the screen and hide boot messages. + if (!mKeyguardDrawnOnce && mAwake) { + mKeyguardDrawnOnce = true; + enableScreen = true; + if (mBootMessageNeedsHiding) { + mBootMessageNeedsHiding = false; + hideBootMessages(); + } + } else { + enableScreen = false; + } + } + + if (listener != null) { + listener.onScreenOn(); + } + + if (enableScreen) { + try { + mWindowManager.enableScreenIfNeeded(); + } catch (RemoteException unhandled) { + } + } + } + + private void handleHideBootMessage() { + synchronized (mLock) { + if (!mKeyguardDrawnOnce) { + mBootMessageNeedsHiding = true; + return; // keyguard hasn't drawn the first time yet, not done booting + } + } + + if (mBootMsgDialog != null) { + if (DEBUG_WAKEUP) Slog.d(TAG, "handleHideBootMessage: dismissing"); + mBootMsgDialog.dismiss(); + mBootMsgDialog = null; + } + } + + @Override + public boolean isScreenOn() { + return mScreenOnFully; + } + + /** {@inheritDoc} */ + @Override + public void enableKeyguard(boolean enabled) { + if (mKeyguardDelegate != null) { + mKeyguardDelegate.setKeyguardEnabled(enabled); + } + } + + /** {@inheritDoc} */ + @Override + public void exitKeyguardSecurely(OnKeyguardExitResult callback) { + if (mKeyguardDelegate != null) { + mKeyguardDelegate.verifyUnlock(callback); + } + } + + private boolean isKeyguardShowingAndNotOccluded() { + if (mKeyguardDelegate == null) return false; + return mKeyguardDelegate.isShowing() && !mKeyguardOccluded; + } + + /** {@inheritDoc} */ + @Override + public boolean isKeyguardLocked() { + return keyguardOn(); + } + + /** {@inheritDoc} */ + @Override + public boolean isKeyguardSecure() { + if (mKeyguardDelegate == null) return false; + return mKeyguardDelegate.isSecure(); + } + + /** {@inheritDoc} */ + @Override + public boolean inKeyguardRestrictedKeyInputMode() { + if (mKeyguardDelegate == null) return false; + return mKeyguardDelegate.isInputRestricted(); + } + + @Override + public void dismissKeyguardLw() { + if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) { + if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.dismissKeyguardLw"); + mHandler.post(new Runnable() { + @Override + public void run() { + // ask the keyguard to prompt the user to authenticate if necessary + mKeyguardDelegate.dismiss(); + } + }); + } + } + + public void notifyActivityDrawnForKeyguardLw() { + if (mKeyguardDelegate != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + mKeyguardDelegate.onActivityDrawn(); + } + }); + } + } + + @Override + public boolean isKeyguardDrawnLw() { + synchronized (mLock) { + return mKeyguardDrawnOnce; + } + } + + @Override + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + if (mKeyguardDelegate != null) { + if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.startKeyguardExitAnimation"); + mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration); + } + } + + void sendCloseSystemWindows() { + PhoneWindow.sendCloseSystemWindows(mContext, null); + } + + void sendCloseSystemWindows(String reason) { + PhoneWindow.sendCloseSystemWindows(mContext, reason); + } + + @Override + public int rotationForOrientationLw(int orientation, int lastRotation) { + if (false) { + Slog.v(TAG, "rotationForOrientationLw(orient=" + + orientation + ", last=" + lastRotation + + "); user=" + mUserRotation + " " + + ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED) + ? "USER_ROTATION_LOCKED" : "") + ); + } + + if (mForceDefaultOrientation) { + return Surface.ROTATION_0; + } + + synchronized (mLock) { + int sensorRotation = mOrientationListener.getProposedRotation(); // may be -1 + if (sensorRotation < 0) { + sensorRotation = lastRotation; + } + + final int preferredRotation; + if (mLidState == LID_OPEN && mLidOpenRotation >= 0) { + // Ignore sensor when lid switch is open and rotation is forced. + preferredRotation = mLidOpenRotation; + } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR + && (mCarDockEnablesAccelerometer || mCarDockRotation >= 0)) { + // Ignore sensor when in car dock unless explicitly enabled. + // This case can override the behavior of NOSENSOR, and can also + // enable 180 degree rotation while docked. + preferredRotation = mCarDockEnablesAccelerometer + ? sensorRotation : mCarDockRotation; + } else if ((mDockMode == Intent.EXTRA_DOCK_STATE_DESK + || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK + || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK) + && (mDeskDockEnablesAccelerometer || mDeskDockRotation >= 0)) { + // Ignore sensor when in desk dock unless explicitly enabled. + // This case can override the behavior of NOSENSOR, and can also + // enable 180 degree rotation while docked. + preferredRotation = mDeskDockEnablesAccelerometer + ? sensorRotation : mDeskDockRotation; + } else if (mHdmiPlugged && mDemoHdmiRotationLock) { + // Ignore sensor when plugged into HDMI when demo HDMI rotation lock enabled. + // Note that the dock orientation overrides the HDMI orientation. + preferredRotation = mDemoHdmiRotation; + } else if (mHdmiPlugged && mDockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED + && mUndockedHdmiRotation >= 0) { + // Ignore sensor when plugged into HDMI and an undocked orientation has + // been specified in the configuration (only for legacy devices without + // full multi-display support). + // Note that the dock orientation overrides the HDMI orientation. + preferredRotation = mUndockedHdmiRotation; + } else if (mDemoRotationLock) { + // Ignore sensor when demo rotation lock is enabled. + // Note that the dock orientation and HDMI rotation lock override this. + preferredRotation = mDemoRotation; + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) { + // Application just wants to remain locked in the last rotation. + preferredRotation = lastRotation; + } else if (!mSupportAutoRotation) { + // If we don't support auto-rotation then bail out here and ignore + // the sensor and any rotation lock settings. + preferredRotation = -1; + } else if ((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE + && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER + || orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE + || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT + || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER)) + || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR + || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR + || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE + || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { + // Otherwise, use sensor only if requested by the application or enabled + // by default for USER or UNSPECIFIED modes. Does not apply to NOSENSOR. + if (mAllowAllRotations < 0) { + // Can't read this during init() because the context doesn't + // have display metrics at that time so we cannot determine + // tablet vs. phone then. + mAllowAllRotations = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_allowAllRotations) ? 1 : 0; + } + if (sensorRotation != Surface.ROTATION_180 + || mAllowAllRotations == 1 + || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR + || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) { + preferredRotation = sensorRotation; + } else { + preferredRotation = lastRotation; + } + } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED + && orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { + // Apply rotation lock. Does not apply to NOSENSOR. + // The idea is that the user rotation expresses a weak preference for the direction + // of gravity and as NOSENSOR is never affected by gravity, then neither should + // NOSENSOR be affected by rotation lock (although it will be affected by docks). + preferredRotation = mUserRotation; + } else { + // No overriding preference. + // We will do exactly what the application asked us to do. + preferredRotation = -1; + } + + switch (orientation) { + case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: + // Return portrait unless overridden. + if (isAnyPortrait(preferredRotation)) { + return preferredRotation; + } + return mPortraitRotation; + + case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE: + // Return landscape unless overridden. + if (isLandscapeOrSeascape(preferredRotation)) { + return preferredRotation; + } + return mLandscapeRotation; + + case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT: + // Return reverse portrait unless overridden. + if (isAnyPortrait(preferredRotation)) { + return preferredRotation; + } + return mUpsideDownRotation; + + case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE: + // Return seascape unless overridden. + if (isLandscapeOrSeascape(preferredRotation)) { + return preferredRotation; + } + return mSeascapeRotation; + + case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE: + case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE: + // Return either landscape rotation. + if (isLandscapeOrSeascape(preferredRotation)) { + return preferredRotation; + } + if (isLandscapeOrSeascape(lastRotation)) { + return lastRotation; + } + return mLandscapeRotation; + + case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT: + case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT: + // Return either portrait rotation. + if (isAnyPortrait(preferredRotation)) { + return preferredRotation; + } + if (isAnyPortrait(lastRotation)) { + return lastRotation; + } + return mPortraitRotation; + + default: + // For USER, UNSPECIFIED, NOSENSOR, SENSOR and FULL_SENSOR, + // just return the preferred orientation we already calculated. + if (preferredRotation >= 0) { + return preferredRotation; + } + return Surface.ROTATION_0; + } + } + } + + @Override + public boolean rotationHasCompatibleMetricsLw(int orientation, int rotation) { + switch (orientation) { + case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: + case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT: + case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT: + return isAnyPortrait(rotation); + + case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE: + case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE: + case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE: + return isLandscapeOrSeascape(rotation); + + default: + return true; + } + } + + @Override + public void setRotationLw(int rotation) { + mOrientationListener.setCurrentRotation(rotation); + } + + private boolean isLandscapeOrSeascape(int rotation) { + return rotation == mLandscapeRotation || rotation == mSeascapeRotation; + } + + private boolean isAnyPortrait(int rotation) { + return rotation == mPortraitRotation || rotation == mUpsideDownRotation; + } + + @Override + public int getUserRotationMode() { + return Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) != 0 ? + WindowManagerPolicy.USER_ROTATION_FREE : + WindowManagerPolicy.USER_ROTATION_LOCKED; + } + + // User rotation: to be used when all else fails in assigning an orientation to the device + @Override + public void setUserRotationMode(int mode, int rot) { + ContentResolver res = mContext.getContentResolver(); + + // mUserRotationMode and mUserRotation will be assigned by the content observer + if (mode == WindowManagerPolicy.USER_ROTATION_LOCKED) { + Settings.System.putIntForUser(res, + Settings.System.USER_ROTATION, + rot, + UserHandle.USER_CURRENT); + Settings.System.putIntForUser(res, + Settings.System.ACCELEROMETER_ROTATION, + 0, + UserHandle.USER_CURRENT); + } else { + Settings.System.putIntForUser(res, + Settings.System.ACCELEROMETER_ROTATION, + 1, + UserHandle.USER_CURRENT); + } + } + + @Override + public void setSafeMode(boolean safeMode) { + mSafeMode = safeMode; + performHapticFeedbackLw(null, safeMode + ? HapticFeedbackConstants.SAFE_MODE_ENABLED + : HapticFeedbackConstants.SAFE_MODE_DISABLED, true); + } + + static long[] getLongIntArray(Resources r, int resid) { + int[] ar = r.getIntArray(resid); + if (ar == null) { + return null; + } + long[] out = new long[ar.length]; + for (int i=0; i<ar.length; i++) { + out[i] = ar[i]; + } + return out; + } + + /** {@inheritDoc} */ + @Override + public void systemReady() { + mKeyguardDelegate = new KeyguardServiceDelegate(mContext); + mKeyguardDelegate.onSystemReady(); + + readCameraLensCoverState(); + updateUiMode(); + synchronized (mLock) { + updateOrientationListenerLp(); + mSystemReady = true; + mHandler.post(new Runnable() { + @Override + public void run() { + updateSettings(); + } + }); + } + } + + /** {@inheritDoc} */ + @Override + public void systemBooted() { + if (mKeyguardDelegate != null) { + mKeyguardDelegate.bindService(mContext); + mKeyguardDelegate.onBootCompleted(); + } + synchronized (mLock) { + mSystemBooted = true; + } + wakingUp(); + screenTurningOn(null); + } + + ProgressDialog mBootMsgDialog = null; + + /** {@inheritDoc} */ + @Override + public void showBootMessage(final CharSequence msg, final boolean always) { + mHandler.post(new Runnable() { + @Override public void run() { + if (mBootMsgDialog == null) { + int theme; + if (mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WATCH)) { + theme = com.android.internal.R.style.Theme_Micro_Dialog_Alert; + } else if (mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TELEVISION)) { + theme = com.android.internal.R.style.Theme_Leanback_Dialog_Alert; + } else { + theme = 0; + } + + mBootMsgDialog = new ProgressDialog(mContext, theme) { + // This dialog will consume all events coming in to + // it, to avoid it trying to do things too early in boot. + @Override public boolean dispatchKeyEvent(KeyEvent event) { + return true; + } + @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { + return true; + } + @Override public boolean dispatchTouchEvent(MotionEvent ev) { + return true; + } + @Override public boolean dispatchTrackballEvent(MotionEvent ev) { + return true; + } + @Override public boolean dispatchGenericMotionEvent(MotionEvent ev) { + return true; + } + @Override public boolean dispatchPopulateAccessibilityEvent( + AccessibilityEvent event) { + return true; + } + }; + if (mContext.getPackageManager().isUpgrade()) { + mBootMsgDialog.setTitle(R.string.android_upgrading_title); + } else { + mBootMsgDialog.setTitle(R.string.android_start_title); + } + mBootMsgDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mBootMsgDialog.setIndeterminate(true); + mBootMsgDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_BOOT_PROGRESS); + mBootMsgDialog.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_DIM_BEHIND + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + mBootMsgDialog.getWindow().setDimAmount(1); + WindowManager.LayoutParams lp = mBootMsgDialog.getWindow().getAttributes(); + lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + mBootMsgDialog.getWindow().setAttributes(lp); + mBootMsgDialog.setCancelable(false); + mBootMsgDialog.show(); + } + mBootMsgDialog.setMessage(msg); + } + }); + } + + /** {@inheritDoc} */ + @Override + public void hideBootMessages() { + mHandler.sendEmptyMessage(MSG_HIDE_BOOT_MESSAGE); + } + + /** {@inheritDoc} */ + @Override + public void userActivity() { + // *************************************** + // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE + // *************************************** + // THIS IS CALLED FROM DEEP IN THE POWER MANAGER + // WITH ITS LOCKS HELD. + // + // This code must be VERY careful about the locks + // it acquires. + // In fact, the current code acquires way too many, + // and probably has lurking deadlocks. + + synchronized (mScreenLockTimeout) { + if (mLockScreenTimerActive) { + // reset the timer + mHandler.removeCallbacks(mScreenLockTimeout); + mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout); + } + } + } + + class ScreenLockTimeout implements Runnable { + Bundle options; + + @Override + public void run() { + synchronized (this) { + if (localLOGV) Log.v(TAG, "mScreenLockTimeout activating keyguard"); + if (mKeyguardDelegate != null) { + mKeyguardDelegate.doKeyguardTimeout(options); + } + mLockScreenTimerActive = false; + options = null; + } + } + + public void setLockOptions(Bundle options) { + this.options = options; + } + } + + ScreenLockTimeout mScreenLockTimeout = new ScreenLockTimeout(); + + @Override + public void lockNow(Bundle options) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + mHandler.removeCallbacks(mScreenLockTimeout); + if (options != null) { + // In case multiple calls are made to lockNow, we don't wipe out the options + // until the runnable actually executes. + mScreenLockTimeout.setLockOptions(options); + } + mHandler.post(mScreenLockTimeout); + } + + private void updateLockScreenTimeout() { + synchronized (mScreenLockTimeout) { + boolean enable = (mAllowLockscreenWhenOn && mAwake && + mKeyguardDelegate != null && mKeyguardDelegate.isSecure()); + if (mLockScreenTimerActive != enable) { + if (enable) { + if (localLOGV) Log.v(TAG, "setting lockscreen timer"); + mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout); + } else { + if (localLOGV) Log.v(TAG, "clearing lockscreen timer"); + mHandler.removeCallbacks(mScreenLockTimeout); + } + mLockScreenTimerActive = enable; + } + } + } + + private void updateDreamingSleepToken(boolean acquire) { + if (acquire) { + if (mDreamingSleepToken == null) { + mDreamingSleepToken = mActivityManagerInternal.acquireSleepToken("Dream"); + } + } else { + if (mDreamingSleepToken != null) { + mDreamingSleepToken.release(); + } + } + } + + /** {@inheritDoc} */ + @Override + public void enableScreenAfterBoot() { + readLidState(); + applyLidSwitchState(); + updateRotation(true); + } + + private void applyLidSwitchState() { + if (mLidState == LID_CLOSED && mLidControlsSleep) { + mPowerManager.goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); + } + + synchronized (mLock) { + updateWakeGestureListenerLp(); + } + } + + void updateUiMode() { + if (mUiModeManager == null) { + mUiModeManager = IUiModeManager.Stub.asInterface( + ServiceManager.getService(Context.UI_MODE_SERVICE)); + } + try { + mUiMode = mUiModeManager.getCurrentModeType(); + } catch (RemoteException e) { + } + } + + void updateRotation(boolean alwaysSendConfiguration) { + try { + //set orientation on WindowManager + mWindowManager.updateRotation(alwaysSendConfiguration, false); + } catch (RemoteException e) { + // Ignore + } + } + + void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) { + try { + //set orientation on WindowManager + mWindowManager.updateRotation(alwaysSendConfiguration, forceRelayout); + } catch (RemoteException e) { + // Ignore + } + } + + /** + * Return an Intent to launch the currently active dock app as home. Returns + * null if the standard home should be launched, which is the case if any of the following is + * true: + * <ul> + * <li>The device is not in either car mode or desk mode + * <li>The device is in car mode but ENABLE_CAR_DOCK_HOME_CAPTURE is false + * <li>The device is in desk mode but ENABLE_DESK_DOCK_HOME_CAPTURE is false + * <li>The device is in car mode but there's no CAR_DOCK app with METADATA_DOCK_HOME + * <li>The device is in desk mode but there's no DESK_DOCK app with METADATA_DOCK_HOME + * </ul> + * @return A dock intent. + */ + Intent createHomeDockIntent() { + Intent intent = null; + + // What home does is based on the mode, not the dock state. That + // is, when in car mode you should be taken to car home regardless + // of whether we are actually in a car dock. + if (mUiMode == Configuration.UI_MODE_TYPE_CAR) { + if (ENABLE_CAR_DOCK_HOME_CAPTURE) { + intent = mCarDockIntent; + } + } else if (mUiMode == Configuration.UI_MODE_TYPE_DESK) { + if (ENABLE_DESK_DOCK_HOME_CAPTURE) { + intent = mDeskDockIntent; + } + } else if (mUiMode == Configuration.UI_MODE_TYPE_WATCH + && (mDockMode == Intent.EXTRA_DOCK_STATE_DESK + || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK + || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK)) { + // Always launch dock home from home when watch is docked, if it exists. + intent = mDeskDockIntent; + } + + if (intent == null) { + return null; + } + + ActivityInfo ai = null; + ResolveInfo info = mContext.getPackageManager().resolveActivityAsUser( + intent, + PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, + mCurrentUserId); + if (info != null) { + ai = info.activityInfo; + } + if (ai != null + && ai.metaData != null + && ai.metaData.getBoolean(Intent.METADATA_DOCK_HOME)) { + intent = new Intent(intent); + intent.setClassName(ai.packageName, ai.name); + return intent; + } + + return null; + } + + void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) { + if (awakenFromDreams) { + awakenDreams(); + } + + Intent dock = createHomeDockIntent(); + if (dock != null) { + try { + if (fromHomeKey) { + dock.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey); + } + startActivityAsUser(dock, UserHandle.CURRENT); + return; + } catch (ActivityNotFoundException e) { + } + } + + Intent intent; + + if (fromHomeKey) { + intent = new Intent(mHomeIntent); + intent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey); + } else { + intent = mHomeIntent; + } + + startActivityAsUser(intent, UserHandle.CURRENT); + } + + /** + * goes to the home screen + * @return whether it did anything + */ + boolean goHome() { + if (!isUserSetupComplete()) { + Slog.i(TAG, "Not going home because user setup is in progress."); + return false; + } + if (false) { + // This code always brings home to the front. + try { + ActivityManagerNative.getDefault().stopAppSwitches(); + } catch (RemoteException e) { + } + sendCloseSystemWindows(); + startDockOrHome(false /*fromHomeKey*/, true /* awakenFromDreams */); + } else { + // This code brings home to the front or, if it is already + // at the front, puts the device to sleep. + try { + if (SystemProperties.getInt("persist.sys.uts-test-mode", 0) == 1) { + /// Roll back EndcallBehavior as the cupcake design to pass P1 lab entry. + Log.d(TAG, "UTS-TEST-MODE"); + } else { + ActivityManagerNative.getDefault().stopAppSwitches(); + sendCloseSystemWindows(); + Intent dock = createHomeDockIntent(); + if (dock != null) { + int result = ActivityManagerNative.getDefault() + .startActivityAsUser(null, null, dock, + dock.resolveTypeIfNeeded(mContext.getContentResolver()), + null, null, 0, + ActivityManager.START_FLAG_ONLY_IF_NEEDED, + null, null, UserHandle.USER_CURRENT); + if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) { + return false; + } + } + } + int result = ActivityManagerNative.getDefault() + .startActivityAsUser(null, null, mHomeIntent, + mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()), + null, null, 0, + ActivityManager.START_FLAG_ONLY_IF_NEEDED, + null, null, UserHandle.USER_CURRENT); + if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) { + return false; + } + } catch (RemoteException ex) { + // bummer, the activity manager, which is in this process, is dead + } + } + return true; + } + + @Override + public void setCurrentOrientationLw(int newOrientation) { + synchronized (mLock) { + if (newOrientation != mCurrentAppOrientation) { + mCurrentAppOrientation = newOrientation; + updateOrientationListenerLp(); + } + } + } + + private void performAuditoryFeedbackForAccessibilityIfNeed() { + if (!isGlobalAccessibilityGestureEnabled()) { + return; + } + AudioManager audioManager = (AudioManager) mContext.getSystemService( + Context.AUDIO_SERVICE); + if (audioManager.isSilentMode()) { + return; + } + Ringtone ringTone = RingtoneManager.getRingtone(mContext, + Settings.System.DEFAULT_NOTIFICATION_URI); + ringTone.setStreamType(AudioManager.STREAM_MUSIC); + ringTone.play(); + } + + private boolean isTheaterModeEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0) == 1; + } + + private boolean isGlobalAccessibilityGestureEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1; + } + + @Override + public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) { + if (!mVibrator.hasVibrator()) { + return false; + } + final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0; + if (hapticsDisabled && !always) { + return false; + } + long[] pattern = null; + switch (effectId) { + case HapticFeedbackConstants.LONG_PRESS: + pattern = mLongPressVibePattern; + break; + case HapticFeedbackConstants.VIRTUAL_KEY: + pattern = mVirtualKeyVibePattern; + break; + case HapticFeedbackConstants.KEYBOARD_TAP: + pattern = mKeyboardTapVibePattern; + break; + case HapticFeedbackConstants.CLOCK_TICK: + pattern = mClockTickVibePattern; + break; + case HapticFeedbackConstants.CALENDAR_DATE: + pattern = mCalendarDateVibePattern; + break; + case HapticFeedbackConstants.SAFE_MODE_DISABLED: + pattern = mSafeModeDisabledVibePattern; + break; + case HapticFeedbackConstants.SAFE_MODE_ENABLED: + pattern = mSafeModeEnabledVibePattern; + break; + default: + return false; + } + int owningUid; + String owningPackage; + if (win != null) { + owningUid = win.getOwningUid(); + owningPackage = win.getOwningPackage(); + } else { + owningUid = android.os.Process.myUid(); + owningPackage = mContext.getOpPackageName(); + } + if (pattern.length == 1) { + // One-shot vibration + mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES); + } else { + // Pattern vibration + mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES); + } + return true; + } + + @Override + public void keepScreenOnStartedLw() { + } + + @Override + public void keepScreenOnStoppedLw() { + if (isKeyguardShowingAndNotOccluded()) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + } + } + + private int updateSystemUiVisibilityLw() { + // If there is no window focused, there will be nobody to handle the events + // anyway, so just hang on in whatever state we're in until things settle down. + final WindowState win = mFocusedWindow != null ? mFocusedWindow + : mTopFullscreenOpaqueWindowState; + if (win == null) { + return 0; + } + if ((win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 && mHideLockScreen == true) { + // We are updating at a point where the keyguard has gotten + // focus, but we were last in a state where the top window is + // hiding it. This is probably because the keyguard as been + // shown while the top window was displayed, so we want to ignore + // it here because this is just a very transient change and it + // will quickly lose focus once it correctly gets hidden. + return 0; + } + + int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null) + & ~mResettingSystemUiFlags + & ~mForceClearedSystemUiFlags; + if (mForcingShowNavBar && win.getSurfaceLayer() < mForcingShowNavBarLayer) { + tmpVisibility &= ~PolicyControl.adjustClearableFlags(win, View.SYSTEM_UI_CLEARABLE_FLAGS); + } + tmpVisibility = updateLightStatusBarLw(tmpVisibility); + final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility); + final int diff = visibility ^ mLastSystemUiFlags; + final boolean needsMenu = win.getNeedsMenuLw(mTopFullscreenOpaqueWindowState); + if (diff == 0 && mLastFocusNeedsMenu == needsMenu + && mFocusedApp == win.getAppToken()) { + return 0; + } + mLastSystemUiFlags = visibility; + mLastFocusNeedsMenu = needsMenu; + mFocusedApp = win.getAppToken(); + mHandler.post(new Runnable() { + @Override + public void run() { + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.setSystemUiVisibility(visibility, 0xffffffff, win.toString()); + statusbar.topAppWindowChanged(needsMenu); + } + } catch (RemoteException e) { + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + }); + return diff; + } + + private int updateLightStatusBarLw(int vis) { + WindowState statusColorWin = isStatusBarKeyguard() && !mHideLockScreen + ? mStatusBar + : mTopFullscreenOpaqueOrDimmingWindowState; + + if (statusColorWin != null) { + if (statusColorWin == mTopFullscreenOpaqueWindowState) { + // If the top fullscreen-or-dimming window is also the top fullscreen, respect + // its light flag. + vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null) + & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else if (statusColorWin != null && statusColorWin.isDimming()) { + // Otherwise if it's dimming, clear the light flag. + vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + } + return vis; + } + + private int updateSystemBarsLw(WindowState win, int oldVis, int vis) { + // apply translucent bar vis flags + WindowState transWin = isStatusBarKeyguard() && !mHideLockScreen + ? mStatusBar + : mTopFullscreenOpaqueWindowState; + vis = mStatusBarController.applyTranslucentFlagLw(transWin, vis, oldVis); + vis = mNavigationBarController.applyTranslucentFlagLw(transWin, vis, oldVis); + + // prevent status bar interaction from clearing certain flags + boolean statusBarHasFocus = win.getAttrs().type == TYPE_STATUS_BAR; + if (statusBarHasFocus && !isStatusBarKeyguard()) { + int flags = View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + if (mHideLockScreen) { + flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT; + } + vis = (vis & ~flags) | (oldVis & flags); + } + + if (!areTranslucentBarsAllowed() && transWin != mStatusBar) { + vis &= ~(View.NAVIGATION_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSLUCENT + | View.SYSTEM_UI_TRANSPARENT); + } + + // update status bar + boolean immersiveSticky = + (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0; + boolean hideStatusBarWM = + mTopFullscreenOpaqueWindowState != null && + (PolicyControl.getWindowFlags(mTopFullscreenOpaqueWindowState, null) + & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; + boolean hideStatusBarSysui = + (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; + boolean hideNavBarSysui = + (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0; + + boolean transientStatusBarAllowed = + mStatusBar != null && ( + hideStatusBarWM + || (hideStatusBarSysui && immersiveSticky) + || statusBarHasFocus); + + boolean transientNavBarAllowed = + mNavigationBar != null && + hideNavBarSysui && immersiveSticky; + + boolean denyTransientStatus = mStatusBarController.isTransientShowRequested() + && !transientStatusBarAllowed && hideStatusBarSysui; + boolean denyTransientNav = mNavigationBarController.isTransientShowRequested() + && !transientNavBarAllowed; + if (denyTransientStatus || denyTransientNav) { + // clear the clearable flags instead + clearClearableFlagsLw(); + vis &= ~View.SYSTEM_UI_CLEARABLE_FLAGS; + } + + vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis); + + // update navigation bar + boolean oldImmersiveMode = isImmersiveMode(oldVis); + boolean newImmersiveMode = isImmersiveMode(vis); + if (win != null && oldImmersiveMode != newImmersiveMode) { + final String pkg = win.getOwningPackage(); + mImmersiveModeConfirmation.immersiveModeChanged(pkg, newImmersiveMode, + isUserSetupComplete()); + } + + vis = mNavigationBarController.updateVisibilityLw(transientNavBarAllowed, oldVis, vis); + + return vis; + } + + private void clearClearableFlagsLw() { + int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS; + if (newVal != mResettingSystemUiFlags) { + mResettingSystemUiFlags = newVal; + mWindowManagerFuncs.reevaluateStatusBarVisibility(); + } + } + + private boolean isImmersiveMode(int vis) { + final int flags = View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + return mNavigationBar != null + && (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 + && (vis & flags) != 0 + && canHideNavigationBar(); + } + + /** + * @return whether the navigation or status bar can be made translucent + * + * This should return true unless touch exploration is not enabled or + * R.boolean.config_enableTranslucentDecor is false. + */ + private boolean areTranslucentBarsAllowed() { + return mTranslucentDecorEnabled + && !mAccessibilityManager.isTouchExplorationEnabled(); + } + + // Use this instead of checking config_showNavigationBar so that it can be consistently + // overridden by qemu.hw.mainkeys in the emulator. + @Override + public boolean hasNavigationBar() { + return mHasNavigationBar; + } + + @Override + public void setLastInputMethodWindowLw(WindowState ime, WindowState target) { + mLastInputMethodWindow = ime; + mLastInputMethodTargetWindow = target; + } + + @Override + public int getInputMethodWindowVisibleHeightLw() { + return mDockBottom - mCurBottom; + } + + @Override + public void setCurrentUserLw(int newUserId) { + mCurrentUserId = newUserId; + if (mKeyguardDelegate != null) { + mKeyguardDelegate.setCurrentUser(newUserId); + } + if (mStatusBarService != null) { + try { + mStatusBarService.setCurrentUser(newUserId); + } catch (RemoteException e) { + // oh well + } + } + setLastInputMethodWindowLw(null, null); + } + + @Override + public boolean canMagnifyWindow(int windowType) { + switch (windowType) { + case WindowManager.LayoutParams.TYPE_INPUT_METHOD: + case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG: + case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR: + case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: { + return false; + } + } + return true; + } + + @Override + public boolean isTopLevelWindow(int windowType) { + if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW + && windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + return (windowType == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG); + } + return true; + } + + @Override + public void dump(String prefix, PrintWriter pw, String[] args) { + pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode); + pw.print(" mSystemReady="); pw.print(mSystemReady); + pw.print(" mSystemBooted="); pw.println(mSystemBooted); + pw.print(prefix); pw.print("mLidState="); pw.print(mLidState); + pw.print(" mLidOpenRotation="); pw.print(mLidOpenRotation); + pw.print(" mCameraLensCoverState="); pw.print(mCameraLensCoverState); + pw.print(" mHdmiPlugged="); pw.println(mHdmiPlugged); + if (mLastSystemUiFlags != 0 || mResettingSystemUiFlags != 0 + || mForceClearedSystemUiFlags != 0) { + pw.print(prefix); pw.print("mLastSystemUiFlags=0x"); + pw.print(Integer.toHexString(mLastSystemUiFlags)); + pw.print(" mResettingSystemUiFlags=0x"); + pw.print(Integer.toHexString(mResettingSystemUiFlags)); + pw.print(" mForceClearedSystemUiFlags=0x"); + pw.println(Integer.toHexString(mForceClearedSystemUiFlags)); + } + if (mLastFocusNeedsMenu) { + pw.print(prefix); pw.print("mLastFocusNeedsMenu="); + pw.println(mLastFocusNeedsMenu); + } + pw.print(prefix); pw.print("mWakeGestureEnabledSetting="); + pw.println(mWakeGestureEnabledSetting); + + pw.print(prefix); pw.print("mSupportAutoRotation="); pw.println(mSupportAutoRotation); + pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode); + pw.print(" mDockMode="); pw.print(mDockMode); + pw.print(" mCarDockRotation="); pw.print(mCarDockRotation); + pw.print(" mDeskDockRotation="); pw.println(mDeskDockRotation); + pw.print(prefix); pw.print("mUserRotationMode="); pw.print(mUserRotationMode); + pw.print(" mUserRotation="); pw.print(mUserRotation); + pw.print(" mAllowAllRotations="); pw.println(mAllowAllRotations); + pw.print(prefix); pw.print("mCurrentAppOrientation="); pw.println(mCurrentAppOrientation); + pw.print(prefix); pw.print("mCarDockEnablesAccelerometer="); + pw.print(mCarDockEnablesAccelerometer); + pw.print(" mDeskDockEnablesAccelerometer="); + pw.println(mDeskDockEnablesAccelerometer); + pw.print(prefix); pw.print("mLidKeyboardAccessibility="); + pw.print(mLidKeyboardAccessibility); + pw.print(" mLidNavigationAccessibility="); pw.print(mLidNavigationAccessibility); + pw.print(" mLidControlsSleep="); pw.println(mLidControlsSleep); + pw.print(prefix); + pw.print("mShortPressOnPowerBehavior="); pw.print(mShortPressOnPowerBehavior); + pw.print(" mLongPressOnPowerBehavior="); pw.println(mLongPressOnPowerBehavior); + pw.print(prefix); + pw.print("mDoublePressOnPowerBehavior="); pw.print(mDoublePressOnPowerBehavior); + pw.print(" mTriplePressOnPowerBehavior="); pw.println(mTriplePressOnPowerBehavior); + pw.print(prefix); pw.print("mHasSoftInput="); pw.println(mHasSoftInput); + pw.print(prefix); pw.print("mAwake="); pw.println(mAwake); + pw.print(prefix); pw.print("mScreenOnEarly="); pw.print(mScreenOnEarly); + pw.print(" mScreenOnFully="); pw.println(mScreenOnFully); + pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete); + pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete); + pw.print(prefix); pw.print("mOrientationSensorEnabled="); + pw.println(mOrientationSensorEnabled); + pw.print(prefix); pw.print("mOverscanScreen=("); pw.print(mOverscanScreenLeft); + pw.print(","); pw.print(mOverscanScreenTop); + pw.print(") "); pw.print(mOverscanScreenWidth); + pw.print("x"); pw.println(mOverscanScreenHeight); + if (mOverscanLeft != 0 || mOverscanTop != 0 + || mOverscanRight != 0 || mOverscanBottom != 0) { + pw.print(prefix); pw.print("mOverscan left="); pw.print(mOverscanLeft); + pw.print(" top="); pw.print(mOverscanTop); + pw.print(" right="); pw.print(mOverscanRight); + pw.print(" bottom="); pw.println(mOverscanBottom); + } + pw.print(prefix); pw.print("mRestrictedOverscanScreen=("); + pw.print(mRestrictedOverscanScreenLeft); + pw.print(","); pw.print(mRestrictedOverscanScreenTop); + pw.print(") "); pw.print(mRestrictedOverscanScreenWidth); + pw.print("x"); pw.println(mRestrictedOverscanScreenHeight); + pw.print(prefix); pw.print("mUnrestrictedScreen=("); pw.print(mUnrestrictedScreenLeft); + pw.print(","); pw.print(mUnrestrictedScreenTop); + pw.print(") "); pw.print(mUnrestrictedScreenWidth); + pw.print("x"); pw.println(mUnrestrictedScreenHeight); + pw.print(prefix); pw.print("mRestrictedScreen=("); pw.print(mRestrictedScreenLeft); + pw.print(","); pw.print(mRestrictedScreenTop); + pw.print(") "); pw.print(mRestrictedScreenWidth); + pw.print("x"); pw.println(mRestrictedScreenHeight); + pw.print(prefix); pw.print("mStableFullscreen=("); pw.print(mStableFullscreenLeft); + pw.print(","); pw.print(mStableFullscreenTop); + pw.print(")-("); pw.print(mStableFullscreenRight); + pw.print(","); pw.print(mStableFullscreenBottom); pw.println(")"); + pw.print(prefix); pw.print("mStable=("); pw.print(mStableLeft); + pw.print(","); pw.print(mStableTop); + pw.print(")-("); pw.print(mStableRight); + pw.print(","); pw.print(mStableBottom); pw.println(")"); + pw.print(prefix); pw.print("mSystem=("); pw.print(mSystemLeft); + pw.print(","); pw.print(mSystemTop); + pw.print(")-("); pw.print(mSystemRight); + pw.print(","); pw.print(mSystemBottom); pw.println(")"); + pw.print(prefix); pw.print("mCur=("); pw.print(mCurLeft); + pw.print(","); pw.print(mCurTop); + pw.print(")-("); pw.print(mCurRight); + pw.print(","); pw.print(mCurBottom); pw.println(")"); + pw.print(prefix); pw.print("mContent=("); pw.print(mContentLeft); + pw.print(","); pw.print(mContentTop); + pw.print(")-("); pw.print(mContentRight); + pw.print(","); pw.print(mContentBottom); pw.println(")"); + pw.print(prefix); pw.print("mVoiceContent=("); pw.print(mVoiceContentLeft); + pw.print(","); pw.print(mVoiceContentTop); + pw.print(")-("); pw.print(mVoiceContentRight); + pw.print(","); pw.print(mVoiceContentBottom); pw.println(")"); + pw.print(prefix); pw.print("mDock=("); pw.print(mDockLeft); + pw.print(","); pw.print(mDockTop); + pw.print(")-("); pw.print(mDockRight); + pw.print(","); pw.print(mDockBottom); pw.println(")"); + pw.print(prefix); pw.print("mDockLayer="); pw.print(mDockLayer); + pw.print(" mStatusBarLayer="); pw.println(mStatusBarLayer); + pw.print(prefix); pw.print("mShowingLockscreen="); pw.print(mShowingLockscreen); + pw.print(" mShowingDream="); pw.print(mShowingDream); + pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen); + pw.print(" mDreamingSleepToken="); pw.println(mDreamingSleepToken); + if (mLastInputMethodWindow != null) { + pw.print(prefix); pw.print("mLastInputMethodWindow="); + pw.println(mLastInputMethodWindow); + } + if (mLastInputMethodTargetWindow != null) { + pw.print(prefix); pw.print("mLastInputMethodTargetWindow="); + pw.println(mLastInputMethodTargetWindow); + } + if (mStatusBar != null) { + pw.print(prefix); pw.print("mStatusBar="); + pw.print(mStatusBar); pw.print(" isStatusBarKeyguard="); + pw.println(isStatusBarKeyguard()); + } + if (mNavigationBar != null) { + pw.print(prefix); pw.print("mNavigationBar="); + pw.println(mNavigationBar); + } + if (mFocusedWindow != null) { + pw.print(prefix); pw.print("mFocusedWindow="); + pw.println(mFocusedWindow); + } + if (mFocusedApp != null) { + pw.print(prefix); pw.print("mFocusedApp="); + pw.println(mFocusedApp); + } + if (mWinDismissingKeyguard != null) { + pw.print(prefix); pw.print("mWinDismissingKeyguard="); + pw.println(mWinDismissingKeyguard); + } + if (mTopFullscreenOpaqueWindowState != null) { + pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState="); + pw.println(mTopFullscreenOpaqueWindowState); + } + if (mTopFullscreenOpaqueOrDimmingWindowState != null) { + pw.print(prefix); pw.print("mTopFullscreenOpaqueOrDimmingWindowState="); + pw.println(mTopFullscreenOpaqueOrDimmingWindowState); + } + if (mForcingShowNavBar) { + pw.print(prefix); pw.print("mForcingShowNavBar="); + pw.println(mForcingShowNavBar); pw.print( "mForcingShowNavBarLayer="); + pw.println(mForcingShowNavBarLayer); + } + pw.print(prefix); pw.print("mTopIsFullscreen="); pw.print(mTopIsFullscreen); + pw.print(" mHideLockScreen="); pw.println(mHideLockScreen); + pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar); + pw.print(" mForceStatusBarFromKeyguard="); + pw.println(mForceStatusBarFromKeyguard); + pw.print(prefix); pw.print("mDismissKeyguard="); pw.print(mDismissKeyguard); + pw.print(" mWinDismissingKeyguard="); pw.print(mWinDismissingKeyguard); + pw.print(" mHomePressed="); pw.println(mHomePressed); + pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.print(mAllowLockscreenWhenOn); + pw.print(" mLockScreenTimeout="); pw.print(mLockScreenTimeout); + pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive); + pw.print(prefix); pw.print("mEndcallBehavior="); pw.print(mEndcallBehavior); + pw.print(" mIncallPowerBehavior="); pw.print(mIncallPowerBehavior); + pw.print(" mLongPressOnHomeBehavior="); pw.println(mLongPressOnHomeBehavior); + pw.print(prefix); pw.print("mLandscapeRotation="); pw.print(mLandscapeRotation); + pw.print(" mSeascapeRotation="); pw.println(mSeascapeRotation); + pw.print(prefix); pw.print("mPortraitRotation="); pw.print(mPortraitRotation); + pw.print(" mUpsideDownRotation="); pw.println(mUpsideDownRotation); + pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation); + pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock); + pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation); + + mGlobalKeyManager.dump(prefix, pw); + mStatusBarController.dump(pw, prefix); + mNavigationBarController.dump(pw, prefix); + PolicyControl.dump(prefix, pw); + + if (mWakeGestureListener != null) { + mWakeGestureListener.dump(pw, prefix); + } + if (mOrientationListener != null) { + mOrientationListener.dump(pw, prefix); + } + if (mBurnInProtectionHelper != null) { + mBurnInProtectionHelper.dump(prefix, pw); + } + } +} diff --git a/services/core/java/com/android/server/policy/PolicyControl.java b/services/core/java/com/android/server/policy/PolicyControl.java new file mode 100644 index 0000000..dbafc42 --- /dev/null +++ b/services/core/java/com/android/server/policy/PolicyControl.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2014 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.policy; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.ArraySet; +import android.util.Slog; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.view.WindowManagerPolicy.WindowState; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Runtime adjustments applied to the global window policy. + * + * This includes forcing immersive mode behavior for one or both system bars (based on a package + * list) and permanently disabling immersive mode confirmations for specific packages. + * + * Control by setting {@link Settings.Global.POLICY_CONTROL} to one or more name-value pairs. + * e.g. + * to force immersive mode everywhere: + * "immersive.full=*" + * to force transient status for all apps except a specific package: + * "immersive.status=apps,-com.package" + * to disable the immersive mode confirmations for specific packages: + * "immersive.preconfirms=com.package.one,com.package.two" + * + * Separate multiple name-value pairs with ':' + * e.g. "immersive.status=apps:immersive.preconfirms=*" + */ +public class PolicyControl { + private static String TAG = "PolicyControl"; + private static boolean DEBUG = false; + + private static final String NAME_IMMERSIVE_FULL = "immersive.full"; + private static final String NAME_IMMERSIVE_STATUS = "immersive.status"; + private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation"; + private static final String NAME_IMMERSIVE_PRECONFIRMATIONS = "immersive.preconfirms"; + + private static String sSettingValue; + private static Filter sImmersivePreconfirmationsFilter; + private static Filter sImmersiveStatusFilter; + private static Filter sImmersiveNavigationFilter; + + public static int getSystemUiVisibility(WindowState win, LayoutParams attrs) { + attrs = attrs != null ? attrs : win.getAttrs(); + int vis = win != null ? win.getSystemUiVisibility() : attrs.systemUiVisibility; + if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { + vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.STATUS_BAR_TRANSLUCENT); + } + if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) { + vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.NAVIGATION_BAR_TRANSLUCENT); + } + return vis; + } + + public static int getWindowFlags(WindowState win, LayoutParams attrs) { + attrs = attrs != null ? attrs : win.getAttrs(); + int flags = attrs.flags; + if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { + flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + flags &= ~(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS + | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + } + if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) { + flags &= ~WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; + } + return flags; + } + + public static int adjustClearableFlags(WindowState win, int clearableFlags) { + final LayoutParams attrs = win != null ? win.getAttrs() : null; + if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) { + clearableFlags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN; + } + return clearableFlags; + } + + public static boolean disableImmersiveConfirmation(String pkg) { + return (sImmersivePreconfirmationsFilter != null + && sImmersivePreconfirmationsFilter.matches(pkg)) + || ActivityManager.isRunningInTestHarness(); + } + + public static void reloadFromSetting(Context context) { + if (DEBUG) Slog.d(TAG, "reloadFromSetting()"); + String value = null; + try { + value = Settings.Global.getStringForUser(context.getContentResolver(), + Settings.Global.POLICY_CONTROL, + UserHandle.USER_CURRENT); + if (sSettingValue != null && sSettingValue.equals(value)) return; + setFilters(value); + sSettingValue = value; + } catch (Throwable t) { + Slog.w(TAG, "Error loading policy control, value=" + value, t); + } + } + + public static void dump(String prefix, PrintWriter pw) { + dump("sImmersiveStatusFilter", sImmersiveStatusFilter, prefix, pw); + dump("sImmersiveNavigationFilter", sImmersiveNavigationFilter, prefix, pw); + dump("sImmersivePreconfirmationsFilter", sImmersivePreconfirmationsFilter, prefix, pw); + } + + private static void dump(String name, Filter filter, String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("PolicyControl."); pw.print(name); pw.print('='); + if (filter == null) { + pw.println("null"); + } else { + filter.dump(pw); pw.println(); + } + } + + private static void setFilters(String value) { + if (DEBUG) Slog.d(TAG, "setFilters: " + value); + sImmersiveStatusFilter = null; + sImmersiveNavigationFilter = null; + sImmersivePreconfirmationsFilter = null; + if (value != null) { + String[] nvps = value.split(":"); + for (String nvp : nvps) { + int i = nvp.indexOf('='); + if (i == -1) continue; + String n = nvp.substring(0, i); + String v = nvp.substring(i + 1); + if (n.equals(NAME_IMMERSIVE_FULL)) { + Filter f = Filter.parse(v); + sImmersiveStatusFilter = sImmersiveNavigationFilter = f; + if (sImmersivePreconfirmationsFilter == null) { + sImmersivePreconfirmationsFilter = f; + } + } else if (n.equals(NAME_IMMERSIVE_STATUS)) { + Filter f = Filter.parse(v); + sImmersiveStatusFilter = f; + } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) { + Filter f = Filter.parse(v); + sImmersiveNavigationFilter = f; + if (sImmersivePreconfirmationsFilter == null) { + sImmersivePreconfirmationsFilter = f; + } + } else if (n.equals(NAME_IMMERSIVE_PRECONFIRMATIONS)) { + Filter f = Filter.parse(v); + sImmersivePreconfirmationsFilter = f; + } + } + } + if (DEBUG) { + Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter); + Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter); + Slog.d(TAG, "immersivePreconfirmationsFilter: " + sImmersivePreconfirmationsFilter); + } + } + + private static class Filter { + private static final String ALL = "*"; + private static final String APPS = "apps"; + + private final ArraySet<String> mWhitelist; + private final ArraySet<String> mBlacklist; + + private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) { + mWhitelist = whitelist; + mBlacklist = blacklist; + } + + boolean matches(LayoutParams attrs) { + if (attrs == null) return false; + boolean isApp = attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; + if (isApp && mBlacklist.contains(APPS)) return false; + if (onBlacklist(attrs.packageName)) return false; + if (isApp && mWhitelist.contains(APPS)) return true; + return onWhitelist(attrs.packageName); + } + + boolean matches(String packageName) { + return !onBlacklist(packageName) && onWhitelist(packageName); + } + + private boolean onBlacklist(String packageName) { + return mBlacklist.contains(packageName) || mBlacklist.contains(ALL); + } + + private boolean onWhitelist(String packageName) { + return mWhitelist.contains(ALL) || mWhitelist.contains(packageName); + } + + void dump(PrintWriter pw) { + pw.print("Filter["); + dump("whitelist", mWhitelist, pw); pw.print(','); + dump("blacklist", mBlacklist, pw); pw.print(']'); + } + + private void dump(String name, ArraySet<String> set, PrintWriter pw) { + pw.print(name); pw.print("=("); + final int n = set.size(); + for (int i = 0; i < n; i++) { + if (i > 0) pw.print(','); + pw.print(set.valueAt(i)); + } + pw.print(')'); + } + + @Override + public String toString() { + StringWriter sw = new StringWriter(); + dump(new PrintWriter(sw, true)); + return sw.toString(); + } + + // value = comma-delimited list of tokens, where token = (package name|apps|*) + // e.g. "com.package1", or "apps, com.android.keyguard" or "*" + static Filter parse(String value) { + if (value == null) return null; + ArraySet<String> whitelist = new ArraySet<String>(); + ArraySet<String> blacklist = new ArraySet<String>(); + for (String token : value.split(",")) { + token = token.trim(); + if (token.startsWith("-") && token.length() > 1) { + token = token.substring(1); + blacklist.add(token); + } else { + whitelist.add(token); + } + } + return new Filter(whitelist, blacklist); + } + } +} diff --git a/services/core/java/com/android/server/policy/RecentApplicationsBackground.java b/services/core/java/com/android/server/policy/RecentApplicationsBackground.java new file mode 100644 index 0000000..694a110 --- /dev/null +++ b/services/core/java/com/android/server/policy/RecentApplicationsBackground.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2010 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.policy; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.LinearLayout; + +/** + * A vertical linear layout. However, instead of drawing the background + * behnd the items, it draws the background outside the items based on the + * padding. If there isn't enough room to draw both, it clips the background + * instead of the contents. + */ +public class RecentApplicationsBackground extends LinearLayout { + private static final String TAG = "RecentApplicationsBackground"; + + private boolean mBackgroundSizeChanged; + private Drawable mBackground; + private Rect mTmp0 = new Rect(); + private Rect mTmp1 = new Rect(); + + public RecentApplicationsBackground(Context context) { + this(context, null); + init(); + } + + public RecentApplicationsBackground(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + mBackground = getBackground(); + setBackgroundDrawable(null); + setPadding(0, 0, 0, 0); + setGravity(Gravity.CENTER); + } + + @Override + protected boolean setFrame(int left, int top, int right, int bottom) { + setWillNotDraw(false); + if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { + mBackgroundSizeChanged = true; + } + return super.setFrame(left, top, right, bottom); + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return who == mBackground || super.verifyDrawable(who); + } + + @Override + public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + if (mBackground != null) mBackground.jumpToCurrentState(); + } + + @Override + protected void drawableStateChanged() { + Drawable d = mBackground; + if (d != null && d.isStateful()) { + d.setState(getDrawableState()); + } + super.drawableStateChanged(); + } + + @Override + public void draw(Canvas canvas) { + final Drawable background = mBackground; + if (background != null) { + if (mBackgroundSizeChanged) { + mBackgroundSizeChanged = false; + Rect chld = mTmp0; + Rect bkg = mTmp1; + mBackground.getPadding(bkg); + getChildBounds(chld); + // This doesn't clamp to this view's bounds, which is what we want, + // so that the drawing is clipped. + final int top = chld.top - bkg.top; + final int bottom = chld.bottom + bkg.bottom; + // The background here is a gradient that wants to + // extend the full width of the screen (whatever that + // may be). + int left, right; + if (false) { + // This limits the width of the drawable. + left = chld.left - bkg.left; + right = chld.right + bkg.right; + } else { + // This expands it to full width. + left = 0; + right = getRight(); + } + background.setBounds(left, top, right, bottom); + } + } + mBackground.draw(canvas); + + if (false) { + android.graphics.Paint p = new android.graphics.Paint(); + p.setColor(0x88ffff00); + canvas.drawRect(background.getBounds(), p); + } + canvas.drawARGB((int)(0.75*0xff), 0, 0, 0); + + super.draw(canvas); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mBackground.setCallback(this); + setWillNotDraw(false); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mBackground.setCallback(null); + } + + private void getChildBounds(Rect r) { + r.left = r.top = Integer.MAX_VALUE; + r.bottom = r.right = Integer.MIN_VALUE; + final int N = getChildCount(); + for (int i=0; i<N; i++) { + View v = getChildAt(i); + if (v.getVisibility() == View.VISIBLE) { + r.left = Math.min(r.left, v.getLeft()); + r.top = Math.min(r.top, v.getTop()); + r.right = Math.max(r.right, v.getRight()); + r.bottom = Math.max(r.bottom, v.getBottom()); + } + } + } +} diff --git a/services/core/java/com/android/server/policy/ShortcutManager.java b/services/core/java/com/android/server/policy/ShortcutManager.java new file mode 100644 index 0000000..76f56bc --- /dev/null +++ b/services/core/java/com/android/server/policy/ShortcutManager.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.XmlResourceParser; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; +import android.view.KeyCharacterMap; +import com.android.internal.util.XmlUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * Manages quick launch shortcuts by: + * <li> Keeping the local copy in sync with the database (this is an observer) + * <li> Returning a shortcut-matching intent to clients + */ +class ShortcutManager { + private static final String TAG = "ShortcutManager"; + + private static final String TAG_BOOKMARKS = "bookmarks"; + private static final String TAG_BOOKMARK = "bookmark"; + + private static final String ATTRIBUTE_PACKAGE = "package"; + private static final String ATTRIBUTE_CLASS = "class"; + private static final String ATTRIBUTE_SHORTCUT = "shortcut"; + private static final String ATTRIBUTE_CATEGORY = "category"; + + private final SparseArray<ShortcutInfo> mShortcuts = new SparseArray<>(); + + private final Context mContext; + + public ShortcutManager(Context context) { + mContext = context; + loadShortcuts(); + } + + /** + * Gets the shortcut intent for a given keycode+modifier. Make sure you + * strip whatever modifier is used for invoking shortcuts (for example, + * if 'Sym+A' should invoke a shortcut on 'A', you should strip the + * 'Sym' bit from the modifiers before calling this method. + * <p> + * This will first try an exact match (with modifiers), and then try a + * match without modifiers (primary character on a key). + * + * @param kcm The key character map of the device on which the key was pressed. + * @param keyCode The key code. + * @param metaState The meta state, omitting any modifiers that were used + * to invoke the shortcut. + * @return The intent that matches the shortcut, or null if not found. + */ + public Intent getIntent(KeyCharacterMap kcm, int keyCode, int metaState) { + ShortcutInfo shortcut = null; + + // First try the exact keycode (with modifiers). + int shortcutChar = kcm.get(keyCode, metaState); + if (shortcutChar != 0) { + shortcut = mShortcuts.get(shortcutChar); + } + + // Next try the primary character on that key. + if (shortcut == null) { + shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode)); + if (shortcutChar != 0) { + shortcut = mShortcuts.get(shortcutChar); + } + } + + return (shortcut != null) ? shortcut.intent : null; + } + + private void loadShortcuts() { + PackageManager packageManager = mContext.getPackageManager(); + try { + XmlResourceParser parser = mContext.getResources().getXml( + com.android.internal.R.xml.bookmarks); + XmlUtils.beginDocument(parser, TAG_BOOKMARKS); + + while (true) { + XmlUtils.nextElement(parser); + + if (parser.getEventType() == XmlPullParser.END_DOCUMENT) { + break; + } + + if (!TAG_BOOKMARK.equals(parser.getName())) { + break; + } + + String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE); + String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS); + String shortcutName = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT); + String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY); + + if (TextUtils.isEmpty(shortcutName)) { + Log.w(TAG, "Unable to get shortcut for: " + packageName + "/" + className); + continue; + } + + final int shortcutChar = shortcutName.charAt(0); + + final Intent intent; + final String title; + if (packageName != null && className != null) { + ActivityInfo info = null; + ComponentName componentName = new ComponentName(packageName, className); + try { + info = packageManager.getActivityInfo(componentName, 0); + } catch (PackageManager.NameNotFoundException e) { + String[] packages = packageManager.canonicalToCurrentPackageNames( + new String[] { packageName }); + componentName = new ComponentName(packages[0], className); + try { + info = packageManager.getActivityInfo(componentName, 0); + } catch (PackageManager.NameNotFoundException e1) { + Log.w(TAG, "Unable to add bookmark: " + packageName + + "/" + className, e); + continue; + } + } + + intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(componentName); + title = info.loadLabel(packageManager).toString(); + } else if (categoryName != null) { + intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName); + title = ""; + } else { + Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutName + + ": missing package/class or category attributes"); + continue; + } + + ShortcutInfo shortcut = new ShortcutInfo(title, intent); + mShortcuts.put(shortcutChar, shortcut); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Got exception parsing bookmarks.", e); + } catch (IOException e) { + Log.w(TAG, "Got exception parsing bookmarks.", e); + } + } + + private static final class ShortcutInfo { + public final String title; + public final Intent intent; + + public ShortcutInfo(String title, Intent intent) { + this.title = title; + this.intent = intent; + } + } +} diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java new file mode 100644 index 0000000..d1b50da --- /dev/null +++ b/services/core/java/com/android/server/policy/StatusBarController.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2015 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.policy; + +import android.app.StatusBarManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Slog; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.Interpolator; +import android.view.animation.TranslateAnimation; + +import com.android.internal.statusbar.IStatusBarService; + +import static android.view.WindowManagerInternal.*; + +/** + * Implements status bar specific behavior. + */ +public class StatusBarController extends BarController { + + private static final long TRANSITION_DURATION = 120L; + + private final AppTransitionListener mAppTransitionListener + = new AppTransitionListener() { + + @Override + public void onAppTransitionPendingLocked() { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.appTransitionPending(); + } + } catch (RemoteException e) { + Slog.e(mTag, "RemoteException when app transition is pending", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + }); + } + + @Override + public void onAppTransitionStartingLocked(IBinder openToken, IBinder closeToken, + final Animation openAnimation, final Animation closeAnimation) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + long startTime = calculateStatusBarTransitionStartTime(openAnimation, + closeAnimation); + statusbar.appTransitionStarting(startTime, TRANSITION_DURATION); + } + } catch (RemoteException e) { + Slog.e(mTag, "RemoteException when app transition is starting", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + }); + } + + @Override + public void onAppTransitionCancelledLocked() { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.appTransitionCancelled(); + } + } catch (RemoteException e) { + Slog.e(mTag, "RemoteException when app transition is cancelled", e); + // re-acquire status bar service next time it is needed. + mStatusBarService = null; + } + } + }); + } + }; + + public StatusBarController() { + super("StatusBar", + View.STATUS_BAR_TRANSIENT, + View.STATUS_BAR_UNHIDE, + View.STATUS_BAR_TRANSLUCENT, + StatusBarManager.WINDOW_STATUS_BAR, + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + + public AppTransitionListener getAppTransitionListener() { + return mAppTransitionListener; + } + + /** + * For a given app transition with {@code openAnimation} and {@code closeAnimation}, this + * calculates the timings for the corresponding status bar transition. + * + * @return the desired start time of the status bar transition, in uptime millis + */ + private long calculateStatusBarTransitionStartTime(Animation openAnimation, + Animation closeAnimation) { + if (openAnimation != null && closeAnimation != null) { + TranslateAnimation openTranslateAnimation = findTranslateAnimation(openAnimation); + TranslateAnimation closeTranslateAnimation = findTranslateAnimation(closeAnimation); + if (openTranslateAnimation != null) { + + // Some interpolators are extremely quickly mostly finished, but not completely. For + // our purposes, we need to find the fraction for which ther interpolator is mostly + // there, and use that value for the calculation. + float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator()); + return SystemClock.uptimeMillis() + + openTranslateAnimation.getStartOffset() + + (long)(openTranslateAnimation.getDuration()*t) - TRANSITION_DURATION; + } else if (closeTranslateAnimation != null) { + return SystemClock.uptimeMillis(); + } else { + return SystemClock.uptimeMillis(); + } + } else { + return SystemClock.uptimeMillis(); + } + } + + /** + * Tries to find a {@link TranslateAnimation} inside the {@code animation}. + * + * @return the found animation, {@code null} otherwise + */ + private TranslateAnimation findTranslateAnimation(Animation animation) { + if (animation instanceof TranslateAnimation) { + return (TranslateAnimation) animation; + } else if (animation instanceof AnimationSet) { + AnimationSet set = (AnimationSet) animation; + for (int i = 0; i < set.getAnimations().size(); i++) { + Animation a = set.getAnimations().get(i); + if (a instanceof TranslateAnimation) { + return (TranslateAnimation) a; + } + } + } + return null; + } + + /** + * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which + * {@code interpolator(t + eps) > 0.99}. + */ + private float findAlmostThereFraction(Interpolator interpolator) { + float val = 0.5f; + float adj = 0.25f; + while (adj >= 0.01f) { + if (interpolator.getInterpolation(val) < 0.99f) { + val += adj; + } else { + val -= adj; + } + adj /= 2; + } + return val; + } +} diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java new file mode 100644 index 0000000..627b328 --- /dev/null +++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java @@ -0,0 +1,201 @@ +/* + * 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.policy; + +import android.content.Context; +import android.util.Slog; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy.PointerEventListener; + +/* + * Listens for system-wide input gestures, firing callbacks when detected. + * @hide + */ +public class SystemGesturesPointerEventListener implements PointerEventListener { + private static final String TAG = "SystemGestures"; + private static final boolean DEBUG = false; + private static final long SWIPE_TIMEOUT_MS = 500; + private static final int MAX_TRACKED_POINTERS = 32; // max per input system + private static final int UNTRACKED_POINTER = -1; + + private static final int SWIPE_NONE = 0; + private static final int SWIPE_FROM_TOP = 1; + private static final int SWIPE_FROM_BOTTOM = 2; + private static final int SWIPE_FROM_RIGHT = 3; + + private final int mSwipeStartThreshold; + private final int mSwipeDistanceThreshold; + private final Callbacks mCallbacks; + private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS]; + private final float[] mDownX = new float[MAX_TRACKED_POINTERS]; + private final float[] mDownY = new float[MAX_TRACKED_POINTERS]; + private final long[] mDownTime = new long[MAX_TRACKED_POINTERS]; + + int screenHeight; + int screenWidth; + private int mDownPointers; + private boolean mSwipeFireable; + private boolean mDebugFireable; + + public SystemGesturesPointerEventListener(Context context, Callbacks callbacks) { + mCallbacks = checkNull("callbacks", callbacks); + mSwipeStartThreshold = checkNull("context", context).getResources() + .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + mSwipeDistanceThreshold = mSwipeStartThreshold; + if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold + + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold); + } + + private static <T> T checkNull(String name, T arg) { + if (arg == null) { + throw new IllegalArgumentException(name + " must not be null"); + } + return arg; + } + + @Override + public void onPointerEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mSwipeFireable = true; + mDebugFireable = true; + mDownPointers = 0; + captureDown(event, 0); + mCallbacks.onDown(); + break; + case MotionEvent.ACTION_POINTER_DOWN: + captureDown(event, event.getActionIndex()); + if (mDebugFireable) { + mDebugFireable = event.getPointerCount() < 5; + if (!mDebugFireable) { + if (DEBUG) Slog.d(TAG, "Firing debug"); + mCallbacks.onDebug(); + } + } + break; + case MotionEvent.ACTION_MOVE: + if (mSwipeFireable) { + final int swipe = detectSwipe(event); + mSwipeFireable = swipe == SWIPE_NONE; + if (swipe == SWIPE_FROM_TOP) { + if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop"); + mCallbacks.onSwipeFromTop(); + } else if (swipe == SWIPE_FROM_BOTTOM) { + if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom"); + mCallbacks.onSwipeFromBottom(); + } else if (swipe == SWIPE_FROM_RIGHT) { + if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight"); + mCallbacks.onSwipeFromRight(); + } + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mSwipeFireable = false; + mDebugFireable = false; + mCallbacks.onUpOrCancel(); + break; + default: + if (DEBUG) Slog.d(TAG, "Ignoring " + event); + } + } + + private void captureDown(MotionEvent event, int pointerIndex) { + final int pointerId = event.getPointerId(pointerIndex); + final int i = findIndex(pointerId); + if (DEBUG) Slog.d(TAG, "pointer " + pointerId + + " down pointerIndex=" + pointerIndex + " trackingIndex=" + i); + if (i != UNTRACKED_POINTER) { + mDownX[i] = event.getX(pointerIndex); + mDownY[i] = event.getY(pointerIndex); + mDownTime[i] = event.getEventTime(); + if (DEBUG) Slog.d(TAG, "pointer " + pointerId + + " down x=" + mDownX[i] + " y=" + mDownY[i]); + } + } + + private int findIndex(int pointerId) { + for (int i = 0; i < mDownPointers; i++) { + if (mDownPointerId[i] == pointerId) { + return i; + } + } + if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) { + return UNTRACKED_POINTER; + } + mDownPointerId[mDownPointers++] = pointerId; + return mDownPointers - 1; + } + + private int detectSwipe(MotionEvent move) { + final int historySize = move.getHistorySize(); + final int pointerCount = move.getPointerCount(); + for (int p = 0; p < pointerCount; p++) { + final int pointerId = move.getPointerId(p); + final int i = findIndex(pointerId); + if (i != UNTRACKED_POINTER) { + for (int h = 0; h < historySize; h++) { + final long time = move.getHistoricalEventTime(h); + final float x = move.getHistoricalX(p, h); + final float y = move.getHistoricalY(p, h); + final int swipe = detectSwipe(i, time, x, y); + if (swipe != SWIPE_NONE) { + return swipe; + } + } + final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p)); + if (swipe != SWIPE_NONE) { + return swipe; + } + } + } + return SWIPE_NONE; + } + + private int detectSwipe(int i, long time, float x, float y) { + final float fromX = mDownX[i]; + final float fromY = mDownY[i]; + final long elapsed = time - mDownTime[i]; + if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i] + + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed); + if (fromY <= mSwipeStartThreshold + && y > fromY + mSwipeDistanceThreshold + && elapsed < SWIPE_TIMEOUT_MS) { + return SWIPE_FROM_TOP; + } + if (fromY >= screenHeight - mSwipeStartThreshold + && y < fromY - mSwipeDistanceThreshold + && elapsed < SWIPE_TIMEOUT_MS) { + return SWIPE_FROM_BOTTOM; + } + if (fromX >= screenWidth - mSwipeStartThreshold + && x < fromX - mSwipeDistanceThreshold + && elapsed < SWIPE_TIMEOUT_MS) { + return SWIPE_FROM_RIGHT; + } + return SWIPE_NONE; + } + + interface Callbacks { + void onSwipeFromTop(); + void onSwipeFromBottom(); + void onSwipeFromRight(); + void onDown(); + void onUpOrCancel(); + void onDebug(); + } +} diff --git a/services/core/java/com/android/server/policy/WakeGestureListener.java b/services/core/java/com/android/server/policy/WakeGestureListener.java new file mode 100644 index 0000000..1d5d7ba --- /dev/null +++ b/services/core/java/com/android/server/policy/WakeGestureListener.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014 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.policy; + +import android.os.Handler; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.hardware.TriggerEvent; +import android.hardware.TriggerEventListener; + +import java.io.PrintWriter; + +/** + * Watches for wake gesture sensor events then invokes the listener. + */ +public abstract class WakeGestureListener { + private static final String TAG = "WakeGestureListener"; + + private final SensorManager mSensorManager; + private final Handler mHandler; + + private final Object mLock = new Object(); + + private boolean mTriggerRequested; + private Sensor mSensor; + + public WakeGestureListener(Context context, Handler handler) { + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mHandler = handler; + + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_WAKE_GESTURE); + } + + public abstract void onWakeUp(); + + public boolean isSupported() { + synchronized (mLock) { + return mSensor != null; + } + } + + public void requestWakeUpTrigger() { + synchronized (mLock) { + if (mSensor != null && !mTriggerRequested) { + mTriggerRequested = true; + mSensorManager.requestTriggerSensor(mListener, mSensor); + } + } + } + + public void cancelWakeUpTrigger() { + synchronized (mLock) { + if (mSensor != null && mTriggerRequested) { + mTriggerRequested = false; + mSensorManager.cancelTriggerSensor(mListener, mSensor); + } + } + } + + public void dump(PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.println(prefix + TAG); + prefix += " "; + pw.println(prefix + "mTriggerRequested=" + mTriggerRequested); + pw.println(prefix + "mSensor=" + mSensor); + } + } + + private final TriggerEventListener mListener = new TriggerEventListener() { + @Override + public void onTrigger(TriggerEvent event) { + synchronized (mLock) { + mTriggerRequested = false; + mHandler.post(mWakeUpRunnable); + } + } + }; + + private final Runnable mWakeUpRunnable = new Runnable() { + @Override + public void run() { + onWakeUp(); + } + }; +} diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java new file mode 100644 index 0000000..c8fd82e --- /dev/null +++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java @@ -0,0 +1,847 @@ +/* + * 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.policy; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.util.Log; +import android.util.Slog; + +import java.io.PrintWriter; + +/** + * A special helper class used by the WindowManager + * for receiving notifications from the SensorManager when + * the orientation of the device has changed. + * + * NOTE: If changing anything here, please run the API demo + * "App/Activity/Screen Orientation" to ensure that all orientation + * modes still work correctly. + * + * You can also visualize the behavior of the WindowOrientationListener. + * Refer to frameworks/base/tools/orientationplot/README.txt for details. + * + * @hide + */ +public abstract class WindowOrientationListener { + private static final String TAG = "WindowOrientationListener"; + private static final boolean LOG = SystemProperties.getBoolean( + "debug.orientation.log", false); + + private static final boolean USE_GRAVITY_SENSOR = false; + + private Handler mHandler; + private SensorManager mSensorManager; + private boolean mEnabled; + private int mRate; + private Sensor mSensor; + private SensorEventListenerImpl mSensorEventListener; + private int mCurrentRotation = -1; + + private final Object mLock = new Object(); + + /** + * Creates a new WindowOrientationListener. + * + * @param context for the WindowOrientationListener. + * @param handler Provides the Looper for receiving sensor updates. + */ + public WindowOrientationListener(Context context, Handler handler) { + this(context, handler, SensorManager.SENSOR_DELAY_UI); + } + + /** + * Creates a new WindowOrientationListener. + * + * @param context for the WindowOrientationListener. + * @param handler Provides the Looper for receiving sensor updates. + * @param rate at which sensor events are processed (see also + * {@link android.hardware.SensorManager SensorManager}). Use the default + * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL + * SENSOR_DELAY_NORMAL} for simple screen orientation change detection. + * + * This constructor is private since no one uses it. + */ + private WindowOrientationListener(Context context, Handler handler, int rate) { + mHandler = handler; + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mRate = rate; + mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR + ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER); + if (mSensor != null) { + // Create listener only if sensors do exist + mSensorEventListener = new SensorEventListenerImpl(); + } + } + + /** + * Enables the WindowOrientationListener so it will monitor the sensor and call + * {@link #onProposedRotationChanged(int)} when the device orientation changes. + */ + public void enable() { + synchronized (mLock) { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Not enabled"); + return; + } + if (mEnabled == false) { + if (LOG) { + Log.d(TAG, "WindowOrientationListener enabled"); + } + mSensorEventListener.resetLocked(); + mSensorManager.registerListener(mSensorEventListener, mSensor, mRate, mHandler); + mEnabled = true; + } + } + } + + /** + * Disables the WindowOrientationListener. + */ + public void disable() { + synchronized (mLock) { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Invalid disable"); + return; + } + if (mEnabled == true) { + if (LOG) { + Log.d(TAG, "WindowOrientationListener disabled"); + } + mSensorManager.unregisterListener(mSensorEventListener); + mEnabled = false; + } + } + } + + public void onTouchStart() { + synchronized (mLock) { + if (mSensorEventListener != null) { + mSensorEventListener.onTouchStartLocked(); + } + } + } + + public void onTouchEnd() { + long whenElapsedNanos = SystemClock.elapsedRealtimeNanos(); + + synchronized (mLock) { + if (mSensorEventListener != null) { + mSensorEventListener.onTouchEndLocked(whenElapsedNanos); + } + } + } + + /** + * Sets the current rotation. + * + * @param rotation The current rotation. + */ + public void setCurrentRotation(int rotation) { + synchronized (mLock) { + mCurrentRotation = rotation; + } + } + + /** + * Gets the proposed rotation. + * + * This method only returns a rotation if the orientation listener is certain + * of its proposal. If the rotation is indeterminate, returns -1. + * + * @return The proposed rotation, or -1 if unknown. + */ + public int getProposedRotation() { + synchronized (mLock) { + if (mEnabled) { + return mSensorEventListener.getProposedRotationLocked(); + } + return -1; + } + } + + /** + * Returns true if sensor is enabled and false otherwise + */ + public boolean canDetectOrientation() { + synchronized (mLock) { + return mSensor != null; + } + } + + /** + * Called when the rotation view of the device has changed. + * + * This method is called whenever the orientation becomes certain of an orientation. + * It is called each time the orientation determination transitions from being + * uncertain to being certain again, even if it is the same orientation as before. + * + * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants. + * @see android.view.Surface + */ + public abstract void onProposedRotationChanged(int rotation); + + public void dump(PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.println(prefix + TAG); + prefix += " "; + pw.println(prefix + "mEnabled=" + mEnabled); + pw.println(prefix + "mCurrentRotation=" + mCurrentRotation); + pw.println(prefix + "mSensor=" + mSensor); + pw.println(prefix + "mRate=" + mRate); + + if (mSensorEventListener != null) { + mSensorEventListener.dumpLocked(pw, prefix); + } + } + } + + /** + * This class filters the raw accelerometer data and tries to detect actual changes in + * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, + * but here's the outline: + * + * - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in + * cartesian space because the orientation calculations are sensitive to the + * absolute magnitude of the acceleration. In particular, there are singularities + * in the calculation as the magnitude approaches 0. By performing the low-pass + * filtering early, we can eliminate most spurious high-frequency impulses due to noise. + * + * - Convert the acceleromter vector from cartesian to spherical coordinates. + * Since we're dealing with rotation of the device, this is the sensible coordinate + * system to work in. The zenith direction is the Z-axis, the direction the screen + * is facing. The radial distance is referred to as the magnitude below. + * The elevation angle is referred to as the "tilt" below. + * The azimuth angle is referred to as the "orientation" below (and the azimuth axis is + * the Y-axis). + * See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference. + * + * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing. + * The orientation angle is not meaningful when the device is nearly horizontal. + * The tilt angle thresholds are set differently for each orientation and different + * limits are applied when the device is facing down as opposed to when it is facing + * forward or facing up. + * + * - When the orientation angle reaches a certain threshold, consider transitioning + * to the corresponding orientation. These thresholds have some hysteresis built-in + * to avoid oscillations between adjacent orientations. + * + * - Wait for the device to settle for a little bit. Once that happens, issue the + * new orientation proposal. + * + * Details are explained inline. + * + * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for + * signal processing background. + */ + final class SensorEventListenerImpl implements SensorEventListener { + // We work with all angles in degrees in this class. + private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); + + // Number of nanoseconds per millisecond. + private static final long NANOS_PER_MS = 1000000; + + // Indices into SensorEvent.values for the accelerometer sensor. + private static final int ACCELEROMETER_DATA_X = 0; + private static final int ACCELEROMETER_DATA_Y = 1; + private static final int ACCELEROMETER_DATA_Z = 2; + + // The minimum amount of time that a predicted rotation must be stable before it + // is accepted as a valid rotation proposal. This value can be quite small because + // the low-pass filter already suppresses most of the noise so we're really just + // looking for quick confirmation that the last few samples are in agreement as to + // the desired orientation. + private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS; + + // The minimum amount of time that must have elapsed since the device last exited + // the flat state (time since it was picked up) before the proposed rotation + // can change. + private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS; + + // The minimum amount of time that must have elapsed since the device stopped + // swinging (time since device appeared to be in the process of being put down + // or put away into a pocket) before the proposed rotation can change. + private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS; + + // The minimum amount of time that must have elapsed since the device stopped + // undergoing external acceleration before the proposed rotation can change. + private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS = + 500 * NANOS_PER_MS; + + // The minimum amount of time that must have elapsed since the screen was last touched + // before the proposed rotation can change. + private static final long PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS = + 500 * NANOS_PER_MS; + + // If the tilt angle remains greater than the specified angle for a minimum of + // the specified time, then the device is deemed to be lying flat + // (just chillin' on a table). + private static final float FLAT_ANGLE = 75; + private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS; + + // If the tilt angle has increased by at least delta degrees within the specified amount + // of time, then the device is deemed to be swinging away from the user + // down towards flat (tilt = 90). + private static final float SWING_AWAY_ANGLE_DELTA = 20; + private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS; + + // The maximum sample inter-arrival time in milliseconds. + // If the acceleration samples are further apart than this amount in time, we reset the + // state of the low-pass filter and orientation properties. This helps to handle + // boundary conditions when the device is turned on, wakes from suspend or there is + // a significant gap in samples. + private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS; + + // The acceleration filter time constant. + // + // This time constant is used to tune the acceleration filter such that + // impulses and vibrational noise (think car dock) is suppressed before we + // try to calculate the tilt and orientation angles. + // + // The filter time constant is related to the filter cutoff frequency, which is the + // frequency at which signals are attenuated by 3dB (half the passband power). + // Each successive octave beyond this frequency is attenuated by an additional 6dB. + // + // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz + // is given by Fc = 1 / (2pi * t). + // + // The higher the time constant, the lower the cutoff frequency, so more noise + // will be suppressed. + // + // Filtering adds latency proportional the time constant (inversely proportional + // to the cutoff frequency) so we don't want to make the time constant too + // large or we can lose responsiveness. Likewise we don't want to make it too + // small or we do a poor job suppressing acceleration spikes. + // Empirically, 100ms seems to be too small and 500ms is too large. + private static final float FILTER_TIME_CONSTANT_MS = 200.0f; + + /* State for orientation detection. */ + + // Thresholds for minimum and maximum allowable deviation from gravity. + // + // If the device is undergoing external acceleration (being bumped, in a car + // that is turning around a corner or a plane taking off) then the magnitude + // may be substantially more or less than gravity. This can skew our orientation + // detection by making us think that up is pointed in a different direction. + // + // Conversely, if the device is in freefall, then there will be no gravity to + // measure at all. This is problematic because we cannot detect the orientation + // without gravity to tell us which way is up. A magnitude near 0 produces + // singularities in the tilt and orientation calculations. + // + // In both cases, we postpone choosing an orientation. + // + // However, we need to tolerate some acceleration because the angular momentum + // of turning the device can skew the observed acceleration for a short period of time. + private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2 + private static final float ACCELERATION_TOLERANCE = 4; // m/s^2 + private static final float MIN_ACCELERATION_MAGNITUDE = + SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE; + private static final float MAX_ACCELERATION_MAGNITUDE = + SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE; + + // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. + // when screen is facing the sky or ground), we completely ignore orientation data. + private static final int MAX_TILT = 75; + + // The tilt angle range in degrees for each orientation. + // Beyond these tilt angles, we don't even consider transitioning into the + // specified orientation. We place more stringent requirements on unnatural + // orientations than natural ones to make it less likely to accidentally transition + // into those states. + // The first value of each pair is negative so it applies a limit when the device is + // facing down (overhead reading in bed). + // The second value of each pair is positive so it applies a limit when the device is + // facing up (resting on a table). + // The ideal tilt angle is 0 (when the device is vertical) so the limits establish + // how close to vertical the device must be in order to change orientation. + private final int[][] TILT_TOLERANCE = new int[][] { + /* ROTATION_0 */ { -25, 70 }, + /* ROTATION_90 */ { -25, 65 }, + /* ROTATION_180 */ { -25, 60 }, + /* ROTATION_270 */ { -25, 65 } + }; + + // The tilt angle below which we conclude that the user is holding the device + // overhead reading in bed and lock into that state. + private final int TILT_OVERHEAD_ENTER = -40; + + // The tilt angle above which we conclude that the user would like a rotation + // change to occur and unlock from the overhead state. + private final int TILT_OVERHEAD_EXIT = -15; + + // The gap angle in degrees between adjacent orientation angles for hysteresis. + // This creates a "dead zone" between the current orientation and a proposed + // adjacent orientation. No orientation proposal is made when the orientation + // angle is within the gap between the current orientation and the adjacent + // orientation. + private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45; + + // Timestamp and value of the last accelerometer sample. + private long mLastFilteredTimestampNanos; + private float mLastFilteredX, mLastFilteredY, mLastFilteredZ; + + // The last proposed rotation, -1 if unknown. + private int mProposedRotation; + + // Value of the current predicted rotation, -1 if unknown. + private int mPredictedRotation; + + // Timestamp of when the predicted rotation most recently changed. + private long mPredictedRotationTimestampNanos; + + // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed). + private long mFlatTimestampNanos; + private boolean mFlat; + + // Timestamp when the device last appeared to be swinging. + private long mSwingTimestampNanos; + private boolean mSwinging; + + // Timestamp when the device last appeared to be undergoing external acceleration. + private long mAccelerationTimestampNanos; + private boolean mAccelerating; + + // Timestamp when the last touch to the touch screen ended + private long mTouchEndedTimestampNanos = Long.MIN_VALUE; + private boolean mTouched; + + // Whether we are locked into an overhead usage mode. + private boolean mOverhead; + + // History of observed tilt angles. + private static final int TILT_HISTORY_SIZE = 40; + private float[] mTiltHistory = new float[TILT_HISTORY_SIZE]; + private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE]; + private int mTiltHistoryIndex; + + public int getProposedRotationLocked() { + return mProposedRotation; + } + + public void dumpLocked(PrintWriter pw, String prefix) { + pw.println(prefix + "mProposedRotation=" + mProposedRotation); + pw.println(prefix + "mPredictedRotation=" + mPredictedRotation); + pw.println(prefix + "mLastFilteredX=" + mLastFilteredX); + pw.println(prefix + "mLastFilteredY=" + mLastFilteredY); + pw.println(prefix + "mLastFilteredZ=" + mLastFilteredZ); + pw.println(prefix + "mTiltHistory={last: " + getLastTiltLocked() + "}"); + pw.println(prefix + "mFlat=" + mFlat); + pw.println(prefix + "mSwinging=" + mSwinging); + pw.println(prefix + "mAccelerating=" + mAccelerating); + pw.println(prefix + "mOverhead=" + mOverhead); + pw.println(prefix + "mTouched=" + mTouched); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + + @Override + public void onSensorChanged(SensorEvent event) { + int proposedRotation; + int oldProposedRotation; + + synchronized (mLock) { + // The vector given in the SensorEvent points straight up (towards the sky) under + // ideal conditions (the phone is not accelerating). I'll call this up vector + // elsewhere. + float x = event.values[ACCELEROMETER_DATA_X]; + float y = event.values[ACCELEROMETER_DATA_Y]; + float z = event.values[ACCELEROMETER_DATA_Z]; + + if (LOG) { + Slog.v(TAG, "Raw acceleration vector: " + + "x=" + x + ", y=" + y + ", z=" + z + + ", magnitude=" + Math.sqrt(x * x + y * y + z * z)); + } + + // Apply a low-pass filter to the acceleration up vector in cartesian space. + // Reset the orientation listener state if the samples are too far apart in time + // or when we see values of (0, 0, 0) which indicates that we polled the + // accelerometer too soon after turning it on and we don't have any data yet. + final long now = event.timestamp; + final long then = mLastFilteredTimestampNanos; + final float timeDeltaMS = (now - then) * 0.000001f; + final boolean skipSample; + if (now < then + || now > then + MAX_FILTER_DELTA_TIME_NANOS + || (x == 0 && y == 0 && z == 0)) { + if (LOG) { + Slog.v(TAG, "Resetting orientation listener."); + } + resetLocked(); + skipSample = true; + } else { + final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS); + x = alpha * (x - mLastFilteredX) + mLastFilteredX; + y = alpha * (y - mLastFilteredY) + mLastFilteredY; + z = alpha * (z - mLastFilteredZ) + mLastFilteredZ; + if (LOG) { + Slog.v(TAG, "Filtered acceleration vector: " + + "x=" + x + ", y=" + y + ", z=" + z + + ", magnitude=" + Math.sqrt(x * x + y * y + z * z)); + } + skipSample = false; + } + mLastFilteredTimestampNanos = now; + mLastFilteredX = x; + mLastFilteredY = y; + mLastFilteredZ = z; + + boolean isAccelerating = false; + boolean isFlat = false; + boolean isSwinging = false; + if (!skipSample) { + // Calculate the magnitude of the acceleration vector. + final float magnitude = (float) Math.sqrt(x * x + y * y + z * z); + if (magnitude < NEAR_ZERO_MAGNITUDE) { + if (LOG) { + Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero."); + } + clearPredictedRotationLocked(); + } else { + // Determine whether the device appears to be undergoing external + // acceleration. + if (isAcceleratingLocked(magnitude)) { + isAccelerating = true; + mAccelerationTimestampNanos = now; + } + + // Calculate the tilt angle. + // This is the angle between the up vector and the x-y plane (the plane of + // the screen) in a range of [-90, 90] degrees. + // -90 degrees: screen horizontal and facing the ground (overhead) + // 0 degrees: screen vertical + // 90 degrees: screen horizontal and facing the sky (on table) + final int tiltAngle = (int) Math.round( + Math.asin(z / magnitude) * RADIANS_TO_DEGREES); + addTiltHistoryEntryLocked(now, tiltAngle); + + // Determine whether the device appears to be flat or swinging. + if (isFlatLocked(now)) { + isFlat = true; + mFlatTimestampNanos = now; + } + if (isSwingingLocked(now, tiltAngle)) { + isSwinging = true; + mSwingTimestampNanos = now; + } + + // If the tilt angle is too close to horizontal then we cannot determine + // the orientation angle of the screen. + if (tiltAngle <= TILT_OVERHEAD_ENTER) { + mOverhead = true; + } else if (tiltAngle >= TILT_OVERHEAD_EXIT) { + mOverhead = false; + } + if (mOverhead) { + if (LOG) { + Slog.v(TAG, "Ignoring sensor data, device is overhead: " + + "tiltAngle=" + tiltAngle); + } + clearPredictedRotationLocked(); + } else if (Math.abs(tiltAngle) > MAX_TILT) { + if (LOG) { + Slog.v(TAG, "Ignoring sensor data, tilt angle too high: " + + "tiltAngle=" + tiltAngle); + } + clearPredictedRotationLocked(); + } else { + // Calculate the orientation angle. + // This is the angle between the x-y projection of the up vector onto + // the +y-axis, increasing clockwise in a range of [0, 360] degrees. + int orientationAngle = (int) Math.round( + -Math.atan2(-x, y) * RADIANS_TO_DEGREES); + if (orientationAngle < 0) { + // atan2 returns [-180, 180]; normalize to [0, 360] + orientationAngle += 360; + } + + // Find the nearest rotation. + int nearestRotation = (orientationAngle + 45) / 90; + if (nearestRotation == 4) { + nearestRotation = 0; + } + + // Determine the predicted orientation. + if (isTiltAngleAcceptableLocked(nearestRotation, tiltAngle) + && isOrientationAngleAcceptableLocked(nearestRotation, + orientationAngle)) { + updatePredictedRotationLocked(now, nearestRotation); + if (LOG) { + Slog.v(TAG, "Predicted: " + + "tiltAngle=" + tiltAngle + + ", orientationAngle=" + orientationAngle + + ", predictedRotation=" + mPredictedRotation + + ", predictedRotationAgeMS=" + + ((now - mPredictedRotationTimestampNanos) + * 0.000001f)); + } + } else { + if (LOG) { + Slog.v(TAG, "Ignoring sensor data, no predicted rotation: " + + "tiltAngle=" + tiltAngle + + ", orientationAngle=" + orientationAngle); + } + clearPredictedRotationLocked(); + } + } + } + } + mFlat = isFlat; + mSwinging = isSwinging; + mAccelerating = isAccelerating; + + // Determine new proposed rotation. + oldProposedRotation = mProposedRotation; + if (mPredictedRotation < 0 || isPredictedRotationAcceptableLocked(now)) { + mProposedRotation = mPredictedRotation; + } + proposedRotation = mProposedRotation; + + // Write final statistics about where we are in the orientation detection process. + if (LOG) { + Slog.v(TAG, "Result: currentRotation=" + mCurrentRotation + + ", proposedRotation=" + proposedRotation + + ", predictedRotation=" + mPredictedRotation + + ", timeDeltaMS=" + timeDeltaMS + + ", isAccelerating=" + isAccelerating + + ", isFlat=" + isFlat + + ", isSwinging=" + isSwinging + + ", isOverhead=" + mOverhead + + ", isTouched=" + mTouched + + ", timeUntilSettledMS=" + remainingMS(now, + mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) + + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now, + mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) + + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now, + mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) + + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now, + mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) + + ", timeUntilTouchDelayExpiredMS=" + remainingMS(now, + mTouchEndedTimestampNanos + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS)); + } + } + + // Tell the listener. + if (proposedRotation != oldProposedRotation && proposedRotation >= 0) { + if (LOG) { + Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + proposedRotation + + ", oldProposedRotation=" + oldProposedRotation); + } + onProposedRotationChanged(proposedRotation); + } + } + + /** + * Returns true if the tilt angle is acceptable for a given predicted rotation. + */ + private boolean isTiltAngleAcceptableLocked(int rotation, int tiltAngle) { + return tiltAngle >= TILT_TOLERANCE[rotation][0] + && tiltAngle <= TILT_TOLERANCE[rotation][1]; + } + + /** + * Returns true if the orientation angle is acceptable for a given predicted rotation. + * + * This function takes into account the gap between adjacent orientations + * for hysteresis. + */ + private boolean isOrientationAngleAcceptableLocked(int rotation, int orientationAngle) { + // If there is no current rotation, then there is no gap. + // The gap is used only to introduce hysteresis among advertised orientation + // changes to avoid flapping. + final int currentRotation = mCurrentRotation; + if (currentRotation >= 0) { + // If the specified rotation is the same or is counter-clockwise adjacent + // to the current rotation, then we set a lower bound on the orientation angle. + // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90, + // then we want to check orientationAngle > 45 + GAP / 2. + if (rotation == currentRotation + || rotation == (currentRotation + 1) % 4) { + int lowerBound = rotation * 90 - 45 + + ADJACENT_ORIENTATION_ANGLE_GAP / 2; + if (rotation == 0) { + if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) { + return false; + } + } else { + if (orientationAngle < lowerBound) { + return false; + } + } + } + + // If the specified rotation is the same or is clockwise adjacent, + // then we set an upper bound on the orientation angle. + // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270, + // then we want to check orientationAngle < 315 - GAP / 2. + if (rotation == currentRotation + || rotation == (currentRotation + 3) % 4) { + int upperBound = rotation * 90 + 45 + - ADJACENT_ORIENTATION_ANGLE_GAP / 2; + if (rotation == 0) { + if (orientationAngle <= 45 && orientationAngle > upperBound) { + return false; + } + } else { + if (orientationAngle > upperBound) { + return false; + } + } + } + } + return true; + } + + /** + * Returns true if the predicted rotation is ready to be advertised as a + * proposed rotation. + */ + private boolean isPredictedRotationAcceptableLocked(long now) { + // The predicted rotation must have settled long enough. + if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) { + return false; + } + + // The last flat state (time since picked up) must have been sufficiently long ago. + if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) { + return false; + } + + // The last swing state (time since last movement to put down) must have been + // sufficiently long ago. + if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) { + return false; + } + + // The last acceleration state must have been sufficiently long ago. + if (now < mAccelerationTimestampNanos + + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) { + return false; + } + + // The last touch must have ended sufficiently long ago. + if (mTouched || now < mTouchEndedTimestampNanos + + PROPOSAL_MIN_TIME_SINCE_TOUCH_END_NANOS) { + return false; + } + + // Looks good! + return true; + } + + private void resetLocked() { + mLastFilteredTimestampNanos = Long.MIN_VALUE; + mProposedRotation = -1; + mFlatTimestampNanos = Long.MIN_VALUE; + mFlat = false; + mSwingTimestampNanos = Long.MIN_VALUE; + mSwinging = false; + mAccelerationTimestampNanos = Long.MIN_VALUE; + mAccelerating = false; + mOverhead = false; + clearPredictedRotationLocked(); + clearTiltHistoryLocked(); + } + + private void clearPredictedRotationLocked() { + mPredictedRotation = -1; + mPredictedRotationTimestampNanos = Long.MIN_VALUE; + } + + private void updatePredictedRotationLocked(long now, int rotation) { + if (mPredictedRotation != rotation) { + mPredictedRotation = rotation; + mPredictedRotationTimestampNanos = now; + } + } + + private boolean isAcceleratingLocked(float magnitude) { + return magnitude < MIN_ACCELERATION_MAGNITUDE + || magnitude > MAX_ACCELERATION_MAGNITUDE; + } + + private void clearTiltHistoryLocked() { + mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE; + mTiltHistoryIndex = 1; + } + + private void addTiltHistoryEntryLocked(long now, float tilt) { + mTiltHistory[mTiltHistoryIndex] = tilt; + mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now; + mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE; + mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE; + } + + private boolean isFlatLocked(long now) { + for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) { + if (mTiltHistory[i] < FLAT_ANGLE) { + break; + } + if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) { + // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS. + return true; + } + } + return false; + } + + private boolean isSwingingLocked(long now, float tilt) { + for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndexLocked(i)) >= 0; ) { + if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) { + break; + } + if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) { + // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS. + return true; + } + } + return false; + } + + private int nextTiltHistoryIndexLocked(int index) { + index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1; + return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1; + } + + private float getLastTiltLocked() { + int index = nextTiltHistoryIndexLocked(mTiltHistoryIndex); + return index >= 0 ? mTiltHistory[index] : Float.NaN; + } + + private float remainingMS(long now, long until) { + return now >= until ? 0 : (until - now) * 0.000001f; + } + + private void onTouchStartLocked() { + mTouched = true; + } + + private void onTouchEndLocked(long whenElapsedNanos) { + mTouched = false; + mTouchEndedTimestampNanos = whenElapsedNanos; + } + } +} diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java new file mode 100644 index 0000000..1a52933 --- /dev/null +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -0,0 +1,338 @@ +package com.android.server.policy.keyguard; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowManagerPolicy.OnKeyguardExitResult; + +import com.android.internal.policy.IKeyguardExitCallback; +import com.android.internal.policy.IKeyguardService; +import com.android.internal.policy.IKeyguardShowCallback; + +/** + * A local class that keeps a cache of keyguard state that can be restored in the event + * keyguard crashes. It currently also allows runtime-selectable + * local or remote instances of keyguard. + */ +public class KeyguardServiceDelegate { + private static final String TAG = "KeyguardServiceDelegate"; + private static final boolean DEBUG = true; + + protected KeyguardServiceWrapper mKeyguardService; + private final Context mContext; + private final View mScrim; // shown if keyguard crashes + private final KeyguardState mKeyguardState = new KeyguardState(); + private ShowListener mShowListenerWhenConnect; + + /* package */ static final class KeyguardState { + KeyguardState() { + // Assume keyguard is showing and secure until we know for sure. This is here in + // the event something checks before the service is actually started. + // KeyguardService itself should default to this state until the real state is known. + showing = true; + showingAndNotOccluded = true; + secure = true; + deviceHasKeyguard = true; + } + boolean showing; + boolean showingAndNotOccluded; + boolean inputRestricted; + boolean occluded; + boolean secure; + boolean dreaming; + boolean systemIsReady; + boolean deviceHasKeyguard; + public boolean enabled; + public boolean dismissable; + public int offReason; + public int currentUser; + public boolean screenIsOn; + public boolean bootCompleted; + }; + + public interface ShowListener { + public void onShown(IBinder windowToken); + } + + // A delegate class to map a particular invocation with a ShowListener object. + private final class KeyguardShowDelegate extends IKeyguardShowCallback.Stub { + private ShowListener mShowListener; + + KeyguardShowDelegate(ShowListener showListener) { + mShowListener = showListener; + } + + @Override + public void onShown(IBinder windowToken) throws RemoteException { + if (DEBUG) Log.v(TAG, "**** SHOWN CALLED ****"); + if (mShowListener != null) { + mShowListener.onShown(windowToken); + } + hideScrim(); + } + }; + + // A delegate class to map a particular invocation with an OnKeyguardExitResult object. + private final class KeyguardExitDelegate extends IKeyguardExitCallback.Stub { + private OnKeyguardExitResult mOnKeyguardExitResult; + + KeyguardExitDelegate(OnKeyguardExitResult onKeyguardExitResult) { + mOnKeyguardExitResult = onKeyguardExitResult; + } + + @Override + public void onKeyguardExitResult(boolean success) throws RemoteException { + if (DEBUG) Log.v(TAG, "**** onKeyguardExitResult(" + success +") CALLED ****"); + if (mOnKeyguardExitResult != null) { + mOnKeyguardExitResult.onKeyguardExitResult(success); + } + } + }; + + public KeyguardServiceDelegate(Context context) { + mContext = context; + mScrim = createScrim(context); + } + + public void bindService(Context context) { + Intent intent = new Intent(); + final Resources resources = context.getApplicationContext().getResources(); + + final ComponentName keyguardComponent = ComponentName.unflattenFromString( + resources.getString(com.android.internal.R.string.config_keyguardComponent)); + intent.setComponent(keyguardComponent); + + if (!context.bindServiceAsUser(intent, mKeyguardConnection, + Context.BIND_AUTO_CREATE, UserHandle.OWNER)) { + Log.v(TAG, "*** Keyguard: can't bind to " + keyguardComponent); + mKeyguardState.showing = false; + mKeyguardState.showingAndNotOccluded = false; + mKeyguardState.secure = false; + mKeyguardState.deviceHasKeyguard = false; + hideScrim(); + } else { + if (DEBUG) Log.v(TAG, "*** Keyguard started"); + } + } + + private final ServiceConnection mKeyguardConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) Log.v(TAG, "*** Keyguard connected (yay!)"); + mKeyguardService = new KeyguardServiceWrapper(mContext, + IKeyguardService.Stub.asInterface(service)); + if (mKeyguardState.systemIsReady) { + // If the system is ready, it means keyguard crashed and restarted. + mKeyguardService.onSystemReady(); + // This is used to hide the scrim once keyguard displays. + mKeyguardService.onScreenTurnedOn(new KeyguardShowDelegate( + mShowListenerWhenConnect)); + mShowListenerWhenConnect = null; + } + if (mKeyguardState.bootCompleted) { + mKeyguardService.onBootCompleted(); + } + if (mKeyguardState.occluded) { + mKeyguardService.setOccluded(mKeyguardState.occluded); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Log.v(TAG, "*** Keyguard disconnected (boo!)"); + mKeyguardService = null; + } + + }; + + public boolean isShowing() { + if (mKeyguardService != null) { + mKeyguardState.showing = mKeyguardService.isShowing(); + } + return mKeyguardState.showing; + } + + public boolean isInputRestricted() { + if (mKeyguardService != null) { + mKeyguardState.inputRestricted = mKeyguardService.isInputRestricted(); + } + return mKeyguardState.inputRestricted; + } + + public void verifyUnlock(final OnKeyguardExitResult onKeyguardExitResult) { + if (mKeyguardService != null) { + mKeyguardService.verifyUnlock(new KeyguardExitDelegate(onKeyguardExitResult)); + } + } + + public void keyguardDone(boolean authenticated, boolean wakeup) { + if (mKeyguardService != null) { + mKeyguardService.keyguardDone(authenticated, wakeup); + } + } + + public void setOccluded(boolean isOccluded) { + if (mKeyguardService != null) { + mKeyguardService.setOccluded(isOccluded); + } + mKeyguardState.occluded = isOccluded; + } + + public void dismiss() { + if (mKeyguardService != null) { + mKeyguardService.dismiss(); + } + } + + public boolean isSecure() { + if (mKeyguardService != null) { + mKeyguardState.secure = mKeyguardService.isSecure(); + } + return mKeyguardState.secure; + } + + public void onDreamingStarted() { + if (mKeyguardService != null) { + mKeyguardService.onDreamingStarted(); + } + mKeyguardState.dreaming = true; + } + + public void onDreamingStopped() { + if (mKeyguardService != null) { + mKeyguardService.onDreamingStopped(); + } + mKeyguardState.dreaming = false; + } + + public void onScreenTurnedOn(final ShowListener showListener) { + if (mKeyguardService != null) { + if (DEBUG) Log.v(TAG, "onScreenTurnedOn(showListener = " + showListener + ")"); + mKeyguardService.onScreenTurnedOn(new KeyguardShowDelegate(showListener)); + } else { + // try again when we establish a connection + Slog.w(TAG, "onScreenTurnedOn(): no keyguard service!"); + // This shouldn't happen, but if it does, show the scrim immediately and + // invoke the listener's callback after the service actually connects. + mShowListenerWhenConnect = showListener; + showScrim(); + } + mKeyguardState.screenIsOn = true; + } + + public void onScreenTurnedOff(int why) { + if (mKeyguardService != null) { + mKeyguardService.onScreenTurnedOff(why); + } + mKeyguardState.offReason = why; + mKeyguardState.screenIsOn = false; + } + + public void setKeyguardEnabled(boolean enabled) { + if (mKeyguardService != null) { + mKeyguardService.setKeyguardEnabled(enabled); + } + mKeyguardState.enabled = enabled; + } + + public void onSystemReady() { + if (mKeyguardService != null) { + mKeyguardService.onSystemReady(); + } else { + mKeyguardState.systemIsReady = true; + } + } + + public void doKeyguardTimeout(Bundle options) { + if (mKeyguardService != null) { + mKeyguardService.doKeyguardTimeout(options); + } + } + + public void setCurrentUser(int newUserId) { + if (mKeyguardService != null) { + mKeyguardService.setCurrentUser(newUserId); + } + mKeyguardState.currentUser = newUserId; + } + + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + if (mKeyguardService != null) { + mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration); + } + } + + private static final View createScrim(Context context) { + View view = new View(context); + + int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR + | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN + | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER + ; + + final int stretch = ViewGroup.LayoutParams.MATCH_PARENT; + final int type = WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM; + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + stretch, stretch, type, flags, PixelFormat.TRANSLUCENT); + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED; + lp.setTitle("KeyguardScrim"); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + wm.addView(view, lp); + // Disable pretty much everything in statusbar until keyguard comes back and we know + // the state of the world. + view.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME + | View.STATUS_BAR_DISABLE_BACK + | View.STATUS_BAR_DISABLE_RECENT + | View.STATUS_BAR_DISABLE_EXPAND + | View.STATUS_BAR_DISABLE_SEARCH); + return view; + } + + public void showScrim() { + if (!mKeyguardState.deviceHasKeyguard) return; + mScrim.post(new Runnable() { + @Override + public void run() { + mScrim.setVisibility(View.VISIBLE); + } + }); + } + + public void hideScrim() { + mScrim.post(new Runnable() { + @Override + public void run() { + mScrim.setVisibility(View.GONE); + } + }); + } + + public void onBootCompleted() { + if (mKeyguardService != null) { + mKeyguardService.onBootCompleted(); + } + mKeyguardState.bootCompleted = true; + } + + public void onActivityDrawn() { + if (mKeyguardService != null) { + mKeyguardService.onActivityDrawn(); + } + } +} diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java new file mode 100644 index 0000000..2dc685b --- /dev/null +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -0,0 +1,206 @@ +/* + * 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.policy.keyguard; + +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.policy.IKeyguardExitCallback; +import com.android.internal.policy.IKeyguardService; +import com.android.internal.policy.IKeyguardShowCallback; +import com.android.internal.policy.IKeyguardStateCallback; + +/** + * A wrapper class for KeyguardService. It implements IKeyguardService to ensure the interface + * remains consistent. + * + */ +public class KeyguardServiceWrapper implements IKeyguardService { + private KeyguardStateMonitor mKeyguardStateMonitor; + private IKeyguardService mService; + private String TAG = "KeyguardServiceWrapper"; + + public KeyguardServiceWrapper(Context context, IKeyguardService service) { + mService = service; + mKeyguardStateMonitor = new KeyguardStateMonitor(context, service); + } + + @Override // Binder interface + public void verifyUnlock(IKeyguardExitCallback callback) { + try { + mService.verifyUnlock(callback); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void keyguardDone(boolean authenticated, boolean wakeup) { + try { + mService.keyguardDone(authenticated, wakeup); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void setOccluded(boolean isOccluded) { + try { + mService.setOccluded(isOccluded); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override + public void addStateMonitorCallback(IKeyguardStateCallback callback) { + try { + mService.addStateMonitorCallback(callback); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void dismiss() { + try { + mService.dismiss(); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void onDreamingStarted() { + try { + mService.onDreamingStarted(); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void onDreamingStopped() { + try { + mService.onDreamingStopped(); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void onScreenTurnedOff(int reason) { + try { + mService.onScreenTurnedOff(reason); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void onScreenTurnedOn(IKeyguardShowCallback result) { + try { + mService.onScreenTurnedOn(result); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void setKeyguardEnabled(boolean enabled) { + try { + mService.setKeyguardEnabled(enabled); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void onSystemReady() { + try { + mService.onSystemReady(); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void doKeyguardTimeout(Bundle options) { + try { + mService.doKeyguardTimeout(options); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void setCurrentUser(int userId) { + mKeyguardStateMonitor.setCurrentUser(userId); + try { + mService.setCurrentUser(userId); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void onBootCompleted() { + try { + mService.onBootCompleted(); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) { + try { + mService.startKeyguardExitAnimation(startTime, fadeoutDuration); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public void onActivityDrawn() { + try { + mService.onActivityDrawn(); + } catch (RemoteException e) { + Slog.w(TAG , "Remote Exception", e); + } + } + + @Override // Binder interface + public IBinder asBinder() { + return mService.asBinder(); + } + + public boolean isShowing() { + return mKeyguardStateMonitor.isShowing(); + } + + public boolean isSecure() { + return mKeyguardStateMonitor.isSecure(); + } + + public boolean isInputRestricted() { + return mKeyguardStateMonitor.isInputRestricted(); + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java new file mode 100644 index 0000000..926090e --- /dev/null +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -0,0 +1,86 @@ +/* + * 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.policy.keyguard; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.policy.IKeyguardService; +import com.android.internal.policy.IKeyguardStateCallback; +import com.android.internal.widget.LockPatternUtils; + +/** + * Maintains a cached copy of Keyguard's state. + * @hide + */ +public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { + private static final String TAG = "KeyguardStateMonitor"; + + // These cache the current state of Keyguard to improve performance and avoid deadlock. After + // Keyguard changes its state, it always triggers a layout in window manager. Because + // IKeyguardStateCallback is synchronous and because these states are declared volatile, it's + // guaranteed that window manager picks up the new state all the time in the layout caused by + // the state change of Keyguard. + private volatile boolean mIsShowing; + private volatile boolean mSimSecure; + private volatile boolean mInputRestricted; + + private final LockPatternUtils mLockPatternUtils; + + public KeyguardStateMonitor(Context context, IKeyguardService service) { + mLockPatternUtils = new LockPatternUtils(context); + mLockPatternUtils.setCurrentUser(ActivityManager.getCurrentUser()); + try { + service.addStateMonitorCallback(this); + } catch (RemoteException e) { + Slog.w(TAG, "Remote Exception", e); + } + } + + public boolean isShowing() { + return mIsShowing; + } + + public boolean isSecure() { + return mLockPatternUtils.isSecure() || mSimSecure; + } + + public boolean isInputRestricted() { + return mInputRestricted; + } + + @Override // Binder interface + public void onShowingStateChanged(boolean showing) { + mIsShowing = showing; + } + + @Override // Binder interface + public void onSimSecureStateChanged(boolean simSecure) { + mSimSecure = simSecure; + } + + public void setCurrentUser(int userId) { + mLockPatternUtils.setCurrentUser(userId); + } + + @Override // Binder interface + public void onInputRestrictedStateChanged(boolean inputRestricted) { + mInputRestricted = inputRestricted; + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/power/DeviceIdleController.java b/services/core/java/com/android/server/power/DeviceIdleController.java new file mode 100644 index 0000000..6b29b9a --- /dev/null +++ b/services/core/java/com/android/server/power/DeviceIdleController.java @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2015 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.power; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.hardware.TriggerEvent; +import android.hardware.TriggerEventListener; +import android.hardware.display.DisplayManager; +import android.net.INetworkPolicyManager; +import android.os.Binder; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.TimeUtils; +import android.view.Display; +import com.android.internal.app.IBatteryStats; +import com.android.server.SystemService; +import com.android.server.am.BatteryStatsService; +import com.android.server.EventLogTags; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Keeps track of device idleness and drives low power mode based on that. + */ +public class DeviceIdleController extends SystemService { + private static final String TAG = "DeviceIdleController"; + + private static final String ACTION_STEP_IDLE_STATE = + "com.android.server.device_idle.STEP_IDLE_STATE"; + + // TODO: These need to be moved to system settings. + + /** + * This is the time, after becoming inactive, at which we start looking at the + * motion sensor to determine if the device is being left alone. We don't do this + * immediately after going inactive just because we don't want to be continually running + * the significant motion sensor whenever the screen is off. + */ + private static final long DEFAULT_INACTIVE_TIMEOUT = 30*60*1000L; + /** + * This is the time, after seeing motion, that we wait after becoming inactive from + * that until we start looking for motion again. + */ + private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT = 10*60*1000L; + /** + * This is the time, after the inactive timeout elapses, that we will wait looking + * for significant motion until we truly consider the device to be idle. + */ + private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = 30*60*1000L; + /** + * This is the initial time, after being idle, that we will allow ourself to be back + * in the IDLE_PENDING state allowing the system to run normally until we return to idle. + */ + private static final long DEFAULT_IDLE_PENDING_TIMEOUT = 5*60*1000L; + /** + * Maximum pending idle timeout (time spent running) we will be allowed to use. + */ + private static final long DEFAULT_MAX_IDLE_PENDING_TIMEOUT = 10*60*1000L; + /** + * Scaling factor to apply to current pending idle timeout each time we cycle through + * that state. + */ + private static final float DEFAULT_IDLE_PENDING_FACTOR = 2f; + /** + * This is the initial time that we want to sit in the idle state before waking up + * again to return to pending idle and allowing normal work to run. + */ + private static final long DEFAULT_IDLE_TIMEOUT = 60*60*1000L; + /** + * Maximum idle duration we will be allowed to use. + */ + private static final long DEFAULT_MAX_IDLE_TIMEOUT = 6*60*60*1000L; + /** + * Scaling factor to apply to current idle timeout each time we cycle through that state. + */ + private static final float DEFAULT_IDLE_FACTOR = 2f; + /** + * This is the minimum time we will allow until the next upcoming alarm for us to + * actually go in to idle mode. + */ + private static final long DEFAULT_MIN_TIME_TO_ALARM = 60*60*1000L; + + private AlarmManager mAlarmManager; + private IBatteryStats mBatteryStats; + private PowerManagerInternal mLocalPowerManager; + private INetworkPolicyManager mNetworkPolicyManager; + private DisplayManager mDisplayManager; + private SensorManager mSensorManager; + private Sensor mSigMotionSensor; + private PendingIntent mAlarmIntent; + private Intent mIdleIntent; + private Display mCurDisplay; + private boolean mScreenOn; + private boolean mCharging; + private boolean mSigMotionActive; + + /** Device is currently active. */ + private static final int STATE_ACTIVE = 0; + /** Device is inactve (screen off, no motion) and we are waiting to for idle. */ + private static final int STATE_INACTIVE = 1; + /** Device is past the initial inactive period, and waiting for the next idle period. */ + private static final int STATE_IDLE_PENDING = 2; + /** Device is in the idle state, trying to stay asleep as much as possible. */ + private static final int STATE_IDLE = 3; + private static String stateToString(int state) { + switch (state) { + case STATE_ACTIVE: return "ACTIVE"; + case STATE_INACTIVE: return "INACTIVE"; + case STATE_IDLE_PENDING: return "IDLE_PENDING"; + case STATE_IDLE: return "IDLE"; + default: return Integer.toString(state); + } + } + + private int mState; + + private long mInactiveTimeout; + private long mNextAlarmTime; + private long mNextIdlePendingDelay; + private long mNextIdleDelay; + + private final Binder mBinder = new Binder() { + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + DeviceIdleController.this.dump(fd, pw, args); + } + }; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + int plugged = intent.getIntExtra("plugged", 0); + updateChargingLocked(plugged != 0); + } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) { + synchronized (DeviceIdleController.this) { + stepIdleStateLocked(); + } + } + } + }; + + private final DisplayManager.DisplayListener mDisplayListener + = new DisplayManager.DisplayListener() { + @Override public void onDisplayAdded(int displayId) { + } + + @Override public void onDisplayRemoved(int displayId) { + } + + @Override public void onDisplayChanged(int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + synchronized (DeviceIdleController.this) { + updateDisplayLocked(); + } + } + } + }; + + private final TriggerEventListener mSigMotionListener = new TriggerEventListener() { + @Override public void onTrigger(TriggerEvent event) { + synchronized (DeviceIdleController.this) { + significantMotionLocked(); + } + } + }; + + public DeviceIdleController(Context context) { + super(context); + } + + @Override + public void onStart() { + synchronized (this) { + mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); + mBatteryStats = BatteryStatsService.getService(); + mLocalPowerManager = getLocalService(PowerManagerInternal.class); + mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface( + ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); + mDisplayManager = (DisplayManager) getContext().getSystemService( + Context.DISPLAY_SERVICE); + mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE); + mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION); + + Intent intent = new Intent(ACTION_STEP_IDLE_STATE) + .setPackage("android") + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0); + + mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); + mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(ACTION_STEP_IDLE_STATE); + getContext().registerReceiver(mReceiver, filter); + + mDisplayManager.registerDisplayListener(mDisplayListener, null); + + mScreenOn = true; + // Start out assuming we are charging. If we aren't, we will at least get + // a battery update the next time the level drops. + mCharging = true; + mState = STATE_ACTIVE; + mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT; + updateDisplayLocked(); + } + + publishBinderService("deviceidle", mBinder); + } + + void updateDisplayLocked() { + mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); + // We consider any situation where the display is showing something to be it on, + // because if there is anything shown we are going to be updating it at some + // frequency so can't be allowed to go into deep sleeps. + boolean screenOn = mCurDisplay.getState() != Display.STATE_OFF;; + if (!screenOn && mScreenOn) { + mScreenOn = false; + becomeInactiveIfAppropriateLocked(); + } else if (screenOn) { + mScreenOn = true; + becomeActiveLocked("screen"); + } + } + + void updateChargingLocked(boolean charging) { + if (!charging && mCharging) { + mCharging = false; + becomeInactiveIfAppropriateLocked(); + } else if (charging) { + mCharging = charging; + becomeActiveLocked("charging"); + } + } + + void becomeActiveLocked(String reason) { + if (mState != STATE_ACTIVE) { + EventLogTags.writeDeviceIdle(STATE_ACTIVE, reason); + mLocalPowerManager.setDeviceIdleMode(false); + try { + mNetworkPolicyManager.setDeviceIdleMode(false); + mBatteryStats.noteDeviceIdleMode(false, true, false); + } catch (RemoteException e) { + } + if (mState == STATE_IDLE) { + getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL); + } + mState = STATE_ACTIVE; + mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT; + mNextIdlePendingDelay = 0; + mNextIdleDelay = 0; + cancelAlarmLocked(); + stopMonitoringSignificantMotion(); + } + } + + void becomeInactiveIfAppropriateLocked() { + if (!mScreenOn && !mCharging && mState == STATE_ACTIVE) { + // Screen has turned off; we are now going to become inactive and start + // waiting to see if we will ultimately go idle. + mState = STATE_INACTIVE; + mNextIdlePendingDelay = 0; + mNextIdleDelay = 0; + scheduleAlarmLocked(mInactiveTimeout, false); + EventLogTags.writeDeviceIdle(mState, "no activity"); + } + } + + void stepIdleStateLocked() { + EventLogTags.writeDeviceIdleStep(); + + final long now = SystemClock.elapsedRealtime(); + if ((now+DEFAULT_MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) { + // Whoops, there is an upcoming alarm. We don't actually want to go idle. + if (mState != STATE_ACTIVE) { + becomeActiveLocked("alarm"); + } + return; + } + + switch (mState) { + case STATE_INACTIVE: + // We have now been inactive long enough, it is time to start looking + // for significant motion and sleep some more while doing so. + startMonitoringSignificantMotion(); + scheduleAlarmLocked(DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT, false); + // Reset the upcoming idle delays. + mNextIdlePendingDelay = DEFAULT_IDLE_PENDING_TIMEOUT; + mNextIdleDelay = DEFAULT_IDLE_TIMEOUT; + mState = STATE_IDLE_PENDING; + EventLogTags.writeDeviceIdle(mState, "step"); + break; + case STATE_IDLE_PENDING: + // We have been waiting to become idle, and now it is time! This is the + // only case where we want to use a wakeup alarm, because we do want to + // drag the device out of its sleep state in this case to do the next + // scheduled work. + scheduleAlarmLocked(mNextIdleDelay, true); + mNextIdleDelay = (long)(mNextIdleDelay*DEFAULT_IDLE_FACTOR); + if (mNextIdleDelay > DEFAULT_MAX_IDLE_TIMEOUT) { + mNextIdleDelay = DEFAULT_MAX_IDLE_TIMEOUT; + } + mState = STATE_IDLE; + EventLogTags.writeDeviceIdle(mState, "step"); + mLocalPowerManager.setDeviceIdleMode(true); + try { + mNetworkPolicyManager.setDeviceIdleMode(true); + mBatteryStats.noteDeviceIdleMode(true, false, false); + } catch (RemoteException e) { + } + getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL); + break; + case STATE_IDLE: + // We have been idling long enough, now it is time to do some work. + scheduleAlarmLocked(mNextIdlePendingDelay, false); + mNextIdlePendingDelay = (long)(mNextIdlePendingDelay*DEFAULT_IDLE_PENDING_FACTOR); + if (mNextIdlePendingDelay > DEFAULT_MAX_IDLE_PENDING_TIMEOUT) { + mNextIdlePendingDelay = DEFAULT_MAX_IDLE_PENDING_TIMEOUT; + } + mState = STATE_IDLE_PENDING; + EventLogTags.writeDeviceIdle(mState, "step"); + mLocalPowerManager.setDeviceIdleMode(false); + try { + mNetworkPolicyManager.setDeviceIdleMode(false); + mBatteryStats.noteDeviceIdleMode(false, false, false); + } catch (RemoteException e) { + } + getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL); + break; + } + } + + void significantMotionLocked() { + // When the sensor goes off, its trigger is automatically removed. + mSigMotionActive = false; + // The device is not yet active, so we want to go back to the pending idle + // state to wait again for no motion. Note that we only monitor for significant + // motion after moving out of the inactive state, so no need to worry about that. + if (mState != STATE_ACTIVE) { + mLocalPowerManager.setDeviceIdleMode(false); + try { + mNetworkPolicyManager.setDeviceIdleMode(false); + mBatteryStats.noteDeviceIdleMode(false, false, true); + } catch (RemoteException e) { + } + if (mState == STATE_IDLE) { + getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL); + } + mState = STATE_ACTIVE; + mInactiveTimeout = DEFAULT_MOTION_INACTIVE_TIMEOUT; + EventLogTags.writeDeviceIdle(mState, "motion"); + becomeInactiveIfAppropriateLocked(); + } + } + + void startMonitoringSignificantMotion() { + if (mSigMotionSensor != null && !mSigMotionActive) { + mSensorManager.requestTriggerSensor(mSigMotionListener, mSigMotionSensor); + mSigMotionActive = true; + } + } + + void stopMonitoringSignificantMotion() { + if (mSigMotionActive) { + mSensorManager.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor); + mSigMotionActive = false; + } + } + + void cancelAlarmLocked() { + if (mNextAlarmTime != 0) { + mNextAlarmTime = 0; + mAlarmManager.cancel(mAlarmIntent); + } + } + + void scheduleAlarmLocked(long delay, boolean idleUntil) { + if (mSigMotionSensor == null) { + // If there is no significant motion sensor on this device, then we won't schedule + // alarms, because we can't determine if the device is not moving. This effectively + // turns off normal exeuction of device idling, although it is still possible to + // manually poke it by pretending like the alarm is going off. + return; + } + mNextAlarmTime = SystemClock.elapsedRealtime() + delay; + if (idleUntil) { + mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mNextAlarmTime, mAlarmIntent); + } else { + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mNextAlarmTime, mAlarmIntent); + } + } + + private void dumpHelp(PrintWriter pw) { + pw.println("Device idle controller (deviceidle) dump options:"); + pw.println(" [-h] [CMD]"); + pw.println(" -h: print this help text."); + pw.println("Commands:"); + pw.println(" step"); + pw.println(" Immediately step to next state, without waiting for alarm."); + } + + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump DeviceIdleController from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + + if (args != null) { + for (int i=0; i<args.length; i++) { + String arg = args[i]; + if ("-h".equals(arg)) { + dumpHelp(pw); + return; + } else if ("step".equals(arg)) { + synchronized (this) { + stepIdleStateLocked(); + pw.print("Stepped to: "); pw.println(stateToString(mState)); + } + return; + } else if (arg.length() > 0 && arg.charAt(0) == '-'){ + pw.println("Unknown option: " + arg); + dumpHelp(pw); + return; + } else { + pw.println("Unknown command: " + arg); + dumpHelp(pw); + return; + } + } + } + + synchronized (this) { + pw.print(" mSigMotionSensor="); pw.println(mSigMotionSensor); + pw.print(" mCurDisplay="); pw.println(mCurDisplay); + pw.print(" mScreenOn="); pw.println(mScreenOn); + pw.print(" mCharging="); pw.println(mCharging); + pw.print(" mSigMotionActive="); pw.println(mSigMotionActive); + pw.print(" mState="); pw.println(stateToString(mState)); + pw.print(" mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw); + pw.println(); + if (mNextAlarmTime != 0) { + pw.print(" mNextAlarmTime="); + TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw); + pw.println(); + } + if (mNextIdlePendingDelay != 0) { + pw.print(" mNextIdlePendingDelay="); + TimeUtils.formatDuration(mNextIdlePendingDelay, pw); + pw.println(); + } + if (mNextIdleDelay != 0) { + pw.print(" mNextIdleDelay="); + TimeUtils.formatDuration(mNextIdleDelay, pw); + pw.println(); + } + } + } +} diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 1349926..c48367e 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -78,6 +78,7 @@ final class Notifier { private static final int MSG_USER_ACTIVITY = 1; private static final int MSG_BROADCAST = 2; private static final int MSG_WIRELESS_CHARGING_STARTED = 3; + private static final int MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED = 4; private final Object mLock = new Object(); @@ -92,6 +93,7 @@ final class Notifier { private final NotifierHandler mHandler; private final Intent mScreenOnIntent; private final Intent mScreenOffIntent; + private final Intent mScreenBrightnessBoostIntent; // The current interactive state. private int mActualInteractiveState; @@ -128,6 +130,10 @@ final class Notifier { mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF); mScreenOffIntent.addFlags( Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + mScreenBrightnessBoostIntent = + new Intent(PowerManager.ACTION_SCREEN_BRIGHTNESS_BOOST_CHANGED); + mScreenBrightnessBoostIntent.addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); // Initialize interactive state for battery stats. try { @@ -349,6 +355,19 @@ final class Notifier { } /** + * Called when screen brightness boost begins or ends. + */ + public void onScreenBrightnessBoostChanged() { + if (DEBUG) { + Slog.d(TAG, "onScreenBrightnessBoostChanged"); + } + + mSuspendBlocker.acquire(); + Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + /** * Called when there has been user activity. */ public void onUserActivity(int event, int uid) { @@ -457,6 +476,22 @@ final class Notifier { } } + private void sendBrightnessBoostChangedBroadcast() { + if (DEBUG) { + Slog.d(TAG, "Sending brightness boost changed broadcast."); + } + + mContext.sendOrderedBroadcastAsUser(mScreenBrightnessBoostIntent, UserHandle.ALL, null, + mScreeBrightnessBoostChangedDone, mHandler, 0, null, null); + } + + private final BroadcastReceiver mScreeBrightnessBoostChangedDone = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mSuspendBlocker.release(); + } + }; + private void sendWakeUpBroadcast() { if (DEBUG) { Slog.d(TAG, "Sending wake up broadcast."); @@ -539,6 +574,9 @@ final class Notifier { case MSG_WIRELESS_CHARGING_STARTED: playWirelessChargingStartedSound(); break; + case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED: + sendBrightnessBoostChangedBroadcast(); + break; } } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 9786b42..6c8959c 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -128,6 +128,7 @@ public final class PowerManagerService extends SystemService private static final int WAKE_LOCK_PROXIMITY_SCREEN_OFF = 1 << 4; private static final int WAKE_LOCK_STAY_AWAKE = 1 << 5; // only set if already awake private static final int WAKE_LOCK_DOZE = 1 << 6; + private static final int WAKE_LOCK_DRAW = 1 << 7; // Summarizes the user activity state. private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0; @@ -419,6 +420,9 @@ public final class PowerManagerService extends SystemService // True if the battery level is currently considered low. private boolean mBatteryLevelLow; + // True if we are currently in device idle mode. + private boolean mDeviceIdleMode; + // True if theater mode is enabled private boolean mTheaterModeEnabled; @@ -1079,6 +1083,9 @@ public final class PowerManagerService extends SystemService case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON: Slog.i(TAG, "Going to sleep due to power button (uid " + uid +")..."); break; + case PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON: + Slog.i(TAG, "Going to sleep due to sleep button (uid " + uid +")..."); + break; case PowerManager.GO_TO_SLEEP_REASON_HDMI: Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")..."); break; @@ -1398,12 +1405,15 @@ public final class PowerManagerService extends SystemService case PowerManager.DOZE_WAKE_LOCK: mWakeLockSummary |= WAKE_LOCK_DOZE; break; + case PowerManager.DRAW_WAKE_LOCK: + mWakeLockSummary |= WAKE_LOCK_DRAW; + break; } } // Cancel wake locks that make no sense based on the current state. if (mWakefulness != WAKEFULNESS_DOZING) { - mWakeLockSummary &= ~WAKE_LOCK_DOZE; + mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW); } if (mWakefulness == WAKEFULNESS_ASLEEP || (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) { @@ -1422,6 +1432,9 @@ public final class PowerManagerService extends SystemService mWakeLockSummary |= WAKE_LOCK_CPU; } } + if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) { + mWakeLockSummary |= WAKE_LOCK_CPU; + } if (DEBUG_SPEW) { Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness=" @@ -1845,6 +1858,10 @@ public final class PowerManagerService extends SystemService if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) { mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager; + if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND + && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) { + mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE; + } mDisplayPowerRequest.dozeScreenBrightness = mDozeScreenBrightnessOverrideFromDreamManager; } else { @@ -1886,6 +1903,7 @@ public final class PowerManagerService extends SystemService } } mScreenBrightnessBoostInProgress = false; + mNotifier.onScreenBrightnessBoostChanged(); userActivityNoUpdateLocked(now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); } @@ -2164,6 +2182,12 @@ public final class PowerManagerService extends SystemService } } + private boolean isDeviceIdleModeInternal() { + synchronized (mLock) { + return mDeviceIdleMode; + } + } + private void handleBatteryStateChangedLocked() { mDirty |= DIRTY_BATTERY_STATE; updatePowerStateLocked(); @@ -2261,7 +2285,10 @@ public final class PowerManagerService extends SystemService Slog.i(TAG, "Brightness boost activated (uid " + uid +")..."); mLastScreenBrightnessBoostTime = eventTime; - mScreenBrightnessBoostInProgress = true; + if (!mScreenBrightnessBoostInProgress) { + mScreenBrightnessBoostInProgress = true; + mNotifier.onScreenBrightnessBoostChanged(); + } mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST; userActivityNoUpdateLocked(eventTime, @@ -2270,6 +2297,12 @@ public final class PowerManagerService extends SystemService } } + private boolean isScreenBrightnessBoostedInternal() { + synchronized (mLock) { + return mScreenBrightnessBoostInProgress; + } + } + /** * Called when a screen brightness boost timeout has occurred. * @@ -2712,6 +2745,8 @@ public final class PowerManagerService extends SystemService return "PROXIMITY_SCREEN_OFF_WAKE_LOCK"; case PowerManager.DOZE_WAKE_LOCK: return "DOZE_WAKE_LOCK "; + case PowerManager.DRAW_WAKE_LOCK: + return "DRAW_WAKE_LOCK "; default: return "??? "; } @@ -3034,6 +3069,16 @@ public final class PowerManagerService extends SystemService } } + @Override // Binder call + public boolean isDeviceIdleMode() { + final long ident = Binder.clearCallingIdentity(); + try { + return isDeviceIdleModeInternal(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * Reboots the device. * @@ -3202,6 +3247,16 @@ public final class PowerManagerService extends SystemService } @Override // Binder call + public boolean isScreenBrightnessBoosted() { + final long ident = Binder.clearCallingIdentity(); + try { + return isScreenBrightnessBoostedInternal(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -3279,5 +3334,12 @@ public final class PowerManagerService extends SystemService mLowPowerModeListeners.add(listener); } } + + @Override + public void setDeviceIdleMode(boolean enabled) { + synchronized (mLock) { + mDeviceIdleMode = enabled; + } + } } } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index da11387..1e0185d 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -39,6 +39,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.os.Vibrator; import android.os.SystemVibrator; import android.os.storage.IMountService; @@ -202,6 +203,11 @@ public final class ShutdownThread extends Thread { * @param confirm true if user confirmation is needed before shutting down. */ public static void rebootSafeMode(final Context context, boolean confirm) { + UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); + if (um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + return; + } + mReboot = true; mRebootSafeMode = true; mRebootReason = null; diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java index ddf02e9..2b2b2ac 100644 --- a/services/core/java/com/android/server/search/SearchManagerService.java +++ b/services/core/java/com/android/server/search/SearchManagerService.java @@ -34,7 +34,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.os.Binder; -import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; @@ -265,7 +264,7 @@ public class SearchManagerService extends ISearchManager.Stub { } @Override - public boolean launchAssistAction(int requestType, String hint, int userHandle) { + public boolean launchAssistAction(String hint, int userHandle) { ComponentName comp = getAssistIntent(userHandle); if (comp == null) { return false; @@ -275,7 +274,8 @@ public class SearchManagerService extends ISearchManager.Stub { Intent intent = new Intent(Intent.ACTION_ASSIST); intent.setComponent(comp); IActivityManager am = ActivityManagerNative.getDefault(); - return am.launchAssistIntent(intent, requestType, hint, userHandle); + return am.launchAssistIntent(intent, ActivityManager.ASSIST_CONTEXT_BASIC, hint, + userHandle); } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index cf2ed07..184224b 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -79,7 +79,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { public void binderDied() { Slog.i(TAG, "binder died for pkg=" + pkg); - disableInternal(userId, 0, token, pkg); + disableForUser(0, token, pkg, userId); token.unlinkToDeath(this, 0); } } @@ -194,10 +194,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub { @Override public void disable(int what, IBinder token, String pkg) { - disableInternal(mCurrentUserId, what, token, pkg); + disableForUser(what, token, pkg, mCurrentUserId); } - private void disableInternal(int userId, int what, IBinder token, String pkg) { + @Override + public void disableForUser(int what, IBinder token, String pkg, int userId) { enforceStatusBar(); synchronized (mLock) { @@ -454,6 +455,35 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } + @Override + public void appTransitionPending() { + if (mBar != null) { + try { + mBar.appTransitionPending(); + } catch (RemoteException ex) {} + } + } + + @Override + public void appTransitionCancelled() { + if (mBar != null) { + try { + mBar.appTransitionCancelled(); + } catch (RemoteException ex) {} + } + } + + @Override + public void appTransitionStarting(long statusBarAnimationsStartTime, + long statusBarAnimationsDuration) { + if (mBar != null) { + try { + mBar.appTransitionStarting( + statusBarAnimationsStartTime, statusBarAnimationsDuration); + } catch (RemoteException ex) {} + } + } + private void enforceStatusBar() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, "StatusBarManagerService"); @@ -625,7 +655,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } - // ================================================================================ // Can be called from any thread // ================================================================================ diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java index 111c09b..cbbcb0e 100644 --- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java +++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java @@ -18,8 +18,7 @@ package com.android.server.storage; import com.android.server.EventLogTags; import com.android.server.SystemService; -import com.android.server.pm.PackageManagerService; - +import com.android.server.pm.InstructionSets; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -75,6 +74,8 @@ import dalvik.system.VMRuntime; public class DeviceStorageMonitorService extends SystemService { static final String TAG = "DeviceStorageMonitorService"; + // TODO: extend to watch and manage caches on all private volumes + static final boolean DEBUG = false; static final boolean localLOGV = false; @@ -221,7 +222,7 @@ public class DeviceStorageMonitorService extends SystemService { try { if (localLOGV) Slog.i(TAG, "Clearing cache"); IPackageManager.Stub.asInterface(ServiceManager.getService("package")). - freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver); + freeStorageAndNotify(null, mMemCacheTrimToThreshold, mClearCacheObserver); } catch (RemoteException e) { Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e); mClearingCache = false; @@ -341,7 +342,7 @@ public class DeviceStorageMonitorService extends SystemService { } private static boolean isBootImageOnDisk() { - for (String instructionSet : PackageManagerService.getAllDexCodeInstructionSets()) { + for (String instructionSet : InstructionSets.getAllDexCodeInstructionSets()) { if (!VMRuntime.isBootClassPathOnDisk(instructionSet)) { return false; } @@ -472,7 +473,7 @@ public class DeviceStorageMonitorService extends SystemService { Notification notification = new Notification.Builder(context) .setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full) .setTicker(title) - .setColor(context.getResources().getColor( + .setColor(context.getColor( com.android.internal.R.color.system_notification_accent_color)) .setContentTitle(title) .setContentText(details) diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 57b204d..fb7d186 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -40,6 +40,7 @@ import android.util.Slog; import android.service.trust.ITrustAgentService; import android.service.trust.ITrustAgentServiceCallback; +import java.util.Collections; import java.util.List; /** @@ -115,7 +116,7 @@ public class TrustAgentWrapper { } mTrusted = true; mMessage = (CharSequence) msg.obj; - boolean initiatedByUser = msg.arg1 != 0; + int flags = msg.arg1; long durationMs = msg.getData().getLong(DATA_DURATION); if (durationMs > 0) { final long duration; @@ -140,8 +141,8 @@ public class TrustAgentWrapper { } mTrustManagerService.mArchive.logGrantTrust(mUserId, mName, (mMessage != null ? mMessage.toString() : null), - durationMs, initiatedByUser); - mTrustManagerService.updateTrust(mUserId, initiatedByUser); + durationMs, flags); + mTrustManagerService.updateTrust(mUserId, flags); break; case MSG_TRUST_TIMEOUT: if (DEBUG) Slog.v(TAG, "Trust timed out : " + mName.flattenToShortString()); @@ -155,7 +156,7 @@ public class TrustAgentWrapper { if (msg.what == MSG_REVOKE_TRUST) { mTrustManagerService.mArchive.logRevokeTrust(mUserId, mName); } - mTrustManagerService.updateTrust(mUserId, false); + mTrustManagerService.updateTrust(mUserId, 0); break; case MSG_RESTART_TIMEOUT: destroy(); @@ -170,7 +171,7 @@ public class TrustAgentWrapper { if (DEBUG) Log.v(TAG, "Re-enabling agent because it acknowledged " + "enabled features: " + mName); mTrustDisabledByDpm = false; - mTrustManagerService.updateTrust(mUserId, false); + mTrustManagerService.updateTrust(mUserId, 0); } } else { if (DEBUG) Log.w(TAG, "Ignoring MSG_SET_TRUST_AGENT_FEATURES_COMPLETED " @@ -184,7 +185,7 @@ public class TrustAgentWrapper { mMessage = null; } mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust); - mTrustManagerService.updateTrust(mUserId, false); + mTrustManagerService.updateTrust(mUserId, 0); break; } } @@ -193,12 +194,12 @@ public class TrustAgentWrapper { private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() { @Override - public void grantTrust(CharSequence userMessage, long durationMs, boolean initiatedByUser) { + public void grantTrust(CharSequence userMessage, long durationMs, int flags) { if (DEBUG) Slog.v(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs - + ", initiatedByUser = " + initiatedByUser + ")"); + + ", flags = " + flags + ")"); Message msg = mHandler.obtainMessage( - MSG_GRANT_TRUST, initiatedByUser ? 1 : 0, 0, userMessage); + MSG_GRANT_TRUST, flags, 0, userMessage); msg.getData().putLong(DATA_DURATION, durationMs); msg.sendToTarget(); } @@ -271,13 +272,14 @@ public class TrustAgentWrapper { alarmFilter.addDataScheme(mAlarmIntent.getScheme()); final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME); alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL); - mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null); // Schedules a restart for when connecting times out. If the connection succeeds, // the restart is canceled in mCallback's onConnected. scheduleRestart(); mBound = context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, user); - if (!mBound) { + if (mBound) { + mContext.registerReceiver(mBroadcastReceiver, alarmFilter, PERMISSION, null); + } else { Log.e(TAG, "Can't bind to TrustAgent " + mName.flattenToShortString()); } } @@ -359,6 +361,8 @@ public class TrustAgentWrapper { mSetTrustAgentFeaturesToken = new Binder(); mTrustAgentService.onConfigure(config, mSetTrustAgentFeaturesToken); } + } else { + mTrustAgentService.onConfigure(Collections.EMPTY_LIST, null); } final long maxTimeToLock = dpm.getMaximumTimeToLock(null); if (maxTimeToLock != mMaximumTimeToLock) { @@ -377,7 +381,7 @@ public class TrustAgentWrapper { } if (mTrustDisabledByDpm != trustDisabled) { mTrustDisabledByDpm = trustDisabled; - mTrustManagerService.updateTrust(mUserId, false); + mTrustManagerService.updateTrust(mUserId, 0); } return trustDisabled; } @@ -404,6 +408,7 @@ public class TrustAgentWrapper { mTrustManagerService.mArchive.logAgentStopped(mUserId, mName); mContext.unbindService(mConnection); mBound = false; + mContext.unregisterReceiver(mBroadcastReceiver); mTrustAgentService = null; mSetTrustAgentFeaturesToken = null; mHandler.sendEmptyMessage(MSG_REVOKE_TRUST); diff --git a/services/core/java/com/android/server/trust/TrustArchive.java b/services/core/java/com/android/server/trust/TrustArchive.java index 7253716..fd63d48 100644 --- a/services/core/java/com/android/server/trust/TrustArchive.java +++ b/services/core/java/com/android/server/trust/TrustArchive.java @@ -19,6 +19,7 @@ package com.android.server.trust; import android.content.ComponentName; import android.os.SystemClock; import android.os.UserHandle; +import android.service.trust.TrustAgentService; import android.util.TimeUtils; import java.io.PrintWriter; @@ -48,20 +49,20 @@ public class TrustArchive { // grantTrust final String message; final long duration; - final boolean userInitiated; + final int flags; // managingTrust final boolean managingTrust; private Event(int type, int userId, ComponentName agent, String message, - long duration, boolean userInitiated, boolean managingTrust) { + long duration, int flags, boolean managingTrust) { this.type = type; this.userId = userId; this.agent = agent; this.elapsedTimestamp = SystemClock.elapsedRealtime(); this.message = message; this.duration = duration; - this.userInitiated = userInitiated; + this.flags = flags; this.managingTrust = managingTrust; } } @@ -69,33 +70,33 @@ public class TrustArchive { ArrayDeque<Event> mEvents = new ArrayDeque<Event>(); public void logGrantTrust(int userId, ComponentName agent, String message, - long duration, boolean userInitiated) { + long duration, int flags) { addEvent(new Event(TYPE_GRANT_TRUST, userId, agent, message, duration, - userInitiated, false)); + flags, false)); } public void logRevokeTrust(int userId, ComponentName agent) { - addEvent(new Event(TYPE_REVOKE_TRUST, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_REVOKE_TRUST, userId, agent, null, 0, 0, false)); } public void logTrustTimeout(int userId, ComponentName agent) { - addEvent(new Event(TYPE_TRUST_TIMEOUT, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_TRUST_TIMEOUT, userId, agent, null, 0, 0, false)); } public void logAgentDied(int userId, ComponentName agent) { - addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_AGENT_DIED, userId, agent, null, 0, 0, false)); } public void logAgentConnected(int userId, ComponentName agent) { - addEvent(new Event(TYPE_AGENT_CONNECTED, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_AGENT_CONNECTED, userId, agent, null, 0, 0, false)); } public void logAgentStopped(int userId, ComponentName agent) { - addEvent(new Event(TYPE_AGENT_STOPPED, userId, agent, null, 0, false, false)); + addEvent(new Event(TYPE_AGENT_STOPPED, userId, agent, null, 0, 0, false)); } public void logManagingTrust(int userId, ComponentName agent, boolean managing) { - addEvent(new Event(TYPE_MANAGING_TRUST, userId, agent, null, 0, false, managing)); + addEvent(new Event(TYPE_MANAGING_TRUST, userId, agent, null, 0, 0, managing)); } private void addEvent(Event e) { @@ -129,8 +130,8 @@ public class TrustArchive { } switch (ev.type) { case TYPE_GRANT_TRUST: - writer.printf(", message=\"%s\", duration=%s, initiatedByUser=%d", - ev.message, formatDuration(ev.duration), ev.userInitiated ? 1 : 0); + writer.printf(", message=\"%s\", duration=%s, flags=%s", + ev.message, formatDuration(ev.duration), dumpGrantFlags(ev.flags)); break; case TYPE_MANAGING_TRUST: writer.printf(", managingTrust=" + ev.managingTrust); @@ -184,4 +185,20 @@ public class TrustArchive { return "Unknown(" + type + ")"; } } + + private String dumpGrantFlags(int flags) { + StringBuilder sb = new StringBuilder(); + if ((flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0) { + if (sb.length() != 0) sb.append('|'); + sb.append("INITIATED_BY_USER"); + } + if ((flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0) { + if (sb.length() != 0) sb.append('|'); + sb.append("DISMISS_KEYGUARD"); + } + if (sb.length() == 0) { + sb.append('0'); + } + return sb.toString(); + } } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index cd1c9a5..7d2fb43 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -179,11 +179,11 @@ public class TrustManagerService extends SystemService { private void updateTrustAll() { List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */); for (UserInfo userInfo : userInfos) { - updateTrust(userInfo.id, false); + updateTrust(userInfo.id, 0); } } - public void updateTrust(int userId, boolean initiatedByUser) { + public void updateTrust(int userId, int flags) { dispatchOnTrustManagedChanged(aggregateIsTrustManaged(userId), userId); boolean trusted = aggregateIsTrusted(userId); boolean changed; @@ -191,7 +191,7 @@ public class TrustManagerService extends SystemService { changed = mUserIsTrusted.get(userId) != trusted; mUserIsTrusted.put(userId, trusted); } - dispatchOnTrustChanged(trusted, userId, initiatedByUser); + dispatchOnTrustChanged(trusted, userId, flags); if (changed) { refreshDeviceLockedForUser(userId); } @@ -281,7 +281,7 @@ public class TrustManagerService extends SystemService { if (userId == UserHandle.USER_ALL) { updateTrustAll(); } else { - updateTrust(userId, false /* initiatedByUser */); + updateTrust(userId, 0); } } } @@ -394,7 +394,7 @@ public class TrustManagerService extends SystemService { } } if (trustMayHaveChanged) { - updateTrust(userId, false); + updateTrust(userId, 0); } refreshAgentList(userId); } @@ -587,11 +587,11 @@ public class TrustManagerService extends SystemService { } } - private void dispatchOnTrustChanged(boolean enabled, int userId, boolean initiatedByUser) { - if (!enabled) initiatedByUser = false; + private void dispatchOnTrustChanged(boolean enabled, int userId, int flags) { + if (!enabled) flags = 0; for (int i = 0; i < mTrustListeners.size(); i++) { try { - mTrustListeners.get(i).onTrustChanged(enabled, userId, initiatedByUser); + mTrustListeners.get(i).onTrustChanged(enabled, userId, flags); } catch (DeadObjectException e) { Slog.d(TAG, "Removing dead TrustListener."); mTrustListeners.remove(i); @@ -691,6 +691,20 @@ public class TrustManagerService extends SystemService { return isDeviceLockedInner(userId); } + @Override + public boolean isDeviceSecure(int userId) throws RemoteException { + userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, + false /* allowAll */, true /* requireFull */, "isDeviceSecure", null); + userId = resolveProfileParent(userId); + + long token = Binder.clearCallingIdentity(); + try { + return new LockPatternUtils(mContext).isSecure(userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + private void enforceReportPermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events"); diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 50b2262..ac8ad30 100644 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -55,11 +55,9 @@ import android.util.SparseBooleanArray; import android.view.KeyEvent; import android.view.Surface; -import com.android.internal.os.SomeArgs; import com.android.server.SystemService; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; @@ -104,7 +102,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { }; private int mCurrentIndex = 0; private int mCurrentMaxIndex = 0; - private final boolean mUseMasterVolume; // TODO: Should handle STANDBY case. private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); @@ -119,8 +116,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { mContext = context; mListener = listener; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - mUseMasterVolume = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_useMasterVolume); mHal.init(); } @@ -141,12 +136,10 @@ class TvInputHardwareManager implements TvInputHal.Callback { } else { Slog.w(TAG, "HdmiControlService is not available"); } - if (!mUseMasterVolume) { - final IntentFilter filter = new IntentFilter(); - filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); - filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); - mContext.registerReceiver(mVolumeReceiver, filter); - } + final IntentFilter filter = new IntentFilter(); + filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); + filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); + mContext.registerReceiver(mVolumeReceiver, filter); updateVolume(); } } @@ -547,7 +540,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { } private float getMediaStreamVolume() { - return mUseMasterVolume ? 1.0f : ((float) mCurrentIndex / (float) mCurrentMaxIndex); + return (float) mCurrentIndex / (float) mCurrentMaxIndex; } private class Connection implements IBinder.DeathRecipient { @@ -699,15 +692,14 @@ class TvInputHardwareManager implements TvInputHal.Callback { private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) { sinks.clear(); - ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); + ArrayList<AudioDevicePort> devicePorts = new ArrayList<AudioDevicePort>(); if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { return; } int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); - for (AudioPort port : devicePorts) { - AudioDevicePort devicePort = (AudioDevicePort) port; - if ((devicePort.type() & sinkDevice) != 0) { - sinks.add(devicePort); + for (AudioDevicePort port : devicePorts) { + if ((port.type() & sinkDevice) != 0) { + sinks.add(port); } } } @@ -716,14 +708,13 @@ class TvInputHardwareManager implements TvInputHal.Callback { if (type == AudioManager.DEVICE_NONE) { return null; } - ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); + ArrayList<AudioDevicePort> devicePorts = new ArrayList<AudioDevicePort>(); if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { return null; } - for (AudioPort port : devicePorts) { - AudioDevicePort devicePort = (AudioDevicePort) port; - if (devicePort.type() == type && devicePort.address().equals(address)) { - return devicePort; + for (AudioDevicePort port : devicePorts) { + if (port.type() == type && port.address().equals(address)) { + return port; } } return null; diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 5375bfc..5972247 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -18,8 +18,6 @@ package com.android.server.tv; import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED; import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY; -import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED; -import static android.media.tv.TvInputManager.INPUT_STATE_UNKNOWN; import android.app.ActivityManager; import android.content.BroadcastReceiver; @@ -204,6 +202,14 @@ public final class TvInputManagerService extends SystemService { } @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + // The input list needs to be updated in any cases, regardless of whether + // it happened to the whole package or a specific component. Returning true so that + // the update can be handled in {@link #onSomePackagesChanged}. + return true; + } + + @Override public void onPackageRemoved(String packageName, int uid) { synchronized (mLock) { UserState userState = getUserStateLocked(getChangingUserId()); @@ -792,7 +798,7 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { UserState userState = getUserStateLocked(resolvedUserId); TvInputState state = userState.inputMap.get(inputId); - return state == null ? INPUT_STATE_UNKNOWN : state.state; + return state == null ? INPUT_STATE_CONNECTED : state.state; } } finally { Binder.restoreCallingIdentity(identity); @@ -1341,6 +1347,108 @@ public final class TvInputManagerService extends SystemService { } @Override + public void timeShiftPause(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftPause"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftPause(); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftPause", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void timeShiftResume(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftResume"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftResume(); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftResume", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftSeekTo"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftSeekTo(timeMs); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftSeekTo", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void timeShiftSetPlaybackRate(IBinder sessionToken, float rate, int audioMode, + int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftSetPlaybackRate"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftSetPlaybackRate(rate, audioMode); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftSetPlaybackRate", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable, + int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftEnablePositionTracking"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftEnablePositionTracking(enable); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftEnablePositionTracking", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public List<TvInputHardwareInfo> getHardwareList() throws RemoteException { if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE) != PackageManager.PERMISSION_GRANTED) { @@ -1798,7 +1906,7 @@ public final class TvInputManagerService extends SystemService { for (TvInputState inputState : userState.inputMap.values()) { if (inputState.info.getComponent().equals(component) - && inputState.state != INPUT_STATE_DISCONNECTED) { + && inputState.state != INPUT_STATE_CONNECTED) { notifyInputStateChangedLocked(userState, inputState.info.getId(), inputState.state, null); } @@ -1847,13 +1955,6 @@ public final class TvInputManagerService extends SystemService { serviceState.callback = null; abortPendingCreateSessionRequestsLocked(serviceState, null, mUserId); - - for (TvInputState inputState : userState.inputMap.values()) { - if (inputState.info.getComponent().equals(component)) { - notifyInputStateChangedLocked(userState, inputState.info.getId(), - INPUT_STATE_DISCONNECTED, null); - } - } } } } @@ -2144,6 +2245,58 @@ public final class TvInputManagerService extends SystemService { } } } + + @Override + public void onTimeShiftStatusChanged(int status) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTimeShiftStatusChanged()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTimeShiftStatusChanged", e); + } + } + } + + @Override + public void onTimeShiftStartPositionChanged(long timeMs) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTimeShiftStartPositionChanged()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e); + } + } + } + + @Override + public void onTimeShiftCurrentPositionChanged(long timeMs) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTimeShiftCurrentPositionChanged()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs, + mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e); + } + } + } } private static final class WatchLogHandler extends Handler { @@ -2346,9 +2499,6 @@ public final class TvInputManagerService extends SystemService { } private static class SessionNotFoundException extends IllegalArgumentException { - public SessionNotFoundException() { - } - public SessionNotFoundException(String name) { super(name); } diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java index 7f7aae3..8fc979c 100644 --- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java +++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java @@ -16,29 +16,21 @@ package com.android.server.updates; +import com.android.server.EventLogTags; + import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.provider.Settings; -import android.util.Base64; import android.util.EventLog; import android.util.Slog; -import com.android.server.EventLogTags; - -import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; -import java.io.InputStream; import java.io.IOException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; +import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.Signature; import libcore.io.IoUtils; import libcore.io.Streams; @@ -47,13 +39,9 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { private static final String TAG = "ConfigUpdateInstallReceiver"; - private static final String EXTRA_CONTENT_PATH = "CONTENT_PATH"; private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH"; - private static final String EXTRA_SIGNATURE = "SIGNATURE"; private static final String EXTRA_VERSION_NUMBER = "VERSION"; - private static final String UPDATE_CERTIFICATE_KEY = "config_update_certificate"; - protected final File updateDir; protected final File updateContent; protected final File updateVersion; @@ -72,16 +60,12 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { @Override public void run() { try { - // get the certificate from Settings.Secure - X509Certificate cert = getCert(context.getContentResolver()); // get the content path from the extras byte[] altContent = getAltContent(context, intent); // get the version from the extras int altVersion = getVersionFromIntent(intent); // get the previous value from the extras String altRequiredHash = getRequiredHashFromIntent(intent); - // get the signature from the extras - String altSig = getSignatureFromIntent(intent); // get the version currently being used int currentVersion = getCurrentVersion(); // get the hash of the currently used value @@ -91,10 +75,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { } else if (!verifyPreviousHash(currentHash, altRequiredHash)) { EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, "Current hash did not match required value"); - } else if (!verifySignature(altContent, altVersion, altRequiredHash, altSig, - cert)) { - EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, - "Signature did not verify"); } else { // install the new content Slog.i(TAG, "Found new update, installing..."); @@ -115,20 +95,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { }.start(); } - private X509Certificate getCert(ContentResolver cr) { - // get the cert from settings - String cert = Settings.Secure.getString(cr, UPDATE_CERTIFICATE_KEY); - // convert it into a real certificate - try { - byte[] derCert = Base64.decode(cert.getBytes(), Base64.DEFAULT); - InputStream istream = new ByteArrayInputStream(derCert); - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - return (X509Certificate) cf.generateCertificate(istream); - } catch (CertificateException e) { - throw new IllegalStateException("Got malformed certificate from settings, ignoring"); - } - } - private Uri getContentFromIntent(Intent i) { Uri data = i.getData(); if (data == null) { @@ -153,14 +119,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { return extraValue.trim(); } - private String getSignatureFromIntent(Intent i) { - String extraValue = i.getStringExtra(EXTRA_SIGNATURE); - if (extraValue == null) { - throw new IllegalStateException("Missing required signature, ignoring."); - } - return extraValue.trim(); - } - private int getCurrentVersion() throws NumberFormatException { try { String strVersion = IoUtils.readFileAsString(updateVersion.getCanonicalPath()).trim(); @@ -216,16 +174,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { return current.equals(required); } - private boolean verifySignature(byte[] content, int version, String requiredPrevious, - String signature, X509Certificate cert) throws Exception { - Signature signer = Signature.getInstance("SHA512withRSA"); - signer.initVerify(cert); - signer.update(content); - signer.update(Long.toString(version).getBytes()); - signer.update(requiredPrevious.getBytes()); - return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT)); - } - protected void writeUpdate(File dir, File file, byte[] content) throws IOException { FileOutputStream out = null; File tmp = null; diff --git a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java index 24318df..4e53687 100644 --- a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java +++ b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java @@ -19,7 +19,6 @@ package com.android.server.updates; import android.content.Context; import android.content.Intent; import android.os.SystemProperties; -import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.util.Base64; @@ -48,39 +47,6 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver { super("/data/security/bundle", "sepolicy_bundle", "metadata/", "version"); } - private void backupContexts(File contexts) { - new File(contexts, versionPath).renameTo( - new File(contexts, versionPath + "_backup")); - - new File(contexts, macPermissionsPath).renameTo( - new File(contexts, macPermissionsPath + "_backup")); - - new File(contexts, seappContextsPath).renameTo( - new File(contexts, seappContextsPath + "_backup")); - - new File(contexts, propertyContextsPath).renameTo( - new File(contexts, propertyContextsPath + "_backup")); - - new File(contexts, fileContextsPath).renameTo( - new File(contexts, fileContextsPath + "_backup")); - - new File(contexts, sepolicyPath).renameTo( - new File(contexts, sepolicyPath + "_backup")); - - new File(contexts, serviceContextsPath).renameTo( - new File(contexts, serviceContextsPath + "_backup")); - } - - private void copyUpdate(File contexts) { - new File(updateDir, versionPath).renameTo(new File(contexts, versionPath)); - new File(updateDir, macPermissionsPath).renameTo(new File(contexts, macPermissionsPath)); - new File(updateDir, seappContextsPath).renameTo(new File(contexts, seappContextsPath)); - new File(updateDir, propertyContextsPath).renameTo(new File(contexts, propertyContextsPath)); - new File(updateDir, fileContextsPath).renameTo(new File(contexts, fileContextsPath)); - new File(updateDir, sepolicyPath).renameTo(new File(contexts, sepolicyPath)); - new File(updateDir, serviceContextsPath).renameTo(new File(contexts, serviceContextsPath)); - } - private int readInt(BufferedInputStream reader) throws IOException { int value = 0; for (int i=0; i < 4; i++) { @@ -108,17 +74,27 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver { writeUpdate(updateDir, destination, Base64.decode(chunk, Base64.DEFAULT)); } + private void deleteRecursive(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) + for (File child : fileOrDirectory.listFiles()) + deleteRecursive(child); + fileOrDirectory.delete(); + } + private void unpackBundle() throws IOException { BufferedInputStream stream = new BufferedInputStream(new FileInputStream(updateContent)); + File tmp = new File(updateDir.getParentFile(), "tmp"); try { int[] chunkLengths = readChunkLengths(stream); - installFile(new File(updateDir, versionPath), stream, chunkLengths[0]); - installFile(new File(updateDir, macPermissionsPath), stream, chunkLengths[1]); - installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[2]); - installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[3]); - installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[4]); - installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[5]); - installFile(new File(updateDir, serviceContextsPath), stream, chunkLengths[6]); + deleteRecursive(tmp); + tmp.mkdirs(); + installFile(new File(tmp, versionPath), stream, chunkLengths[0]); + installFile(new File(tmp, macPermissionsPath), stream, chunkLengths[1]); + installFile(new File(tmp, seappContextsPath), stream, chunkLengths[2]); + installFile(new File(tmp, propertyContextsPath), stream, chunkLengths[3]); + installFile(new File(tmp, fileContextsPath), stream, chunkLengths[4]); + installFile(new File(tmp, sepolicyPath), stream, chunkLengths[5]); + installFile(new File(tmp, serviceContextsPath), stream, chunkLengths[6]); } finally { IoUtils.closeQuietly(stream); } @@ -126,22 +102,22 @@ public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver { private void applyUpdate() throws IOException, ErrnoException { Slog.i(TAG, "Applying SELinux policy"); - File contexts = new File(updateDir.getParentFile(), "contexts"); + File backup = new File(updateDir.getParentFile(), "backup"); File current = new File(updateDir.getParentFile(), "current"); - File update = new File(updateDir.getParentFile(), "update"); File tmp = new File(updateDir.getParentFile(), "tmp"); if (current.exists()) { - Os.symlink(updateDir.getPath(), update.getPath()); - Os.rename(update.getPath(), current.getPath()); - } else { - Os.symlink(updateDir.getPath(), current.getPath()); + deleteRecursive(backup); + Os.rename(current.getPath(), backup.getPath()); + } + try { + Os.rename(tmp.getPath(), current.getPath()); + SystemProperties.set("selinux.reload_policy", "1"); + } catch (ErrnoException e) { + Slog.e(TAG, "Could not update selinux policy: ", e); + if (backup.exists()) { + Os.rename(backup.getPath(), current.getPath()); + } } - contexts.mkdirs(); - backupContexts(contexts); - copyUpdate(contexts); - Os.symlink(contexts.getPath(), tmp.getPath()); - Os.rename(tmp.getPath(), current.getPath()); - SystemProperties.set("selinux.reload_policy", "1"); } @Override diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java deleted file mode 100644 index 2fe68f8..0000000 --- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.updates; - -import android.util.Base64; - -import java.io.IOException; - -public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver { - - public TZInfoInstallReceiver() { - super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version"); - } - - @Override - protected void install(byte[] encodedContent, int version) throws IOException { - super.install(Base64.decode(encodedContent, Base64.DEFAULT), version); - } -} diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java new file mode 100644 index 0000000..b260e4e --- /dev/null +++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 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.updates; + +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import libcore.tzdata.update.TzDataBundleInstaller; + +/** + * An install receiver responsible for installing timezone data updates. + */ +public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver { + + private static final String TAG = "TZDataInstallReceiver"; + + private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo"); + private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/"; + private static final String UPDATE_METADATA_DIR_NAME = "metadata/"; + private static final String UPDATE_VERSION_FILE_NAME = "version"; + private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip"; + + private final TzDataBundleInstaller installer; + + public TzDataInstallReceiver() { + super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME, + UPDATE_VERSION_FILE_NAME); + installer = new TzDataBundleInstaller(TAG, TZ_DATA_DIR); + } + + @Override + protected void install(byte[] content, int version) throws IOException { + boolean valid = installer.install(content); + Slog.i(TAG, "Timezone data install valid for this device: " + valid); + // Even if !valid, we call super.install(). Only in the event of an exception should we + // not. If we didn't do this we could attempt to install repeatedly. + super.install(content, version); + } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index bd2e923..54be380 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -20,6 +20,7 @@ import static android.os.ParcelFileDescriptor.*; import android.app.ActivityManagerNative; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.IUserSwitchObserver; import android.app.IWallpaperManager; import android.app.IWallpaperManagerCallback; @@ -164,6 +165,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { final IWindowManager mIWindowManager; final IPackageManager mIPackageManager; final MyPackageMonitor mMonitor; + final AppOpsManager mAppOpsManager; WallpaperData mLastWallpaper; /** @@ -478,6 +480,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); mIPackageManager = AppGlobals.getPackageManager(); + mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mMonitor = new MyPackageMonitor(); mMonitor.register(context, null, UserHandle.ALL, true); getWallpaperDir(UserHandle.USER_OWNER).mkdirs(); @@ -535,6 +538,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { @Override public void onUserSwitchComplete(int newUserId) throws RemoteException { } + + @Override + public void onForegroundProfileSwitch(int newProfileId) { + // Ignore. + } }); } catch (RemoteException e) { // TODO Auto-generated catch block @@ -613,8 +621,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } - public void clearWallpaper() { + public void clearWallpaper(String callingPackage) { if (DEBUG) Slog.v(TAG, "clearWallpaper"); + checkPermission(android.Manifest.permission.SET_WALLPAPER); + if (!isWallpaperSupported(callingPackage)) { + return; + } synchronized (mLock) { clearWallpaperLocked(false, UserHandle.getCallingUserId(), null); } @@ -622,6 +634,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) { WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper == null) { + return; + } File f = new File(getWallpaperDir(userId), WALLPAPER); if (f.exists()) { f.delete(); @@ -668,6 +683,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { Binder.restoreCallingIdentity(ident); } for (UserInfo user: users) { + // ignore managed profiles + if (user.isManagedProfile()) { + continue; + } WallpaperData wd = mWallpaperMap.get(user.id); if (wd == null) { // User hasn't started yet, so load her settings to peek at the wallpaper @@ -690,8 +709,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { return p; } - public void setDimensionHints(int width, int height) throws RemoteException { + public void setDimensionHints(int width, int height, String callingPackage) + throws RemoteException { checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); + if (!isWallpaperSupported(callingPackage)) { + return; + } synchronized (mLock) { int userId = UserHandle.getCallingUserId(); WallpaperData wallpaper = mWallpaperMap.get(userId); @@ -733,19 +756,30 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { public int getWidthHint() throws RemoteException { synchronized (mLock) { WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId()); - return wallpaper.width; + if (wallpaper != null) { + return wallpaper.width; + } else { + return 0; + } } } public int getHeightHint() throws RemoteException { synchronized (mLock) { WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId()); - return wallpaper.height; + if (wallpaper != null) { + return wallpaper.height; + } else { + return 0; + } } } - public void setDisplayPadding(Rect padding) { + public void setDisplayPadding(Rect padding, String callingPackage) { checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); + if (!isWallpaperSupported(callingPackage)) { + return; + } synchronized (mLock) { int userId = UserHandle.getCallingUserId(); WallpaperData wallpaper = mWallpaperMap.get(userId); @@ -791,6 +825,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaperUserId = UserHandle.getUserId(callingUid); } WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId); + if (wallpaper == null) { + return null; + } try { if (outParams != null) { outParams.putInt("width", wallpaper.width); @@ -814,15 +851,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { int userId = UserHandle.getCallingUserId(); synchronized (mLock) { WallpaperData wallpaper = mWallpaperMap.get(userId); - if (wallpaper.connection != null) { + if (wallpaper != null && wallpaper.connection != null) { return wallpaper.connection.mInfo; } return null; } } - public ParcelFileDescriptor setWallpaper(String name) { + public ParcelFileDescriptor setWallpaper(String name, String callingPackage) { checkPermission(android.Manifest.permission.SET_WALLPAPER); + if (!isWallpaperSupported(callingPackage)) { + return null; + } synchronized (mLock) { if (DEBUG) Slog.v(TAG, "setWallpaper"); int userId = UserHandle.getCallingUserId(); @@ -868,6 +908,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { return null; } + public void setWallpaperComponentChecked(ComponentName name, String callingPackage) { + if (isWallpaperSupported(callingPackage)) { + setWallpaperComponent(name); + } + } + + // ToDo: Remove this version of the function public void setWallpaperComponent(ComponentName name) { checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); synchronized (mLock) { @@ -1097,6 +1144,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + /** + * Certain user types do not support wallpapers (e.g. managed profiles). The check is + * implemented through through the OP_WRITE_WALLPAPER AppOp. + */ + public boolean isWallpaperSupported(String callingPackage) { + return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_WALLPAPER, Binder.getCallingUid(), + callingPackage) == AppOpsManager.MODE_ALLOWED; + } + private static JournaledFile makeJournaledFile(int userId) { final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath(); return new JournaledFile(new File(base), new File(base + ".tmp")); @@ -1135,6 +1191,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { out.endTag(null, "wp"); out.endDocument(); + stream.flush(); + FileUtils.sync(stream); stream.close(); journal.commit(); } catch (IOException e) { @@ -1172,7 +1230,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { private void loadSettingsLocked(int userId) { if (DEBUG) Slog.v(TAG, "loadSettingsLocked"); - + JournaledFile journal = makeJournaledFile(userId); FileInputStream stream = null; File file = journal.chooseForRead(); diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java index d4c5f87..ac79b36 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java @@ -40,6 +40,8 @@ public class WebViewUpdateService extends SystemService { private boolean mRelroReady32Bit = false; private boolean mRelroReady64Bit = false; + private String oldWebViewPackageName = null; + private BroadcastReceiver mWebViewUpdatedReceiver; public WebViewUpdateService(Context context) { @@ -51,9 +53,22 @@ public class WebViewUpdateService extends SystemService { mWebViewUpdatedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - String webviewPackage = "package:" + WebViewFactory.getWebViewPackageName(); - if (webviewPackage.equals(intent.getDataString())) { - onWebViewUpdateInstalled(); + + for (String packageName : WebViewFactory.getWebViewPackageNames()) { + String webviewPackage = "package:" + packageName; + + if (webviewPackage.equals(intent.getDataString())) { + String usedPackageName = + WebViewFactory.findPreferredWebViewPackage().packageName; + // Only trigger update actions if the updated package is the one that + // will be used, or the one that was in use before the update. + if (packageName.equals(usedPackageName) || + packageName.equals(oldWebViewPackageName)) { + onWebViewUpdateInstalled(); + oldWebViewPackageName = usedPackageName; + } + return; + } } } }; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 08754f9..91ce739 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -339,6 +339,7 @@ final class AccessibilityController { case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL: case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: case WindowManager.LayoutParams.TYPE_SEARCH_BAR: case WindowManager.LayoutParams.TYPE_PHONE: @@ -397,8 +398,6 @@ final class AccessibilityController { private final class MagnifiedViewport { - private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5; - private final SparseArray<WindowState> mTempWindowStates = new SparseArray<WindowState>(); @@ -411,6 +410,8 @@ final class AccessibilityController { private final Region mMagnifiedBounds = new Region(); private final Region mOldMagnifiedBounds = new Region(); + private final Path mCircularPath; + private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain(); private final WindowManager mWindowManager; @@ -425,12 +426,22 @@ final class AccessibilityController { public MagnifiedViewport() { mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE); - mBorderWidth = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP, - mContext.getResources().getDisplayMetrics()); + mBorderWidth = mContext.getResources().getDimension( + com.android.internal.R.dimen.accessibility_magnification_indicator_width); mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2); mDrawBorderInset = (int) mBorderWidth / 2; mWindow = new ViewportWindow(mContext); + + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_windowIsRound)) { + mCircularPath = new Path(); + mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); + final int centerXY = mTempPoint.x / 2; + mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW); + } else { + mCircularPath = null; + } + recomputeBoundsLocked(); } @@ -459,6 +470,10 @@ final class AccessibilityController { Region availableBounds = mTempRegion1; availableBounds.set(0, 0, screenWidth, screenHeight); + if (mCircularPath != null) { + availableBounds.setPath(mCircularPath, availableBounds); + } + Region nonMagnifiedBounds = mTempRegion4; nonMagnifiedBounds.set(0, 0, 0, 0); @@ -606,9 +621,8 @@ final class AccessibilityController { final int windowCount = windowList.size(); for (int i = 0; i < windowCount; i++) { WindowState windowState = windowList.get(i); - if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager - .LayoutParams.TYPE_UNIVERSE_BACKGROUND) - && !windowState.mWinAnimator.mEnterAnimationPending) { + if (windowState.isOnScreen() && + !windowState.mWinAnimator.mEnterAnimationPending) { outWindows.put(windowState.mLayer, windowState); } } @@ -656,7 +670,7 @@ final class AccessibilityController { TypedValue typedValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight, typedValue, true); - final int borderColor = context.getResources().getColor(typedValue.resourceId); + final int borderColor = context.getColor(typedValue.resourceId); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mBorderWidth); @@ -1237,7 +1251,6 @@ final class AccessibilityController { && windowType != WindowManager.LayoutParams.TYPE_DRAG && windowType != WindowManager.LayoutParams.TYPE_HIDDEN_NAV_CONSUMER && windowType != WindowManager.LayoutParams.TYPE_POINTER - && windowType != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND && windowType != WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index c0d54e1..9033c9c 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -22,6 +22,7 @@ import android.graphics.Bitmap; import android.graphics.Rect; import android.os.Debug; import android.os.Handler; +import android.os.IBinder; import android.os.IRemoteCallback; import android.util.Slog; import android.view.WindowManager; @@ -30,15 +31,22 @@ import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.animation.ClipRectAnimation; +import android.view.animation.ClipRectLRAnimation; +import android.view.animation.ClipRectTBAnimation; import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; +import android.view.animation.TranslateXAnimation; +import android.view.animation.TranslateYAnimation; import com.android.internal.util.DumpUtils.Dump; import com.android.server.AttributeCache; import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; +import java.util.ArrayList; +import static android.view.WindowManagerInternal.AppTransitionListener; import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation; @@ -134,6 +142,7 @@ public class AppTransition implements Dump { private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP = 5; private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN = 6; private static final int NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE = 7; + private static final int NEXT_TRANSIT_TYPE_CLIP_REVEAL = 8; private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE; // These are the possible states for the enter/exit activities during a thumbnail transition @@ -169,19 +178,26 @@ public class AppTransition implements Dump { private final Interpolator mDecelerateInterpolator; private final Interpolator mThumbnailFadeInInterpolator; private final Interpolator mThumbnailFadeOutInterpolator; - private final Interpolator mThumbnailFastOutSlowInInterpolator; + private final Interpolator mLinearOutSlowInInterpolator; + private final Interpolator mFastOutSlowInInterpolator; + private final LinearInterpolator mLinearInterpolator; private int mCurrentUserId = 0; + private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>(); + AppTransition(Context context, Handler h) { mContext = context; mH = h; + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.linear_out_slow_in); + mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_slow_in); + mLinearInterpolator = new LinearInterpolator(); mConfigShortAnimTime = context.getResources().getInteger( com.android.internal.R.integer.config_shortAnimTime); mDecelerateInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.decelerate_cubic); - mThumbnailFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.fast_out_slow_in); mThumbnailFadeInInterpolator = new Interpolator() { @Override public float getInterpolation(float input) { @@ -276,12 +292,18 @@ public class AppTransition implements Dump { void prepare() { if (!isRunning()) { mAppTransitionState = APP_STATE_IDLE; + notifyAppTransitionPendingLocked(); } } - void goodToGo() { + void goodToGo(AppWindowAnimator openingAppAnimator, AppWindowAnimator closingAppAnimator) { mNextAppTransition = TRANSIT_UNSET; mAppTransitionState = APP_STATE_RUNNING; + notifyAppTransitionStartingLocked( + openingAppAnimator != null ? openingAppAnimator.mAppToken.token : null, + closingAppAnimator != null ? closingAppAnimator.mAppToken.token : null, + openingAppAnimator != null ? openingAppAnimator.animation : null, + closingAppAnimator != null ? closingAppAnimator.animation : null); } void clear() { @@ -294,6 +316,38 @@ public class AppTransition implements Dump { setAppTransition(AppTransition.TRANSIT_UNSET); clear(); setReady(); + notifyAppTransitionCancelledLocked(); + } + + void registerListenerLocked(AppTransitionListener listener) { + mListeners.add(listener); + } + + public void notifyAppTransitionFinishedLocked(AppWindowAnimator animator) { + IBinder token = animator != null ? animator.mAppToken.token : null; + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onAppTransitionFinishedLocked(token); + } + } + + private void notifyAppTransitionPendingLocked() { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onAppTransitionPendingLocked(); + } + } + + private void notifyAppTransitionCancelledLocked() { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onAppTransitionCancelledLocked(); + } + } + + private void notifyAppTransitionStartingLocked(IBinder openToken, + IBinder closeToken, Animation openAnimation, Animation closeAnimation) { + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onAppTransitionStartingLocked(openToken, closeToken, openAnimation, + closeAnimation); + } } private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) { @@ -447,6 +501,86 @@ public class AppTransition implements Dump { return a; } + private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame) { + final Animation anim; + if (enter) { + // Reveal will expand and move faster in horizontal direction + + // Start from upper left of start and move to final position + final int appWidth = appFrame.width(); + final int appHeight = appFrame.height(); + + // Start from size of launch icon, expand to full width/height + Animation clipAnimLR = new ClipRectLRAnimation( + (appWidth - mNextAppTransitionStartWidth) / 2, + (appWidth + mNextAppTransitionStartWidth) / 2, 0, appWidth); + clipAnimLR.setInterpolator(mLinearOutSlowInInterpolator); + clipAnimLR.setDuration(DEFAULT_APP_TRANSITION_DURATION); + Animation clipAnimTB = new ClipRectTBAnimation( + (appHeight - mNextAppTransitionStartHeight) / 2, + (appHeight + mNextAppTransitionStartHeight) / 2, 0, appHeight); + clipAnimTB.setInterpolator(mFastOutSlowInInterpolator); + clipAnimTB.setDuration(DEFAULT_APP_TRANSITION_DURATION); + + // Start from middle of launch icon area, move to 0, 0 + int startMiddleX = mNextAppTransitionStartX + + (mNextAppTransitionStartWidth - appWidth) / 2 - appFrame.left; + int startMiddleY = mNextAppTransitionStartY + + (mNextAppTransitionStartHeight - appHeight) / 2 - appFrame.top; + + TranslateXAnimation translateX = new TranslateXAnimation( + Animation.ABSOLUTE, startMiddleX, Animation.ABSOLUTE, 0); + translateX.setInterpolator(mLinearOutSlowInInterpolator); + translateX.setDuration(DEFAULT_APP_TRANSITION_DURATION); + TranslateYAnimation translateY = new TranslateYAnimation( + Animation.ABSOLUTE, startMiddleY, Animation.ABSOLUTE, 0); + translateY.setInterpolator(mFastOutSlowInInterpolator); + translateY.setDuration(DEFAULT_APP_TRANSITION_DURATION); + + // Quick fade-in from icon to app window + final int alphaDuration = 100; + AlphaAnimation alpha = new AlphaAnimation(0.1f, 1); + alpha.setDuration(alphaDuration); + alpha.setInterpolator(mLinearInterpolator); + + AnimationSet set = new AnimationSet(false); + set.addAnimation(clipAnimLR); + set.addAnimation(clipAnimTB); + set.addAnimation(translateX); + set.addAnimation(translateY); + set.addAnimation(alpha); + set.initialize(appWidth, appHeight, appWidth, appHeight); + anim = set; + } else { + final long duration; + switch (transit) { + case TRANSIT_ACTIVITY_OPEN: + case TRANSIT_ACTIVITY_CLOSE: + duration = mConfigShortAnimTime; + break; + default: + duration = DEFAULT_APP_TRANSITION_DURATION; + break; + } + if (transit == TRANSIT_WALLPAPER_INTRA_OPEN || + transit == TRANSIT_WALLPAPER_INTRA_CLOSE) { + // If we are on top of the wallpaper, we need an animation that + // correctly handles the wallpaper staying static behind all of + // the animated elements. To do this, will just have the existing + // element fade out. + anim = new AlphaAnimation(1, 0); + anim.setDetachWallpaper(true); + } else { + // For normal animations, the exiting element just holds in place. + anim = new AlphaAnimation(1, 1); + } + anim.setInterpolator(mDecelerateInterpolator); + anim.setDuration(duration); + anim.setFillAfter(true); + } + return anim; + } + /** * Prepares the specified animation with a standard duration, interpolator, etc. */ @@ -522,14 +656,14 @@ public class AppTransition implements Dump { Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, mNextAppTransitionStartX + (thumbWidth / 2f), mNextAppTransitionStartY + (thumbHeight / 2f)); - scale.setInterpolator(mThumbnailFastOutSlowInInterpolator); + scale.setInterpolator(mFastOutSlowInInterpolator); scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); Animation alpha = new AlphaAnimation(1, 0); alpha.setInterpolator(mThumbnailFadeOutInterpolator); alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION); Animation translate = new TranslateAnimation(0, 0, 0, -unscaledStartY + mNextAppTransitionInsets.top); - translate.setInterpolator(mThumbnailFastOutSlowInInterpolator); + translate.setInterpolator(mFastOutSlowInInterpolator); translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); // This AnimationSet uses the Interpolators assigned above. @@ -543,14 +677,14 @@ public class AppTransition implements Dump { Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, mNextAppTransitionStartX + (thumbWidth / 2f), mNextAppTransitionStartY + (thumbHeight / 2f)); - scale.setInterpolator(mThumbnailFastOutSlowInInterpolator); + scale.setInterpolator(mFastOutSlowInInterpolator); scale.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); Animation alpha = new AlphaAnimation(0f, 1f); alpha.setInterpolator(mThumbnailFadeInInterpolator); alpha.setDuration(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION); Animation translate = new TranslateAnimation(0, 0, -unscaledStartY + mNextAppTransitionInsets.top, 0); - translate.setInterpolator(mThumbnailFastOutSlowInInterpolator); + translate.setInterpolator(mFastOutSlowInInterpolator); translate.setDuration(THUMBNAIL_APP_TRANSITION_DURATION); // This AnimationSet uses the Interpolators assigned above. @@ -562,7 +696,7 @@ public class AppTransition implements Dump { } return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, 0, - mThumbnailFastOutSlowInInterpolator); + mFastOutSlowInInterpolator); } /** @@ -698,7 +832,7 @@ public class AppTransition implements Dump { int duration = Math.max(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION, THUMBNAIL_APP_TRANSITION_DURATION); return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration, - mThumbnailFastOutSlowInInterpolator); + mFastOutSlowInInterpolator); } /** @@ -809,7 +943,7 @@ public class AppTransition implements Dump { Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, int appWidth, int appHeight, int orientation, Rect containingFrame, Rect contentInsets, - boolean isFullScreen, boolean isVoiceInteraction) { + Rect appFrame, boolean isFullScreen, boolean isVoiceInteraction) { Animation a; if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN || transit == TRANSIT_TASK_OPEN @@ -845,6 +979,12 @@ public class AppTransition implements Dump { "applyAnimation:" + " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE" + " transit=" + transit + " Callers=" + Debug.getCallers(3)); + } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) { + a = createClipRevealAnimationLocked(transit, enter, appFrame); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "applyAnimation:" + + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL" + + " Callers=" + Debug.getCallers(3)); } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) { a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, @@ -987,6 +1127,19 @@ public class AppTransition implements Dump { } } + void overridePendingAppTransitionClipReveal(int startX, int startY, + int startWidth, int startHeight) { + if (isTransitionSet()) { + mNextAppTransitionType = NEXT_TRANSIT_TYPE_CLIP_REVEAL; + mNextAppTransitionStartX = startX; + mNextAppTransitionStartY = startY; + mNextAppTransitionStartWidth = startWidth; + mNextAppTransitionStartHeight = startHeight; + postAnimationCallback(); + mNextAppTransitionCallback = null; + } + } + void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY, IRemoteCallback startedCallback, boolean scaleUp) { if (isTransitionSet()) { @@ -1130,32 +1283,34 @@ public class AppTransition implements Dump { } @Override - public void dump(PrintWriter pw) { - pw.print(" " + this); - pw.print(" mAppTransitionState="); pw.println(appStateToString()); + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.println(this); + pw.print(prefix); pw.print("mAppTransitionState="); pw.println(appStateToString()); if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) { - pw.print(" mNextAppTransitionType="); pw.println(transitTypeToString()); + pw.print(prefix); pw.print("mNextAppTransitionType="); + pw.println(transitTypeToString()); } switch (mNextAppTransitionType) { case NEXT_TRANSIT_TYPE_CUSTOM: - pw.print(" mNextAppTransitionPackage="); + pw.print(prefix); pw.print("mNextAppTransitionPackage="); pw.println(mNextAppTransitionPackage); - pw.print(" mNextAppTransitionEnter=0x"); + pw.print(prefix); pw.print("mNextAppTransitionEnter=0x"); pw.print(Integer.toHexString(mNextAppTransitionEnter)); pw.print(" mNextAppTransitionExit=0x"); pw.println(Integer.toHexString(mNextAppTransitionExit)); break; case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE: - pw.print(" mNextAppTransitionPackage="); + pw.print(prefix); pw.print("mNextAppTransitionPackage="); pw.println(mNextAppTransitionPackage); - pw.print(" mNextAppTransitionInPlace=0x"); + pw.print(prefix); pw.print("mNextAppTransitionInPlace=0x"); pw.print(Integer.toHexString(mNextAppTransitionInPlace)); break; case NEXT_TRANSIT_TYPE_SCALE_UP: - pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX); + pw.print(prefix); pw.print("mNextAppTransitionStartX="); + pw.print(mNextAppTransitionStartX); pw.print(" mNextAppTransitionStartY="); pw.println(mNextAppTransitionStartY); - pw.print(" mNextAppTransitionStartWidth="); + pw.print(prefix); pw.print("mNextAppTransitionStartWidth="); pw.print(mNextAppTransitionStartWidth); pw.print(" mNextAppTransitionStartHeight="); pw.println(mNextAppTransitionStartHeight); @@ -1164,22 +1319,23 @@ public class AppTransition implements Dump { case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN: case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP: case NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN: - pw.print(" mNextAppTransitionThumbnail="); + pw.print(prefix); pw.print("mNextAppTransitionThumbnail="); pw.print(mNextAppTransitionThumbnail); pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX); pw.print(" mNextAppTransitionStartY="); pw.println(mNextAppTransitionStartY); - pw.print(" mNextAppTransitionStartWidth="); + pw.print(prefix); pw.print("mNextAppTransitionStartWidth="); pw.print(mNextAppTransitionStartWidth); pw.print(" mNextAppTransitionStartHeight="); pw.println(mNextAppTransitionStartHeight); - pw.print(" mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp); + pw.print(prefix); pw.print("mNextAppTransitionScaleUp="); + pw.println(mNextAppTransitionScaleUp); break; } if (mNextAppTransitionCallback != null) { - pw.print(" mNextAppTransitionCallback="); - pw.println(mNextAppTransitionCallback); + pw.print(prefix); pw.print("mNextAppTransitionCallback="); + pw.println(mNextAppTransitionCallback); } } diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index 5c81126..55ec9fc 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -17,7 +17,6 @@ package com.android.server.wm; import android.graphics.Matrix; -import android.os.RemoteException; import android.util.Slog; import android.util.TimeUtils; import android.view.Display; @@ -142,6 +141,10 @@ public class AppWindowAnimator { } } + public boolean isAnimating() { + return animation != null || mAppToken.inPendingTransaction; + } + public void clearThumbnail() { if (thumbnail != null) { thumbnail.destroy(); @@ -239,7 +242,7 @@ public class AppWindowAnimator { } // This must be called while inside a transaction. - boolean stepAnimationLocked(long currentTime) { + boolean stepAnimationLocked(long currentTime, final int displayId) { if (mService.okToDisplay()) { // We will run animations as long as the display isn't frozen. @@ -289,7 +292,7 @@ public class AppWindowAnimator { } mAnimator.setAppLayoutChanges(this, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, - "AppWindowToken"); + "AppWindowToken", displayId); clearAnimation(); animating = false; @@ -312,23 +315,7 @@ public class AppWindowAnimator { for (int i = 0; i < numAllAppWinAnimators; i++) { mAllAppWinAnimators.get(i).finishExit(); } - if (mAppToken.mLaunchTaskBehind) { - try { - mService.mActivityManager.notifyLaunchTaskBehindComplete(mAppToken.token); - } catch (RemoteException e) { - } - mAppToken.mLaunchTaskBehind = false; - } else { - mAppToken.updateReportedVisibilityLocked(); - if (mAppToken.mEnteringAnimation) { - mAppToken.mEnteringAnimation = false; - try { - mService.mActivityManager.notifyEnterAnimationComplete(mAppToken.token); - } catch (RemoteException e) { - } - } - } - + mService.mAppTransition.notifyAppTransitionFinishedLocked(this); return false; } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index da25c53..a210223 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -52,11 +52,11 @@ class AppWindowToken extends WindowToken { final boolean voiceInteraction; - int groupId = -1; + Task mTask; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; boolean layoutConfigChanges; - boolean showWhenLocked; + boolean showForAllUsers; // The input dispatching timeout for this application token in nanoseconds. long inputDispatchingTimeoutNanos; @@ -107,7 +107,7 @@ class AppWindowToken extends WindowToken { // Input application handle used by the input dispatcher. final InputApplicationHandle mInputApplicationHandle; - boolean mDeferRemoval; + boolean mIsExiting; boolean mLaunchTaskBehind; boolean mEnteringAnimation; @@ -252,6 +252,21 @@ class AppWindowToken extends WindowToken { return false; } + void removeAppFromTaskLocked() { + mIsExiting = false; + removeAllWindows(); + + // Use local variable because removeAppToken will null out mTask. + final Task task = mTask; + if (task != null) { + if (!task.removeAppToken(this)) { + Slog.e(WindowManagerService.TAG, "removeAppFromTaskLocked: token=" + this + + " not found."); + } + task.mStack.mExitingAppTokens.remove(this); + } + } + @Override void removeAllWindows() { for (int winNdx = allAppWindows.size() - 1; winNdx >= 0; @@ -266,8 +281,10 @@ class AppWindowToken extends WindowToken { Slog.w(WindowManagerService.TAG, "removeAllWindows: removing win=" + win); } - win.mService.removeWindowLocked(win.mSession, win); + service.removeWindowLocked(win); } + allAppWindows.clear(); + windows.clear(); } @Override @@ -279,8 +296,8 @@ class AppWindowToken extends WindowToken { if (allAppWindows.size() > 0) { pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows); } - pw.print(prefix); pw.print("groupId="); pw.print(groupId); - pw.print(" appFullscreen="); pw.print(appFullscreen); + pw.print(prefix); pw.print("task="); pw.println(mTask); + pw.print(prefix); pw.print(" appFullscreen="); pw.print(appFullscreen); pw.print(" requestedOrientation="); pw.println(requestedOrientation); pw.print(prefix); pw.print("hiddenRequested="); pw.print(hiddenRequested); pw.print(" clientHidden="); pw.print(clientHidden); @@ -304,11 +321,11 @@ class AppWindowToken extends WindowToken { pw.print(prefix); pw.print("inPendingTransaction="); pw.println(inPendingTransaction); } - if (startingData != null || removed || firstWindowDrawn || mDeferRemoval) { + if (startingData != null || removed || firstWindowDrawn || mIsExiting) { pw.print(prefix); pw.print("startingData="); pw.print(startingData); pw.print(" removed="); pw.print(removed); pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn); - pw.print(" mDeferRemoval="); pw.println(mDeferRemoval); + pw.print(" mIsExiting="); pw.println(mIsExiting); } if (startingWindow != null || startingView != null || startingDisplayed || startingMoved) { diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java index 9fdfc47..7c2da2d 100644 --- a/services/core/java/com/android/server/wm/CircularDisplayMask.java +++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java @@ -50,9 +50,10 @@ class CircularDisplayMask { private int mRotation; private boolean mVisible; private boolean mDimensionsUnequal = false; + private int mMaskThickness; public CircularDisplayMask(Display display, SurfaceSession session, int zOrder, - int screenOffset) { + int screenOffset, int maskThickness) { mScreenSize = new Point(); display.getSize(mScreenSize); if (mScreenSize.x != mScreenSize.y) { @@ -84,6 +85,7 @@ class CircularDisplayMask { mPaint.setAntiAlias(true); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mScreenOffset = screenOffset; + mMaskThickness = maskThickness; } private void drawIfNeeded() { @@ -121,8 +123,8 @@ class CircularDisplayMask { int circleRadius = mScreenSize.x / 2; c.drawColor(Color.BLACK); - // The radius is reduced by 1 to provide an anti aliasing effect on the display edges. - c.drawCircle(circleRadius, circleRadius, circleRadius - 1, mPaint); + // The radius is reduced by mMaskThickness to provide an anti aliasing effect on the display edges. + c.drawCircle(circleRadius, circleRadius, circleRadius - mMaskThickness, mPaint); mSurface.unlockCanvasAndPost(c); } diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java index c09ea5c..e385be3 100644 --- a/services/core/java/com/android/server/wm/DimLayer.java +++ b/services/core/java/com/android/server/wm/DimLayer.java @@ -140,10 +140,9 @@ public class DimLayer { } /** - * @param layer The new layer value. - * @param inTransaction Whether the call is made within a surface transaction. + * NOTE: Must be called with Surface transaction open. */ - void adjustSurface(int layer, boolean inTransaction) { + private void adjustBounds() { final int dw, dh; final float xPos, yPos; if (!mStack.isFullscreen()) { @@ -163,29 +162,24 @@ public class DimLayer { yPos = -1 * dh / 6; } - try { - if (!inTransaction) { - SurfaceControl.openTransaction(); - } - mDimSurface.setPosition(xPos, yPos); - mDimSurface.setSize(dw, dh); - mDimSurface.setLayer(layer); - } catch (RuntimeException e) { - Slog.w(TAG, "Failure setting size or layer", e); - } finally { - if (!inTransaction) { - SurfaceControl.closeTransaction(); - } - } + mDimSurface.setPosition(xPos, yPos); + mDimSurface.setSize(dw, dh); + mLastBounds.set(mBounds); - mLayer = layer; } - // Assumes that surface transactions are currently closed. + /** @param bounds The new bounds to set */ void setBounds(Rect bounds) { mBounds.set(bounds); if (isDimming() && !mLastBounds.equals(bounds)) { - adjustSurface(mLayer, false); + try { + SurfaceControl.openTransaction(); + adjustBounds(); + } catch (RuntimeException e) { + Slog.w(TAG, "Failure setting size", e); + } finally { + SurfaceControl.closeTransaction(); + } } } @@ -224,9 +218,10 @@ public class DimLayer { return; } - if (!mLastBounds.equals(mBounds) || mLayer != layer) { - adjustSurface(layer, true); + if (!mLastBounds.equals(mBounds)) { + adjustBounds(); } + setLayer(layer); long curTime = SystemClock.uptimeMillis(); final boolean animating = isAnimating(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index f57adaf..f914369 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -66,6 +66,7 @@ class DisplayContent { int mBaseDisplayWidth = 0; int mBaseDisplayHeight = 0; int mBaseDisplayDensity = 0; + boolean mDisplayScalingDisabled; private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Display mDisplay; @@ -241,22 +242,24 @@ class DisplayContent { mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE); } } + if (mTapDetector != null) { + mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion); + } } - void switchUserStacks(int newUserId) { + void switchUserStacks() { 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); + if (DEBUG_VISIBILITY) Slog.w(TAG, "user changing, hiding " + win + + ", attrs=" + win.mAttrs.type + ", belonging to " + win.mOwnerUid); win.hideLw(false); } } for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { - mStacks.get(stackNdx).switchUser(newUserId); + mStacks.get(stackNdx).switchUser(); } } @@ -327,16 +330,10 @@ class DisplayContent { AppTokenList tokens = task.mAppTokens; for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { AppWindowToken wtoken = tokens.get(tokenNdx); - if (wtoken.mDeferRemoval) { - stack.mExitingAppTokens.remove(wtoken); - wtoken.mDeferRemoval = false; - mService.removeAppFromTaskLocked(wtoken); + if (wtoken.mIsExiting) { + wtoken.removeAppFromTaskLocked(); } } - if (task.mDeferRemoval) { - task.mDeferRemoval = false; - mService.removeTaskLocked(task); - } } } } @@ -345,6 +342,12 @@ class DisplayContent { } } + static int deltaRotation(int oldRotation, int newRotation) { + int delta = newRotation - oldRotation; + if (delta < 0) delta += 4; + return delta; + } + public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId); final String subPrefix = " " + prefix; @@ -358,6 +361,9 @@ class DisplayContent { pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight); pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi"); } + if (mDisplayScalingDisabled) { + pw.println(" noscale"); + } pw.print(" cur="); pw.print(mDisplayInfo.logicalWidth); pw.print("x"); pw.print(mDisplayInfo.logicalHeight); @@ -384,7 +390,7 @@ class DisplayContent { ArrayList<Task> tasks = stack.getTasks(); for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { final Task task = tasks.get(taskNdx); - pw.print(" mTaskId="); pw.println(task.taskId); + pw.print(" mTaskId="); pw.println(task.mTaskId); AppTokenList tokens = task.mAppTokens; for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx, ++ndx) { final AppWindowToken wtoken = tokens.get(tokenNdx); diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index c6951bd..1a125d4 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -24,6 +24,7 @@ import com.android.server.wm.WindowManagerService.H; import android.content.ClipData; import android.content.ClipDescription; import android.graphics.Point; +import android.graphics.Rect; import android.graphics.Region; import android.os.IBinder; import android.os.Message; @@ -63,6 +64,7 @@ class DragState { Display mDisplay; private final Region mTmpRegion = new Region(); + private final Rect mTmpRect = new Rect(); DragState(WindowManagerService service, IBinder token, SurfaceControl surface, int flags, IBinder localWin) { @@ -411,6 +413,12 @@ class DragState { continue; } + child.getStackBounds(mTmpRect); + if (!mTmpRect.contains(x, y)) { + // outside of this window's activity stack == don't tell about drags + continue; + } + child.getTouchableRegion(mTmpRegion); final int touchFlags = flags & diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java index 62f2b48..4c8a6f9 100644 --- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java +++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java @@ -25,7 +25,6 @@ import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.util.Slog; import android.view.Display; import android.view.Surface; import android.view.Surface.OutOfResourcesException; @@ -93,7 +92,9 @@ class EmulatorDisplayOverlay { } c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC); mSurfaceControl.setPosition(0, 0); - mOverlay.setBounds(0, 0, mScreenSize.x, mScreenSize.y); + // Always draw the overlay with square dimensions + int size = Math.max(mScreenSize.x, mScreenSize.y); + mOverlay.setBounds(0, 0, size, size); mOverlay.draw(c); mSurface.unlockCanvasAndPost(c); } diff --git a/services/core/java/com/android/server/wm/FocusedStackFrame.java b/services/core/java/com/android/server/wm/FocusedStackFrame.java index f1f5fe8..826fe97 100644 --- a/services/core/java/com/android/server/wm/FocusedStackFrame.java +++ b/services/core/java/com/android/server/wm/FocusedStackFrame.java @@ -16,14 +16,14 @@ 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 static com.android.server.wm.WindowManagerService.SHOW_LIGHT_TRANSACTIONS; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; 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; @@ -35,14 +35,17 @@ import com.android.server.wm.WindowStateAnimator.SurfaceTrace; class FocusedStackFrame { private static final String TAG = "FocusedStackFrame"; - private static final int THICKNESS = 10; + private static final boolean DEBUG = false; + private static final int THICKNESS = 2; private static final float ALPHA = 0.3f; private final SurfaceControl mSurfaceControl; private final Surface mSurface = new Surface(); + private final Paint mInnerPaint = new Paint(); + private final Paint mOuterPaint = new Paint(); + private final Rect mBounds = new Rect(); private final Rect mLastBounds = new Rect(); - final Rect mBounds = new Rect(); - private final Rect mTmpDrawRect = new Rect(); + private int mLayer = -1; public FocusedStackFrame(Display display, SurfaceSession session) { SurfaceControl ctrl = null; @@ -60,83 +63,84 @@ class FocusedStackFrame { } catch (OutOfResourcesException e) { } mSurfaceControl = ctrl; + + mInnerPaint.setStyle(Paint.Style.STROKE); + mInnerPaint.setStrokeWidth(THICKNESS); + mInnerPaint.setColor(Color.WHITE); + mOuterPaint.setStyle(Paint.Style.STROKE); + mOuterPaint.setStrokeWidth(THICKNESS); + mOuterPaint.setColor(Color.BLACK); } - 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); + private void draw() { + if (mLastBounds.isEmpty()) { + // Currently unset. Set it. + mLastBounds.set(mBounds); + } + + if (DEBUG) Slog.i(TAG, "draw: mBounds=" + mBounds + " mLastBounds=" + mLastBounds); + Canvas c = null; try { - c = mSurface.lockCanvas(mTmpDrawRect); + c = mSurface.lockCanvas(mLastBounds); } catch (IllegalArgumentException e) { + Slog.e(TAG, "Unable to lock canvas", e); } catch (Surface.OutOfResourcesException e) { + Slog.e(TAG, "Unable to lock canvas", e); } if (c == null) { + if (DEBUG) Slog.w(TAG, "Canvas is 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); - + c.drawRect(0, 0, mBounds.width(), mBounds.height(), mOuterPaint); + c.drawRect(THICKNESS, THICKNESS, mBounds.width() - THICKNESS, mBounds.height() - THICKNESS, + mInnerPaint); + if (DEBUG) Slog.w(TAG, "c.width=" + c.getWidth() + " c.height=" + c.getHeight() + + " c.clip=" + c .getClipBounds()); mSurface.unlockCanvasAndPost(c); + mLastBounds.set(mBounds); } - 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()); + private void setupSurface(boolean visible) { 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); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setupSurface"); + SurfaceControl.openTransaction(); + try { + if (visible) { + mSurfaceControl.setPosition(mBounds.left, mBounds.top); + mSurfaceControl.setSize(mBounds.width(), mBounds.height()); + mSurfaceControl.show(); + } else { + mSurfaceControl.hide(); } - mSurfaceControl.show(); - } else { - mSurfaceControl.hide(); + } finally { + SurfaceControl.closeTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setupSurface"); } } - public void setBounds(TaskStack stack) { - stack.getBounds(mBounds); - if (false && DEBUG_STACK) Slog.i(TAG, "setBounds: bounds=" + mBounds); + void setVisibility(TaskStack stack) { + if (stack == null || stack.isFullscreen()) { + setupSurface(false); + } else { + stack.getBounds(mBounds); + setupSurface(true); + if (!mBounds.equals(mLastBounds)) { + draw(); + } + } } - public void setLayer(int layer) { - mSurfaceControl.setLayer(layer); + // Note: caller responsible for being inside + // Surface.openTransaction() / closeTransaction() + void setLayer(int layer) { + if (mLayer == layer) { + return; + } + mLayer = layer; + mSurfaceControl.setLayer(mLayer); } } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 27ac32a..c24fcb3 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -78,7 +78,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { WindowState windowState = (WindowState) inputWindowHandle.windowState; if (windowState != null) { Slog.i(WindowManagerService.TAG, "WINDOW DIED " + windowState); - mService.removeWindowLocked(windowState.mSession, windowState); + mService.removeWindowLocked(windowState); } } } @@ -148,7 +148,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (timeout >= 0) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. - return timeout; + return timeout * 1000000L; // nanoseconds } } catch (RemoteException ex) { } @@ -239,9 +239,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { // As an optimization, we could try to prune the list of windows but this turns // out to be difficult because only the native code knows for sure which window // currently has touch focus. - final WindowStateAnimator universeBackground = mService.mAnimator.mUniverseBackground; - final int aboveUniverseLayer = mService.mAnimator.mAboveUniverseLayer; - boolean addedUniverse = false; boolean disableWallpaperTouchEvents = false; // If there's a drag in flight, provide a pseudowindow to catch drag input @@ -299,20 +296,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { mService.mDragState.sendDragStartedIfNeededLw(child); } - if (universeBackground != null && !addedUniverse - && child.mBaseLayer < aboveUniverseLayer && onDefaultDisplay) { - 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); - } - addedUniverse = true; - } - - if (child.mWinAnimator != universeBackground) { - addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, - hasFocus, hasWallpaper); - } + addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, hasFocus, + hasWallpaper); } } diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index f79896b..7dd716e 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -291,12 +291,6 @@ class ScreenRotationAnimation { return mSurfaceControl != null; } - static int deltaRotation(int oldRotation, int newRotation) { - int delta = newRotation - oldRotation; - if (delta < 0) delta += 4; - return delta; - } - private void setSnapshotTransformInTransaction(Matrix matrix, float alpha) { if (mSurfaceControl != null) { matrix.getValues(mTmpFloats); @@ -352,7 +346,7 @@ class ScreenRotationAnimation { // Compute the transformation matrix that must be applied // to the snapshot to make it stay in the same original position // with the current screen rotation. - int delta = deltaRotation(rotation, Surface.ROTATION_0); + int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0); createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta); @@ -391,7 +385,7 @@ class ScreenRotationAnimation { boolean firstStart = false; // Figure out how the screen has moved from the original rotation. - int delta = deltaRotation(mCurRotation, mOriginalRotation); + int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation); if (TWO_PHASE_ANIMATION && mFinishExitAnimation == null && (!dismissing || delta != Surface.ROTATION_0)) { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index a4dfd8a..487483e 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -446,20 +446,6 @@ final class Session extends IWindowSession.Stub mService.wallpaperCommandComplete(window, result); } - public void setUniverseTransform(IBinder window, float alpha, float offx, float offy, - float dsdx, float dtdx, float dsdy, float dtdy) { - synchronized(mService.mWindowMap) { - long ident = Binder.clearCallingIdentity(); - try { - mService.setUniverseTransformLocked( - mService.windowForClientLocked(this, window, true), - alpha, offx, offy, dsdx, dtdx, dsdy, dtdy); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) { synchronized(mService.mWindowMap) { final long identity = Binder.clearCallingIdentity(); @@ -475,6 +461,16 @@ final class Session extends IWindowSession.Stub return mService.getWindowId(window); } + @Override + public void pokeDrawLock(IBinder window) { + final long identity = Binder.clearCallingIdentity(); + try { + mService.pokeDrawLock(this, window); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + void windowAddedLocked() { if (mSurfaceSession == null) { if (WindowManagerService.localLOGV) Slog.v( diff --git a/services/core/java/com/android/server/wm/StackTapPointerEventListener.java b/services/core/java/com/android/server/wm/StackTapPointerEventListener.java index 8938358..1a85993 100644 --- a/services/core/java/com/android/server/wm/StackTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/StackTapPointerEventListener.java @@ -31,7 +31,7 @@ public class StackTapPointerEventListener implements PointerEventListener { private float mDownX; private float mDownY; private int mPointerId; - final private Region mTouchExcludeRegion; + final private Region mTouchExcludeRegion = new Region(); private final WindowManagerService mService; private final DisplayContent mDisplayContent; @@ -39,7 +39,6 @@ public class StackTapPointerEventListener implements PointerEventListener { DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; - mTouchExcludeRegion = displayContent.mTouchExcludeRegion; DisplayInfo info = displayContent.getDisplayInfo(); mMotionSlop = (int)(info.logicalDensityDpi * TAP_MOTION_SLOP_INCHES); } @@ -58,8 +57,8 @@ public class StackTapPointerEventListener implements PointerEventListener { int index = motionEvent.findPointerIndex(mPointerId); if ((motionEvent.getEventTime() - motionEvent.getDownTime()) > TAP_TIMEOUT_MSEC || index < 0 - || (motionEvent.getX(index) - mDownX) > mMotionSlop - || (motionEvent.getY(index) - mDownY) > mMotionSlop) { + || Math.abs(motionEvent.getX(index) - mDownX) > mMotionSlop + || Math.abs(motionEvent.getY(index) - mDownY) > mMotionSlop) { mPointerId = -1; } } @@ -72,12 +71,15 @@ public class StackTapPointerEventListener implements PointerEventListener { 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(); + synchronized(this) { + if ((motionEvent.getEventTime() - motionEvent.getDownTime()) + < TAP_TIMEOUT_MSEC + && Math.abs(x - mDownX) < mMotionSlop + && Math.abs(y - mDownY) < mMotionSlop + && !mTouchExcludeRegion.contains(x, y)) { + mService.mH.obtainMessage(H.TAP_OUTSIDE_STACK, x, y, + mDisplayContent).sendToTarget(); + } } mPointerId = -1; } @@ -85,4 +87,10 @@ public class StackTapPointerEventListener implements PointerEventListener { } } } + + void setTouchExcludeRegion(Region newRegion) { + synchronized (this) { + mTouchExcludeRegion.set(newRegion); + } + } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index b49b87c..0c3cf65 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -17,22 +17,25 @@ package com.android.server.wm; import static com.android.server.wm.WindowManagerService.TAG; +import static com.android.server.wm.WindowManagerService.DEBUG_STACK; import android.util.EventLog; import android.util.Slog; +import com.android.server.EventLogTags; class Task { TaskStack mStack; final AppTokenList mAppTokens = new AppTokenList(); - final int taskId; + final int mTaskId; final int mUserId; boolean mDeferRemoval = false; + final WindowManagerService mService; - Task(AppWindowToken wtoken, TaskStack stack, int userId) { - taskId = wtoken.groupId; - mAppTokens.add(wtoken); + Task(int taskId, TaskStack stack, int userId, WindowManagerService service) { + mTaskId = taskId; mStack = stack; mUserId = userId; + mService = service; } DisplayContent getDisplayContent() { @@ -41,22 +44,60 @@ class Task { void addAppToken(int addPos, AppWindowToken wtoken) { final int lastPos = mAppTokens.size(); - for (int pos = 0; pos < lastPos && pos < addPos; ++pos) { - if (mAppTokens.get(pos).removed) { - // addPos assumes removed tokens are actually gone. - ++addPos; + if (addPos >= lastPos) { + addPos = lastPos; + } else { + for (int pos = 0; pos < lastPos && pos < addPos; ++pos) { + if (mAppTokens.get(pos).removed) { + // addPos assumes removed tokens are actually gone. + ++addPos; + } } } mAppTokens.add(addPos, wtoken); + wtoken.mTask = this; mDeferRemoval = false; } + void removeLocked() { + if (!mAppTokens.isEmpty() && mStack.isAnimating()) { + if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId); + mDeferRemoval = true; + return; + } + if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId); + EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeTask"); + mDeferRemoval = false; + mStack.removeTask(this); + mService.mTaskIdToTask.delete(mTaskId); + } + + void moveTaskToStack(TaskStack stack, boolean toTop) { + if (stack == mStack) { + return; + } + if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId + + " from stack=" + mStack); + EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask"); + if (mStack != null) { + mStack.removeTask(this); + } + stack.addTask(this, toTop); + } + boolean removeAppToken(AppWindowToken wtoken) { boolean removed = mAppTokens.remove(wtoken); if (mAppTokens.size() == 0) { - EventLog.writeEvent(com.android.server.EventLogTags.WM_TASK_REMOVED, taskId, + EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeAppToken: last token"); + if (mDeferRemoval) { + removeLocked(); + } } + wtoken.mTask = null; + /* Leave mTaskId for now, it might be useful for debug + wtoken.mTaskId = -1; + */ return removed; } @@ -66,8 +107,13 @@ class Task { } } + boolean showForAllUsers() { + final int tokensCount = mAppTokens.size(); + return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers; + } + @Override public String toString() { - return "{taskId=" + taskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}"; + return "{taskId=" + mTaskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}"; } } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 238c77e..7cdf8b2 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -19,11 +19,15 @@ 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.content.res.Configuration; import android.graphics.Rect; import android.os.Debug; +import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Slog; import android.util.TypedValue; +import android.view.Surface; + import com.android.server.EventLogTags; import java.io.PrintWriter; @@ -49,6 +53,8 @@ public class TaskStack { /** For comparison with DisplayContent bounds. */ private Rect mTmpRect = new Rect(); + /** For handling display rotations. */ + private Rect mTmpRect2 = new Rect(); /** Content limits relative to the DisplayContent this sits in. */ private Rect mBounds = new Rect(); @@ -78,9 +84,24 @@ public class TaskStack { /** Detach this stack from its display when animation completes. */ boolean mDeferDetach; + // Contains configurations settings that are different from the global configuration due to + // stack specific operations. E.g. {@link #setBounds}. + Configuration mOverrideConfig; + // True if the stack was forced to fullscreen disregarding the override configuration. + private boolean mForceFullscreen; + // The {@link #mBounds} before the stack was forced to fullscreen. Will be restored as the + // stack bounds once the stack is no longer forced to fullscreen. + final private Rect mPreForceFullscreenBounds; + + // Device rotation as of the last time {@link #mBounds} was set. + int mRotation; + TaskStack(WindowManagerService service, int stackId) { mService = service; mStackId = stackId; + mOverrideConfig = Configuration.EMPTY; + mForceFullscreen = false; + mPreForceFullscreenBounds = new Rect(); // TODO: remove bounds from log, they are always 0. EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); @@ -95,8 +116,6 @@ public class TaskStack { } void resizeWindows() { - final boolean underStatusBar = mBounds.top == 0; - final ArrayList<WindowState> resizingWindows = mService.mResizingWindows; for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens; @@ -109,27 +128,40 @@ public class TaskStack { "setBounds: Resizing " + win); resizingWindows.add(win); } - win.mUnderStatusBar = underStatusBar; } } } } + /** Set the stack bounds. Passing in null sets the bounds to fullscreen. */ boolean setBounds(Rect bounds) { boolean oldFullscreen = mFullscreen; + int rotation = Surface.ROTATION_0; if (mDisplayContent != null) { mDisplayContent.getLogicalDisplayRect(mTmpRect); - mFullscreen = mTmpRect.equals(bounds); + rotation = mDisplayContent.getDisplayInfo().rotation; + if (bounds == null) { + bounds = mTmpRect; + mFullscreen = true; + } else { + bounds.intersect(mTmpRect); // ensure bounds are entirely within the display rect + mFullscreen = mTmpRect.equals(bounds); + } } - if (mBounds.equals(bounds) && oldFullscreen == mFullscreen) { + if (bounds == null) { + // Can't set to fullscreen if we don't have a display to get bounds from... + return false; + } + if (mBounds.equals(bounds) && oldFullscreen == mFullscreen && mRotation == rotation) { return false; } mDimLayer.setBounds(bounds); mAnimationBackgroundSurface.setBounds(bounds); mBounds.set(bounds); - + mRotation = rotation; + updateOverrideConfiguration(); return true; } @@ -137,10 +169,67 @@ public class TaskStack { out.set(mBounds); } + private void updateOverrideConfiguration() { + final Configuration serviceConfig = mService.mCurConfiguration; + if (mFullscreen) { + mOverrideConfig = Configuration.EMPTY; + return; + } + + if (mOverrideConfig == Configuration.EMPTY) { + mOverrideConfig = new Configuration(); + } + + // TODO(multidisplay): Update Dp to that of display stack is on. + final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + mOverrideConfig.screenWidthDp = + Math.min((int)(mBounds.width() / density), serviceConfig.screenWidthDp); + mOverrideConfig.screenHeightDp = + Math.min((int)(mBounds.height() / density), serviceConfig.screenHeightDp); + mOverrideConfig.smallestScreenWidthDp = + Math.min(mOverrideConfig.screenWidthDp, mOverrideConfig.screenHeightDp); + mOverrideConfig.orientation = + (mOverrideConfig.screenWidthDp <= mOverrideConfig.screenHeightDp) + ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE; + } + void updateDisplayInfo() { - if (mFullscreen && mDisplayContent != null) { + if (mFullscreen) { + setBounds(null); + } else if (mDisplayContent != null) { + final int newRotation = mDisplayContent.getDisplayInfo().rotation; + if (mRotation == newRotation) { + return; + } + + // Device rotation changed. We don't want the stack to move around on the screen when + // this happens, so update the stack bounds so it stays in the same place. + final int rotationDelta = DisplayContent.deltaRotation(mRotation, newRotation); mDisplayContent.getLogicalDisplayRect(mTmpRect); - setBounds(mTmpRect); + switch (rotationDelta) { + case Surface.ROTATION_0: + mTmpRect2.set(mBounds); + break; + case Surface.ROTATION_90: + mTmpRect2.top = mTmpRect.bottom - mBounds.right; + mTmpRect2.left = mBounds.top; + mTmpRect2.right = mTmpRect2.left + mBounds.height(); + mTmpRect2.bottom = mTmpRect2.top + mBounds.width(); + break; + case Surface.ROTATION_180: + mTmpRect2.top = mTmpRect.bottom - mBounds.bottom; + mTmpRect2.left = mTmpRect.right - mBounds.right; + mTmpRect2.right = mTmpRect2.left + mBounds.width(); + mTmpRect2.bottom = mTmpRect2.top + mBounds.height(); + break; + case Surface.ROTATION_270: + mTmpRect2.top = mBounds.left; + mTmpRect2.left = mTmpRect.right - mBounds.bottom; + mTmpRect2.right = mTmpRect2.left + mBounds.height(); + mTmpRect2.bottom = mTmpRect2.top + mBounds.width(); + break; + } + setBounds(mTmpRect2); } } @@ -148,6 +237,28 @@ public class TaskStack { return mFullscreen; } + /** Forces the stack to fullscreen if input is true, else un-forces the stack from fullscreen. + * Returns true if something happened. + */ + boolean forceFullscreen(boolean forceFullscreen) { + if (mForceFullscreen == forceFullscreen) { + return false; + } + mForceFullscreen = forceFullscreen; + if (forceFullscreen) { + if (mFullscreen) { + return false; + } + mPreForceFullscreenBounds.set(mBounds); + return setBounds(null); + } else { + if (!mFullscreen || mPreForceFullscreenBounds.isEmpty()) { + return false; + } + return setBounds(mPreForceFullscreenBounds); + } + } + boolean isAnimating() { for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens; @@ -164,21 +275,28 @@ public class TaskStack { return false; } + void addTask(Task task, boolean toTop) { + addTask(task, toTop, task.showForAllUsers()); + } + /** * 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. + * @param showForAllUsers Whether to show the task regardless of the current user. */ - void addTask(Task task, boolean toTop) { + void addTask(Task task, boolean toTop, boolean showForAllUsers) { int stackNdx; if (!toTop) { stackNdx = 0; } else { stackNdx = mTasks.size(); - if (!mService.isCurrentProfileLocked(task.mUserId)) { + if (!showForAllUsers && !mService.isCurrentProfileLocked(task.mUserId)) { // Place the task below all current user tasks. while (--stackNdx >= 0) { - if (!mService.isCurrentProfileLocked(mTasks.get(stackNdx).mUserId)) { + final Task tmpTask = mTasks.get(stackNdx); + if (!tmpTask.showForAllUsers() + || !mService.isCurrentProfileLocked(tmpTask.mUserId)) { break; } } @@ -191,8 +309,10 @@ public class TaskStack { mTasks.add(stackNdx, task); task.mStack = this; - mDisplayContent.moveStack(this, true); - EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, stackNdx); + if (toTop) { + mDisplayContent.moveStack(this, true); + } + EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.mTaskId, toTop ? 1 : 0, stackNdx); } void moveTaskToTop(Task task) { @@ -222,6 +342,13 @@ public class TaskStack { } mDisplayContent.layoutNeeded = true; } + for (int appNdx = mExitingAppTokens.size() - 1; appNdx >= 0; --appNdx) { + final AppWindowToken wtoken = mExitingAppTokens.get(appNdx); + if (wtoken.mTask == task) { + wtoken.mIsExiting = false; + mExitingAppTokens.remove(appNdx); + } + } } void attachDisplayContent(DisplayContent displayContent) { @@ -244,7 +371,7 @@ public class TaskStack { for (int appNdx = appWindowTokens.size() - 1; appNdx >= 0; --appNdx) { final WindowList appWindows = appWindowTokens.get(appNdx).allAppWindows; for (int winNdx = appWindows.size() - 1; winNdx >= 0; --winNdx) { - mService.removeWindowInnerLocked(null, appWindows.get(winNdx)); + mService.removeWindowInnerLocked(appWindows.get(winNdx)); doAnotherLayoutPass = true; } } @@ -253,10 +380,8 @@ public class TaskStack { mService.requestTraversalLocked(); } - mAnimationBackgroundSurface.destroySurface(); - mAnimationBackgroundSurface = null; - mDimLayer.destroySurface(); - mDimLayer = null; + close(); + mDisplayContent = null; } @@ -336,18 +461,24 @@ public class TaskStack { 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)) { + if (newWinAnimator.mSurfaceShown && (mDimWinAnimator == null + || !mDimWinAnimator.mSurfaceShown + || mDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) { mDimWinAnimator = newWinAnimator; + if (mDimWinAnimator.mWin.mAppToken == null + && !mFullscreen && mDisplayContent != null) { + // Dim should cover the entire screen for system windows. + mDisplayContent.getLogicalDisplayRect(mTmpRect); + mDimLayer.setBounds(mTmpRect); + } } } void stopDimmingIfNeeded() { if (!mDimmingTag && isDimming()) { mDimWinAnimator = null; + mDimLayer.setBounds(mBounds); } } @@ -362,11 +493,11 @@ public class TaskStack { } } - void switchUser(int userId) { + void switchUser() { int top = mTasks.size(); for (int taskNdx = 0; taskNdx < top; ++taskNdx) { Task task = mTasks.get(taskNdx); - if (mService.isCurrentProfileLocked(task.mUserId)) { + if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) { mTasks.remove(taskNdx); mTasks.add(task); --top; @@ -375,8 +506,14 @@ public class TaskStack { } void close() { - mDimLayer.mDimSurface.destroy(); - mAnimationBackgroundSurface.mDimSurface.destroy(); + if (mAnimationBackgroundSurface != null) { + mAnimationBackgroundSurface.destroySurface(); + mAnimationBackgroundSurface = null; + } + if (mDimLayer != null) { + mDimLayer.destroySurface(); + mDimLayer = null; + } } public void dump(String prefix, PrintWriter pw) { @@ -391,7 +528,7 @@ public class TaskStack { } if (mDimLayer.isDimming()) { pw.print(prefix); pw.println("mDimLayer:"); - mDimLayer.printTo(prefix, pw); + mDimLayer.printTo(prefix + " ", pw); pw.print(prefix); pw.print("mDimWinAnimator="); pw.println(mDimWinAnimator); } if (!mExitingAppTokens.isEmpty()) { diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 64713d9..897b865 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -30,8 +30,6 @@ import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENT import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_ACTION_PENDING; import android.content.Context; -import android.os.Debug; -import android.os.SystemClock; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -41,6 +39,7 @@ import android.view.SurfaceControl; import android.view.WindowManagerPolicy; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; +import android.view.Choreographer; import com.android.server.wm.WindowManagerService.LayoutFields; @@ -61,9 +60,13 @@ public class WindowAnimator { final Context mContext; final WindowManagerPolicy mPolicy; + /** Is any window animating? */ boolean mAnimating; - final Runnable mAnimationRunnable; + /** Is any app window animating? */ + boolean mAppWindowAnimating; + + final Choreographer.FrameCallback mAnimationFrameCallback; /** Time of current animation step. Reset on each iteration */ long mCurrentTime; @@ -78,9 +81,6 @@ public class WindowAnimator { * seen. If multiple windows satisfy this, use the lowest window. */ WindowState mWindowDetachedWallpaper = null; - WindowStateAnimator mUniverseBackground = null; - int mAboveUniverseLayer = 0; - int mBulkUpdateParams = 0; Object mLastWindowFreezeSource; @@ -118,12 +118,11 @@ public class WindowAnimator { mContext = service.mContext; mPolicy = service.mPolicy; - mAnimationRunnable = new Runnable() { - @Override - public void run() { + mAnimationFrameCallback = new Choreographer.FrameCallback() { + public void doFrame(long frameTimeNs) { synchronized (mService.mWindowMap) { mService.mAnimationScheduled = false; - animateLocked(); + animateLocked(frameTimeNs); } } }; @@ -149,35 +148,6 @@ public class WindowAnimator { mDisplayContentsAnimators.delete(displayId); } - void hideWallpapersLocked(final WindowState w) { - final WindowState wallpaperTarget = mService.mWallpaperTarget; - final WindowState lowerWallpaperTarget = mService.mLowerWallpaperTarget; - final ArrayList<WindowToken> wallpaperTokens = mService.mWallpaperTokens; - - if ((wallpaperTarget == w && lowerWallpaperTarget == null) || wallpaperTarget == null) { - final int numTokens = wallpaperTokens.size(); - for (int i = numTokens - 1; i >= 0; i--) { - final WindowToken token = wallpaperTokens.get(i); - final int numWindows = token.windows.size(); - for (int j = numWindows - 1; j >= 0; j--) { - final WindowState wallpaper = token.windows.get(j); - final WindowStateAnimator winAnimator = wallpaper.mWinAnimator; - if (!winAnimator.mLastHidden) { - winAnimator.hide(); - mService.dispatchWallpaperVisibility(wallpaper, false); - setPendingLayoutChanges(Display.DEFAULT_DISPLAY, - WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); - } - } - if (WindowManagerService.DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, - "Hiding wallpaper " + token + " from " + w - + " target=" + wallpaperTarget + " lower=" + lowerWallpaperTarget - + "\n" + Debug.getCallers(5, " ")); - token.hidden = true; - } - } - } - private void updateAppWindowsLocked(int displayId) { ArrayList<TaskStack> stacks = mService.getDisplayContentLocked(displayId).getStacks(); for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { @@ -189,13 +159,13 @@ public class WindowAnimator { final AppWindowAnimator appAnimator = tokens.get(tokenNdx).mAppAnimator; final boolean wasAnimating = appAnimator.animation != null && appAnimator.animation != AppWindowAnimator.sDummyAnimation; - if (appAnimator.stepAnimationLocked(mCurrentTime)) { - mAnimating = true; + if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) { + mAnimating = mAppWindowAnimating = true; } else if (wasAnimating) { // stopped animating, do one more pass through the layout setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, - "appToken " + appAnimator.mAppToken + " done"); + "appToken " + appAnimator.mAppToken + " done", displayId); if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + appAnimator.mAppToken); } @@ -208,12 +178,12 @@ public class WindowAnimator { final AppWindowAnimator appAnimator = exitingAppTokens.get(i).mAppAnimator; final boolean wasAnimating = appAnimator.animation != null && appAnimator.animation != AppWindowAnimator.sDummyAnimation; - if (appAnimator.stepAnimationLocked(mCurrentTime)) { - mAnimating = true; + if (appAnimator.stepAnimationLocked(mCurrentTime, displayId)) { + mAnimating = mAppWindowAnimating = true; } else if (wasAnimating) { // stopped animating, do one more pass through the layout setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, - "exiting appToken " + appAnimator.mAppToken + " done"); + "exiting appToken " + appAnimator.mAppToken + " done", displayId); if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken); } @@ -605,11 +575,11 @@ public class WindowAnimator { // This will set mOrientationChangeComplete and cause a pass through layout. setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, - "testTokenMayBeDrawnLocked: freezingScreen"); + "testTokenMayBeDrawnLocked: freezingScreen", displayId); } else { setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, - "testTokenMayBeDrawnLocked"); + "testTokenMayBeDrawnLocked", displayId); // We can now show all of the drawn windows! if (!mService.mOpeningApps.contains(wtoken)) { @@ -624,15 +594,16 @@ public class WindowAnimator { /** Locked on mService.mWindowMap. */ - private void animateLocked() { + private void animateLocked(long frameTimeNs) { if (!mInitialized) { return; } - mCurrentTime = SystemClock.uptimeMillis(); + mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS; mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE; boolean wasAnimating = mAnimating; mAnimating = false; + mAppWindowAnimating = false; if (WindowManagerService.DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime); } @@ -794,6 +765,7 @@ public class WindowAnimator { } else if (dumpAll) { pw.print(subPrefix); pw.println("no ScreenRotationAnimation "); } + pw.println(); } pw.println(); @@ -814,38 +786,36 @@ public class WindowAnimator { pw.print(prefix); pw.print("mWindowDetachedWallpaper="); pw.println(mWindowDetachedWallpaper); } - if (mUniverseBackground != null) { - pw.print(prefix); pw.print("mUniverseBackground="); pw.print(mUniverseBackground); - pw.print(" mAboveUniverseLayer="); pw.println(mAboveUniverseLayer); - } } int getPendingLayoutChanges(final int displayId) { if (displayId < 0) { return 0; } - return mService.getDisplayContentLocked(displayId).pendingLayoutChanges; + final DisplayContent displayContent = mService.getDisplayContentLocked(displayId); + return (displayContent != null) ? displayContent.pendingLayoutChanges : 0; } void setPendingLayoutChanges(final int displayId, final int changes) { - if (displayId >= 0) { - mService.getDisplayContentLocked(displayId).pendingLayoutChanges |= changes; + if (displayId < 0) { + return; + } + final DisplayContent displayContent = mService.getDisplayContentLocked(displayId); + if (displayContent != null) { + displayContent.pendingLayoutChanges |= changes; } } - void setAppLayoutChanges(final AppWindowAnimator appAnimator, final int changes, String s) { - // Used to track which displays layout changes have been done. - SparseIntArray displays = new SparseIntArray(2); + void setAppLayoutChanges(final AppWindowAnimator appAnimator, final int changes, String reason, + final int displayId) { WindowList windows = appAnimator.mAppToken.allAppWindows; for (int i = windows.size() - 1; i >= 0; i--) { - final int displayId = windows.get(i).getDisplayId(); - if (displayId >= 0 && displays.indexOfKey(displayId) < 0) { + if (displayId == windows.get(i).getDisplayId()) { setPendingLayoutChanges(displayId, changes); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats(s, getPendingLayoutChanges(displayId)); + mService.debugLayoutRepeats(reason, getPendingLayoutChanges(displayId)); } - // Keep from processing this display again. - displays.put(displayId, changes); + break; } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 089d897..c5bdbb0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import static android.view.WindowManager.LayoutParams.*; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -28,9 +29,8 @@ import android.view.IWindowId; import android.view.IWindowSessionCallback; import android.view.WindowContentFrameStats; +import com.android.internal.app.IAssistScreenshotReceiver; 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; @@ -39,12 +39,14 @@ import com.android.internal.view.WindowManagerPolicyThread; import com.android.server.AttributeCache; import com.android.server.DisplayThread; import com.android.server.EventLogTags; +import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; import com.android.server.input.InputManagerService; import com.android.server.power.ShutdownThread; +import com.android.server.policy.PhoneWindowManager; import android.Manifest; import android.app.ActivityManagerNative; @@ -65,11 +67,9 @@ import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; @@ -128,7 +128,6 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; -import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; @@ -137,7 +136,6 @@ 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; import java.io.BufferedWriter; import java.io.DataInputStream; @@ -153,6 +151,7 @@ import java.io.StringWriter; import java.net.Socket; import java.text.DateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -181,7 +180,6 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_CONFIGURATION = false; static final boolean DEBUG_APP_TRANSITIONS = false; static final boolean DEBUG_STARTING_WINDOW = false; - static final boolean DEBUG_REORDER = false; static final boolean DEBUG_WALLPAPER = false; static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER; static final boolean DEBUG_DRAG = false; @@ -194,6 +192,7 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_TASK_MOVEMENT = false; static final boolean DEBUG_STACK = false; static final boolean DEBUG_DISPLAY = false; + static final boolean DEBUG_POWER = false; static final boolean SHOW_SURFACE_ALLOC = false; static final boolean SHOW_TRANSACTIONS = false; static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS; @@ -283,12 +282,6 @@ public class WindowManagerService extends IWindowManager.Stub // The name of the boot animation service in init.rc. private static final String BOOT_ANIMATION_SERVICE = "bootanim"; - /** Minimum value for attachStack and resizeStack weight value */ - public static final float STACK_WEIGHT_MIN = 0.2f; - - /** Maximum value for attachStack 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; @@ -338,12 +331,13 @@ public class WindowManagerService extends IWindowManager.Stub final boolean mHaveInputMethods; final boolean mHasPermanentDpad; + final long mDrawLockTimeoutMillis; final boolean mAllowBootMessages; final boolean mLimitedAlphaCompositing; - final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager(); + final WindowManagerPolicy mPolicy = new PhoneWindowManager(); final IActivityManager mActivityManager; @@ -419,7 +413,7 @@ public class WindowManagerService extends IWindowManager.Stub * This is set when we have run out of memory, and will either be an empty * list or contain windows that need to be force removed. */ - ArrayList<WindowState> mForceRemoves; + final ArrayList<WindowState> mForceRemoves = new ArrayList<>(); /** * Windows that clients are waiting to have drawn. @@ -504,10 +498,16 @@ public class WindowManagerService extends IWindowManager.Stub int mLastDisplayFreezeDuration = 0; Object mLastFinishedFreezeSource = null; boolean mWaitingForConfig = false; - boolean mWindowsFreezingScreen = false; + + final static int WINDOWS_FREEZING_SCREENS_NONE = 0; + final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1; + final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2; + private int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE; + boolean mClientFreezingScreen = false; int mAppsFreezingScreen = 0; int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + int mLastKeyguardForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; int mLayoutSeq = 0; @@ -555,6 +555,10 @@ public class WindowManagerService extends IWindowManager.Stub WindowState mInputMethodWindow = null; final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<WindowState>(); + /** Temporary list for comparison. Always clear this after use so we don't end up with + * orphaned windows references */ + final ArrayList<WindowState> mTmpWindows = new ArrayList<>(); + boolean mHardKeyboardAvailable; boolean mShowImeWithHardKeyboard; OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener; @@ -584,7 +588,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); + private final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); // If non-null, this is the currently visible window that is associated // with the wallpaper. @@ -613,6 +617,19 @@ public class WindowManagerService extends IWindowManager.Stub static final long WALLPAPER_TIMEOUT_RECOVERY = 10000; boolean mAnimateWallpaperWithTarget; + // We give a wallpaper up to 500ms to finish drawing before playing app transitions. + static final long WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION = 500; + static final int WALLPAPER_DRAW_NORMAL = 0; + static final int WALLPAPER_DRAW_PENDING = 1; + static final int WALLPAPER_DRAW_TIMEOUT = 2; + int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL; + + // Set to the wallpaper window we would like to hide once the transition animations are done. + // This is useful in cases where we don't want the wallpaper to be hidden when the close app + // is a wallpaper target and is done animating out, but the opening app isn't a wallpaper + // target and isn't done animating in. + WindowState mDeferredHideWallpaper = null; + AppWindowToken mFocusedApp = null; PowerManager mPowerManager; @@ -687,17 +704,22 @@ public class WindowManagerService extends IWindowManager.Stub final WindowAnimator mAnimator; - SparseArray<Task> mTaskIdToTask = new SparseArray<Task>(); + SparseArray<Task> mTaskIdToTask = new SparseArray<>(); /** All of the TaskStacks in the window manager, unordered. For an ordered list call * DisplayContent.getStacks(). */ - SparseArray<TaskStack> mStackIdToStack = new SparseArray<TaskStack>(); + SparseArray<TaskStack> mStackIdToStack = new SparseArray<>(); private final PointerEventDispatcher mPointerEventDispatcher; private WindowContentFrameStats mTempWindowRenderStats; final class DragInputEventReceiver extends InputEventReceiver { + // Set, if stylus button was down at the start of the drag. + private boolean mStylusButtonDownAtStart; + // Indicates the first event to check for button state. + private boolean mIsStartEvent = true; + public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @@ -713,6 +735,18 @@ public class WindowManagerService extends IWindowManager.Stub boolean endDrag = false; final float newX = motionEvent.getRawX(); final float newY = motionEvent.getRawY(); + final boolean isStylusButtonDown = + (motionEvent.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) + && (motionEvent.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0; + + if (mIsStartEvent) { + if (isStylusButtonDown) { + // First event and the button was down, check for the button being + // lifted in the future, if that happens we'll drop the item. + mStylusButtonDownAtStart = true; + } + mIsStartEvent = false; + } switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: { @@ -722,9 +756,17 @@ public class WindowManagerService extends IWindowManager.Stub } break; case MotionEvent.ACTION_MOVE: { - synchronized (mWindowMap) { - // move the surface and tell the involved window(s) where we are - mDragState.notifyMoveLw(newX, newY); + if (mStylusButtonDownAtStart && !isStylusButtonDown) { + if (DEBUG_DRAG) Slog.d(TAG, "Button no longer pressed; dropping at " + + newX + "," + newY); + synchronized (mWindowMap) { + endDrag = mDragState.notifyDropLw(newX, newY); + } + } else { + synchronized (mWindowMap) { + // move the surface and tell the involved window(s) where we are + mDragState.notifyMoveLw(newX, newY); + } } } break; @@ -748,6 +790,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { mDragState.endDragLw(); } + mStylusButtonDownAtStart = false; + mIsStartEvent = true; } handled = true; @@ -786,6 +830,35 @@ public class WindowManagerService extends IWindowManager.Stub // For example, when this flag is true, there will be no wallpaper service. final boolean mOnlyCore; + /** Listener to notify activity manager about app transitions. */ + private final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier + = new WindowManagerInternal.AppTransitionListener() { + + @Override + public void onAppTransitionFinishedLocked(IBinder token) { + AppWindowToken atoken = findAppWindowToken(token); + if (atoken == null) { + return; + } + if (atoken.mLaunchTaskBehind) { + try { + mActivityManager.notifyLaunchTaskBehindComplete(atoken.token); + } catch (RemoteException e) { + } + atoken.mLaunchTaskBehind = false; + } else { + atoken.updateReportedVisibilityLocked(); + if (atoken.mEnteringAnimation) { + atoken.mEnteringAnimation = false; + try { + mActivityManager.notifyEnterAnimationComplete(atoken.token); + } catch (RemoteException e) { + } + } + } + } + }; + public static WindowManagerService main(final Context context, final InputManagerService im, final boolean haveInputMethods, final boolean showBootMsgs, @@ -808,9 +881,6 @@ public class WindowManagerService extends IWindowManager.Stub WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper()); mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this); - mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer() - * TYPE_LAYER_MULTIPLIER - + TYPE_LAYER_OFFSET; } }, 0); } @@ -827,6 +897,8 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_hasPermanentDpad); mInTouchMode = context.getResources().getBoolean( com.android.internal.R.bool.config_defaultInTouchMode); + mDrawLockTimeoutMillis = context.getResources().getInteger( + com.android.internal.R.integer.config_drawLockTimeoutMillis); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mDisplaySettings = new DisplaySettings(); @@ -865,6 +937,7 @@ public class WindowManagerService extends IWindowManager.Stub mScreenFrozenLock.setReferenceCounted(false); mAppTransition = new AppTransition(context, mH); + mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier); mActivityManager = ActivityManagerNative.getDefault(); mBatteryStats = BatteryStatsService.getService(); @@ -1699,7 +1772,7 @@ public class WindowManagerService extends IWindowManager.Stub return true; } - final boolean isWallpaperVisible(WindowState wallpaperTarget) { + private boolean isWallpaperVisible(WindowState wallpaperTarget) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured=" + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??") + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null) @@ -1713,10 +1786,42 @@ public class WindowManagerService extends IWindowManager.Stub || mLowerWallpaperTarget != null; } - static final int ADJUST_WALLPAPER_LAYERS_CHANGED = 1<<1; - static final int ADJUST_WALLPAPER_VISIBILITY_CHANGED = 1<<2; + void hideWallpapersLocked(final WindowState winGoingAway) { + if (mWallpaperTarget != null + && (mWallpaperTarget != winGoingAway || mLowerWallpaperTarget != null)) { + return; + } + if (mAppTransition.isRunning()) { + // Defer hiding the wallpaper when app transition is running until the animations + // are done. + mDeferredHideWallpaper = winGoingAway; + return; + } + + final boolean wasDeferred = (mDeferredHideWallpaper == winGoingAway); + for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { + final WindowToken token = mWallpaperTokens.get(i); + for (int j = token.windows.size() - 1; j >= 0; j--) { + final WindowState wallpaper = token.windows.get(j); + final WindowStateAnimator winAnimator = wallpaper.mWinAnimator; + if (!winAnimator.mLastHidden || wasDeferred) { + winAnimator.hide(); + dispatchWallpaperVisibility(wallpaper, false); + final DisplayContent displayContent = wallpaper.getDisplayContent(); + if (displayContent != null) { + displayContent.pendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + } + } + } + if (DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG, "Hiding wallpaper " + token + + " from " + winGoingAway + " target=" + mWallpaperTarget + " lower=" + + mLowerWallpaperTarget + "\n" + Debug.getCallers(5, " ")); + token.hidden = true; + } + } - int adjustWallpaperWindowsLocked() { + boolean adjustWallpaperWindowsLocked() { mInnerFields.mWallpaperMayChange = false; boolean targetChanged = false; @@ -1895,7 +2000,7 @@ public class WindowManagerService extends IWindowManager.Stub // AND any starting window associated with it, AND below the // maximum layer the policy allows for wallpapers. while (foundI > 0) { - WindowState wb = windows.get(foundI-1); + WindowState wb = windows.get(foundI - 1); if (wb.mBaseLayer < maxLayer && wb.mAttachedWindow != foundW && (foundW.mAttachedWindow == null || @@ -1921,7 +2026,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { // Okay i is the position immediately above the wallpaper. Look at // what is below it for later. - foundW = foundI > 0 ? windows.get(foundI-1) : null; + foundW = foundI > 0 ? windows.get(foundI - 1) : null; } if (visible) { @@ -1943,44 +2048,37 @@ public class WindowManagerService extends IWindowManager.Stub // Start stepping backwards from here, ensuring that our wallpaper windows // are correctly placed. - int changed = 0; - int curTokenIndex = mWallpaperTokens.size(); - while (curTokenIndex > 0) { - curTokenIndex--; - WindowToken token = mWallpaperTokens.get(curTokenIndex); + boolean changed = false; + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + WindowToken token = mWallpaperTokens.get(curTokenNdx); if (token.hidden == visible) { if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "Wallpaper token " + token + " hidden=" + !visible); - changed |= ADJUST_WALLPAPER_VISIBILITY_CHANGED; token.hidden = !visible; - // Need to do a layout to ensure the wallpaper now has the - // correct size. + // Need to do a layout to ensure the wallpaper now has the correct size. getDefaultDisplayContentLocked().layoutNeeded = true; } - int curWallpaperIndex = token.windows.size(); - while (curWallpaperIndex > 0) { - curWallpaperIndex--; - WindowState wallpaper = token.windows.get(curWallpaperIndex); + final WindowList tokenWindows = token.windows; + for (int wallpaperNdx = tokenWindows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { + WindowState wallpaper = tokenWindows.get(wallpaperNdx); if (visible) { updateWallpaperOffsetLocked(wallpaper, dw, dh, false); } - // First, make sure the client has the current visibility - // state. + // First, make sure the client has the current visibility state. dispatchWallpaperVisibility(wallpaper, visible); - wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + mWallpaperAnimLayerAdjustment; + wallpaper.mWinAnimator.mAnimLayer = + wallpaper.mLayer + mWallpaperAnimLayerAdjustment; if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win " + wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer); - // First, if this window is at the current index, then all - // is well. + // First, if this window is at the current index, then all is well. if (wallpaper == foundW) { foundI--; - foundW = foundI > 0 - ? windows.get(foundI-1) : null; + foundW = foundI > 0 ? windows.get(foundI - 1) : null; continue; } @@ -2016,52 +2114,24 @@ public class WindowManagerService extends IWindowManager.Stub 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); - } + changed = true; } } - */ - if (targetChanged && DEBUG_WALLPAPER_LIGHT) { - Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget - + " lower=" + mLowerWallpaperTarget + " upper=" - + mUpperWallpaperTarget); - } + if (targetChanged && DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + + mWallpaperTarget + " lower=" + mLowerWallpaperTarget + " upper=" + + mUpperWallpaperTarget); return changed; } void setWallpaperAnimLayerAdjustmentLocked(int adj) { - if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, - "Setting wallpaper layer adj to " + adj); + if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "Setting wallpaper layer adj to " + adj); mWallpaperAnimLayerAdjustment = adj; - int curTokenIndex = mWallpaperTokens.size(); - while (curTokenIndex > 0) { - curTokenIndex--; - WindowToken token = mWallpaperTokens.get(curTokenIndex); - int curWallpaperIndex = token.windows.size(); - while (curWallpaperIndex > 0) { - curWallpaperIndex--; - WindowState wallpaper = token.windows.get(curWallpaperIndex); + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + WindowList windows = mWallpaperTokens.get(curTokenNdx).windows; + for (int wallpaperNdx = windows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { + WindowState wallpaper = windows.get(wallpaperNdx); wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + adj; if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "setWallpaper win " + wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer); @@ -2195,14 +2265,10 @@ public class WindowManagerService extends IWindowManager.Stub } } - int curTokenIndex = mWallpaperTokens.size(); - while (curTokenIndex > 0) { - curTokenIndex--; - WindowToken token = mWallpaperTokens.get(curTokenIndex); - int curWallpaperIndex = token.windows.size(); - while (curWallpaperIndex > 0) { - curWallpaperIndex--; - WindowState wallpaper = token.windows.get(curWallpaperIndex); + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + WindowList windows = mWallpaperTokens.get(curTokenNdx).windows; + for (int wallpaperNdx = windows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { + WindowState wallpaper = windows.get(wallpaperNdx); if (updateWallpaperOffsetLocked(wallpaper, dw, dh, sync)) { WindowStateAnimator winAnimator = wallpaper.mWinAnimator; winAnimator.computeShownFrameLocked(); @@ -2217,12 +2283,15 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Check wallpaper for visiblity change and notify window if so. + * Check wallpaper for visibility change and notify window if so. * @param wallpaper The wallpaper to test and notify. * @param visible Current visibility. */ void dispatchWallpaperVisibility(final WindowState wallpaper, final boolean visible) { - if (wallpaper.mWallpaperVisible != visible) { + // Only send notification if the visibility actually changed and we are not trying to hide + // the wallpaper when we are deferring hiding of the wallpaper. + if (wallpaper.mWallpaperVisible != visible + && (mDeferredHideWallpaper == null || visible)) { wallpaper.mWallpaperVisible = visible; try { if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, @@ -2234,7 +2303,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - void updateWallpaperVisibilityLocked() { + private void updateWallpaperVisibilityLocked() { final boolean visible = isWallpaperVisible(mWallpaperTarget); final DisplayContent displayContent = mWallpaperTarget.getDisplayContent(); if (displayContent == null) { @@ -2244,21 +2313,18 @@ public class WindowManagerService extends IWindowManager.Stub final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; - int curTokenIndex = mWallpaperTokens.size(); - while (curTokenIndex > 0) { - curTokenIndex--; - WindowToken token = mWallpaperTokens.get(curTokenIndex); + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + WindowToken token = mWallpaperTokens.get(curTokenNdx); if (token.hidden == visible) { token.hidden = !visible; // Need to do a layout to ensure the wallpaper now has the // correct size. - getDefaultDisplayContentLocked().layoutNeeded = true; + displayContent.layoutNeeded = true; } - int curWallpaperIndex = token.windows.size(); - while (curWallpaperIndex > 0) { - curWallpaperIndex--; - WindowState wallpaper = token.windows.get(curWallpaperIndex); + final WindowList windows = token.windows; + for (int wallpaperNdx = windows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { + WindowState wallpaper = windows.get(wallpaperNdx); if (visible) { updateWallpaperOffsetLocked(wallpaper, dw, dh, false); } @@ -2594,11 +2660,11 @@ public class WindowManagerService extends IWindowManager.Stub if (win == null) { return; } - removeWindowLocked(session, win); + removeWindowLocked(win); } } - public void removeWindowLocked(Session session, WindowState win) { + void removeWindowLocked(WindowState win) { if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win); } @@ -2672,7 +2738,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - removeWindowInnerLocked(session, win); + removeWindowInnerLocked(win); // Removing a visible window will effect the computed orientation // So just update orientation if needed. if (wasVisible && updateOrientationFromAppTokensLocked(false)) { @@ -2682,7 +2748,7 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } - void removeWindowInnerLocked(Session session, WindowState win) { + void removeWindowInnerLocked(WindowState win) { if (win.mRemoved) { // Nothing to do. return; @@ -2692,7 +2758,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState cwin = win.mChildWindows.get(i); Slog.w(TAG, "Force-removing child win " + cwin + " from container " + win); - removeWindowInnerLocked(cwin.mSession, cwin); + removeWindowInnerLocked(cwin); } win.mRemoved = true; @@ -2916,14 +2982,10 @@ public class WindowManagerService extends IWindowManager.Stub if (window == mWallpaperTarget || window == mLowerWallpaperTarget || window == mUpperWallpaperTarget) { boolean doWait = sync; - int curTokenIndex = mWallpaperTokens.size(); - while (curTokenIndex > 0) { - curTokenIndex--; - WindowToken token = mWallpaperTokens.get(curTokenIndex); - int curWallpaperIndex = token.windows.size(); - while (curWallpaperIndex > 0) { - curWallpaperIndex--; - WindowState wallpaper = token.windows.get(curWallpaperIndex); + for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { + final WindowList windows = mWallpaperTokens.get(curTokenNdx).windows; + for (int wallpaperNdx = windows.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) { + WindowState wallpaper = windows.get(wallpaperNdx); try { wallpaper.mClient.dispatchWallpaperCommand(action, x, y, z, extras, sync); @@ -2942,37 +3004,6 @@ public class WindowManagerService extends IWindowManager.Stub return null; } - public void setUniverseTransformLocked(WindowState window, float alpha, - float offx, float offy, float dsdx, float dtdx, float dsdy, float dtdy) { - Transformation transform = window.mWinAnimator.mUniverseTransform; - transform.setAlpha(alpha); - Matrix matrix = transform.getMatrix(); - matrix.getValues(mTmpFloats); - mTmpFloats[Matrix.MTRANS_X] = offx; - mTmpFloats[Matrix.MTRANS_Y] = offy; - mTmpFloats[Matrix.MSCALE_X] = dsdx; - mTmpFloats[Matrix.MSKEW_Y] = dtdx; - mTmpFloats[Matrix.MSKEW_X] = dsdy; - mTmpFloats[Matrix.MSCALE_Y] = dtdy; - matrix.setValues(mTmpFloats); - final DisplayContent displayContent = window.getDisplayContent(); - if (displayContent == null) { - return; - } - - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - final RectF dispRect = new RectF(0, 0, - displayInfo.logicalWidth, displayInfo.logicalHeight); - matrix.mapRect(dispRect); - window.mGivenTouchableRegion.set(0, 0, - displayInfo.logicalWidth, displayInfo.logicalHeight); - window.mGivenTouchableRegion.op((int)dispRect.left, (int)dispRect.top, - (int)dispRect.right, (int)dispRect.bottom, Region.Op.DIFFERENCE); - window.mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; - displayContent.layoutNeeded = true; - performLayoutAndPlaceSurfacesLocked(); - } - public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) { synchronized (mWindowMap) { if (mAccessibilityController != null) { @@ -2992,6 +3023,15 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void pokeDrawLock(Session session, IBinder token) { + synchronized (mWindowMap) { + WindowState window = windowForClientLocked(session, token, false); + if (window != null) { + window.pokeDrawLockLw(mDrawLockTimeoutMillis); + } + } + } + public int relayoutWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, @@ -3119,6 +3159,10 @@ public class WindowManagerService extends IWindowManager.Stub } winAnimator.mEnteringAnimation = true; if (toBeDisplayed) { + if ((win.mAttrs.softInputMode & SOFT_INPUT_MASK_ADJUST) + == SOFT_INPUT_ADJUST_RESIZE) { + win.mLayoutNeeded = true; + } if (win.isDrawnLw() && okToDisplay()) { winAnimator.applyEnterAnimationLocked(); } @@ -3384,6 +3428,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState win = atoken.findMainWindow(); Rect containingFrame = new Rect(0, 0, width, height); Rect contentInsets = new Rect(); + Rect appFrame = new Rect(0, 0, width, height); boolean isFullScreen = true; if (win != null) { if (win.mContainingFrame != null) { @@ -3392,6 +3437,9 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mContentInsets != null) { contentInsets.set(win.mContentInsets); } + if (win.mFrame != null) { + appFrame.set(win.mFrame); + } isFullScreen = ((win.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) == SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) || @@ -3405,8 +3453,8 @@ public class WindowManagerService extends IWindowManager.Stub enter = false; } Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height, - mCurConfiguration.orientation, containingFrame, contentInsets, isFullScreen, - isVoiceInteraction); + mCurConfiguration.orientation, containingFrame, contentInsets, appFrame, + isFullScreen, isVoiceInteraction); if (a != null) { if (DEBUG_ANIM) { RuntimeException e = null; @@ -3592,13 +3640,13 @@ public class WindowManagerService extends IWindowManager.Stub false /*updateInputWindows*/); } - if (delayed) { - if (displayContent != null) { - displayContent.mExitingTokens.add(wtoken); - } + if (delayed && displayContent != null) { + displayContent.mExitingTokens.add(wtoken); } else if (wtoken.windowType == TYPE_WALLPAPER) { mWallpaperTokens.remove(wtoken); } + } else if (wtoken.windowType == TYPE_WALLPAPER) { + mWallpaperTokens.remove(wtoken); } mInputMonitor.updateInputWindowsLw(true /*force*/); @@ -3609,23 +3657,23 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } - private Task createTask(int taskId, int stackId, int userId, AppWindowToken atoken) { - if (DEBUG_STACK) Slog.i(TAG, "createTask: taskId=" + taskId + " stackId=" + stackId + private Task createTaskLocked(int taskId, int stackId, int userId, AppWindowToken atoken) { + if (DEBUG_STACK) Slog.i(TAG, "createTaskLocked: taskId=" + taskId + " stackId=" + stackId + " atoken=" + atoken); final TaskStack stack = mStackIdToStack.get(stackId); if (stack == null) { throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId); } EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId); - Task task = new Task(atoken, stack, userId); + Task task = new Task(taskId, stack, userId, this); mTaskIdToTask.put(taskId, task); - stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */); + stack.addTask(task, !atoken.mLaunchTaskBehind /* toTop */, atoken.showForAllUsers); return task; } @Override public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId, - int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, + int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int userId, int configChanges, boolean voiceInteraction, boolean launchTaskBehind) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addAppToken()")) { @@ -3654,9 +3702,8 @@ public class WindowManagerService extends IWindowManager.Stub } atoken = new AppWindowToken(this, token, voiceInteraction); atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; - atoken.groupId = taskId; atoken.appFullscreen = fullscreen; - atoken.showWhenLocked = showWhenLocked; + atoken.showForAllUsers = showForAllUsers; atoken.requestedOrientation = requestedOrientation; atoken.layoutConfigChanges = (configChanges & (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0; @@ -3666,10 +3713,9 @@ public class WindowManagerService extends IWindowManager.Stub Task task = mTaskIdToTask.get(taskId); if (task == null) { - createTask(taskId, stackId, userId, atoken); - } else { - task.addAppToken(addPos, atoken); + task = createTaskLocked(taskId, stackId, userId, atoken); } + task.addAppToken(addPos, atoken); mTokenMap.put(token.asBinder(), atoken); @@ -3682,68 +3728,92 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setAppGroupId(IBinder token, int groupId) { + public void setAppTask(IBinder token, int taskId) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, - "setAppGroupId()")) { + "setAppTask()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } synchronized(mWindowMap) { final AppWindowToken atoken = findAppWindowToken(token); if (atoken == null) { - Slog.w(TAG, "Attempted to set group id of non-existing app token: " + token); + Slog.w(TAG, "Attempted to set task id of non-existing app token: " + token); return; } - final Task oldTask = mTaskIdToTask.get(atoken.groupId); + final Task oldTask = atoken.mTask; oldTask.removeAppToken(atoken); - atoken.groupId = groupId; - Task newTask = mTaskIdToTask.get(groupId); + Task newTask = mTaskIdToTask.get(taskId); if (newTask == null) { - newTask = createTask(groupId, oldTask.mStack.mStackId, oldTask.mUserId, atoken); - } else { - newTask.mAppTokens.add(atoken); + newTask = + createTaskLocked(taskId, oldTask.mStack.mStackId, oldTask.mUserId, atoken); } + newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken); } } - public int getOrientationFromWindowsLocked() { - if (mDisplayFrozen || mOpeningApps.size() > 0 || mClosingApps.size() > 0) { - // If the display is frozen, some activities may be in the middle - // of restarting, and thus have removed their old window. If the - // window has the flag to hide the lock screen, then the lock screen - // can re-appear and inflict its own orientation on us. Keep the - // orientation stable until this all settles down. - return mLastWindowForcedOrientation; - } - - // TODO(multidisplay): Change to the correct display. - final WindowList windows = getDefaultWindowListLocked(); - int pos = windows.size() - 1; - while (pos >= 0) { - WindowState win = windows.get(pos); - pos--; - if (win.mAppToken != null) { - // We hit an application window. so the orientation will be determined by the - // app window. No point in continuing further. - return (mLastWindowForcedOrientation=ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - } - if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) { - continue; + public int getOrientationLocked() { + if (mDisplayFrozen) { + if (mLastWindowForcedOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { + if (DEBUG_ORIENTATION) Slog.v(TAG, "Display is frozen, return " + + mLastWindowForcedOrientation); + // If the display is frozen, some activities may be in the middle + // of restarting, and thus have removed their old window. If the + // window has the flag to hide the lock screen, then the lock screen + // can re-appear and inflict its own orientation on us. Keep the + // orientation stable until this all settles down. + return mLastWindowForcedOrientation; } - int req = win.mAttrs.screenOrientation; - if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) || - (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){ - continue; + } else { + // TODO(multidisplay): Change to the correct display. + final WindowList windows = getDefaultWindowListLocked(); + for (int pos = windows.size() - 1; pos >= 0; --pos) { + WindowState win = windows.get(pos); + if (win.mAppToken != null) { + // We hit an application window. so the orientation will be determined by the + // app window. No point in continuing further. + break; + } + if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) { + continue; + } + int req = win.mAttrs.screenOrientation; + if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) || + (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){ + continue; + } + + if (DEBUG_ORIENTATION) Slog.v(TAG, win + " forcing orientation to " + req); + if (mPolicy.isKeyguardHostWindow(win.mAttrs)) { + mLastKeyguardForcedOrientation = req; + } + return (mLastWindowForcedOrientation = req); } + mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - if (DEBUG_ORIENTATION) Slog.v(TAG, win + " forcing orientation to " + req); - return (mLastWindowForcedOrientation=req); + if (mPolicy.isKeyguardLocked()) { + // The screen is locked and no top system window is requesting an orientation. + // Return either the orientation of the show-when-locked app (if there is any) or + // the orientation of the keyguard. No point in searching from the rest of apps. + WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw(); + AppWindowToken appShowWhenLocked = winShowWhenLocked == null ? + null : winShowWhenLocked.mAppToken; + if (appShowWhenLocked != null) { + int req = appShowWhenLocked.requestedOrientation; + if (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND) { + req = mLastKeyguardForcedOrientation; + } + if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + appShowWhenLocked + + " -- show when locked, return " + req); + return req; + } + if (DEBUG_ORIENTATION) Slog.v(TAG, + "No one is requesting an orientation when the screen is locked"); + return mLastKeyguardForcedOrientation; + } } - return (mLastWindowForcedOrientation=ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - } - public int getOrientationFromAppTokensLocked() { + // Top system windows are not requesting an orientation. Start searching from apps. int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; boolean findingBehind = false; boolean lastFullscreen = false; @@ -3798,14 +3868,12 @@ public class WindowManagerService extends IWindowManager.Stub // 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 (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 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 @@ -3815,8 +3883,11 @@ public class WindowManagerService extends IWindowManager.Stub findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND); } } - if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation"); - return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation, return " + + mForcedAppOrientation); + // The next app has not been requested to be visible, so we keep the current orientation + // to prevent freezing/unfreezing the display too early. + return mForcedAppOrientation; } @Override @@ -3841,6 +3912,9 @@ public class WindowManagerService extends IWindowManager.Stub private Configuration updateOrientationFromAppTokensLocked( Configuration currentConfig, IBinder freezeThisOneIfNeeded) { + if (!mDisplayReady) { + return null; + } Configuration config = null; if (updateOrientationFromAppTokensLocked(false)) { @@ -3859,20 +3933,19 @@ public class WindowManagerService extends IWindowManager.Stub // the value of the previous configuration. mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = currentConfig.fontScale; - if (computeScreenConfigurationLocked(mTempConfiguration)) { - if (currentConfig.diff(mTempConfiguration) != 0) { - mWaitingForConfig = true; - final DisplayContent displayContent = getDefaultDisplayContentLocked(); - displayContent.layoutNeeded = true; - int anim[] = new int[2]; - if (displayContent.isDimming()) { - anim[0] = anim[1] = 0; - } else { - mPolicy.selectRotationAnimationLw(anim); - } - startFreezingDisplayLocked(false, anim[0], anim[1]); - config = new Configuration(mTempConfiguration); + computeScreenConfigurationLocked(mTempConfiguration); + if (currentConfig.diff(mTempConfiguration) != 0) { + mWaitingForConfig = true; + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + displayContent.layoutNeeded = true; + int anim[] = new int[2]; + if (displayContent.isDimming()) { + anim[0] = anim[1] = 0; + } else { + mPolicy.selectRotationAnimationLw(anim); } + startFreezingDisplayLocked(false, anim[0], anim[1]); + config = new Configuration(mTempConfiguration); } } @@ -3896,11 +3969,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean updateOrientationFromAppTokensLocked(boolean inTransaction) { long ident = Binder.clearCallingIdentity(); try { - int req = getOrientationFromWindowsLocked(); - if (req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { - req = getOrientationFromAppTokensLocked(); - } - + int req = getOrientationLocked(); if (req != mForcedAppOrientation) { mForcedAppOrientation = req; //send a message to Policy indicating orientation change to take @@ -3987,7 +4056,7 @@ public class WindowManagerService extends IWindowManager.Stub void setFocusedStackFrame() { final TaskStack stack; if (mFocusedApp != null) { - Task task = mTaskIdToTask.get(mFocusedApp.groupId); + final Task task = mFocusedApp.mTask; stack = task.mStack; final DisplayContent displayContent = task.getDisplayContent(); if (displayContent != null) { @@ -3996,20 +4065,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { stack = null; } - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setFocusedStackFrame"); - SurfaceControl.openTransaction(); - try { - if (stack == null) { - mFocusedStackFrame.setVisibility(false); - } else { - mFocusedStackFrame.setBounds(stack); - final boolean multipleStacks = !stack.isFullscreen(); - mFocusedStackFrame.setVisibility(multipleStacks); - } - } finally { - SurfaceControl.closeTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setFocusedStackFrame"); - } + mFocusedStackFrame.setVisibility(stack); } @Override @@ -4037,6 +4093,15 @@ public class WindowManagerService extends IWindowManager.Stub if (changed) { mFocusedApp = newFocus; mInputMonitor.setFocusedAppLw(newFocus); + setFocusedStackFrame(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setFocusedApp"); + SurfaceControl.openTransaction(); + try { + setFocusedStackLayer(); + } finally { + SurfaceControl.closeTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setFocusedApp"); + } } if (moveFocusNow && changed) { @@ -4079,6 +4144,8 @@ public class WindowManagerService extends IWindowManager.Stub mAppTransition.prepare(); mStartingIconInTransition = false; mSkipAppTransitionAnimation = false; + } + if (mAppTransition.isTransitionSet()) { mH.removeMessages(H.APP_TRANSITION_TIMEOUT); mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, 5000); } @@ -4109,6 +4176,15 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void overridePendingAppTransitionClipReveal(int startX, int startY, + int startWidth, int startHeight) { + synchronized(mWindowMap) { + mAppTransition.overridePendingAppTransitionClipReveal(startX, startY, startWidth, + startHeight); + } + } + + @Override public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY, IRemoteCallback startedCallback, boolean scaleUp) { synchronized(mWindowMap) { @@ -4142,8 +4218,8 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized(mWindowMap) { - if (DEBUG_APP_TRANSITIONS) Slog.w(TAG, "Execute app transition: " + mAppTransition, - new RuntimeException("here").fillInStackTrace()); + if (DEBUG_APP_TRANSITIONS) Slog.w(TAG, "Execute app transition: " + mAppTransition + + " Callers=" + Debug.getCallers(5)); if (mAppTransition.isTransitionSet()) { mAppTransition.setReady(); final long origId = Binder.clearCallingIdentity(); @@ -4334,8 +4410,13 @@ public class WindowManagerService extends IWindowManager.Stub + " ShowWallpaper=" + ent.array.getBoolean( com.android.internal.R.styleable.Window_windowShowWallpaper, false)); - if (ent.array.getBoolean( - com.android.internal.R.styleable.Window_windowIsTranslucent, false)) { + final boolean windowIsTranslucentDefined = ent.array.hasValue( + com.android.internal.R.styleable.Window_windowIsTranslucent); + final boolean windowIsTranslucent = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowIsTranslucent, false); + final boolean windowSwipeToDismiss = ent.array.getBoolean( + com.android.internal.R.styleable.Window_windowSwipeToDismiss, false); + if (windowIsTranslucent || (!windowIsTranslucentDefined && windowSwipeToDismiss)) { return; } if (ent.array.getBoolean( @@ -4574,22 +4655,26 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) Slog.v(TAG, "setAppVisibility(" + token + ", visible=" + visible + "): " + mAppTransition + " hidden=" + wtoken.hidden + " hiddenRequested=" + - wtoken.hiddenRequested, HIDE_STACK_CRAWLS ? - null : new RuntimeException("here").fillInStackTrace()); + wtoken.hiddenRequested + " Callers=" + Debug.getCallers(6)); + + mOpeningApps.remove(wtoken); + mClosingApps.remove(wtoken); + wtoken.waitingToShow = wtoken.waitingToHide = false; + wtoken.hiddenRequested = !visible; + + mOpeningApps.remove(wtoken); + mClosingApps.remove(wtoken); + wtoken.waitingToShow = wtoken.waitingToHide = false; + wtoken.hiddenRequested = !visible; // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. if (okToDisplay() && mAppTransition.isTransitionSet()) { - wtoken.hiddenRequested = !visible; - if (!wtoken.startingDisplayed) { if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Setting dummy animation on: " + wtoken); wtoken.mAppAnimator.setDummyAnimation(); } - mOpeningApps.remove(wtoken); - mClosingApps.remove(wtoken); - wtoken.waitingToShow = wtoken.waitingToHide = false; wtoken.inPendingTransaction = true; if (visible) { mOpeningApps.add(wtoken); @@ -4644,6 +4729,7 @@ public class WindowManagerService extends IWindowManager.Stub } final long origId = Binder.clearCallingIdentity(); + wtoken.inPendingTransaction = false; setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET, true, wtoken.voiceInteraction); wtoken.updateReportedVisibilityLocked(); @@ -4662,7 +4748,8 @@ public class WindowManagerService extends IWindowManager.Stub WindowState w = wtoken.allAppWindows.get(i); if (w.mAppFreezing) { w.mAppFreezing = false; - if (w.mHasSurface && !w.mOrientationChanging) { + if (w.mHasSurface && !w.mOrientationChanging + && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) { if (DEBUG_ORIENTATION) Slog.v(TAG, "set mOrientationChanging of " + w); w.mOrientationChanging = true; mInnerFields.mOrientationChangeComplete = false; @@ -4711,7 +4798,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mAppsFreezingScreen == 1) { startFreezingDisplayLocked(false, 0, 0); mH.removeMessages(H.APP_FREEZE_TIMEOUT); - mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 5000); + mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000); } } final int N = wtoken.allAppWindows.size(); @@ -4766,17 +4853,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void removeAppFromTaskLocked(AppWindowToken wtoken) { - wtoken.removeAllWindows(); - - final Task task = mTaskIdToTask.get(wtoken.groupId); - if (task != null) { - if (!task.removeAppToken(wtoken)) { - Slog.e(TAG, "removeAppFromTaskLocked: token=" + wtoken + " not found."); - } - } - } - @Override public void removeAppToken(IBinder token) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, @@ -4811,20 +4887,20 @@ public class WindowManagerService extends IWindowManager.Stub + " animating=" + wtoken.mAppAnimator.animating); if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "removeAppToken: " + wtoken + " delayed=" + delayed + " Callers=" + Debug.getCallers(4)); - final TaskStack stack = mTaskIdToTask.get(wtoken.groupId).mStack; + final TaskStack stack = wtoken.mTask.mStack; if (delayed && !wtoken.allAppWindows.isEmpty()) { // 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); stack.mExitingAppTokens.add(wtoken); - wtoken.mDeferRemoval = true; + wtoken.mIsExiting = true; } else { // 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 wtoken.mAppAnimator.clearAnimation(); wtoken.mAppAnimator.animating = false; - removeAppFromTaskLocked(wtoken); + wtoken.removeAppFromTaskLocked(); } wtoken.removed = true; @@ -4867,28 +4943,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - private boolean tmpRemoveAppWindowsLocked(WindowToken token) { - WindowList windows = token.windows; - final int NW = windows.size(); - if (NW > 0) { - mWindowsChanged = true; - } - for (int i = 0; i < NW; i++) { - WindowState win = windows.get(i); - if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing app window " + win); - win.getWindowList().remove(win); - int j = win.mChildWindows.size(); - while (j > 0) { - j--; - WindowState cwin = win.mChildWindows.get(j); - if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, - "Tmp removing child window " + cwin); - cwin.getWindowList().remove(cwin); - } - } - return NW > 0; - } - void dumpAppTokensLocked() { final int numStacks = mStackIdToStack.size(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { @@ -4898,7 +4952,7 @@ public class WindowManagerService extends IWindowManager.Stub final int numTasks = tasks.size(); for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { final Task task = tasks.get(taskNdx); - Slog.v(TAG, " Task #" + task.taskId + " activities from bottom to top:"); + Slog.v(TAG, " Task #" + task.mTaskId + " activities from bottom to top:"); AppTokenList tokens = task.mAppTokens; final int numTokens = tokens.size(); for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { @@ -4920,90 +4974,20 @@ public class WindowManagerService extends IWindowManager.Stub } } - 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(); - - 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; - } - 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; - } - } - } - } - } - } - // 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; - } - private final int reAddWindowLocked(int index, WindowState win) { final WindowList windows = win.getWindowList(); + // Adding child windows relies on mChildWindows being ordered by mSubLayer. final int NCW = win.mChildWindows.size(); - boolean added = false; + boolean winAdded = false; for (int j=0; j<NCW; j++) { WindowState cwin = win.mChildWindows.get(j); - if (!added && cwin.mSubLayer >= 0) { + if (!winAdded && cwin.mSubLayer >= 0) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at " + index + ": " + cwin); win.mRebuilding = false; windows.add(index, win); index++; - added = true; + winAdded = true; } if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at " + index + ": " + cwin); @@ -5011,7 +4995,7 @@ public class WindowManagerService extends IWindowManager.Stub windows.add(index, cwin); index++; } - if (!added) { + if (!winAdded) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at " + index + ": " + win); win.mRebuilding = false; @@ -5036,41 +5020,40 @@ public class WindowManagerService extends IWindowManager.Stub return index; } - void tmpRemoveTaskWindowsLocked(Task task) { - AppTokenList tokens = task.mAppTokens; - for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { - tmpRemoveAppWindowsLocked(tokens.get(tokenNdx)); - } - } void moveStackWindowsLocked(DisplayContent displayContent) { - // First remove all of the windows from the list. - final ArrayList<Task> tasks = displayContent.getTasks(); - final int numTasks = tasks.size(); - for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { - tmpRemoveTaskWindowsLocked(tasks.get(taskNdx)); - } + final WindowList windows = displayContent.getWindowList(); + mTmpWindows.addAll(windows); - // And now add them back at the correct place. - // Where to start adding? - for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { - AppTokenList tokens = tasks.get(taskNdx).mAppTokens; - final int numTokens = tokens.size(); - if (numTokens == 0) { - continue; - } - int pos = findAppWindowInsertionPointLocked(tokens.get(0)); - 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; - } + rebuildAppWindowListLocked(displayContent); + + // Set displayContent.layoutNeeded if window order changed. + final int tmpSize = mTmpWindows.size(); + final int winSize = windows.size(); + int tmpNdx = 0, winNdx = 0; + while (tmpNdx < tmpSize && winNdx < winSize) { + // Skip over all exiting windows, they've been moved out of order. + WindowState tmp; + do { + tmp = mTmpWindows.get(tmpNdx++); + } while (tmpNdx < tmpSize && tmp.mAppToken != null && tmp.mAppToken.mIsExiting); + + WindowState win; + do { + win = windows.get(winNdx++); + } while (winNdx < winSize && win.mAppToken != null && win.mAppToken.mIsExiting); + + if (tmp != win) { + // Window order changed. + displayContent.layoutNeeded = true; + break; } } + if (tmpNdx != winNdx) { + // One list was different from the other. + displayContent.layoutNeeded = true; + } + mTmpWindows.clear(); if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/)) { @@ -5193,30 +5176,6 @@ public class WindowManagerService extends IWindowManager.Stub mStackIdToStack.remove(stackId); } - void removeTaskLocked(Task task) { - final int taskId = task.taskId; - final TaskStack stack = task.mStack; - if (!task.mAppTokens.isEmpty() && stack.isAnimating()) { - if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + taskId); - task.mDeferRemoval = true; - return; - } - if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + taskId); - EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, taskId, "removeTask"); - task.mDeferRemoval = false; - stack.removeTask(task); - mTaskIdToTask.delete(task.taskId); - - final ArrayList<AppWindowToken> exitingApps = stack.mExitingAppTokens; - for (int appNdx = exitingApps.size() - 1; appNdx >= 0; --appNdx) { - final AppWindowToken wtoken = exitingApps.get(appNdx); - if (wtoken.groupId == taskId) { - wtoken.mDeferRemoval = false; - exitingApps.remove(appNdx); - } - } - } - public void removeTask(int taskId) { synchronized (mWindowMap) { Task task = mTaskIdToTask.get(taskId); @@ -5224,7 +5183,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_STACK) Slog.i(TAG, "removeTask: could not find taskId=" + taskId); return; } - removeTaskLocked(task); + task.removeLocked(); } } @@ -5234,6 +5193,7 @@ public class WindowManagerService extends IWindowManager.Stub + " to " + (toTop ? "top" : "bottom")); Task task = mTaskIdToTask.get(taskId); if (task == null) { + if (DEBUG_STACK) Slog.i(TAG, "addTask: could not find taskId=" + taskId); return; } TaskStack stack = mStackIdToStack.get(stackId); @@ -5244,7 +5204,33 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void resizeStack(int stackId, Rect bounds) { + public void moveTaskToStack(int taskId, int stackId, boolean toTop) { + synchronized (mWindowMap) { + if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: moving taskId=" + taskId + + " to stackId=" + stackId + " at " + (toTop ? "top" : "bottom")); + Task task = mTaskIdToTask.get(taskId); + if (task == null) { + if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find taskId=" + taskId); + return; + } + TaskStack stack = mStackIdToStack.get(stackId); + if (stack == null) { + if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find stackId=" + stackId); + return; + } + task.moveTaskToStack(stack, toTop); + final DisplayContent displayContent = stack.getDisplayContent(); + displayContent.layoutNeeded = true; + performLayoutAndPlaceSurfacesLocked(); + } + } + + /** + * Re-sizes the specified stack and its containing windows. + * Returns a {@link Configuration} object that contains configurations settings + * that should be overridden due to the operation. + */ + public Configuration resizeStack(int stackId, Rect bounds) { synchronized (mWindowMap) { final TaskStack stack = mStackIdToStack.get(stackId); if (stack == null) { @@ -5256,6 +5242,7 @@ public class WindowManagerService extends IWindowManager.Stub stack.getDisplayContent().layoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); } + return new Configuration(stack.mOverrideConfig); } } @@ -5268,6 +5255,44 @@ public class WindowManagerService extends IWindowManager.Stub bounds.setEmpty(); } + /** Returns the id of an application (non-home stack) stack that match the input bounds. + * -1 if no stack matches.*/ + public int getStackIdWithBounds(Rect bounds) { + Rect stackBounds = new Rect(); + synchronized (mWindowMap) { + for (int i = mStackIdToStack.size() - 1; i >= 0; --i) { + TaskStack stack = mStackIdToStack.valueAt(i); + if (stack.mStackId != HOME_STACK_ID) { + stack.getBounds(stackBounds); + if (stackBounds.equals(bounds)) { + return stack.mStackId; + } + } + } + } + return -1; + } + + /** Forces the stack to fullscreen if input is true, else un-forces the stack from fullscreen. + * Returns a {@link Configuration} object that contains configurations settings + * that should be overridden due to the operation. + */ + public Configuration forceStackToFullscreen(int stackId, boolean forceFullscreen) { + synchronized (mWindowMap) { + final TaskStack stack = mStackIdToStack.get(stackId); + if (stack == null) { + throw new IllegalArgumentException("resizeStack: stackId " + stackId + + " not found."); + } + if (stack.forceFullscreen(forceFullscreen)) { + stack.resizeWindows(); + stack.getDisplayContent().layoutNeeded = true; + performLayoutAndPlaceSurfacesLocked(); + } + return new Configuration(stack.mOverrideConfig); + } + } + // ------------------------------------------------------------- // Misc IWindowSession methods // ------------------------------------------------------------- @@ -5628,7 +5653,7 @@ public class WindowManagerService extends IWindowManager.Stub final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); - displayContent.switchUserStacks(newUserId); + displayContent.switchUserStacks(); rebuildAppWindowListLocked(displayContent); } performLayoutAndPlaceSurfacesLocked(); @@ -5926,13 +5951,15 @@ public class WindowManagerService extends IWindowManager.Stub if (mCircularDisplayMask == null) { int screenOffset = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.circular_display_mask_offset); + int maskThickness = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.circular_display_mask_thickness); mCircularDisplayMask = new CircularDisplayMask( getDefaultDisplayContentLocked().getDisplay(), mFxSession, mPolicy.windowTypeToLayerLw( WindowManager.LayoutParams.TYPE_POINTER) - * TYPE_LAYER_MULTIPLIER + 10, screenOffset); + * TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness); } mCircularDisplayMask.setVisibility(true); } else if (mCircularDisplayMask != null) { @@ -6055,26 +6082,57 @@ public class WindowManagerService extends IWindowManager.Stub * Takes a snapshot of the screen. In landscape mode this grabs the whole screen. * In portrait mode, it grabs the upper region of the screen based on the vertical dimension * of the target image. + */ + @Override + public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) { + if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER, + "requestAssistScreenshot()")) { + throw new SecurityException("Requires READ_FRAME_BUFFER permission"); + } + + FgThread.getHandler().post(new Runnable() { + @Override + public void run() { + Bitmap bm = screenshotApplicationsInner(null, Display.DEFAULT_DISPLAY, -1, -1, + true); + try { + receiver.send(bm); + } catch (RemoteException e) { + } + } + }); + + return true; + } + + /** + * Takes a snapshot of the screen. In landscape mode this grabs the whole screen. + * In portrait mode, it grabs the upper region of the screen based on the vertical dimension + * of the target image. * * @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, boolean force565) { + public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height) { if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER, "screenshotApplications()")) { throw new SecurityException("Requires READ_FRAME_BUFFER permission"); } + return screenshotApplicationsInner(appToken, displayId, width, height, false); + } - final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent == null) { - if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken - + ": returning null. No Display for displayId=" + displayId); - return null; + Bitmap screenshotApplicationsInner(IBinder appToken, int displayId, int width, int height, + boolean includeFullDisplay) { + final DisplayContent displayContent; + synchronized(mWindowMap) { + displayContent = getDisplayContentLocked(displayId); + if (displayContent == null) { + if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken + + ": returning null. No Display for displayId=" + displayId); + return null; + } } final DisplayInfo displayInfo = displayContent.getDisplayInfo(); int dw = displayInfo.logicalWidth; @@ -6091,9 +6149,6 @@ public class WindowManagerService extends IWindowManager.Stub final Rect frame = new Rect(); final Rect stackBounds = new Rect(); - float scale = 0; - int rot = Surface.ROTATION_0; - boolean screenshotReady; int minLayer; if (appToken == null) { @@ -6174,7 +6229,7 @@ public class WindowManagerService extends IWindowManager.Stub } // Don't include wallpaper in bounds calculation - if (!ws.mIsWallpaper) { + if (!includeFullDisplay && !ws.mIsWallpaper) { final Rect wf = ws.mFrame; final Rect cr = ws.mContentInsets; int left = wf.left + cr.left; @@ -6187,9 +6242,13 @@ public class WindowManagerService extends IWindowManager.Stub } if (ws.mAppToken != null && ws.mAppToken.token == appToken && - ws.isDisplayedLw()) { + ws.isDisplayedLw() && winAnim.mSurfaceShown) { screenshotReady = true; } + + if (ws.isFullscreen(dw, dh) && ws.isOpaqueDrawn()){ + break; + } } if (appToken != null && appWin == null) { @@ -6224,8 +6283,21 @@ public class WindowManagerService extends IWindowManager.Stub return null; } - // Constrain frame to the screen size. - frame.intersect(0, 0, dw, dh); + if (!includeFullDisplay) { + // Constrain frame to the screen size. + frame.intersect(0, 0, dw, dh); + } else { + // Caller just wants entire display. + frame.set(0, 0, dw, dh); + } + + + if (width < 0) { + width = frame.width(); + } + if (height < 0) { + height = frame.height(); + } // Tell surface flinger what part of the image to crop. Take the top // right part of the application, and crop the larger dimension to fit. @@ -6239,7 +6311,7 @@ public class WindowManagerService extends IWindowManager.Stub } // The screenshot API does not apply the current screen rotation. - rot = getDefaultDisplayContentLocked().getDisplay().getRotation(); + int rot = getDefaultDisplayContentLocked().getDisplay().getRotation(); if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90; @@ -6485,7 +6557,7 @@ public class WindowManagerService extends IWindowManager.Stub mAltOrientation = altOrientation; mPolicy.setRotationLw(mRotation); - mWindowsFreezingScreen = true; + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE; mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION); mWaitingForConfig = true; @@ -6502,13 +6574,12 @@ public class WindowManagerService extends IWindowManager.Stub screenRotationAnimation = mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY); - // We need to update our screen size information to match the new - // rotation. Note that this is redundant with the later call to - // sendNewConfiguration() that must be called after this function - // returns... however we need to do the screen size part of that - // before then so we have the correct size to use when initializing - // the rotation animation for the new rotation. - computeScreenConfigurationLocked(null); + // We need to update our screen size information to match the new rotation. If the rotation + // has actually changed then this method will return true and, according to the comment at + // the top of the method, the caller is obligated to call computeNewConfigurationLocked(). + // By updating the Display info here it will be available to + // computeScreenConfigurationLocked later. + updateDisplayAndOrientationLocked(); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); if (!inTransaction) { @@ -7057,9 +7128,11 @@ public class WindowManagerService extends IWindowManager.Stub public Configuration computeNewConfiguration() { synchronized (mWindowMap) { + if (!mDisplayReady) { + return null; + } Configuration config = computeNewConfigurationLocked(); - if (config == null && mWaitingForConfig) { - // Nothing changed but we are waiting for something... stop that! + if (mWaitingForConfig) { mWaitingForConfig = false; mLastFinishedFreezeSource = "new-config"; performLayoutAndPlaceSurfacesLocked(); @@ -7071,9 +7144,7 @@ public class WindowManagerService extends IWindowManager.Stub Configuration computeNewConfigurationLocked() { Configuration config = new Configuration(); config.fontScale = 0; - if (!computeScreenConfigurationLocked(config)) { - return null; - } + computeScreenConfigurationLocked(config); return config; } @@ -7180,11 +7251,8 @@ public class WindowManagerService extends IWindowManager.Stub return sw; } - boolean computeScreenConfigurationLocked(Configuration config) { - if (!mDisplayReady) { - return false; - } - + /** Do not call if mDisplayReady == false */ + DisplayInfo updateDisplayAndOrientationLocked() { // TODO(multidisplay): For now, apply Configuration to main screen only. final DisplayContent displayContent = getDefaultDisplayContentLocked(); @@ -7214,11 +7282,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (config != null) { - config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT : - Configuration.ORIENTATION_LANDSCAPE; - } - // Update application display metrics. final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation); final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation); @@ -7233,96 +7296,113 @@ public class WindowManagerService extends IWindowManager.Stub displayInfo.getLogicalMetrics(mRealDisplayMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); displayInfo.getAppMetrics(mDisplayMetrics); + if (displayContent.mDisplayScalingDisabled) { + displayInfo.flags |= Display.FLAG_SCALING_DISABLED; + } else { + displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; + } + mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( displayContent.getDisplayId(), displayInfo); + + displayContent.mBaseDisplayRect.set(0, 0, dw, dh); } if (false) { Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight); } - final DisplayMetrics dm = mDisplayMetrics; - mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm, + mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics, mCompatDisplayMetrics); + return displayInfo; + } - if (config != null) { - config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation) - / dm.density); - config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) - / dm.density); - computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, dm.density, config); - - config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); - config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); - config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh); - config.densityDpi = displayContent.mBaseDisplayDensity; - - // Update the configuration based on available input devices, lid switch, - // and platform configuration. - config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; - config.keyboard = Configuration.KEYBOARD_NOKEYS; - config.navigation = Configuration.NAVIGATION_NONAV; - - int keyboardPresence = 0; - int navigationPresence = 0; - final InputDevice[] devices = mInputManager.getInputDevices(); - final int len = devices.length; - for (int i = 0; i < len; i++) { - InputDevice device = devices[i]; - if (!device.isVirtual()) { - final int sources = device.getSources(); - final int presenceFlag = device.isExternal() ? - WindowManagerPolicy.PRESENCE_EXTERNAL : - WindowManagerPolicy.PRESENCE_INTERNAL; - - if (mIsTouchDevice) { - if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == - InputDevice.SOURCE_TOUCHSCREEN) { - config.touchscreen = Configuration.TOUCHSCREEN_FINGER; - } - } else { - config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; - } + /** Do not call if mDisplayReady == false */ + void computeScreenConfigurationLocked(Configuration config) { + final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(); - if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) { - config.navigation = Configuration.NAVIGATION_TRACKBALL; - navigationPresence |= presenceFlag; - } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD - && config.navigation == Configuration.NAVIGATION_NONAV) { - config.navigation = Configuration.NAVIGATION_DPAD; - navigationPresence |= presenceFlag; + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; + config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT : + Configuration.ORIENTATION_LANDSCAPE; + config.screenWidthDp = + (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation) / mDisplayMetrics.density); + config.screenHeightDp = + (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) / mDisplayMetrics.density); + final boolean rotated = (mRotation == Surface.ROTATION_90 + || mRotation == Surface.ROTATION_270); + computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, mDisplayMetrics.density, + config); + + config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); + config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); + config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, + mDisplayMetrics, dw, dh); + config.densityDpi = displayInfo.logicalDensityDpi; + + // Update the configuration based on available input devices, lid switch, + // and platform configuration. + config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; + config.keyboard = Configuration.KEYBOARD_NOKEYS; + config.navigation = Configuration.NAVIGATION_NONAV; + + int keyboardPresence = 0; + int navigationPresence = 0; + final InputDevice[] devices = mInputManager.getInputDevices(); + final int len = devices.length; + for (int i = 0; i < len; i++) { + InputDevice device = devices[i]; + if (!device.isVirtual()) { + final int sources = device.getSources(); + final int presenceFlag = device.isExternal() ? + WindowManagerPolicy.PRESENCE_EXTERNAL : + WindowManagerPolicy.PRESENCE_INTERNAL; + + if (mIsTouchDevice) { + if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == + InputDevice.SOURCE_TOUCHSCREEN) { + config.touchscreen = Configuration.TOUCHSCREEN_FINGER; } + } else { + config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; + } - if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { - config.keyboard = Configuration.KEYBOARD_QWERTY; - keyboardPresence |= presenceFlag; - } + if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) { + config.navigation = Configuration.NAVIGATION_TRACKBALL; + navigationPresence |= presenceFlag; + } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD + && config.navigation == Configuration.NAVIGATION_NONAV) { + config.navigation = Configuration.NAVIGATION_DPAD; + navigationPresence |= presenceFlag; } - } - if (config.navigation == Configuration.NAVIGATION_NONAV && mHasPermanentDpad) { - config.navigation = Configuration.NAVIGATION_DPAD; - navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL; + if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + config.keyboard = Configuration.KEYBOARD_QWERTY; + keyboardPresence |= presenceFlag; + } } + } - // Determine whether a hard keyboard is available and enabled. - boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; - if (hardKeyboardAvailable != mHardKeyboardAvailable) { - mHardKeyboardAvailable = hardKeyboardAvailable; - mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); - mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); - } - if (mShowImeWithHardKeyboard) { - config.keyboard = Configuration.KEYBOARD_NOKEYS; - } + if (config.navigation == Configuration.NAVIGATION_NONAV && mHasPermanentDpad) { + config.navigation = Configuration.NAVIGATION_DPAD; + navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL; + } - // Let the policy update hidden states. - config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; - config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; - config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; - mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence); + // Determine whether a hard keyboard is available and enabled. + boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; + if (hardKeyboardAvailable != mHardKeyboardAvailable) { + mHardKeyboardAvailable = hardKeyboardAvailable; + mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + } + if (mShowImeWithHardKeyboard) { + config.keyboard = Configuration.KEYBOARD_NOKEYS; } - return true; + // Let the policy update hidden states. + config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; + config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; + config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; + mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence); } public boolean isHardKeyboardAvailable() { @@ -7393,7 +7473,7 @@ public class WindowManagerService extends IWindowManager.Stub outSurface.copyFrom(surface); final IBinder winBinder = window.asBinder(); token = new Binder(); - mDragState = new DragState(this, token, surface, /*flags*/ 0, winBinder); + mDragState = new DragState(this, token, surface, flags, winBinder); token = mDragState.mToken = new Binder(); // 5 second timeout for this window to actually begin the drag @@ -7521,7 +7601,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { final DisplayContent displayContent = getDefaultDisplayContentLocked(); - readForcedDisplaySizeAndDensityLocked(displayContent); + readForcedDisplayPropertiesLocked(displayContent); mDisplayReady = true; } @@ -7618,6 +7698,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int CHECK_IF_BOOT_ANIMATION_FINISHED = 37; public static final int RESET_ANR_MESSAGE = 38; + public static final int WALLPAPER_DRAW_PENDING_TIMEOUT = 39; @Override public void handleMessage(Message msg) { @@ -7853,6 +7934,7 @@ public class WindowManagerService extends IWindowManager.Stub // TODO(multidisplay): Can non-default displays rotate? synchronized (mWindowMap) { Slog.w(TAG, "Window freeze timeout expired."); + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT; final WindowList windows = getDefaultWindowListLocked(); int i = windows.size(); while (i > 0) { @@ -7872,8 +7954,12 @@ public class WindowManagerService extends IWindowManager.Stub case APP_TRANSITION_TIMEOUT: { synchronized (mWindowMap) { - if (mAppTransition.isTransitionSet()) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** APP TRANSITION TIMEOUT"); + if (mAppTransition.isTransitionSet() || !mOpeningApps.isEmpty() + || !mClosingApps.isEmpty()) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** APP TRANSITION TIMEOUT." + + " isTransitionSet()=" + mAppTransition.isTransitionSet() + + " mOpeningApps.size()=" + mOpeningApps.size() + + " mClosingApps.size()=" + mClosingApps.size()); mAppTransition.setTimeout(); performLayoutAndPlaceSurfacesLocked(); } @@ -7920,6 +8006,7 @@ public class WindowManagerService extends IWindowManager.Stub case APP_FREEZE_TIMEOUT: { synchronized (mWindowMap) { Slog.w(TAG, "App freeze timeout expired."); + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT; final int numStacks = mStackIdToStack.size(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { final TaskStack stack = mStackIdToStack.valueAt(stackNdx); @@ -8062,16 +8149,16 @@ public class WindowManagerService extends IWindowManager.Stub 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) { -// } -// } + 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: @@ -8134,6 +8221,17 @@ public class WindowManagerService extends IWindowManager.Stub } } break; + case WALLPAPER_DRAW_PENDING_TIMEOUT: { + synchronized (mWindowMap) { + if (mWallpaperDrawState == WALLPAPER_DRAW_PENDING) { + mWallpaperDrawState = WALLPAPER_DRAW_TIMEOUT; + if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG, + "*** WALLPAPER DRAW TIMEOUT"); + performLayoutAndPlaceSurfacesLocked(); + } + } + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG, "handleMessage: exit"); @@ -8276,7 +8374,47 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void readForcedDisplaySizeAndDensityLocked(final DisplayContent displayContent) { + @Override + public void setForcedDisplayScalingMode(int displayId, int mode) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_SECURE_SETTINGS) != + PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold permission " + + android.Manifest.permission.WRITE_SECURE_SETTINGS); + } + if (displayId != Display.DEFAULT_DISPLAY) { + throw new IllegalArgumentException("Can only set the default display"); + } + final long ident = Binder.clearCallingIdentity(); + try { + synchronized(mWindowMap) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + if (mode < 0 || mode > 1) { + mode = 0; + } + setForcedDisplayScalingModeLocked(displayContent, mode); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DISPLAY_SCALING_FORCE, mode); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, + int mode) { + Slog.i(TAG, "Using display scaling mode: " + (mode == 0 ? "auto" : "off")); + + synchronized(displayContent.mDisplaySizeLock) { + displayContent.mDisplayScalingDisabled = (mode != 0); + } + reconfigureDisplayLocked(displayContent); + } + + private void readForcedDisplayPropertiesLocked(final DisplayContent displayContent) { + // Display size. String sizeStr = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.DISPLAY_SIZE_FORCED); if (sizeStr == null || sizeStr.length() == 0) { @@ -8301,6 +8439,8 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + // Display density. String densityStr = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.DISPLAY_DENSITY_FORCED); if (densityStr == null || densityStr.length() == 0) { @@ -8319,6 +8459,16 @@ public class WindowManagerService extends IWindowManager.Stub } catch (NumberFormatException ex) { } } + + // Display scaling mode. + int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DISPLAY_SCALING_FORCE, 0); + if (mode != 0) { + synchronized(displayContent.mDisplaySizeLock) { + Slog.i(TAG, "FORCED DISPLAY SCALING DISABLED"); + displayContent.mDisplayScalingDisabled = true; + } + } } // displayContent must not be null @@ -8451,17 +8601,17 @@ 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. + if (!mDisplayReady) { + return; + } configureDisplayPolicyLocked(displayContent); displayContent.layoutNeeded = true; boolean configChanged = updateOrientationFromAppTokensLocked(false); mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = mCurConfiguration.fontScale; - if (computeScreenConfigurationLocked(mTempConfiguration)) { - if (mCurConfiguration.diff(mTempConfiguration) != 0) { - configChanged = true; - } - } + computeScreenConfigurationLocked(mTempConfiguration); + configChanged |= mCurConfiguration.diff(mTempConfiguration) != 0; if (configChanged) { mWaitingForConfig = true; @@ -8587,7 +8737,7 @@ public class WindowManagerService extends IWindowManager.Stub numRemoved++; continue; } else if (lastBelow == i-1) { - if (w.mAttrs.type == TYPE_WALLPAPER || w.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) { + if (w.mAttrs.type == TYPE_WALLPAPER) { lastBelow = i; } } @@ -8622,7 +8772,7 @@ public class WindowManagerService extends IWindowManager.Stub final int numTokens = tokens.size(); for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { final AppWindowToken wtoken = tokens.get(tokenNdx); - if (wtoken.mDeferRemoval) { + if (wtoken.mIsExiting) { continue; } i = reAddAppWindowsLocked(displayContent, i, wtoken); @@ -8632,6 +8782,7 @@ public class WindowManagerService extends IWindowManager.Stub i -= lastBelow; if (i != numRemoved) { + displayContent.layoutNeeded = true; Slog.w(TAG, "On display=" + displayContent.getDisplayId() + " Rebuild removed " + numRemoved + " windows but added " + i, new RuntimeException("here").fillInStackTrace()); @@ -8652,6 +8803,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Final window list:"); dumpWindowsLocked(); } + Arrays.fill(mRebuildTmp, null); } private final void assignLayersLocked(WindowList windows) { @@ -8760,29 +8912,24 @@ public class WindowManagerService extends IWindowManager.Stub Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout"); mInLayout = true; - boolean recoveringMemory = false; - try { - if (mForceRemoves != null) { - recoveringMemory = true; - // Wait a little bit for things to settle down, and off we go. - for (int i=0; i<mForceRemoves.size(); i++) { - WindowState ws = mForceRemoves.get(i); - Slog.i(TAG, "Force removing: " + ws); - removeWindowInnerLocked(ws.mSession, ws); - } - mForceRemoves = null; - Slog.w(TAG, "Due to memory failure, waiting a bit for next layout"); - Object tmp = new Object(); - synchronized (tmp) { - try { - tmp.wait(250); - } catch (InterruptedException e) { - } + boolean recoveringMemory = false; + if (!mForceRemoves.isEmpty()) { + recoveringMemory = true; + // Wait a little bit for things to settle down, and off we go. + while (!mForceRemoves.isEmpty()) { + WindowState ws = mForceRemoves.remove(0); + Slog.i(TAG, "Force removing: " + ws); + removeWindowInnerLocked(ws); + } + Slog.w(TAG, "Due to memory failure, waiting a bit for next layout"); + Object tmp = new Object(); + synchronized (tmp) { + try { + tmp.wait(250); + } catch (InterruptedException e) { } } - } catch (RuntimeException e) { - Slog.wtf(TAG, "Unhandled exception while force removing for memory", e); } try { @@ -8840,8 +8987,6 @@ public class WindowManagerService extends IWindowManager.Stub + displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh); } - WindowStateAnimator universeBackground = null; - mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation); if (isDefaultDisplay) { // Not needed on non-default displays. @@ -8898,8 +9043,8 @@ public class WindowManagerService extends IWindowManager.Stub if (!gone || !win.mHaveFrame || win.mLayoutNeeded || ((win.isConfigChanged() || win.setInsetsChanged()) && ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 || - win.mAppToken != null && win.mAppToken.layoutConfigChanges)) - || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) { + (win.mHasSurface && win.mAppToken != null && + win.mAppToken.layoutConfigChanges)))) { if (!win.mLayoutAttached) { if (initial) { //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial"); @@ -8923,16 +9068,6 @@ public class WindowManagerService extends IWindowManager.Stub if (topAttached < 0) topAttached = i; } } - if (win.mViewVisibility == View.VISIBLE - && win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND - && universeBackground == null) { - universeBackground = win.mWinAnimator; - } - } - - if (mAnimator.mUniverseBackground != universeBackground) { - mFocusMayChange = true; - mAnimator.mUniverseBackground = universeBackground; } boolean attachedBehindDream = false; @@ -8993,13 +9128,13 @@ public class WindowManagerService extends IWindowManager.Stub // If the screen is currently frozen or off, then keep // it frozen/off until this window draws at its new // orientation. - if (!okToDisplay()) { + if (!okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Changing surface while display frozen: " + w); w.mOrientationChanging = true; w.mLastFreezeDuration = 0; mInnerFields.mOrientationChangeComplete = false; - if (!mWindowsFreezingScreen) { - mWindowsFreezingScreen = true; + if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) { + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE; // XXX should probably keep timeout from // when we first froze the display. mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); @@ -9023,10 +9158,7 @@ public class WindowManagerService extends IWindowManager.Stub "Checking " + NN + " opening apps (frozen=" + mDisplayFrozen + " timeout=" + mAppTransition.isTimeout() + ")..."); - if (!mDisplayFrozen && !mAppTransition.isTimeout()) { - // If the display isn't frozen, wait to do anything until - // all of the apps are ready. Otherwise just go because - // we'll unfreeze the display when everyone is ready. + if (!mAppTransition.isTimeout()) { for (i=0; i<NN && goodToGo; i++) { AppWindowToken wtoken = mOpeningApps.valueAt(i); if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, @@ -9039,6 +9171,40 @@ public class WindowManagerService extends IWindowManager.Stub goodToGo = false; } } + + if (goodToGo && isWallpaperVisible(mWallpaperTarget)) { + boolean wallpaperGoodToGo = true; + for (int curTokenIndex = mWallpaperTokens.size() - 1; + curTokenIndex >= 0 && wallpaperGoodToGo; curTokenIndex--) { + WindowToken token = mWallpaperTokens.get(curTokenIndex); + for (int curWallpaperIndex = token.windows.size() - 1; curWallpaperIndex >= 0; + curWallpaperIndex--) { + WindowState wallpaper = token.windows.get(curWallpaperIndex); + if (wallpaper.mWallpaperVisible && !wallpaper.isDrawnLw()) { + // We've told this wallpaper to be visible, but it is not drawn yet + wallpaperGoodToGo = false; + if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) { + // wait for this wallpaper until it is drawn or timeout + goodToGo = false; + } + if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) { + mWallpaperDrawState = WALLPAPER_DRAW_PENDING; + mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT); + mH.sendEmptyMessageDelayed(H.WALLPAPER_DRAW_PENDING_TIMEOUT, + WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION); + } + if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG, + "Wallpaper should be visible but has not been drawn yet. " + + "mWallpaperDrawState=" + mWallpaperDrawState); + break; + } + } + } + if (wallpaperGoodToGo) { + mWallpaperDrawState = WALLPAPER_DRAW_NORMAL; + mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT); + } + } } if (goodToGo) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO"); @@ -9046,7 +9212,6 @@ public class WindowManagerService extends IWindowManager.Stub if (mSkipAppTransitionAnimation) { transit = AppTransition.TRANSIT_UNSET; } - mAppTransition.goodToGo(); mStartingIconInTransition = false; mSkipAppTransitionAnimation = false; @@ -9199,6 +9364,7 @@ public class WindowManagerService extends IWindowManager.Stub for (int j = 0; j < N; j++) { appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator); } + mAnimator.mAppWindowAnimating |= appAnimator.isAnimating(); mAnimator.mAnimating |= appAnimator.showAllWindowsLocked(); } } @@ -9221,6 +9387,7 @@ public class WindowManagerService extends IWindowManager.Stub appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator); } mAnimator.mAnimating |= appAnimator.showAllWindowsLocked(); + mAnimator.mAppWindowAnimating |= appAnimator.isAnimating(); if (animLp != null) { int layer = -1; @@ -9257,6 +9424,7 @@ public class WindowManagerService extends IWindowManager.Stub if (wtoken.startingWindow != null && !wtoken.startingWindow.mExiting) { scheduleRemoveStartingWindowLocked(wtoken); } + mAnimator.mAppWindowAnimating |= appAnimator.isAnimating(); if (animLp != null) { int layer = -1; @@ -9341,6 +9509,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + mAppTransition.goodToGo(openingAppAnimator, closingAppAnimator); mAppTransition.postAnimationCallback(); mAppTransition.clear(); @@ -9374,6 +9543,12 @@ public class WindowManagerService extends IWindowManager.Stub int changes = 0; mAppTransition.setIdle(); + + if (mDeferredHideWallpaper != null) { + hideWallpapersLocked(mDeferredHideWallpaper); + mDeferredHideWallpaper = null; + } + // Restore window app tokens to the ActivityManager views ArrayList<TaskStack> stacks = getDefaultDisplayContentLocked().getStacks(); for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) { @@ -9470,14 +9645,12 @@ public class WindowManagerService extends IWindowManager.Stub /** * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. - * - * @param w WindowState this method is applied to. - * @param currentTime The time which animations use for calculating transitions. + * @param w WindowState this method is applied to. * @param innerDw Width of app window. * @param innerDh Height of app window. */ - private void handleNotObscuredLocked(final WindowState w, final long currentTime, - final int innerDw, final int innerDh) { + private void handleNotObscuredLocked(final WindowState w, + final int innerDw, final int innerDh) { final WindowManager.LayoutParams attrs = w.mAttrs; final int attrFlags = attrs.flags; final boolean canBeSeen = w.isDisplayedLw(); @@ -9596,8 +9769,6 @@ public class WindowManagerService extends IWindowManager.Stub + Debug.getCallers(3)); } - final long currentTime = SystemClock.uptimeMillis(); - int i; boolean updateInputWindowsNeeded = false; @@ -9688,8 +9859,7 @@ public class WindowManagerService extends IWindowManager.Stub if ((displayContent.pendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0 && - (adjustWallpaperWindowsLocked() & - ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { + adjustWallpaperWindowsLocked()) { assignLayersLocked(windows); displayContent.layoutNeeded = true; } @@ -9720,15 +9890,12 @@ public class WindowManagerService extends IWindowManager.Stub // it is animating. displayContent.pendingLayoutChanges = 0; - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("loop number " - + mLayoutRepeatCount, displayContent.pendingLayoutChanges); - if (isDefaultDisplay) { mPolicy.beginPostLayoutPolicyLw(dw, dh); for (i = windows.size() - 1; i >= 0; i--) { WindowState w = windows.get(i); if (w.mHasSurface) { - mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.mAttachedWindow); + mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs); } } displayContent.pendingLayoutChanges |= mPolicy.finishPostLayoutPolicyLw(); @@ -9757,7 +9924,7 @@ public class WindowManagerService extends IWindowManager.Stub // Update effect. w.mObscured = mInnerFields.mObscured; if (!mInnerFields.mObscured) { - handleNotObscuredLocked(w, currentTime, innerDw, innerDh); + handleNotObscuredLocked(w, innerDw, innerDh); } if (stack != null && !stack.testDimmingTag()) { @@ -9806,7 +9973,7 @@ public class WindowManagerService extends IWindowManager.Stub if (w.mHasSurface && !w.isHiddenFromUserLocked()) { // Take care of the window being ready to display. final boolean committed = - winAnimator.commitFinishDrawingLocked(currentTime); + winAnimator.commitFinishDrawingLocked(); if (isDefaultDisplay && committed) { if (w.mAttrs.type == TYPE_DREAM) { // HACK: When a dream is shown, it may at that @@ -9931,7 +10098,7 @@ public class WindowManagerService extends IWindowManager.Stub defaultDisplay.pendingLayoutChanges); } - if (!mAnimator.mAnimating && mAppTransition.isRunning()) { + if (!mAnimator.mAppWindowAnimating && mAppTransition.isRunning()) { // We have finished the animation of an app transition. To do // this, we have delayed a lot of operations like showing and // hiding apps, moving apps in Z-order, etc. The app token list @@ -9994,8 +10161,8 @@ public class WindowManagerService extends IWindowManager.Stub "With display frozen, orientationChangeComplete=" + mInnerFields.mOrientationChangeComplete); if (mInnerFields.mOrientationChangeComplete) { - if (mWindowsFreezingScreen) { - mWindowsFreezingScreen = false; + if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) { + mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE; mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource; mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); } @@ -10044,7 +10211,7 @@ public class WindowManagerService extends IWindowManager.Stub for (i = exitingAppTokens.size() - 1; i >= 0; i--) { AppWindowToken token = exitingAppTokens.get(i); if (!token.hasVisible && !mClosingApps.contains(token) && - (!token.mDeferRemoval || token.allAppWindows.isEmpty())) { + (!token.mIsExiting || token.allAppWindows.isEmpty())) { // 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 @@ -10052,12 +10219,7 @@ public class WindowManagerService extends IWindowManager.Stub token.mAppAnimator.animating = false; if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "performLayout: App token exiting now removed" + token); - removeAppFromTaskLocked(token); - exitingAppTokens.remove(i); - final Task task = mTaskIdToTask.get(token.groupId); - if (task != null && task.mDeferRemoval && task.mAppTokens.isEmpty()) { - removeTaskLocked(task); - } + token.removeAppFromTaskLocked(); } } } @@ -10110,7 +10272,9 @@ public class WindowManagerService extends IWindowManager.Stub if (mAllowTheaterModeWakeFromLayout || Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 0) { - if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!"); + if (DEBUG_VISIBILITY || DEBUG_POWER) { + Slog.v(TAG, "Turning screen on after layout!"); + } mPowerManager.wakeUp(SystemClock.uptimeMillis()); } mTurnOnScreen = false; @@ -10141,7 +10305,7 @@ public class WindowManagerService extends IWindowManager.Stub DisplayContentList displayList = new DisplayContentList(); for (i = 0; i < N; i++) { WindowState w = mPendingRemoveTmp[i]; - removeWindowInnerLocked(w.mSession, w); + removeWindowInnerLocked(w); final DisplayContent displayContent = w.getDisplayContent(); if (displayContent != null && !displayList.contains(displayContent)) { displayList.add(displayContent); @@ -10244,8 +10408,7 @@ public class WindowManagerService extends IWindowManager.Stub void scheduleAnimationLocked() { if (!mAnimationScheduled) { mAnimationScheduled = true; - mChoreographer.postCallback( - Choreographer.CALLBACK_ANIMATION, mAnimator.mAnimationRunnable, null); + mChoreographer.postFrameCallback(mAnimator.mAnimationFrameCallback); } } @@ -10281,7 +10444,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { mInnerFields.mOrientationChangeComplete = true; mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource; - if (mWindowsFreezingScreen) { + if (mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) { doRequest = true; } } @@ -10318,10 +10481,6 @@ public class WindowManagerService extends IWindowManager.Stub EventLog.writeEvent(EventLogTags.WM_NO_SURFACE_MEMORY, winAnimator.mWin.toString(), winAnimator.mSession.mPid, operation); - if (mForceRemoves == null) { - mForceRemoves = new ArrayList<WindowState>(); - } - long callingIdentity = Binder.clearCallingIdentity(); try { // There was some problem... first, do a sanity check of the @@ -10481,11 +10640,6 @@ public class WindowManagerService extends IWindowManager.Stub } private WindowState computeFocusedWindowLocked() { - if (mAnimator.mUniverseBackground != null - && mAnimator.mUniverseBackground.mWin.canReceiveKeys()) { - return mAnimator.mUniverseBackground.mWin; - } - final int displayCount = mDisplayContents.size(); for (int i = 0; i < displayCount; i++) { final DisplayContent displayContent = mDisplayContents.valueAt(i); @@ -10508,6 +10662,10 @@ public class WindowManagerService extends IWindowManager.Stub + ", flags=" + win.mAttrs.flags + ", canReceive=" + win.canReceiveKeys()); + if (!win.canReceiveKeys()) { + continue; + } + AppWindowToken wtoken = win.mAppToken; // If this window's application has been removed, just skip it. @@ -10517,10 +10675,6 @@ public class WindowManagerService extends IWindowManager.Stub continue; } - if (!win.canReceiveKeys()) { - continue; - } - // 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 && @@ -10626,13 +10780,15 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen - || mClientFreezingScreen) { + if (mWaitingForConfig || mAppsFreezingScreen > 0 + || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE + || mClientFreezingScreen || !mOpeningApps.isEmpty()) { if (DEBUG_ORIENTATION) Slog.d(TAG, "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig + ", mAppsFreezingScreen=" + mAppsFreezingScreen + ", mWindowsFreezingScreen=" + mWindowsFreezingScreen - + ", mClientFreezingScreen=" + mClientFreezingScreen); + + ", mClientFreezingScreen=" + mClientFreezingScreen + + ", mOpeningApps.size()=" + mOpeningApps.size()); return; } @@ -10674,15 +10830,13 @@ public class WindowManagerService extends IWindowManager.Stub scheduleAnimationLocked(); } else { screenRotationAnimation.kill(); - screenRotationAnimation = null; - mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation); + mAnimator.setScreenRotationAnimationLocked(displayId, null); updateRotation = true; } } else { if (screenRotationAnimation != null) { screenRotationAnimation.kill(); - screenRotationAnimation = null; - mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation); + mAnimator.setScreenRotationAnimationLocked(displayId, null); } updateRotation = true; } @@ -10949,7 +11103,7 @@ public class WindowManagerService extends IWindowManager.Stub void dumpTokensLocked(PrintWriter pw, boolean dumpAll) { pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)"); - if (mTokenMap.size() > 0) { + if (!mTokenMap.isEmpty()) { pw.println(" All tokens:"); Iterator<WindowToken> it = mTokenMap.values().iterator(); while (it.hasNext()) { @@ -10963,7 +11117,7 @@ public class WindowManagerService extends IWindowManager.Stub } } } - if (mWallpaperTokens.size() > 0) { + if (!mWallpaperTokens.isEmpty()) { pw.println(); pw.println(" Wallpaper tokens:"); for (int i=mWallpaperTokens.size()-1; i>=0; i--) { @@ -10978,7 +11132,7 @@ public class WindowManagerService extends IWindowManager.Stub } } } - if (mFinishedStarting.size() > 0) { + if (!mFinishedStarting.isEmpty()) { pw.println(); pw.println(" Finishing start of application tokens:"); for (int i=mFinishedStarting.size()-1; i>=0; i--) { @@ -10993,7 +11147,7 @@ public class WindowManagerService extends IWindowManager.Stub } } } - if (mOpeningApps.size() > 0 || mClosingApps.size() > 0) { + if (!mOpeningApps.isEmpty() || !mClosingApps.isEmpty()) { pw.println(); if (mOpeningApps.size() > 0) { pw.print(" mOpeningApps="); pw.println(mOpeningApps); @@ -11228,7 +11382,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition); pw.print(" mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation); pw.println(" mLayoutToAnim:"); - mAppTransition.dump(pw); + mAppTransition.dump(pw, " "); } } @@ -11784,5 +11938,12 @@ public class WindowManagerService extends IWindowManager.Stub WindowManagerService.this.removeWindowToken(token); } } + + @Override + public void registerAppTransitionListener(AppTransitionListener listener) { + synchronized (mWindowMap) { + mAppTransition.registerListenerLocked(listener); + } + } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 021a6e4..ec70879 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -16,30 +16,34 @@ package com.android.server.wm; -import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION; -import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; -import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION; -import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE; -import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; - import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 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_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION; +import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; +import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION; +import static com.android.server.wm.WindowManagerService.DEBUG_POWER; +import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE; +import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; import android.app.AppOpsManager; import android.os.Debug; +import android.os.PowerManager; import android.os.RemoteCallbackList; import android.os.SystemClock; +import android.os.WorkSource; import android.util.TimeUtils; import android.view.Display; import android.view.IWindowFocusObserver; import android.view.IWindowId; + import com.android.server.input.InputWindowHandle; import android.content.Context; @@ -130,7 +134,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { int mLayoutSeq = -1; - Configuration mConfiguration = null; + private Configuration mConfiguration = Configuration.EMPTY; + private Configuration mOverrideConfig = Configuration.EMPTY; // Sticky answer to isConfigChanged(), remains true until new Configuration is assigned. // Used only on {@link #TYPE_KEYGUARD}. private boolean mConfigHasChanged; @@ -231,7 +236,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { final Rect mParentFrame = new Rect(); - // The entire screen area of the device. + // The entire screen area of the {@link TaskStack} this window is in. Usually equal to the + // screen area of the device. final Rect mDisplayFrame = new Rect(); // The region of the display frame that the display type supports displaying content on. This @@ -341,9 +347,14 @@ 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; + /** + * Wake lock for drawing. + * Even though it's slightly more expensive to do so, we will use a separate wake lock + * for each app that is requesting to draw while dozing so that we can accurately track + * who is preventing the system from suspending. + * This lock is only acquired on first use. + */ + PowerManager.WakeLock mDrawLock; WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a, @@ -407,28 +418,26 @@ final class WindowState implements WindowManagerPolicy.WindowState { mAttachedWindow = attachedWindow; if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow); - int children_size = mAttachedWindow.mChildWindows.size(); - if (children_size == 0) { - mAttachedWindow.mChildWindows.add(this); + final WindowList childWindows = mAttachedWindow.mChildWindows; + final int numChildWindows = childWindows.size(); + if (numChildWindows == 0) { + childWindows.add(this); } else { - for (int i = 0; i < children_size; i++) { - WindowState child = (WindowState)mAttachedWindow.mChildWindows.get(i); - if (this.mSubLayer < child.mSubLayer) { - mAttachedWindow.mChildWindows.add(i, this); + boolean added = false; + for (int i = 0; i < numChildWindows; i++) { + final int childSubLayer = childWindows.get(i).mSubLayer; + if (mSubLayer < childSubLayer + || (mSubLayer == childSubLayer && childSubLayer < 0)) { + // We insert the child window into the list ordered by the sub-layer. For + // same sub-layers, the negative one should go below others; the positive + // one should go above others. + childWindows.add(i, this); + added = true; break; - } else if (this.mSubLayer > child.mSubLayer) { - continue; - } - - if (this.mBaseLayer <= child.mBaseLayer) { - mAttachedWindow.mChildWindows.add(i, this); - break; - } else { - continue; } } - if (children_size == mAttachedWindow.mChildWindows.size()) { - mAttachedWindow.mChildWindows.add(this); + if (!added) { + childWindows.add(this); } } @@ -470,6 +479,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (mAppToken != null) { final DisplayContent appDisplay = getDisplayContent(); mNotOnAppsDisplay = displayContent != appDisplay; + + if (mAppToken.showForAllUsers) { + // Windows for apps that can show for all users should also show when the + // device is locked. + mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED; + } } mWinAnimator = new WindowStateAnimator(this); @@ -508,18 +523,25 @@ final class WindowState implements WindowManagerPolicy.WindowState { public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf) { mHaveFrame = true; - TaskStack stack = mAppToken != null ? getStack() : null; - if (stack != null && !stack.isFullscreen()) { - getStackBounds(stack, mContainingFrame); - if (mUnderStatusBar) { - mContainingFrame.top = pf.top; + final TaskStack stack = mAppToken != null ? getStack() : null; + final boolean nonFullscreenStack = stack != null && !stack.isFullscreen(); + if (nonFullscreenStack) { + stack.getBounds(mContainingFrame); + final WindowState imeWin = mService.mInputMethodWindow; + if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this + && mContainingFrame.bottom > cf.bottom) { + // IME is up and obscuring this window. Adjust the window position so it is visible. + mContainingFrame.top -= mContainingFrame.bottom - cf.bottom; } + // Make sure the containing frame is within the content frame so we don't layout + // resized window under screen decorations. + mContainingFrame.intersect(cf); + mDisplayFrame.set(mContainingFrame); } else { mContainingFrame.set(pf); + mDisplayFrame.set(df); } - mDisplayFrame.set(df); - final int pw = mContainingFrame.width(); final int ph = mContainingFrame.height(); @@ -577,9 +599,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { 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); - float x, y; if (mEnforceSizeCompat) { x = mAttrs.x * mGlobalScale; @@ -589,14 +608,19 @@ final class WindowState implements WindowManagerPolicy.WindowState { y = mAttrs.y; } + if (nonFullscreenStack) { + // Make sure window fits in containing frame since it is in a non-fullscreen stack as + // required by {@link Gravity#apply} call. + w = Math.min(w, pw); + h = Math.min(h, ph); + } + Gravity.apply(mAttrs.gravity, w, h, mContainingFrame, (int) (x + mAttrs.horizontalMargin * pw), (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, mFrame); + // Now make sure the window fits in the overall display frame. + Gravity.applyDisplay(mAttrs.gravity, mDisplayFrame, mFrame); // Make sure the content and visible frames are inside of the // final window frame. @@ -794,14 +818,14 @@ final class WindowState implements WindowManagerPolicy.WindowState { TaskStack getStack() { AppWindowToken wtoken = mAppToken == null ? mService.mFocusedApp : mAppToken; if (wtoken != null) { - Task task = mService.mTaskIdToTask.get(wtoken.groupId); + Task task = wtoken.mTask; if (task != null) { if (task.mStack != null) { return task.mStack; } Slog.e(TAG, "getStack: mStack null for task=" + task); } else { - Slog.e(TAG, "getStack: " + this + " couldn't find taskId=" + wtoken.groupId + Slog.e(TAG, "getStack: " + this + " couldn't find task for " + wtoken + " Callers=" + Debug.getCallers(4)); } } @@ -809,10 +833,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } void getStackBounds(Rect bounds) { - getStackBounds(getStack(), bounds); - } - - private void getStackBounds(TaskStack stack, Rect bounds) { + final TaskStack stack = getStack(); if (stack != null) { stack.getBounds(bounds); return; @@ -1078,9 +1099,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { } boolean isConfigChanged() { - boolean configChanged = mConfiguration != mService.mCurConfiguration - && (mConfiguration == null - || (mConfiguration.diff(mService.mCurConfiguration) != 0)); + final TaskStack stack = getStack(); + final Configuration overrideConfig = + (stack != null) ? stack.mOverrideConfig : Configuration.EMPTY; + final Configuration serviceConfig = mService.mCurConfiguration; + boolean configChanged = + (mConfiguration != serviceConfig && mConfiguration.diff(serviceConfig) != 0) + || (mOverrideConfig != overrideConfig && !mOverrideConfig.equals(overrideConfig)); if ((mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { // Retain configuration changed status until resetConfiguration called. @@ -1109,8 +1134,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } - void setConfiguration(final Configuration newConfig) { + private void setConfiguration( + final Configuration newConfig, final Configuration newOverrideConfig) { mConfiguration = newConfig; + mOverrideConfig = newOverrideConfig; mConfigHasChanged = false; } @@ -1142,10 +1169,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { WindowState win = mService.windowForClientLocked(mSession, mClient, false); Slog.i(TAG, "WIN DEATH: " + win); if (win != null) { - mService.removeWindowLocked(mSession, win); + mService.removeWindowLocked(win); } else if (mHasSurface) { Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid."); - mService.removeWindowLocked(mSession, WindowState.this); + mService.removeWindowLocked(WindowState.this); } } } catch (IllegalArgumentException ex) { @@ -1272,6 +1299,33 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } + public void pokeDrawLockLw(long timeout) { + if (isVisibleOrAdding()) { + if (mDrawLock == null) { + // We want the tag name to be somewhat stable so that it is easier to correlate + // in wake lock statistics. So in particular, we don't want to include the + // window's hash code as in toString(). + CharSequence tag = mAttrs.getTitle(); + if (tag == null) { + tag = mAttrs.packageName; + } + mDrawLock = mService.mPowerManager.newWakeLock( + PowerManager.DRAW_WAKE_LOCK, "Window:" + tag); + mDrawLock.setReferenceCounted(false); + mDrawLock.setWorkSource(new WorkSource(mOwnerUid, mAttrs.packageName)); + } + // Each call to acquire resets the timeout. + if (DEBUG_POWER) { + Slog.d(TAG, "pokeDrawLock: poking draw lock on behalf of visible window owned by " + + mAttrs.packageName); + } + mDrawLock.acquire(timeout); + } else if (DEBUG_POWER) { + Slog.d(TAG, "pokeDrawLock: suppressed draw lock request for invisible window " + + "owned by " + mAttrs.packageName); + } + } + @Override public boolean isAlive() { return mClient.asBinder().isBinderAlive(); @@ -1291,6 +1345,15 @@ final class WindowState implements WindowManagerPolicy.WindowState { return displayContent.isDefaultDisplay; } + @Override + public boolean isDimming() { + TaskStack stack = getStack(); + if (stack == null) { + return false; + } + return stack.isDimming(mWinAnimator); + } + public void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) { mShowToOwnerOnly = showToOwnerOnly; } @@ -1302,7 +1365,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { win = win.mAttachedWindow; } if (win.mAttrs.type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW - && win.mAppToken != null && win.mAppToken.showWhenLocked) { + && win.mAppToken != null && win.mAppToken.showForAllUsers) { // Save some cycles by not calling getDisplayInfo unless it is an application // window intended for all users. final DisplayContent displayContent = win.getDisplayContent(); @@ -1386,12 +1449,15 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, "Reporting new frame to " + this + ": " + mCompatFrame); boolean configChanged = isConfigChanged(); + final TaskStack stack = getStack(); + final Configuration overrideConfig = + (stack != null) ? stack.mOverrideConfig : Configuration.EMPTY; if ((DEBUG_RESIZE || DEBUG_ORIENTATION || DEBUG_CONFIGURATION) && configChanged) { Slog.i(TAG, "Sending new config to window " + this + ": " - + mWinAnimator.mSurfaceW + "x" + mWinAnimator.mSurfaceH - + " / " + mService.mCurConfiguration); + + mWinAnimator.mSurfaceW + "x" + mWinAnimator.mSurfaceH + " / config=" + + mService.mCurConfiguration + " overrideConfig=" + overrideConfig); } - setConfiguration(mService.mCurConfiguration); + setConfiguration(mService.mCurConfiguration, overrideConfig); if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING) Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING"); @@ -1436,6 +1502,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { mOrientationChanging = false; mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - mService.mDisplayFreezeTime); + // We are assuming the hosting process is dead or in a zombie state. + Slog.w(TAG, "Failed to report 'resized' to the client of " + this + + ", removing this window."); + mService.mPendingRemove.add(this); + mService.requestTraversalLocked(); } } @@ -1463,7 +1534,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { } void dump(PrintWriter pw, String prefix, boolean dumpAll) { + final TaskStack stack = getStack(); pw.print(prefix); pw.print("mDisplayId="); pw.print(getDisplayId()); + if (stack != null) { + pw.print(" stackId="); pw.print(stack.mStackId); + } pw.print(" mSession="); pw.print(mSession); pw.print(" mClient="); pw.println(mClient.asBinder()); pw.print(prefix); pw.print("mOwnerUid="); pw.print(mOwnerUid); @@ -1547,6 +1622,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(prefix); pw.print("touchable region="); pw.println(region); } pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration); + if (mOverrideConfig != Configuration.EMPTY) { + pw.print(prefix); pw.print("mOverrideConfig="); pw.println(mOverrideConfig); + } } pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface); pw.print(" mShownFrame="); mShownFrame.printShortString(pw); @@ -1557,6 +1635,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.println(); pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw); pw.print(" last="); mLastSystemDecorRect.printShortString(pw); + if (mWinAnimator.mHasClipRect) { + pw.print(" mLastClipRect="); + mWinAnimator.mLastClipRect.printShortString(pw); + } pw.println(); } if (mEnforceSizeCompat) { @@ -1626,6 +1708,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(" mWallpaperDisplayOffsetY="); pw.println(mWallpaperDisplayOffsetY); } + if (mDrawLock != null) { + pw.println("mDrawLock=" + mDrawLock); + } } String makeInputChannelName() { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index c2d8004..d6726c1 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -53,21 +53,16 @@ import android.view.View; import android.view.WindowManager; import android.view.WindowManagerPolicy; import android.view.WindowManager.LayoutParams; -import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; -import com.android.internal.R; import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; import java.util.ArrayList; -class WinAnimatorList extends ArrayList<WindowStateAnimator> { -} - /** * Keep track of animations and surface operations for a single WindowState. **/ @@ -85,10 +80,6 @@ class WindowStateAnimator { final Context mContext; final boolean mIsWallpaper; - // If this is a universe background window, this is the transformation - // it is applying to the rest of the universe. - final Transformation mUniverseTransform = new Transformation(); - // Currently running animation. boolean mAnimating; boolean mLocalAnimating; @@ -189,7 +180,7 @@ class WindowStateAnimator { int mAttrType; - public WindowStateAnimator(final WindowState win) { + WindowStateAnimator(final WindowState win) { final WindowManagerService service = win.mService; mService = service; @@ -247,9 +238,7 @@ class WindowStateAnimator { boolean isAnimating() { return mAnimation != null || (mAttachedWinAnimator != null && mAttachedWinAnimator.mAnimation != null) - || (mAppAnimator != null && - (mAppAnimator.animation != null - || mAppAnimator.mAppToken.inPendingTransaction)); + || (mAppAnimator != null && mAppAnimator.isAnimating()); } /** Is the window animating the DummyAnimation? */ @@ -486,7 +475,7 @@ class WindowStateAnimator { mService.mPendingRemove.add(mWin); mWin.mRemoveOnExit = false; } - mAnimator.hideWallpapersLocked(mWin); + mService.hideWallpapersLocked(mWin); } void hide() { @@ -527,7 +516,7 @@ class WindowStateAnimator { } // This must be called while inside a transaction. - boolean commitFinishDrawingLocked(long currentTime) { + boolean commitFinishDrawingLocked() { if (DEBUG_STARTING_WINDOW && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState=" @@ -542,9 +531,9 @@ class WindowStateAnimator { mDrawState = READY_TO_SHOW; final AppWindowToken atoken = mWin.mAppToken; if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) { - performShowLocked(); + return performShowLocked(); } - return true; + return false; } static class SurfaceTrace extends SurfaceControl { @@ -984,7 +973,7 @@ class WindowStateAnimator { } mSurfaceControl.destroy(); } - mAnimator.hideWallpapersLocked(mWin); + mService.hideWallpapersLocked(mWin); } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying Window " + this + " surface " + mSurfaceControl + " session " + mSession @@ -1010,7 +999,7 @@ class WindowStateAnimator { WindowManagerService.logSurface(mWin, "DESTROY PENDING", e); } mPendingDestroySurface.destroy(); - mAnimator.hideWallpapersLocked(mWin); + mService.hideWallpapersLocked(mWin); } } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying Window " @@ -1096,9 +1085,6 @@ class WindowStateAnimator { if (appTransformation != null) { tmpMatrix.postConcat(appTransformation.getMatrix()); } - if (mAnimator.mUniverseBackground != null) { - tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix()); - } if (screenAnimation) { tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix()); } @@ -1164,9 +1150,6 @@ class WindowStateAnimator { mHasClipRect = true; } } - if (mAnimator.mUniverseBackground != null) { - mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha(); - } if (screenAnimation) { mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha(); } @@ -1192,15 +1175,12 @@ class WindowStateAnimator { TAG, "computeShownFrameLocked: " + this + " not attached, mAlpha=" + mAlpha); - final boolean applyUniverseTransformation = (mAnimator.mUniverseBackground != null - && mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND - && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer); MagnificationSpec spec = null; //TODO (multidisplay): Magnification is supported only for the default display. if (mService.mAccessibilityController != null && displayId == Display.DEFAULT_DISPLAY) { spec = mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin); } - if (applyUniverseTransformation || spec != null) { + if (spec != null) { final Rect frame = mWin.mFrame; final float tmpFloats[] = mService.mTmpFloats; final Matrix tmpMatrix = mWin.mTmpMatrix; @@ -1208,10 +1188,6 @@ class WindowStateAnimator { tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale); tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset); - if (applyUniverseTransformation) { - tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix()); - } - if (spec != null && !spec.isNop()) { tmpMatrix.postScale(spec.scale, spec.scale); tmpMatrix.postTranslate(spec.offsetX, spec.offsetY); @@ -1231,9 +1207,6 @@ class WindowStateAnimator { mWin.mShownFrame.set(x, y, x + w, y + h); mShownAlpha = mAlpha; - if (applyUniverseTransformation) { - mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha(); - } } else { mWin.mShownFrame.set(mWin.mFrame); if (mWin.mXOffset != 0 || mWin.mYOffset != 0) { @@ -1248,7 +1221,7 @@ class WindowStateAnimator { } } - void applyDecorRect(final Rect decorRect) { + private void applyDecorRect(final Rect decorRect) { final WindowState w = mWin; final int width = w.mFrame.width(); final int height = w.mFrame.height(); @@ -1301,17 +1274,9 @@ class WindowStateAnimator { displayInfo.logicalHeight - w.mCompatFrame.top); } else if (w.mLayer >= mService.mSystemDecorLayer) { // Above the decor layer is easy, just use the entire window. - // Unless we have a universe background... in which case all the - // windows need to be cropped by the screen, so they don't cover - // the universe background. - if (mAnimator.mUniverseBackground == null) { - w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height()); - } else { - applyDecorRect(mService.mScreenRect); - } - } 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 if (w.mDecorFrame.isEmpty()) { + // Windows without policy decor aren't cropped. w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height()); } else if (w.mAttrs.type == LayoutParams.TYPE_WALLPAPER && mAnimator.mAnimating) { // If we're animating, the wallpaper crop should only be updated at the end of the @@ -1436,6 +1401,9 @@ class WindowStateAnimator { if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "SIZE " + width + "x" + height, null); mSurfaceControl.setSize(width, height); + mSurfaceControl.setMatrix( + mDsDx * w.mHScale, mDtDx * w.mVScale, + mDsDy * w.mHScale, mDtDy * w.mVScale); mAnimator.setPendingLayoutChanges(w.getDisplayId(), WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) { @@ -1482,7 +1450,7 @@ class WindowStateAnimator { hide(); } else if (w.mAttachedHidden || !w.isOnScreen()) { hide(); - mAnimator.hideWallpapersLocked(w); + mService.hideWallpapersLocked(w); // If we are waiting for this window to handle an // orientation change, well, it is hidden, so @@ -1654,13 +1622,8 @@ class WindowStateAnimator { } if (DEBUG_VISIBILITY || (DEBUG_STARTING_WINDOW && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING)) { - RuntimeException e = null; - if (!WindowManagerService.HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } Slog.v(TAG, "performShow on " + this - + ": mDrawState=" + mDrawState + " readyForDisplay=" + + ": mDrawState=" + drawStateToString() + " readyForDisplay=" + mWin.isReadyForDisplayIgnoringKeyguard() + " starting=" + (mWin.mAttrs.type == TYPE_APPLICATION_STARTING) + " during animation: policyVis=" + mWin.mPolicyVisibility @@ -1671,7 +1634,8 @@ class WindowStateAnimator { + (mWin.mAppToken != null ? mWin.mAppToken.hidden : false) + " animating=" + mAnimating + " tok animating=" - + (mAppAnimator != null ? mAppAnimator.animating : false), e); + + (mAppAnimator != null ? mAppAnimator.animating : false) + " Callers=" + + Debug.getCallers(3)); } if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) { if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION) @@ -1932,11 +1896,6 @@ class WindowStateAnimator { pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized); pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred); } - if (mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) { - pw.print(prefix); pw.print("mUniverseTransform="); - mUniverseTransform.printShortString(pw); - pw.println(); - } if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha); pw.print(" mAlpha="); pw.print(mAlpha); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 1a672e6..b303505 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -83,8 +83,9 @@ class WindowToken { WindowState win = windows.get(winNdx); if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) Slog.w(WindowManagerService.TAG, "removeAllWindows: removing win=" + win); - win.mService.removeWindowLocked(win.mSession, win); + win.mService.removeWindowLocked(win); } + windows.clear(); } void dump(PrintWriter pw, String prefix) { diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index d81cdd9..a5546cf 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -2,7 +2,7 @@ # files LOCAL_REL_DIR := core/jni -LOCAL_CFLAGS += -Wno-unused-parameter +LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \ @@ -10,6 +10,7 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_AssetAtlasService.cpp \ $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \ $(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \ + $(LOCAL_REL_DIR)/com_android_server_fingerprint_FingerprintService.cpp \ $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecController.cpp \ $(LOCAL_REL_DIR)/com_android_server_input_InputApplicationHandle.cpp \ $(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \ @@ -22,23 +23,24 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \ $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \ + $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \ $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \ $(LOCAL_REL_DIR)/onload.cpp -include external/stlport/libstlport.mk - LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ frameworks/base/services \ frameworks/base/libs \ + frameworks/base/libs/hwui \ frameworks/base/core/jni \ frameworks/native/services \ libcore/include \ libcore/include/libsuspend \ - $(call include-path-for, libhardware)/hardware \ - $(call include-path-for, libhardware_legacy)/hardware_legacy \ + system/security/keystore/include \ + $(call include-path-for, libhardware)/hardware \ + $(call include-path-for, libhardware_legacy)/hardware_legacy \ LOCAL_SHARED_LIBRARIES += \ libandroid_runtime \ @@ -48,6 +50,7 @@ LOCAL_SHARED_LIBRARIES += \ liblog \ libhardware \ libhardware_legacy \ + libkeystore_binder \ libnativehelper \ libutils \ libui \ diff --git a/services/core/jni/com_android_server_AlarmManagerService.cpp b/services/core/jni/com_android_server_AlarmManagerService.cpp index a58b00bce..3fd0f84 100644 --- a/services/core/jni/com_android_server_AlarmManagerService.cpp +++ b/services/core/jni/com_android_server_AlarmManagerService.cpp @@ -21,7 +21,9 @@ #include "jni.h" #include <utils/Log.h> #include <utils/misc.h> +#include <utils/String8.h> +#include <dirent.h> #include <fcntl.h> #include <stdio.h> #include <string.h> @@ -80,8 +82,8 @@ public: class AlarmImplTimerFd : public AlarmImpl { public: - AlarmImplTimerFd(int fds[N_ANDROID_TIMERFDS], int epollfd) : - AlarmImpl(fds, N_ANDROID_TIMERFDS), epollfd(epollfd) { } + AlarmImplTimerFd(int fds[N_ANDROID_TIMERFDS], int epollfd, int rtc_id) : + AlarmImpl(fds, N_ANDROID_TIMERFDS), epollfd(epollfd), rtc_id(rtc_id) { } ~AlarmImplTimerFd(); int set(int type, struct timespec *ts); @@ -90,6 +92,7 @@ public: private: int epollfd; + int rtc_id; }; AlarmImpl::AlarmImpl(int *fds_, size_t n_fds) : fds(new int[n_fds]), @@ -170,9 +173,16 @@ int AlarmImplTimerFd::setTime(struct timeval *tv) return -1; } - fd = open("/dev/rtc0", O_RDWR); + if (rtc_id < 0) { + ALOGV("Not setting RTC because wall clock RTC was not found"); + errno = ENODEV; + return -1; + } + + android::String8 rtc_dev = String8::format("/dev/rtc%d", rtc_id); + fd = open(rtc_dev.string(), O_RDWR); if (fd < 0) { - ALOGV("Unable to open RTC driver: %s\n", strerror(errno)); + ALOGV("Unable to open %s: %s\n", rtc_dev.string(), strerror(errno)); return res; } @@ -283,6 +293,66 @@ static jlong init_alarm_driver() return reinterpret_cast<jlong>(ret); } +static const char rtc_sysfs[] = "/sys/class/rtc"; + +static bool rtc_is_hctosys(unsigned int rtc_id) +{ + android::String8 hctosys_path = String8::format("%s/rtc%u/hctosys", + rtc_sysfs, rtc_id); + + FILE *file = fopen(hctosys_path.string(), "re"); + if (!file) { + ALOGE("failed to open %s: %s", hctosys_path.string(), strerror(errno)); + return false; + } + + unsigned int hctosys; + bool ret = false; + int err = fscanf(file, "%u", &hctosys); + if (err == EOF) + ALOGE("failed to read from %s: %s", hctosys_path.string(), + strerror(errno)); + else if (err == 0) + ALOGE("%s did not have expected contents", hctosys_path.string()); + else + ret = hctosys; + + fclose(file); + return ret; +} + +static int wall_clock_rtc() +{ + DIR *dir = opendir(rtc_sysfs); + if (!dir) { + ALOGE("failed to open %s: %s", rtc_sysfs, strerror(errno)); + return -1; + } + + struct dirent *dirent; + while (errno = 0, dirent = readdir(dir)) { + unsigned int rtc_id; + int matched = sscanf(dirent->d_name, "rtc%u", &rtc_id); + + if (matched < 0) + break; + else if (matched != 1) + continue; + + if (rtc_is_hctosys(rtc_id)) { + ALOGV("found wall clock RTC %u", rtc_id); + return rtc_id; + } + } + + if (errno == 0) + ALOGW("no wall clock RTC found"); + else + ALOGE("failed to enumerate RTCs: %s", strerror(errno)); + + return -1; +} + static jlong init_timerfd() { int epollfd; @@ -290,7 +360,7 @@ static jlong init_timerfd() epollfd = epoll_create(N_ANDROID_TIMERFDS); if (epollfd < 0) { - ALOGV("epoll_create(%u) failed: %s", N_ANDROID_TIMERFDS, + ALOGV("epoll_create(%zu) failed: %s", N_ANDROID_TIMERFDS, strerror(errno)); return 0; } @@ -308,7 +378,7 @@ static jlong init_timerfd() } } - AlarmImpl *ret = new AlarmImplTimerFd(fds, epollfd); + AlarmImpl *ret = new AlarmImplTimerFd(fds, epollfd, wall_clock_rtc()); for (size_t i = 0; i < N_ANDROID_TIMERFDS; i++) { epoll_event event; diff --git a/services/core/jni/com_android_server_AssetAtlasService.cpp b/services/core/jni/com_android_server_AssetAtlasService.cpp index 3696e24..ad1d0f5 100644 --- a/services/core/jni/com_android_server_AssetAtlasService.cpp +++ b/services/core/jni/com_android_server_AssetAtlasService.cpp @@ -29,8 +29,12 @@ #include <EGL/egl.h> #include <EGL/eglext.h> +// Disable warnings for Skia. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" #include <SkCanvas.h> #include <SkBitmap.h> +#pragma GCC diagnostic pop namespace android { @@ -43,40 +47,9 @@ namespace android { #define FENCE_TIMEOUT 2000000000 // ---------------------------------------------------------------------------- -// JNI Helpers -// ---------------------------------------------------------------------------- - -static struct { - jmethodID setNativeBitmap; -} gCanvasClassInfo; - -#define INVOKEV(object, method, ...) \ - env->CallVoidMethod(object, method, __VA_ARGS__) - -// ---------------------------------------------------------------------------- // Canvas management // ---------------------------------------------------------------------------- -static jlong com_android_server_AssetAtlasService_acquireCanvas(JNIEnv* env, jobject, - jobject canvas, jint width, jint height) { - - SkBitmap* bitmap = new SkBitmap; - bitmap->allocN32Pixels(width, height); - bitmap->eraseColor(0); - INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, reinterpret_cast<jlong>(bitmap)); - - return reinterpret_cast<jlong>(bitmap); -} - -static void com_android_server_AssetAtlasService_releaseCanvas(JNIEnv* env, jobject, - jobject canvas, jlong bitmapHandle) { - - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - INVOKEV(canvas, gCanvasClassInfo.setNativeBitmap, (jlong)0); - - delete bitmap; -} - #define CLEANUP_GL_AND_RETURN(result) \ if (fence != EGL_NO_SYNC_KHR) eglDestroySyncKHR(display, fence); \ if (image) eglDestroyImageKHR(display, image); \ @@ -89,9 +62,11 @@ static void com_android_server_AssetAtlasService_releaseCanvas(JNIEnv* env, jobj return result; static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject, - jobject graphicBuffer, jlong bitmapHandle) { + jobject graphicBuffer, jobject bitmapHandle) { + + SkBitmap& bitmap = *GraphicsJNI::getSkBitmap(env, bitmapHandle); + SkAutoLockPixels alp(bitmap); - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); // The goal of this method is to copy the bitmap into the GraphicBuffer // using the GPU to swizzle the texture content sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer)); @@ -182,9 +157,9 @@ static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject } // Upload the content of the bitmap in the GraphicBuffer - glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap->width(), bitmap->height(), - GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels()); + glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap.bytesPerPixel()); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), + GL_RGBA, GL_UNSIGNED_BYTE, bitmap.getPixels()); if (glGetError() != GL_NO_ERROR) { ALOGW("Could not upload to texture"); CLEANUP_GL_AND_RETURN(JNI_FALSE); @@ -229,20 +204,11 @@ static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject const char* const kClassPathName = "com/android/server/AssetAtlasService"; static JNINativeMethod gMethods[] = { - { "nAcquireAtlasCanvas", "(Landroid/graphics/Canvas;II)J", - (void*) com_android_server_AssetAtlasService_acquireCanvas }, - { "nReleaseAtlasCanvas", "(Landroid/graphics/Canvas;J)V", - (void*) com_android_server_AssetAtlasService_releaseCanvas }, - { "nUploadAtlas", "(Landroid/view/GraphicBuffer;J)Z", + { "nUploadAtlas", "(Landroid/view/GraphicBuffer;Landroid/graphics/Bitmap;)Z", (void*) com_android_server_AssetAtlasService_upload }, }; int register_android_server_AssetAtlasService(JNIEnv* env) { - jclass clazz; - - FIND_CLASS(clazz, "android/graphics/Canvas"); - GET_METHOD_ID(gCanvasClassInfo.setNativeBitmap, clazz, "setNativeBitmap", "(J)V"); - return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/services/core/jni/com_android_server_ConsumerIrService.cpp b/services/core/jni/com_android_server_ConsumerIrService.cpp index 3a50ff7..f5121cd 100644 --- a/services/core/jni/com_android_server_ConsumerIrService.cpp +++ b/services/core/jni/com_android_server_ConsumerIrService.cpp @@ -29,7 +29,7 @@ namespace android { -static jlong halOpen(JNIEnv *env, jobject obj) { +static jlong halOpen(JNIEnv* /* env */, jobject /* obj */) { hw_module_t const* module; consumerir_device_t *dev; int err; @@ -50,7 +50,7 @@ static jlong halOpen(JNIEnv *env, jobject obj) { return reinterpret_cast<jlong>(dev); } -static jint halTransmit(JNIEnv *env, jobject obj, jlong halObject, +static jint halTransmit(JNIEnv *env, jobject /* obj */, jlong halObject, jint carrierFrequency, jintArray pattern) { int ret; @@ -66,7 +66,7 @@ static jint halTransmit(JNIEnv *env, jobject obj, jlong halObject, return reinterpret_cast<jint>(ret); } -static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject obj, +static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject /* obj */, jlong halObject) { consumerir_device_t *dev = reinterpret_cast<consumerir_device_t*>(halObject); consumerir_freq_range_t *ranges; diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp index b889b78..d48d159 100644 --- a/services/core/jni/com_android_server_SerialService.cpp +++ b/services/core/jni/com_android_server_SerialService.cpp @@ -34,7 +34,7 @@ static struct parcel_file_descriptor_offsets_t jmethodID mConstructor; } gParcelFileDescriptorOffsets; -static jobject android_server_SerialService_open(JNIEnv *env, jobject thiz, jstring path) +static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path) { const char *pathStr = env->GetStringUTFChars(path, NULL); diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index 0625544..c50d63c 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -25,7 +25,7 @@ namespace android { -static void android_server_SystemServer_nativeInit(JNIEnv* env, jobject clazz) { +static void android_server_SystemServer_nativeInit(JNIEnv* /* env */, jobject /* clazz */) { char propBuf[PROPERTY_VALUE_MAX]; property_get("system_init.startsensorservice", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { diff --git a/services/core/jni/com_android_server_UsbDeviceManager.cpp b/services/core/jni/com_android_server_UsbDeviceManager.cpp index 3551733..a1bff9d 100644 --- a/services/core/jni/com_android_server_UsbDeviceManager.cpp +++ b/services/core/jni/com_android_server_UsbDeviceManager.cpp @@ -41,20 +41,12 @@ static struct parcel_file_descriptor_offsets_t jmethodID mConstructor; } gParcelFileDescriptorOffsets; -static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { - if (env->ExceptionCheck()) { - ALOGE("An exception was thrown by callback '%s'.", methodName); - LOGE_EX(env); - env->ExceptionClear(); - } -} - static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strArray, int index) { char buffer[256]; buffer[0] = 0; - int length = ioctl(fd, cmd, buffer); + ioctl(fd, cmd, buffer); if (buffer[0]) { jstring obj = env->NewStringUTF(buffer); env->SetObjectArrayElement(strArray, index, obj); @@ -63,7 +55,8 @@ static void set_accessory_string(JNIEnv *env, int fd, int cmd, jobjectArray strA } -static jobjectArray android_server_UsbDeviceManager_getAccessoryStrings(JNIEnv *env, jobject thiz) +static jobjectArray android_server_UsbDeviceManager_getAccessoryStrings(JNIEnv *env, + jobject /* thiz */) { int fd = open(DRIVER_NAME, O_RDWR); if (fd < 0) { @@ -85,7 +78,7 @@ out: return strArray; } -static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobject thiz) +static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobject /* thiz */) { int fd = open(DRIVER_NAME, O_RDWR); if (fd < 0) { @@ -100,7 +93,8 @@ static jobject android_server_UsbDeviceManager_openAccessory(JNIEnv *env, jobjec gParcelFileDescriptorOffsets.mConstructor, fileDescriptor); } -static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv *env, jobject thiz) +static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv* /* env */, + jobject /* thiz */) { int fd = open(DRIVER_NAME, O_RDWR); if (fd < 0) { @@ -112,7 +106,7 @@ static jboolean android_server_UsbDeviceManager_isStartRequested(JNIEnv *env, jo return (result == 1); } -static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv *env, jobject thiz) +static jint android_server_UsbDeviceManager_getAudioMode(JNIEnv* /* env */, jobject /* thiz */) { int fd = open(DRIVER_NAME, O_RDWR); if (fd < 0) { diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp index 32c3f95..ee50ff9 100644 --- a/services/core/jni/com_android_server_UsbHostManager.cpp +++ b/services/core/jni/com_android_server_UsbHostManager.cpp @@ -148,7 +148,7 @@ static int usb_device_removed(const char *devname, void* client_data) { return 0; } -static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv *env, jobject thiz) +static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv* /* env */, jobject thiz) { struct usb_host_context* context = usb_host_init(); if (!context) { @@ -159,7 +159,8 @@ static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv *env, jobject usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)thiz); } -static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject thiz, jstring deviceName) +static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject /* thiz */, + jstring deviceName) { const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL); struct usb_device* device = usb_device_open(deviceNameStr); diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp new file mode 100644 index 0000000..cb70144 --- /dev/null +++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2010 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. + */ + +#define LOG_TAG "UsbMidiDeviceJNI" +#define LOG_NDEBUG 0 +#include "utils/Log.h" + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/Log.h" + +#include <stdio.h> +#include <errno.h> +#include <asm/byteorder.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sound/asound.h> + +namespace android +{ + +static jclass sFileDescriptorClass; + +static jint +android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */, + jint card, jint device) +{ + char path[100]; + + snprintf(path, sizeof(path), "/dev/snd/controlC%d", card); + int fd = open(path, O_RDWR); + if (fd < 0) { + ALOGE("could not open %s", path); + return 0; + } + + struct snd_rawmidi_info info; + memset(&info, 0, sizeof(info)); + info.device = device; + int ret = ioctl(fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, &info); + close(fd); + + if (ret < 0) { + ALOGE("SNDRV_CTL_IOCTL_RAWMIDI_INFO failed, errno: %d path: %s", errno, path); + return -1; + } + + ALOGD("subdevices_count: %d", info.subdevices_count); + return info.subdevices_count; +} + +static jobjectArray +android_server_UsbMidiDevice_open(JNIEnv *env, jobject /* thiz */, jint card, jint device, + jint subdevice_count) +{ + char path[100]; + + snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device); + + jobjectArray fds = env->NewObjectArray(subdevice_count, sFileDescriptorClass, NULL); + if (!fds) { + return NULL; + } + + // to support multiple subdevices we open the same file multiple times + for (int i = 0; i < subdevice_count; i++) { + int fd = open(path, O_RDWR); + if (fd < 0) { + ALOGE("open failed on %s for index %d", path, i); + return NULL; + } + + jobject fileDescriptor = jniCreateFileDescriptor(env, fd); + env->SetObjectArrayElement(fds, i, fileDescriptor); + env->DeleteLocalRef(fileDescriptor); + } + + return fds; +} + +static void +android_server_UsbMidiDevice_close(JNIEnv *env, jobject /* thiz */, jobjectArray fds) +{ + int count = env->GetArrayLength(fds); + for (int i = 0; i < count; i++) { + jobject fd = env->GetObjectArrayElement(fds, i); + close(jniGetFDFromFileDescriptor(env, fd)); + } +} + +static JNINativeMethod method_table[] = { + { "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count }, + { "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open }, + { "nativeClose", "([Ljava/io/FileDescriptor;)V", (void*)android_server_UsbMidiDevice_close }, +}; + +int register_android_server_UsbMidiDevice(JNIEnv *env) +{ + jclass clazz = env->FindClass("java/io/FileDescriptor"); + if (clazz == NULL) { + ALOGE("Can't find java/io/FileDescriptor"); + return -1; + } + sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);; + + clazz = env->FindClass("com/android/server/usb/UsbMidiDevice"); + if (clazz == NULL) { + ALOGE("Can't find com/android/server/usb/UsbMidiDevice"); + return -1; + } + + return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice", + method_table, NELEM(method_table)); +} + +}; diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp index 2b3f74a..fb1166b 100644 --- a/services/core/jni/com_android_server_VibratorService.cpp +++ b/services/core/jni/com_android_server_VibratorService.cpp @@ -29,18 +29,18 @@ namespace android { -static jboolean vibratorExists(JNIEnv *env, jobject clazz) +static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */) { return vibrator_exists() > 0 ? JNI_TRUE : JNI_FALSE; } -static void vibratorOn(JNIEnv *env, jobject clazz, jlong timeout_ms) +static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms) { // ALOGI("vibratorOn\n"); vibrator_on(timeout_ms); } -static void vibratorOff(JNIEnv *env, jobject clazz) +static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */) { // ALOGI("vibratorOff\n"); vibrator_off(); diff --git a/services/core/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp index 2a16dfe..7faeb49 100644 --- a/services/core/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp @@ -226,12 +226,12 @@ static bool modifyAddress(JNIEnv *env, jobject thiz, jstring jName, jstring jAdd jniThrowNullPointerException(env, "address"); } else { if (add) { - if (error = ifc_add_address(name, address, jPrefixLength)) { + if ((error = ifc_add_address(name, address, jPrefixLength)) != 0) { ALOGE("Cannot add address %s/%d on interface %s (%s)", address, jPrefixLength, name, strerror(-error)); } } else { - if (error = ifc_del_address(name, address, jPrefixLength)) { + if ((error = ifc_del_address(name, address, jPrefixLength)) != 0) { ALOGE("Cannot del address %s/%d on interface %s (%s)", address, jPrefixLength, name, strerror(-error)); } @@ -258,7 +258,7 @@ static void throwException(JNIEnv *env, int error, const char *message) } } -static jint create(JNIEnv *env, jobject thiz, jint mtu) +static jint create(JNIEnv *env, jobject /* thiz */, jint mtu) { int tun = create_interface(mtu); if (tun < 0) { @@ -268,7 +268,7 @@ static jint create(JNIEnv *env, jobject thiz, jint mtu) return tun; } -static jstring getName(JNIEnv *env, jobject thiz, jint tun) +static jstring getName(JNIEnv *env, jobject /* thiz */, jint tun) { char name[IFNAMSIZ]; if (get_interface_name(name, tun) < 0) { @@ -278,7 +278,7 @@ static jstring getName(JNIEnv *env, jobject thiz, jint tun) return env->NewStringUTF(name); } -static jint setAddresses(JNIEnv *env, jobject thiz, jstring jName, +static jint setAddresses(JNIEnv *env, jobject /* thiz */, jstring jName, jstring jAddresses) { const char *name = NULL; @@ -311,7 +311,7 @@ error: return count; } -static void reset(JNIEnv *env, jobject thiz, jstring jName) +static void reset(JNIEnv *env, jobject /* thiz */, jstring jName) { const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; if (!name) { @@ -324,7 +324,7 @@ static void reset(JNIEnv *env, jobject thiz, jstring jName) env->ReleaseStringUTFChars(jName, name); } -static jint check(JNIEnv *env, jobject thiz, jstring jName) +static jint check(JNIEnv *env, jobject /* thiz */, jstring jName) { const char *name = jName ? env->GetStringUTFChars(jName, NULL) : NULL; if (!name) { diff --git a/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp new file mode 100644 index 0000000..7dbfaf6 --- /dev/null +++ b/services/core/jni/com_android_server_fingerprint_FingerprintService.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2014 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. + */ + +#define LOG_TAG "Fingerprint-JNI" + +#include "JNIHelp.h" +#include <inttypes.h> + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/Log.h> +#include <android_os_MessageQueue.h> +#include <binder/IServiceManager.h> +#include <utils/String16.h> +#include <utils/Looper.h> +#include <keystore/IKeystoreService.h> +#include <keystore/keystore.h> // for error code + +#include <hardware/hardware.h> +#include <hardware/fingerprint.h> +#include <hardware/hw_auth_token.h> + +#include <utils/Log.h> +#include "core_jni_helpers.h" + + +namespace android { + +static const uint16_t kVersion = HARDWARE_MODULE_API_VERSION(2, 0); + +static const char* FINGERPRINT_SERVICE = "com/android/server/fingerprint/FingerprintService"; +static struct { + jclass clazz; + jmethodID notify; +} gFingerprintServiceClassInfo; + +static struct { + fingerprint_module_t const* module; + fingerprint_device_t *device; +} gContext; + +static sp<Looper> gLooper; +static jobject gCallback; + +class CallbackHandler : public MessageHandler { + int type; + int arg1, arg2, arg3; +public: + CallbackHandler(int type, int arg1, int arg2, int arg3) + : type(type), arg1(arg1), arg2(arg2), arg3(arg3) { } + + virtual void handleMessage(const Message& message) { + //ALOG(LOG_VERBOSE, LOG_TAG, "hal_notify(msg=%d, arg1=%d, arg2=%d)\n", msg.type, arg1, arg2); + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(gCallback, gFingerprintServiceClassInfo.notify, type, arg1, arg2, arg3); + } +}; + +static void notifyKeystore(uint8_t *auth_token, size_t auth_token_length) { + if (auth_token != NULL && auth_token_length > 0) { + // TODO: cache service? + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("android.security.keystore")); + sp<IKeystoreService> service = interface_cast<IKeystoreService>(binder); + if (service != NULL) { + status_t ret = service->addAuthToken(auth_token, auth_token_length); + if (ret != ResponseCode::NO_ERROR) { + ALOGE("Falure sending auth token to KeyStore: %d", ret); + } + } else { + ALOGE("Unable to communicate with KeyStore"); + } + } +} + +// Called by the HAL to notify us of fingerprint events +static void hal_notify_callback(fingerprint_msg_t msg) { + uint32_t arg1 = 0; + uint32_t arg2 = 0; + uint32_t arg3 = 0; + switch (msg.type) { + case FINGERPRINT_ERROR: + arg1 = msg.data.error; + break; + case FINGERPRINT_ACQUIRED: + arg1 = msg.data.acquired.acquired_info; + break; + case FINGERPRINT_AUTHENTICATED: + arg1 = msg.data.authenticated.finger.fid; + arg2 = msg.data.authenticated.finger.gid; + if (arg1 != 0) { + notifyKeystore(reinterpret_cast<uint8_t *>(&msg.data.authenticated.hat), + sizeof(msg.data.authenticated.hat)); + } + break; + case FINGERPRINT_TEMPLATE_ENROLLING: + arg1 = msg.data.enroll.finger.fid; + arg2 = msg.data.enroll.finger.gid; + arg3 = msg.data.enroll.samples_remaining; + break; + case FINGERPRINT_TEMPLATE_REMOVED: + arg1 = msg.data.removed.finger.fid; + arg2 = msg.data.removed.finger.gid; + break; + default: + ALOGE("fingerprint: invalid msg: %d", msg.type); + return; + } + // This call potentially comes in on a thread not owned by us. Hand it off to our + // looper so it runs on our thread when calling back to FingerprintService. + // CallbackHandler object is reference-counted, so no cleanup necessary. + gLooper->sendMessage(new CallbackHandler(msg.type, arg1, arg2, arg3), Message()); +} + +static void nativeInit(JNIEnv *env, jobject clazz, jobject mQueue, jobject callbackObj) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeInit()\n"); + gCallback = MakeGlobalRefOrDie(env, callbackObj); + gLooper = android_os_MessageQueue_getMessageQueue(env, mQueue)->getLooper(); +} + +static jint nativeEnroll(JNIEnv* env, jobject clazz, jbyteArray token, jint groupId, jint timeout) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll(gid=%d, timeout=%d)\n", groupId, timeout); + const int tokenSize = env->GetArrayLength(token); + jbyte* tokenData = env->GetByteArrayElements(token, 0); + if (tokenSize != sizeof(hw_auth_token_t)) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeEnroll() : invalid token size %d\n", tokenSize); + return -1; + } + int ret = gContext.device->enroll(gContext.device, + reinterpret_cast<const hw_auth_token_t*>(tokenData), groupId, timeout); + env->ReleaseByteArrayElements(token, tokenData, 0); + return reinterpret_cast<jint>(ret); +} + +static jlong nativePreEnroll(JNIEnv* env, jobject clazz) { + uint64_t ret = gContext.device->pre_enroll(gContext.device); + // ALOG(LOG_VERBOSE, LOG_TAG, "nativePreEnroll(), result = %llx", ret); + return reinterpret_cast<jlong>((int64_t)ret); +} + +static jint nativeStopEnrollment(JNIEnv* env, jobject clazz) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeStopEnrollment()\n"); + int ret = gContext.device->cancel(gContext.device); + return reinterpret_cast<jint>(ret); +} + +static jint nativeAuthenticate(JNIEnv* env, jobject clazz, jlong sessionId, jint groupId) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeAuthenticate(sid=%" PRId64 ", gid=%d)\n", sessionId, groupId); + int ret = gContext.device->authenticate(gContext.device, sessionId, groupId); + return reinterpret_cast<jint>(ret); +} + +static jint nativeStopAuthentication(JNIEnv* env, jobject clazz) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeStopAuthentication()\n"); + int ret = gContext.device->cancel(gContext.device); + return reinterpret_cast<jint>(ret); +} + +static jint nativeRemove(JNIEnv* env, jobject clazz, jint fingerId, jint groupId) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeRemove(fid=%d, gid=%d)\n", fingerId, groupId); + fingerprint_finger_id_t finger; + finger.fid = fingerId; + finger.gid = groupId; + int ret = gContext.device->remove(gContext.device, finger); + return reinterpret_cast<jint>(ret); +} + +static jlong nativeGetAuthenticatorId(JNIEnv *, jobject clazz) { + return gContext.device->get_authenticator_id(gContext.device); +} + +static jint nativeOpenHal(JNIEnv* env, jobject clazz) { + ALOG(LOG_VERBOSE, LOG_TAG, "nativeOpenHal()\n"); + int err; + const hw_module_t *hw_module = NULL; + if (0 != (err = hw_get_module(FINGERPRINT_HARDWARE_MODULE_ID, &hw_module))) { + ALOGE("Can't open fingerprint HW Module, error: %d", err); + return 0; + } + if (NULL == hw_module) { + ALOGE("No valid fingerprint module"); + return 0; + } + + gContext.module = reinterpret_cast<const fingerprint_module_t*>(hw_module); + + if (gContext.module->common.methods->open == NULL) { + ALOGE("No valid open method"); + return 0; + } + + hw_device_t *device = NULL; + + if (0 != (err = gContext.module->common.methods->open(hw_module, NULL, &device))) { + ALOGE("Can't open fingerprint methods, error: %d", err); + return 0; + } + + if (kVersion != device->version) { + ALOGE("Wrong fp version. Expected %d, got %d", kVersion, device->version); + // return 0; // FIXME + } + + gContext.device = reinterpret_cast<fingerprint_device_t*>(device); + err = gContext.device->set_notify(gContext.device, hal_notify_callback); + if (err < 0) { + ALOGE("Failed in call to set_notify(), err=%d", err); + return 0; + } + + // Sanity check - remove + if (gContext.device->notify != hal_notify_callback) { + ALOGE("NOTIFY not set properly: %p != %p", gContext.device->notify, hal_notify_callback); + } + + ALOG(LOG_VERBOSE, LOG_TAG, "fingerprint HAL successfully initialized"); + return reinterpret_cast<jlong>(gContext.device); +} + +static jint nativeCloseHal(JNIEnv* env, jobject clazz) { + return -ENOSYS; // TODO +} + + +// ---------------------------------------------------------------------------- + + +// TODO: clean up void methods +static const JNINativeMethod g_methods[] = { + { "nativeAuthenticate", "(JI)I", (void*)nativeAuthenticate }, + { "nativeStopAuthentication", "()I", (void*)nativeStopAuthentication }, + { "nativeEnroll", "([BII)I", (void*)nativeEnroll }, + { "nativePreEnroll", "()J", (void*)nativePreEnroll }, + { "nativeStopEnrollment", "()I", (void*)nativeStopEnrollment }, + { "nativeRemove", "(II)I", (void*)nativeRemove }, + { "nativeGetAuthenticatorId", "()J", (void*)nativeGetAuthenticatorId }, + { "nativeOpenHal", "()I", (void*)nativeOpenHal }, + { "nativeCloseHal", "()I", (void*)nativeCloseHal }, + { "nativeInit","(Landroid/os/MessageQueue;" + "Lcom/android/server/fingerprint/FingerprintService;)V", (void*)nativeInit } +}; + +int register_android_server_fingerprint_FingerprintService(JNIEnv* env) { + jclass clazz = FindClassOrDie(env, FINGERPRINT_SERVICE); + gFingerprintServiceClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + gFingerprintServiceClassInfo.notify = + GetMethodIDOrDie(env, gFingerprintServiceClassInfo.clazz,"notify", "(IIII)V"); + int result = RegisterMethodsOrDie(env, FINGERPRINT_SERVICE, g_methods, NELEM(g_methods)); + ALOG(LOG_VERBOSE, LOG_TAG, "FingerprintManager JNI ready.\n"); + return result; +} + +} // namespace android diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp index a35af91..f2d0f06 100644 --- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp +++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp @@ -408,6 +408,7 @@ static JNINativeMethod sMethods[] = { int register_android_server_hdmi_HdmiCecController(JNIEnv* env) { int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods)); LOG_FATAL_IF(res < 0, "Unable to register native methods."); + (void)res; // Don't scream about unused variable in the LOG_NDEBUG case return 0; } diff --git a/services/core/jni/com_android_server_input_InputApplicationHandle.cpp b/services/core/jni/com_android_server_input_InputApplicationHandle.cpp index f943d16..11388d8 100644 --- a/services/core/jni/com_android_server_input_InputApplicationHandle.cpp +++ b/services/core/jni/com_android_server_input_InputApplicationHandle.cpp @@ -137,6 +137,7 @@ static JNINativeMethod gInputApplicationHandleMethods[] = { int register_android_server_InputApplicationHandle(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "com/android/server/input/InputApplicationHandle", gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods)); + (void) res; // Faked use when LOG_NDEBUG. LOG_FATAL_IF(res < 0, "Unable to register native methods."); jclass clazz; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index cddca92..f3edbd1 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -350,14 +350,14 @@ void NativeInputManager::setDisplayViewport(bool external, const DisplayViewport } } -status_t NativeInputManager::registerInputChannel(JNIEnv* env, +status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */, const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle, bool monitor) { return mInputManager->getDispatcher()->registerInputChannel( inputChannel, inputWindowHandle, monitor); } -status_t NativeInputManager::unregisterInputChannel(JNIEnv* env, +status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */, const sp<InputChannel>& inputChannel) { return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel); } @@ -428,7 +428,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon } // release lock } -sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32_t deviceId) { +sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32_t /* deviceId */) { AutoMutex _l(mLock); sp<PointerController> controller = mLocked.pointerController.promote(); @@ -548,7 +548,7 @@ String8 NativeInputManager::getDeviceAlias(const InputDeviceIdentifier& identifi } void NativeInputManager::notifySwitch(nsecs_t when, - uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) { + uint32_t switchValues, uint32_t switchMask, uint32_t /* policyFlags */) { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("notifySwitch - when=%lld, switchValues=0x%08x, switchMask=0x%08x, policyFlags=0x%x", when, switchValues, switchMask, policyFlags); @@ -752,7 +752,7 @@ void NativeInputManager::setInteractive(bool interactive) { void NativeInputManager::reloadCalibration() { mInputManager->getReader()->requestRefreshConfiguration( - InputReaderConfiguration::TOUCH_AFFINE_TRANSFORMATION); + InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION); } TouchAffineTransformation NativeInputManager::getTouchAffineTransformation( @@ -1006,7 +1006,7 @@ void NativeInputManager::loadPointerResources(PointerResources* outResources) { // ---------------------------------------------------------------------------- -static jlong nativeInit(JNIEnv* env, jclass clazz, +static jlong nativeInit(JNIEnv* env, jclass /* clazz */, jobject serviceObj, jobject contextObj, jobject messageQueueObj) { sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); if (messageQueue == NULL) { @@ -1020,7 +1020,7 @@ static jlong nativeInit(JNIEnv* env, jclass clazz, return reinterpret_cast<jlong>(im); } -static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) { +static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); status_t result = im->getInputManager()->start(); @@ -1029,8 +1029,8 @@ static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) { } } -static void nativeSetDisplayViewport(JNIEnv* env, jclass clazz, jlong ptr, jboolean external, - jint displayId, jint orientation, +static void nativeSetDisplayViewport(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, + jboolean external, jint displayId, jint orientation, jint logicalLeft, jint logicalTop, jint logicalRight, jint logicalBottom, jint physicalLeft, jint physicalTop, jint physicalRight, jint physicalBottom, jint deviceWidth, jint deviceHeight) { @@ -1052,7 +1052,7 @@ static void nativeSetDisplayViewport(JNIEnv* env, jclass clazz, jlong ptr, jbool im->setDisplayViewport(external, v); } -static jint nativeGetScanCodeState(JNIEnv* env, jclass clazz, +static jint nativeGetScanCodeState(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint deviceId, jint sourceMask, jint scanCode) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1060,7 +1060,7 @@ static jint nativeGetScanCodeState(JNIEnv* env, jclass clazz, deviceId, uint32_t(sourceMask), scanCode); } -static jint nativeGetKeyCodeState(JNIEnv* env, jclass clazz, +static jint nativeGetKeyCodeState(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint deviceId, jint sourceMask, jint keyCode) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1068,7 +1068,7 @@ static jint nativeGetKeyCodeState(JNIEnv* env, jclass clazz, deviceId, uint32_t(sourceMask), keyCode); } -static jint nativeGetSwitchState(JNIEnv* env, jclass clazz, +static jint nativeGetSwitchState(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint deviceId, jint sourceMask, jint sw) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1076,7 +1076,7 @@ static jint nativeGetSwitchState(JNIEnv* env, jclass clazz, deviceId, uint32_t(sourceMask), sw); } -static jboolean nativeHasKeys(JNIEnv* env, jclass clazz, +static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1106,7 +1106,7 @@ static void throwInputChannelNotInitialized(JNIEnv* env) { } static void handleInputChannelDisposed(JNIEnv* env, - jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) { + jobject /* inputChannelObj */, const sp<InputChannel>& inputChannel, void* data) { NativeInputManager* im = static_cast<NativeInputManager*>(data); ALOGW("Input channel object '%s' was disposed without first being unregistered with " @@ -1114,7 +1114,7 @@ static void handleInputChannelDisposed(JNIEnv* env, im->unregisterInputChannel(env, inputChannel); } -static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz, +static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1143,7 +1143,7 @@ static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz, } } -static void nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, +static void nativeUnregisterInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject inputChannelObj) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1164,14 +1164,14 @@ static void nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, } } -static void nativeSetInputFilterEnabled(JNIEnv* env, jclass clazz, +static void nativeSetInputFilterEnabled(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jboolean enabled) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled); } -static jint nativeInjectInputEvent(JNIEnv* env, jclass clazz, +static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject inputEventObj, jint displayId, jint injectorPid, jint injectorUid, jint syncMode, jint timeoutMillis, jint policyFlags) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1203,36 +1203,36 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass clazz, } } -static void nativeSetInputWindows(JNIEnv* env, jclass clazz, +static void nativeSetInputWindows(JNIEnv* env, jclass /* clazz */, jlong ptr, jobjectArray windowHandleObjArray) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->setInputWindows(env, windowHandleObjArray); } -static void nativeSetFocusedApplication(JNIEnv* env, jclass clazz, +static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject applicationHandleObj) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->setFocusedApplication(env, applicationHandleObj); } -static void nativeSetInputDispatchMode(JNIEnv* env, - jclass clazz, jlong ptr, jboolean enabled, jboolean frozen) { +static void nativeSetInputDispatchMode(JNIEnv* /* env */, + jclass /* clazz */, jlong ptr, jboolean enabled, jboolean frozen) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->setInputDispatchMode(enabled, frozen); } -static void nativeSetSystemUiVisibility(JNIEnv* env, - jclass clazz, jlong ptr, jint visibility) { +static void nativeSetSystemUiVisibility(JNIEnv* /* env */, + jclass /* clazz */, jlong ptr, jint visibility) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->setSystemUiVisibility(visibility); } static jboolean nativeTransferTouchFocus(JNIEnv* env, - jclass clazz, jlong ptr, jobject fromChannelObj, jobject toChannelObj) { + jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); sp<InputChannel> fromChannel = @@ -1252,15 +1252,15 @@ static jboolean nativeTransferTouchFocus(JNIEnv* env, } } -static void nativeSetPointerSpeed(JNIEnv* env, - jclass clazz, jlong ptr, jint speed) { +static void nativeSetPointerSpeed(JNIEnv* /* env */, + jclass /* clazz */, jlong ptr, jint speed) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->setPointerSpeed(speed); } -static void nativeSetShowTouches(JNIEnv* env, - jclass clazz, jlong ptr, jboolean enabled) { +static void nativeSetShowTouches(JNIEnv* /* env */, + jclass /* clazz */, jlong ptr, jboolean enabled) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->setShowTouches(enabled); @@ -1279,7 +1279,7 @@ static void nativeReloadCalibration(JNIEnv* env, jclass clazz, jlong ptr) { } static void nativeVibrate(JNIEnv* env, - jclass clazz, jlong ptr, jint deviceId, jlongArray patternObj, + jclass /* clazz */, jlong ptr, jint deviceId, jlongArray patternObj, jint repeat, jint token) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -1303,30 +1303,30 @@ static void nativeVibrate(JNIEnv* env, im->getInputManager()->getReader()->vibrate(deviceId, pattern, patternSize, repeat, token); } -static void nativeCancelVibrate(JNIEnv* env, - jclass clazz, jlong ptr, jint deviceId, jint token) { +static void nativeCancelVibrate(JNIEnv* /* env */, + jclass /* clazz */, jlong ptr, jint deviceId, jint token) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->getInputManager()->getReader()->cancelVibrate(deviceId, token); } -static void nativeReloadKeyboardLayouts(JNIEnv* env, - jclass clazz, jlong ptr) { +static void nativeReloadKeyboardLayouts(JNIEnv* /* env */, + jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->getInputManager()->getReader()->requestRefreshConfiguration( InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS); } -static void nativeReloadDeviceAliases(JNIEnv* env, - jclass clazz, jlong ptr) { +static void nativeReloadDeviceAliases(JNIEnv* /* env */, + jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->getInputManager()->getReader()->requestRefreshConfiguration( InputReaderConfiguration::CHANGE_DEVICE_ALIAS); } -static jstring nativeDump(JNIEnv* env, jclass clazz, jlong ptr) { +static jstring nativeDump(JNIEnv* env, jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); String8 dump; @@ -1334,7 +1334,7 @@ static jstring nativeDump(JNIEnv* env, jclass clazz, jlong ptr) { return env->NewStringUTF(dump.string()); } -static void nativeMonitor(JNIEnv* env, jclass clazz, jlong ptr) { +static void nativeMonitor(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->getInputManager()->getReader()->monitor(); @@ -1416,6 +1416,7 @@ static JNINativeMethod gInputManagerMethods[] = { int register_android_server_InputManager(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "com/android/server/input/InputManagerService", gInputManagerMethods, NELEM(gInputManagerMethods)); + (void) res; // Faked use when LOG_NDEBUG. LOG_FATAL_IF(res < 0, "Unable to register native methods."); // Callbacks diff --git a/services/core/jni/com_android_server_input_InputWindowHandle.cpp b/services/core/jni/com_android_server_input_InputWindowHandle.cpp index 46ec1f4..01c51cf 100644 --- a/services/core/jni/com_android_server_input_InputWindowHandle.cpp +++ b/services/core/jni/com_android_server_input_InputWindowHandle.cpp @@ -227,6 +227,7 @@ static JNINativeMethod gInputWindowHandleMethods[] = { int register_android_server_InputWindowHandle(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "com/android/server/input/InputWindowHandle", gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods)); + (void) res; // Faked use when LOG_NDEBUG. LOG_FATAL_IF(res < 0, "Unable to register native methods."); jclass clazz; diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp index d51e044..b2b2783 100644 --- a/services/core/jni/com_android_server_lights_LightsService.cpp +++ b/services/core/jni/com_android_server_lights_LightsService.cpp @@ -60,7 +60,7 @@ static light_device_t* get_device(hw_module_t* module, char const* name) } } -static jlong init_native(JNIEnv *env, jobject clazz) +static jlong init_native(JNIEnv* /* env */, jobject /* clazz */) { int err; hw_module_t* module; @@ -93,7 +93,7 @@ static jlong init_native(JNIEnv *env, jobject clazz) return (jlong)devices; } -static void finalize_native(JNIEnv *env, jobject clazz, jlong ptr) +static void finalize_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr) { Devices* devices = (Devices*)ptr; if (devices == NULL) { @@ -103,7 +103,7 @@ static void finalize_native(JNIEnv *env, jobject clazz, jlong ptr) free(devices); } -static void setLight_native(JNIEnv *env, jobject clazz, jlong ptr, +static void setLight_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr, jint light, jint colorARGB, jint flashMode, jint onMS, jint offMS, jint brightnessMode) { Devices* devices = (Devices*)ptr; diff --git a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp index 37a3eaa..2ca5f5a 100644 --- a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp +++ b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp @@ -31,14 +31,18 @@ static jobject sCallbacksObj = NULL; static JNIEnv *sCallbackEnv = NULL; static hw_device_t* sHardwareDevice = NULL; +static jmethodID sSetVersion = NULL; static jmethodID sOnLocationReport = NULL; static jmethodID sOnDataReport = NULL; +static jmethodID sOnBatchingCapabilities = NULL; +static jmethodID sOnBatchingStatus = NULL; static jmethodID sOnGeofenceTransition = NULL; static jmethodID sOnGeofenceMonitorStatus = NULL; static jmethodID sOnGeofenceAdd = NULL; static jmethodID sOnGeofenceRemove = NULL; static jmethodID sOnGeofencePause = NULL; static jmethodID sOnGeofenceResume = NULL; +static jmethodID sOnGeofencingCapabilities = NULL; static const FlpLocationInterface* sFlpInterface = NULL; static const FlpDiagnosticInterface* sFlpDiagnosticInterface = NULL; @@ -85,6 +89,32 @@ static bool IsValidCallbackThread() { return true; } +static void BatchingCapabilitiesCallback(int32_t capabilities) { + if(!IsValidCallbackThread()) { + return; + } + + sCallbackEnv->CallVoidMethod( + sCallbacksObj, + sOnBatchingCapabilities, + capabilities + ); + CheckExceptions(sCallbackEnv, __FUNCTION__); +} + +static void BatchingStatusCallback(int32_t status) { + if(!IsValidCallbackThread()) { + return; + } + + sCallbackEnv->CallVoidMethod( + sCallbacksObj, + sOnBatchingStatus, + status + ); + CheckExceptions(sCallbackEnv, __FUNCTION__); +} + static int SetThreadEvent(ThreadEvent event) { JavaVM* javaVm = AndroidRuntime::getJavaVM(); @@ -112,6 +142,14 @@ static int SetThreadEvent(ThreadEvent event) { } ALOGV("Callback thread attached: %p", sCallbackEnv); + + // Send the version to the upper layer. + sCallbackEnv->CallVoidMethod( + sCallbacksObj, + sSetVersion, + sFlpInterface->size == sizeof(FlpLocationInterface) ? 2 : 1 + ); + CheckExceptions(sCallbackEnv, __FUNCTION__); break; } case DISASSOCIATE_JVM: @@ -147,6 +185,10 @@ static void ClassInit(JNIEnv* env, jclass clazz) { sFlpInterface = NULL; // get references to the Java provider methods + sSetVersion = env->GetMethodID( + clazz, + "setVersion", + "(I)V"); sOnLocationReport = env->GetMethodID( clazz, "onLocationReport", @@ -156,6 +198,18 @@ static void ClassInit(JNIEnv* env, jclass clazz) { "onDataReport", "(Ljava/lang/String;)V" ); + sOnBatchingCapabilities = env->GetMethodID( + clazz, + "onBatchingCapabilities", + "(I)V"); + sOnBatchingStatus = env->GetMethodID( + clazz, + "onBatchingStatus", + "(I)V"); + sOnGeofencingCapabilities = env->GetMethodID( + clazz, + "onGeofencingCapabilities", + "(I)V"); sOnGeofenceTransition = env->GetMethodID( clazz, "onGeofenceTransition", @@ -297,6 +351,14 @@ static void TranslateFromObject( getSourcesToUse ); + jmethodID getSmallestDisplacementMeters = env->GetMethodID( + batchOptionsClass, + "getSmallestDisplacementMeters", + "()F" + ); + batchOptions.smallest_displacement_meters + = env->CallFloatMethod(batchOptionsObject, getSmallestDisplacementMeters); + jmethodID getFlags = env->GetMethodID(batchOptionsClass, "getFlags", "()I"); batchOptions.flags = env->CallIntMethod(batchOptionsObject, getFlags); @@ -526,7 +588,9 @@ FlpCallbacks sFlpCallbacks = { LocationCallback, AcquireWakelock, ReleaseWakelock, - SetThreadEvent + SetThreadEvent, + BatchingCapabilitiesCallback, + BatchingStatusCallback }; static void ReportData(char* data, int length) { @@ -662,6 +726,19 @@ static void GeofenceResumeCallback(int32_t geofenceId, int32_t result) { CheckExceptions(sCallbackEnv, __FUNCTION__); } +static void GeofencingCapabilitiesCallback(int32_t capabilities) { + if(!IsValidCallbackThread()) { + return; + } + + sCallbackEnv->CallVoidMethod( + sCallbacksObj, + sOnGeofencingCapabilities, + capabilities + ); + CheckExceptions(sCallbackEnv, __FUNCTION__); +} + FlpGeofenceCallbacks sFlpGeofenceCallbacks = { sizeof(FlpGeofenceCallbacks), GeofenceTransitionCallback, @@ -670,7 +747,8 @@ FlpGeofenceCallbacks sFlpGeofenceCallbacks = { GeofenceRemoveCallback, GeofencePauseCallback, GeofenceResumeCallback, - SetThreadEvent + SetThreadEvent, + GeofencingCapabilitiesCallback }; /* @@ -698,14 +776,14 @@ static void Init(JNIEnv* env, jobject obj) { // TODO: inject any device context if when needed } -static jboolean IsSupported(JNIEnv* env, jclass clazz) { +static jboolean IsSupported(JNIEnv* /* env */, jclass /* clazz */) { if (sFlpInterface == NULL) { return JNI_FALSE; } return JNI_TRUE; } -static jint GetBatchSize(JNIEnv* env, jobject object) { +static jint GetBatchSize(JNIEnv* env, jobject /* object */) { if(sFlpInterface == NULL) { ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); } @@ -715,7 +793,7 @@ static jint GetBatchSize(JNIEnv* env, jobject object) { static void StartBatching( JNIEnv* env, - jobject object, + jobject /* object */, jint id, jobject optionsObject) { if(sFlpInterface == NULL || optionsObject == NULL) { @@ -730,7 +808,7 @@ static void StartBatching( static void UpdateBatchingOptions( JNIEnv* env, - jobject object, + jobject /* object */, jint id, jobject optionsObject) { if(sFlpInterface == NULL || optionsObject == NULL) { @@ -743,7 +821,7 @@ static void UpdateBatchingOptions( ThrowOnError(env, result, __FUNCTION__); } -static void StopBatching(JNIEnv* env, jobject object, jint id) { +static void StopBatching(JNIEnv* env, jobject /* object */, jint id) { if(sFlpInterface == NULL) { ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); } @@ -751,7 +829,7 @@ static void StopBatching(JNIEnv* env, jobject object, jint id) { sFlpInterface->stop_batching(id); } -static void Cleanup(JNIEnv* env, jobject object) { +static void Cleanup(JNIEnv* env, jobject /* object */) { if(sFlpInterface == NULL) { ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); } @@ -774,7 +852,7 @@ static void Cleanup(JNIEnv* env, jobject object) { } } -static void GetBatchedLocation(JNIEnv* env, jobject object, jint lastNLocations) { +static void GetBatchedLocation(JNIEnv* env, jobject /* object */, jint lastNLocations) { if(sFlpInterface == NULL) { ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); } @@ -782,7 +860,15 @@ static void GetBatchedLocation(JNIEnv* env, jobject object, jint lastNLocations) sFlpInterface->get_batched_location(lastNLocations); } -static void InjectLocation(JNIEnv* env, jobject object, jobject locationObject) { +static void FlushBatchedLocations(JNIEnv* env, jobject /* object */) { + if(sFlpInterface == NULL) { + ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); + } + + sFlpInterface->flush_batched_locations(); +} + +static void InjectLocation(JNIEnv* env, jobject /* object */, jobject locationObject) { if(locationObject == NULL) { ALOGE("Invalid location for injection: %p", locationObject); ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); @@ -806,7 +892,7 @@ static jboolean IsDiagnosticSupported() { return sFlpDiagnosticInterface != NULL; } -static void InjectDiagnosticData(JNIEnv* env, jobject object, jstring stringData) { +static void InjectDiagnosticData(JNIEnv* env, jobject /* object */, jstring stringData) { if(stringData == NULL) { ALOGE("Invalid diagnostic data for injection: %p", stringData); ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); @@ -830,7 +916,7 @@ static jboolean IsDeviceContextSupported() { return sFlpDeviceContextInterface != NULL; } -static void InjectDeviceContext(JNIEnv* env, jobject object, jint enabledMask) { +static void InjectDeviceContext(JNIEnv* env, jobject /* object */, jint enabledMask) { if(sFlpDeviceContextInterface == NULL) { ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); } @@ -845,7 +931,7 @@ static jboolean IsGeofencingSupported() { static void AddGeofences( JNIEnv* env, - jobject object, + jobject /* object */, jobjectArray geofenceRequestsArray) { if(geofenceRequestsArray == NULL) { ALOGE("Invalid Geofences to add: %p", geofenceRequestsArray); @@ -885,7 +971,7 @@ static void AddGeofences( } } -static void PauseGeofence(JNIEnv* env, jobject object, jint geofenceId) { +static void PauseGeofence(JNIEnv* env, jobject /* object */, jint geofenceId) { if(sFlpGeofencingInterface == NULL) { ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); } @@ -895,7 +981,7 @@ static void PauseGeofence(JNIEnv* env, jobject object, jint geofenceId) { static void ResumeGeofence( JNIEnv* env, - jobject object, + jobject /* object */, jint geofenceId, jint monitorTransitions) { if(sFlpGeofencingInterface == NULL) { @@ -907,7 +993,7 @@ static void ResumeGeofence( static void ModifyGeofenceOption( JNIEnv* env, - jobject object, + jobject /* object */, jint geofenceId, jint lastTransition, jint monitorTransitions, @@ -931,7 +1017,7 @@ static void ModifyGeofenceOption( static void RemoveGeofences( JNIEnv* env, - jobject object, + jobject /* object */, jintArray geofenceIdsArray) { if(sFlpGeofencingInterface == NULL) { ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__); @@ -964,6 +1050,9 @@ static JNINativeMethod sMethods[] = { {"nativeRequestBatchedLocation", "(I)V", reinterpret_cast<void*>(GetBatchedLocation)}, + {"nativeFlushBatchedLocations", + "()V", + reinterpret_cast<void*>(FlushBatchedLocations)}, {"nativeInjectLocation", "(Landroid/location/Location;)V", reinterpret_cast<void*>(InjectLocation)}, diff --git a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp index 8183321..3804e1d 100644 --- a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp @@ -214,7 +214,7 @@ static void agps_status_callback(AGpsStatus* agps_status) size_t status_size = agps_status->size; if (status_size == sizeof(AGpsStatus_v3)) { - ALOGV("AGpsStatus is V3: %d", status_size); + ALOGV("AGpsStatus is V3: %zd", status_size); switch (agps_status->addr.ss_family) { case AF_INET: @@ -256,7 +256,7 @@ static void agps_status_callback(AGpsStatus* agps_status) break; } } else if (status_size >= sizeof(AGpsStatus_v2)) { - ALOGV("AGpsStatus is V2+: %d", status_size); + ALOGV("AGpsStatus is V2+: %zd", status_size); // for back-compatibility reasons we check in v2 that the data structure size is greater or // equal to the declared size in gps.h uint32_t ipaddr = agps_status->ipaddr; @@ -266,12 +266,12 @@ static void agps_status_callback(AGpsStatus* agps_status) isSupported = true; } } else if (status_size >= sizeof(AGpsStatus_v1)) { - ALOGV("AGpsStatus is V1+: %d", status_size); + ALOGV("AGpsStatus is V1+: %zd", status_size); // because we have to check for >= with regards to v2, we also need to relax the check here // and only make sure that the size is at least what we expect isSupported = true; } else { - ALOGE("Invalid size of AGpsStatus found: %d.", status_size); + ALOGE("Invalid size of AGpsStatus found: %zd.", status_size); } if (isSupported) { @@ -509,12 +509,22 @@ static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, } } -static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) { - if (sGpsInterface != NULL) { - return JNI_TRUE; - } else { - return JNI_FALSE; - } +static jboolean android_location_GpsLocationProvider_is_supported( + JNIEnv* /* env */, jclass /* clazz */) +{ + return (sGpsInterface != NULL) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean android_location_GpsLocationProvider_is_agps_ril_supported( + JNIEnv* /* env */, jclass /* clazz */) +{ + return (sAGpsRilInterface != NULL) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean android_location_gpsLocationProvider_is_gnss_configuration_supported( + JNIEnv* /* env */, jclass /* jclazz */) +{ + return (sGnssConfigurationInterface != NULL) ? JNI_TRUE : JNI_FALSE; } static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj) @@ -543,14 +553,15 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o return JNI_TRUE; } -static void android_location_GpsLocationProvider_cleanup(JNIEnv* env, jobject obj) +static void android_location_GpsLocationProvider_cleanup(JNIEnv* /* env */, jobject /* obj */) { if (sGpsInterface) sGpsInterface->cleanup(); } -static jboolean android_location_GpsLocationProvider_set_position_mode(JNIEnv* env, jobject obj, - jint mode, jint recurrence, jint min_interval, jint preferred_accuracy, jint preferred_time) +static jboolean android_location_GpsLocationProvider_set_position_mode(JNIEnv* /* env */, + jobject /* obj */, jint mode, jint recurrence, jint min_interval, jint preferred_accuracy, + jint preferred_time) { if (sGpsInterface) { if (sGpsInterface->set_position_mode(mode, recurrence, min_interval, preferred_accuracy, @@ -564,7 +575,7 @@ static jboolean android_location_GpsLocationProvider_set_position_mode(JNIEnv* e return JNI_FALSE; } -static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject obj) +static jboolean android_location_GpsLocationProvider_start(JNIEnv* /* env */, jobject /* obj */) { if (sGpsInterface) { if (sGpsInterface->start() == 0) { @@ -577,7 +588,7 @@ static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject return JNI_FALSE; } -static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject obj) +static jboolean android_location_GpsLocationProvider_stop(JNIEnv* /* env */, jobject /* obj */) { if (sGpsInterface) { if (sGpsInterface->stop() == 0) { @@ -590,13 +601,15 @@ static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject o return JNI_FALSE; } -static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* env, jobject obj, jint flags) +static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* /* env */, + jobject /* obj */, + jint flags) { if (sGpsInterface) sGpsInterface->delete_aiding_data(flags); } -static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject obj, +static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject /* obj */, jintArray prnArray, jfloatArray snrArray, jfloatArray elevArray, jfloatArray azumArray, jintArray maskArray) { @@ -627,8 +640,8 @@ static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, job return (jint) num_svs; } -static void android_location_GpsLocationProvider_agps_set_reference_location_cellid(JNIEnv* env, - jobject obj, jint type, jint mcc, jint mnc, jint lac, jint cid) +static void android_location_GpsLocationProvider_agps_set_reference_location_cellid( + JNIEnv* /* env */, jobject /* obj */, jint type, jint mcc, jint mnc, jint lac, jint cid) { AGpsRefLocation location; @@ -655,7 +668,7 @@ static void android_location_GpsLocationProvider_agps_set_reference_location_cel } static void android_location_GpsLocationProvider_agps_send_ni_message(JNIEnv* env, - jobject obj, jbyteArray ni_msg, jint size) + jobject /* obj */, jbyteArray ni_msg, jint size) { size_t sz; @@ -671,8 +684,8 @@ static void android_location_GpsLocationProvider_agps_send_ni_message(JNIEnv* en env->ReleaseByteArrayElements(ni_msg,b,0); } -static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env, - jobject obj, jint type, jstring setid_string) +static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env, jobject /* obj */, + jint type, jstring setid_string) { if (!sAGpsRilInterface) { ALOGE("no AGPS RIL interface in agps_set_id"); @@ -684,7 +697,7 @@ static void android_location_GpsLocationProvider_agps_set_id(JNIEnv *env, env->ReleaseStringUTFChars(setid_string, setid); } -static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, +static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject /* obj */, jbyteArray nmeaArray, jint buffer_size) { // this should only be called from within a call to reportNmea @@ -697,30 +710,27 @@ static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject return (jint) length; } -static void android_location_GpsLocationProvider_inject_time(JNIEnv* env, jobject obj, +static void android_location_GpsLocationProvider_inject_time(JNIEnv* /* env */, jobject /* obj */, jlong time, jlong timeReference, jint uncertainty) { if (sGpsInterface) sGpsInterface->inject_time(time, timeReference, uncertainty); } -static void android_location_GpsLocationProvider_inject_location(JNIEnv* env, jobject obj, - jdouble latitude, jdouble longitude, jfloat accuracy) +static void android_location_GpsLocationProvider_inject_location(JNIEnv* /* env */, + jobject /* obj */, jdouble latitude, jdouble longitude, jfloat accuracy) { if (sGpsInterface) sGpsInterface->inject_location(latitude, longitude, accuracy); } -static jboolean android_location_GpsLocationProvider_supports_xtra(JNIEnv* env, jobject obj) +static jboolean android_location_GpsLocationProvider_supports_xtra( + JNIEnv* /* env */, jobject /* obj */) { - if (sGpsXtraInterface != NULL) { - return JNI_TRUE; - } else { - return JNI_FALSE; - } + return (sGpsXtraInterface != NULL) ? JNI_TRUE : JNI_FALSE; } -static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject obj, +static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject /* obj */, jbyteArray data, jint length) { if (!sGpsXtraInterface) { @@ -734,7 +744,7 @@ static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, j } static void android_location_GpsLocationProvider_agps_data_conn_open( - JNIEnv* env, jobject obj, jstring apn, jint apnIpType) + JNIEnv* env, jobject /* obj */, jstring apn, jint apnIpType) { if (!sAGpsInterface) { ALOGE("no AGPS interface in agps_data_conn_open"); @@ -753,13 +763,14 @@ static void android_location_GpsLocationProvider_agps_data_conn_open( } else if (interface_size == sizeof(AGpsInterface_v1)) { sAGpsInterface->data_conn_open(apnStr); } else { - ALOGE("Invalid size of AGpsInterface found: %d.", interface_size); + ALOGE("Invalid size of AGpsInterface found: %zd.", interface_size); } env->ReleaseStringUTFChars(apn, apnStr); } -static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* env, jobject obj) +static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* /* env */, + jobject /* obj */) { if (!sAGpsInterface) { ALOGE("no AGPS interface in agps_data_conn_closed"); @@ -768,7 +779,8 @@ static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* e sAGpsInterface->data_conn_closed(); } -static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* env, jobject obj) +static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* /* env */, + jobject /* obj */) { if (!sAGpsInterface) { ALOGE("no AGPS interface in agps_data_conn_failed"); @@ -777,7 +789,7 @@ static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* e sAGpsInterface->data_conn_failed(); } -static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject obj, +static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject /* obj */, jint type, jstring hostname, jint port) { if (!sAGpsInterface) { @@ -789,8 +801,8 @@ static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jo env->ReleaseStringUTFChars(hostname, c_hostname); } -static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj, - jint notifId, jint response) +static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* /* env */, + jobject /* obj */, jint notifId, jint response) { if (!sGpsNiInterface) { ALOGE("no NI interface in send_ni_response"); @@ -800,8 +812,8 @@ static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, j sGpsNiInterface->respond(notifId, response); } -static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj) -{ +static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, + jobject /* obj */) { jstring result = NULL; if (sGpsDebugInterface) { const size_t maxLength = 2047; @@ -814,7 +826,7 @@ static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* e return result; } -static void android_location_GpsLocationProvider_update_network_state(JNIEnv* env, jobject obj, +static void android_location_GpsLocationProvider_update_network_state(JNIEnv* env, jobject /* obj */, jboolean connected, jint type, jboolean roaming, jboolean available, jstring extraInfo, jstring apn) { @@ -837,16 +849,14 @@ static void android_location_GpsLocationProvider_update_network_state(JNIEnv* en } } -static jboolean android_location_GpsLocationProvider_is_geofence_supported(JNIEnv* env, - jobject obj) { - if (sGpsGeofencingInterface != NULL) { - return JNI_TRUE; - } - return JNI_FALSE; +static jboolean android_location_GpsLocationProvider_is_geofence_supported( + JNIEnv* /* env */, jobject /* obj */) +{ + return (sGpsGeofencingInterface != NULL) ? JNI_TRUE : JNI_FALSE; } -static jboolean android_location_GpsLocationProvider_add_geofence(JNIEnv* env, jobject obj, - jint geofence_id, jdouble latitude, jdouble longitude, jdouble radius, +static jboolean android_location_GpsLocationProvider_add_geofence(JNIEnv* /* env */, + jobject /* obj */, jint geofence_id, jdouble latitude, jdouble longitude, jdouble radius, jint last_transition, jint monitor_transition, jint notification_responsiveness, jint unknown_timer) { if (sGpsGeofencingInterface != NULL) { @@ -860,8 +870,8 @@ static jboolean android_location_GpsLocationProvider_add_geofence(JNIEnv* env, j return JNI_FALSE; } -static jboolean android_location_GpsLocationProvider_remove_geofence(JNIEnv* env, jobject obj, - jint geofence_id) { +static jboolean android_location_GpsLocationProvider_remove_geofence(JNIEnv* /* env */, + jobject /* obj */, jint geofence_id) { if (sGpsGeofencingInterface != NULL) { sGpsGeofencingInterface->remove_geofence_area(geofence_id); return JNI_TRUE; @@ -871,8 +881,8 @@ static jboolean android_location_GpsLocationProvider_remove_geofence(JNIEnv* env return JNI_FALSE; } -static jboolean android_location_GpsLocationProvider_pause_geofence(JNIEnv* env, jobject obj, - jint geofence_id) { +static jboolean android_location_GpsLocationProvider_pause_geofence(JNIEnv* /* env */, + jobject /* obj */, jint geofence_id) { if (sGpsGeofencingInterface != NULL) { sGpsGeofencingInterface->pause_geofence(geofence_id); return JNI_TRUE; @@ -882,8 +892,8 @@ static jboolean android_location_GpsLocationProvider_pause_geofence(JNIEnv* env, return JNI_FALSE; } -static jboolean android_location_GpsLocationProvider_resume_geofence(JNIEnv* env, jobject obj, - jint geofence_id, jint monitor_transition) { +static jboolean android_location_GpsLocationProvider_resume_geofence(JNIEnv* /* env */, + jobject /* obj */, jint geofence_id, jint monitor_transition) { if (sGpsGeofencingInterface != NULL) { sGpsGeofencingInterface->resume_geofence(geofence_id, monitor_transition); return JNI_TRUE; @@ -1253,7 +1263,7 @@ static void measurement_callback(GpsData* data) { env->DeleteLocalRef(gpsMeasurementsEventClass); env->DeleteLocalRef(gpsMeasurementsEvent); } else { - ALOGE("Invalid GpsData size found in gps_measurement_callback, size=%d", data->size); + ALOGE("Invalid GpsData size found in gps_measurement_callback, size=%zd", data->size); } } @@ -1304,7 +1314,7 @@ static jobject translate_gps_navigation_message(JNIEnv* env, GpsNavigationMessag size_t dataLength = message->data_length; uint8_t* data = message->data; if (dataLength == 0 || data == NULL) { - ALOGE("Invalid Navigation Message found: data=%p, length=%d", data, dataLength); + ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data, dataLength); return NULL; } @@ -1363,7 +1373,7 @@ static void navigation_message_callback(GpsNavigationMessage* message) { env->DeleteLocalRef(navigationMessageEventClass); env->DeleteLocalRef(navigationMessageEvent); } else { - ALOGE("Invalid GpsNavigationMessage size found: %d", message->size); + ALOGE("Invalid GpsNavigationMessage size found: %zd", message->size); } } @@ -1428,6 +1438,10 @@ static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native}, {"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported}, + {"native_is_agps_ril_supported", "()Z", + (void*)android_location_GpsLocationProvider_is_agps_ril_supported}, + {"native_is_gnss_configuration_supported", "()Z", + (void*)android_location_gpsLocationProvider_is_gnss_configuration_supported}, {"native_init", "()Z", (void*)android_location_GpsLocationProvider_init}, {"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup}, {"native_set_position_mode", diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index 33e0bd7..6dcdd9d 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -112,17 +112,17 @@ static void nativeInit(JNIEnv* env, jobject obj) { } } -static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) { +static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) { ScopedUtfChars name(env, nameStr); acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str()); } -static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) { +static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) { ScopedUtfChars name(env, nameStr); release_wake_lock(name.c_str()); } -static void nativeSetInteractive(JNIEnv *env, jclass clazz, jboolean enable) { +static void nativeSetInteractive(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) { if (gPowerModule) { if (enable) { ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(true) while turning screen on"); @@ -134,7 +134,7 @@ static void nativeSetInteractive(JNIEnv *env, jclass clazz, jboolean enable) { } } -static void nativeSetAutoSuspend(JNIEnv *env, jclass clazz, jboolean enable) { +static void nativeSetAutoSuspend(JNIEnv* /* env */, jclass /* clazz */, jboolean enable) { if (enable) { ALOGD_IF_SLOW(100, "Excessive delay in autosuspend_enable() while turning screen off"); autosuspend_enable(); @@ -189,6 +189,7 @@ static JNINativeMethod gPowerManagerServiceMethods[] = { int register_android_server_PowerManagerService(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "com/android/server/power/PowerManagerService", gPowerManagerServiceMethods, NELEM(gPowerManagerServiceMethods)); + (void) res; // Faked use when LOG_NDEBUG. LOG_FATAL_IF(res < 0, "Unable to register native methods."); // Callbacks diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index dcb5199..507bc9c 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -688,6 +688,7 @@ int register_android_server_tv_TvInputHal(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "com/android/server/tv/TvInputHal", gTvInputHalMethods, NELEM(gTvInputHalMethods)); LOG_FATAL_IF(res < 0, "Unable to register native methods."); + (void)res; // Don't complain about unused variable in the LOG_NDEBUG case jclass clazz; FIND_CLASS(clazz, "com/android/server/tv/TvInputHal"); diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 7b2e408..7db7414 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -32,6 +32,7 @@ int register_android_server_PowerManagerService(JNIEnv* env); int register_android_server_SerialService(JNIEnv* env); int register_android_server_SystemServer(JNIEnv* env); int register_android_server_UsbDeviceManager(JNIEnv* env); +int register_android_server_UsbMidiDevice(JNIEnv* env); int register_android_server_UsbHostManager(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); int register_android_server_location_GpsLocationProvider(JNIEnv* env); @@ -46,7 +47,7 @@ int register_android_server_Watchdog(JNIEnv* env); using namespace android; -extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) +extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { JNIEnv* env = NULL; jint result = -1; @@ -65,6 +66,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) register_android_server_LightsService(env); register_android_server_AlarmManagerService(env); register_android_server_UsbDeviceManager(env); + register_android_server_UsbMidiDevice(env); register_android_server_UsbHostManager(env); register_android_server_VibratorService(env); register_android_server_SystemServer(env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java index 9fd0e09..28ffc57 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceOwner.java @@ -22,6 +22,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Environment; +import android.os.PersistableBundle; import android.os.RemoteException; import android.util.AtomicFile; import android.util.Slog; @@ -39,23 +40,27 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.util.HashMap; +import java.util.Map; import java.util.Set; /** * Stores and restores state for the Device and Profile owners. By definition there can be * only one device owner, but there may be a profile owner for each user. */ -public class DeviceOwner { +class DeviceOwner { private static final String TAG = "DevicePolicyManagerService"; private static final String DEVICE_OWNER_XML = "device_owner.xml"; private static final String TAG_DEVICE_OWNER = "device-owner"; + private static final String TAG_DEVICE_INITIALIZER = "device-initializer"; private static final String TAG_PROFILE_OWNER = "profile-owner"; private static final String ATTR_NAME = "name"; private static final String ATTR_PACKAGE = "package"; private static final String ATTR_COMPONENT_NAME = "component"; private static final String ATTR_USERID = "userId"; + private static final String TAG_SYSTEM_UPDATE_POLICY = "system-update-policy"; private AtomicFile fileForWriting; @@ -66,9 +71,15 @@ public class DeviceOwner { // Internal state for the device owner package. private OwnerInfo mDeviceOwner; + // Internal state for the device initializer package. + private OwnerInfo mDeviceInitializer; + // Internal state for the profile owner packages. private final HashMap<Integer, OwnerInfo> mProfileOwners = new HashMap<Integer, OwnerInfo>(); + // Local system update policy controllable by device owner. + private PersistableBundle mSystemUpdatePolicy; + // Private default constructor. private DeviceOwner() { } @@ -102,12 +113,11 @@ public class DeviceOwner { } /** - * @deprecated Use a component name instead of package name - * Creates an instance of the device owner object with the profile owner set. + * Creates an instance of the device owner object with the device initializer set. */ - static DeviceOwner createWithProfileOwner(String packageName, String ownerName, int userId) { + static DeviceOwner createWithDeviceInitializer(ComponentName admin, String ownerName) { DeviceOwner owner = new DeviceOwner(); - owner.mProfileOwners.put(userId, new OwnerInfo(ownerName, packageName)); + owner.mDeviceInitializer = new OwnerInfo(ownerName, admin); return owner; } @@ -136,11 +146,28 @@ public class DeviceOwner { mDeviceOwner = null; } - /** - * @deprecated - */ - void setProfileOwner(String packageName, String ownerName, int userId) { - mProfileOwners.put(userId, new OwnerInfo(ownerName, packageName)); + ComponentName getDeviceInitializerComponent() { + return mDeviceInitializer.admin; + } + + String getDeviceInitializerPackageName() { + return mDeviceInitializer != null ? mDeviceInitializer.packageName : null; + } + + String getDeviceInitializerName() { + return mDeviceInitializer != null ? mDeviceInitializer.name : null; + } + + void setDeviceInitializer(ComponentName admin, String ownerName) { + mDeviceInitializer = new OwnerInfo(ownerName, admin); + } + + void clearDeviceInitializer() { + mDeviceInitializer = null; + } + + boolean hasDeviceInitializer() { + return mDeviceInitializer != null; } void setProfileOwner(ComponentName admin, String ownerName, int userId) { @@ -151,16 +178,6 @@ public class DeviceOwner { mProfileOwners.remove(userId); } - /** - * @deprecated Use getProfileOwnerComponent - * @param userId - * @return - */ - String getProfileOwnerPackageName(int userId) { - OwnerInfo profileOwner = mProfileOwners.get(userId); - return profileOwner != null ? profileOwner.packageName : null; - } - ComponentName getProfileOwnerComponent(int userId) { OwnerInfo profileOwner = mProfileOwners.get(userId); return profileOwner != null ? profileOwner.admin : null; @@ -175,6 +192,18 @@ public class DeviceOwner { return mProfileOwners.keySet(); } + PersistableBundle getSystemUpdatePolicy() { + return mSystemUpdatePolicy; + } + + void setSystemUpdatePolicy(PersistableBundle systemUpdatePolicy) { + mSystemUpdatePolicy = systemUpdatePolicy; + } + + void clearSystemUpdatePolicy() { + mSystemUpdatePolicy = null; + } + boolean hasDeviceOwner() { return mDeviceOwner != null; } @@ -207,6 +236,7 @@ public class DeviceOwner { return false; } + @VisibleForTesting void readOwnerFile() { try { InputStream input = openRead(); @@ -223,6 +253,20 @@ public class DeviceOwner { String name = parser.getAttributeValue(null, ATTR_NAME); String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); mDeviceOwner = new OwnerInfo(name, packageName); + } else if (tag.equals(TAG_DEVICE_INITIALIZER)) { + String name = parser.getAttributeValue(null, ATTR_NAME); + String packageName = parser.getAttributeValue(null, ATTR_PACKAGE); + String initializerComponentStr = + parser.getAttributeValue(null, ATTR_COMPONENT_NAME); + ComponentName admin = + ComponentName.unflattenFromString(initializerComponentStr); + if (admin != null) { + mDeviceInitializer = new OwnerInfo(name, admin); + } else { + mDeviceInitializer = new OwnerInfo(name, packageName); + Slog.e(TAG, "Error parsing device-owner file. Bad component name " + + initializerComponentStr); + } } else if (tag.equals(TAG_PROFILE_OWNER)) { String profileOwnerPackageName = parser.getAttributeValue(null, ATTR_PACKAGE); String profileOwnerName = parser.getAttributeValue(null, ATTR_NAME); @@ -246,6 +290,8 @@ public class DeviceOwner { profileOwnerInfo = new OwnerInfo(profileOwnerName, profileOwnerPackageName); } mProfileOwners.put(userId, profileOwnerInfo); + } else if (TAG_SYSTEM_UPDATE_POLICY.equals(tag)) { + mSystemUpdatePolicy = PersistableBundle.restoreFromXml(parser); } else { throw new XmlPullParserException( "Unexpected tag in device owner file: " + tag); @@ -259,6 +305,7 @@ public class DeviceOwner { } } + @VisibleForTesting void writeOwnerFile() { synchronized (this) { writeOwnerFileLocked(); @@ -282,6 +329,20 @@ public class DeviceOwner { out.endTag(null, TAG_DEVICE_OWNER); } + // Write device initializer tag + if (mDeviceInitializer != null) { + out.startTag(null, TAG_DEVICE_INITIALIZER); + out.attribute(null, ATTR_PACKAGE, mDeviceInitializer.packageName); + if (mDeviceInitializer.name != null) { + out.attribute(null, ATTR_NAME, mDeviceInitializer.name); + } + if (mDeviceInitializer.admin != null) { + out.attribute( + null, ATTR_COMPONENT_NAME, mDeviceInitializer.admin.flattenToString()); + } + out.endTag(null, TAG_DEVICE_INITIALIZER); + } + // Write profile owner tags if (mProfileOwners.size() > 0) { for (HashMap.Entry<Integer, OwnerInfo> owner : mProfileOwners.entrySet()) { @@ -296,6 +357,17 @@ public class DeviceOwner { out.endTag(null, TAG_PROFILE_OWNER); } } + + // Write system update policy tag + if (mSystemUpdatePolicy != null) { + out.startTag(null, TAG_SYSTEM_UPDATE_POLICY); + try { + mSystemUpdatePolicy.saveToXml(out); + } catch (XmlPullParserException e) { + Slog.e(TAG, "Failed to save system update policy", e); + } + out.endTag(null, TAG_SYSTEM_UPDATE_POLICY); + } out.endDocument(); out.flush(); finishWrite(outputStream); @@ -329,10 +401,10 @@ public class DeviceOwner { } } - static class OwnerInfo { - public String name; - public String packageName; - public ComponentName admin; + private static class OwnerInfo { + public final String name; + public final String packageName; + public final ComponentName admin; public OwnerInfo(String name, String packageName) { this.name = name; @@ -345,5 +417,23 @@ public class DeviceOwner { this.admin = admin; this.packageName = admin.getPackageName(); } + public void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "admin=" + admin); + pw.println(prefix + "name=" + name); + pw.println(); + } + } + + public void dump(String prefix, PrintWriter pw) { + if (mDeviceOwner != null) { + pw.println(prefix + "Device Owner: "); + mDeviceOwner.dump(prefix + " ", pw); + } + if (mProfileOwners != null) { + for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) { + pw.println(prefix + "Profile Owner (User " + entry.getKey() + "): "); + entry.getValue().dump(prefix + " ", pw); + } + } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ec1258c..6fc3103 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -17,10 +17,12 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; import static android.content.pm.PackageManager.GET_UNINSTALLED_PACKAGES; +import android.Manifest.permission; import android.accessibilityservice.AccessibilityServiceInfo; import android.accounts.AccountManager; import android.app.Activity; @@ -31,6 +33,7 @@ import android.app.IActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.StatusBarManager; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; @@ -43,13 +46,16 @@ import android.content.ContentResolver; import android.content.Context; 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; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.graphics.Bitmap; import android.hardware.usb.UsbManager; import android.media.AudioManager; import android.media.IAudioService; @@ -75,8 +81,11 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.ContactsContract.QuickContact; +import android.provider.ContactsInternal; import android.provider.Settings; import android.security.Credentials; +import android.security.IKeyChainAliasCallback; import android.security.IKeyChainService; import android.security.KeyChain; import android.security.KeyChain.KeyChainConnection; @@ -96,8 +105,10 @@ import android.view.IWindowManager; import com.android.internal.R; import com.android.internal.os.storage.ExternalStorageFormatter; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; +import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; @@ -126,6 +137,7 @@ 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.Collections; import java.util.Date; import java.util.HashMap; @@ -141,9 +153,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String LOG_TAG = "DevicePolicyManagerService"; + private static final boolean VERBOSE_LOG = false; // DO NOT SUBMIT WITH TRUE + private static final String DEVICE_POLICIES_XML = "device_policies.xml"; - private static final String LOCK_TASK_COMPONENTS_XML = "lock-task-component"; + private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component"; + + private static final String TAG_STATUS_BAR = "statusbar"; + + private static final String ATTR_ENABLED = "enabled"; + + private static final String DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML = + "do-not-ask-credentials-on-boot"; private static final int REQUEST_EXPIRE_PASSWORD = 5571; @@ -161,6 +182,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; private static final String ATTR_SETUP_COMPLETE = "setup-complete"; + private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer"; + + private static final int STATUS_BAR_DISABLE_MASK = + StatusBarManager.DISABLE_EXPAND | + StatusBarManager.DISABLE_NOTIFICATION_ICONS | + StatusBarManager.DISABLE_NOTIFICATION_ALERTS | + StatusBarManager.DISABLE_SEARCH; + private static final Set<String> DEVICE_OWNER_USER_RESTRICTIONS; static { DEVICE_OWNER_USER_RESTRICTIONS = new HashSet(); @@ -174,6 +203,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_UNMUTE_MICROPHONE); DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_ADJUST_VOLUME); DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SMS); + DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SAFE_BOOT); + } + + // The following user restrictions cannot be changed by any active admin, including device + // owner and profile owner. + private static final Set<String> IMMUTABLE_USER_RESTRICTIONS; + static { + IMMUTABLE_USER_RESTRICTIONS = new HashSet(); + IMMUTABLE_USER_RESTRICTIONS.add(UserManager.DISALLOW_WALLPAPER); } private static final Set<String> SECURE_SETTINGS_WHITELIST; @@ -193,14 +231,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.ADB_ENABLED); GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME); GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME_ZONE); - GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.BLUETOOTH_ON); GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.DATA_ROAMING); GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED); GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.MODE_RINGER); GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.NETWORK_PREFERENCE); GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.USB_MASS_STORAGE_ENABLED); - GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.WIFI_ON); GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.WIFI_SLEEP_POLICY); + GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN); } final Context mContext; @@ -218,6 +255,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Stores and loads state on device and profile owners. private DeviceOwner mDeviceOwner; + private final Binder mToken = new Binder(); + /** * Whether or not device admin feature is supported. If it isn't return defaults for all * public methods. @@ -261,24 +300,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long mLastMaximumTimeToLock = -1; boolean mUserSetupComplete = false; - final HashMap<ComponentName, ActiveAdmin> mAdminMap - = new HashMap<ComponentName, ActiveAdmin>(); - final ArrayList<ActiveAdmin> mAdminList - = new ArrayList<ActiveAdmin>(); - final ArrayList<ComponentName> mRemovingAdmins - = new ArrayList<ComponentName>(); + final HashMap<ComponentName, ActiveAdmin> mAdminMap = new HashMap<>(); + final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>(); + final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>(); // This is the list of component allowed to start lock task mode. - final List<String> mLockTaskPackages = new ArrayList<String>(); + List<String> mLockTaskPackages = new ArrayList<>(); + + boolean mStatusBarEnabledState = true; ComponentName mRestrictionsProvider; + String mDelegatedCertInstallerPackage; + + boolean doNotAskCredentialsOnBoot = false; + public DevicePolicyData(int userHandle) { mUserHandle = userHandle; } } - final SparseArray<DevicePolicyData> mUserData = new SparseArray<DevicePolicyData>(); + final SparseArray<DevicePolicyData> mUserData = new SparseArray<>(); Handler mHandler = new Handler(); @@ -329,6 +371,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features"; private static final String TAG_DISABLE_CAMERA = "disable-camera"; private static final String TAG_DISABLE_CALLER_ID = "disable-caller-id"; + private static final String TAG_DISABLE_BLUETOOTH_CONTACT_SHARING + = "disable-bt-contacts-sharing"; private static final String TAG_DISABLE_SCREEN_CAPTURE = "disable-screen-capture"; private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management"; private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time"; @@ -410,6 +454,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean encryptionRequested = false; boolean disableCamera = false; boolean disableCallerId = false; + boolean disableBluetoothContactSharing = true; boolean disableScreenCapture = false; // Can only be set by a device/profile owner. boolean requireAutoTime = false; // Can only be set by a device owner. @@ -551,6 +596,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, ATTR_VALUE, Boolean.toString(disableCallerId)); out.endTag(null, TAG_DISABLE_CALLER_ID); } + if (disableBluetoothContactSharing) { + out.startTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING); + out.attribute(null, ATTR_VALUE, + Boolean.toString(disableBluetoothContactSharing)); + out.endTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING); + } if (disableScreenCapture) { out.startTag(null, TAG_DISABLE_SCREEN_CAPTURE); out.attribute(null, ATTR_VALUE, Boolean.toString(disableScreenCapture)); @@ -696,6 +747,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (TAG_DISABLE_CALLER_ID.equals(tag)) { disableCallerId = Boolean.parseBoolean( parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_DISABLE_BLUETOOTH_CONTACT_SHARING.equals(tag)) { + disableBluetoothContactSharing = Boolean.parseBoolean(parser + .getAttributeValue(null, ATTR_VALUE)); } else if (TAG_DISABLE_SCREEN_CAPTURE.equals(tag)) { disableScreenCapture = Boolean.parseBoolean( parser.getAttributeValue(null, ATTR_VALUE)); @@ -886,6 +940,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { pw.println(disableCamera); pw.print(prefix); pw.print("disableCallerId="); pw.println(disableCallerId); + pw.print(prefix); pw.print("disableBluetoothContactSharing="); + pw.println(disableBluetoothContactSharing); pw.print(prefix); pw.print("disableScreenCapture="); pw.println(disableScreenCapture); pw.print(prefix); pw.print("requireAutoTime="); @@ -935,6 +991,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { syncDeviceCapabilitiesLocked(policy); saveSettingsLocked(policy.mUserHandle); } + + if (policy.mDelegatedCertInstallerPackage != null && + (packageName == null + || packageName.equals(policy.mDelegatedCertInstallerPackage))) { + try { + // Check if delegated cert installer package is removed. + if (pm.getPackageInfo( + policy.mDelegatedCertInstallerPackage, 0, userHandle) == null) { + policy.mDelegatedCertInstallerPackage = null; + saveSettingsLocked(policy.mUserHandle); + } + } catch (RemoteException e) { + // Shouldn't happen + } + } } } @@ -1035,6 +1106,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void loadDeviceOwner() { synchronized (this) { mDeviceOwner = DeviceOwner.load(); + updateDeviceOwnerLocked(); } } @@ -1108,68 +1180,85 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy) throws SecurityException { final int callingUid = Binder.getCallingUid(); - final int userHandle = UserHandle.getUserId(callingUid); - final DevicePolicyData policy = getUserData(userHandle); - List<ActiveAdmin> candidates = new ArrayList<ActiveAdmin>(); + ActiveAdmin result = getActiveAdminWithPolicyForUidLocked(who, reqPolicy, callingUid); + if (result != null) { + return result; + } + + if (who != null) { + final int userId = UserHandle.getUserId(callingUid); + final DevicePolicyData policy = getUserData(userId); + ActiveAdmin admin = policy.mAdminMap.get(who); + if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) { + throw new SecurityException("Admin " + admin.info.getComponent() + + " does not own the device"); + } + if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) { + throw new SecurityException("Admin " + admin.info.getComponent() + + " does not own the profile"); + } + throw new SecurityException("Admin " + admin.info.getComponent() + + " did not specify uses-policy for: " + + admin.info.getTagForPolicy(reqPolicy)); + } else { + throw new SecurityException("No active admin owned by uid " + + Binder.getCallingUid() + " for policy #" + reqPolicy); + } + } - // Build a list of admins for this uid matching the given ComponentName + private ActiveAdmin getActiveAdminWithPolicyForUidLocked(ComponentName who, int reqPolicy, + int uid) { + // Try to find an admin which can use reqPolicy + final int userId = UserHandle.getUserId(uid); + final DevicePolicyData policy = getUserData(userId); if (who != null) { ActiveAdmin admin = policy.mAdminMap.get(who); if (admin == null) { throw new SecurityException("No active admin " + who); } - if (admin.getUid() != callingUid) { + if (admin.getUid() != uid) { throw new SecurityException("Admin " + who + " is not owned by uid " + Binder.getCallingUid()); } - candidates.add(admin); + if (isActiveAdminWithPolicyForUserLocked(admin, reqPolicy, userId)) { + return admin; + } } else { for (ActiveAdmin admin : policy.mAdminList) { - if (admin.getUid() == callingUid) { - candidates.add(admin); + if (admin.getUid() == uid && isActiveAdminWithPolicyForUserLocked(admin, reqPolicy, + userId)) { + return admin; } } } - // Try to find an admin which can use reqPolicy - for (ActiveAdmin admin : candidates) { - boolean ownsDevice = isDeviceOwner(admin.info.getPackageName()); - boolean ownsProfile = (getProfileOwner(userHandle) != null - && getProfileOwner(userHandle).getPackageName() - .equals(admin.info.getPackageName())); + return null; + } - if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) { - if (ownsDevice) { - return admin; - } - } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) { - if (ownsDevice || ownsProfile) { - return admin; - } - } else { - if (admin.info.usesPolicy(reqPolicy)) { - return admin; - } - } - } + private boolean isActiveAdminWithPolicyForUserLocked(ActiveAdmin admin, int reqPolicy, + int userId) { + boolean ownsDevice = isDeviceOwner(admin.info.getPackageName()); + boolean ownsProfile = (getProfileOwner(userId) != null + && getProfileOwner(userId).getPackageName() + .equals(admin.info.getPackageName())); + boolean ownsInitialization = isDeviceInitializer(admin.info.getPackageName()) + && !hasUserSetupCompleted(userId); - if (who != null) { - if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) { - throw new SecurityException("Admin " + candidates.get(0).info.getComponent() - + " does not own the device"); + if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) { + if (ownsDevice || (userId == UserHandle.USER_OWNER && ownsInitialization)) { + return true; } - if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) { - throw new SecurityException("Admin " + candidates.get(0).info.getComponent() - + " does not own the profile"); + } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) { + if (ownsDevice || ownsProfile || ownsInitialization) { + return true; } - throw new SecurityException("Admin " + candidates.get(0).info.getComponent() - + " did not specify uses-policy for: " - + candidates.get(0).info.getTagForPolicy(reqPolicy)); } else { - throw new SecurityException("No active admin owned by uid " - + Binder.getCallingUid() + " for policy #" + reqPolicy); + if (admin.info.usesPolicy(reqPolicy)) { + return true; + } } + return false; } void sendAdminCommandLocked(ActiveAdmin admin, String action) { @@ -1317,6 +1406,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, ATTR_SETUP_COMPLETE, Boolean.toString(true)); } + if (policy.mDelegatedCertInstallerPackage != null) { + out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER, + policy.mDelegatedCertInstallerPackage); + } + final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { @@ -1360,9 +1454,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { for (int i=0; i<policy.mLockTaskPackages.size(); i++) { String component = policy.mLockTaskPackages.get(i); - out.startTag(null, LOCK_TASK_COMPONENTS_XML); + out.startTag(null, TAG_LOCK_TASK_COMPONENTS); out.attribute(null, "name", component); - out.endTag(null, LOCK_TASK_COMPONENTS_XML); + out.endTag(null, TAG_LOCK_TASK_COMPONENTS); + } + + if (!policy.mStatusBarEnabledState) { + out.startTag(null, TAG_STATUS_BAR); + out.attribute(null, ATTR_ENABLED, Boolean.toString(policy.mStatusBarEnabledState)); + out.endTag(null, TAG_STATUS_BAR); + } + + if (policy.doNotAskCredentialsOnBoot) { + out.startTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML); + out.endTag(null, DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML); } out.endTag(null, "policies"); @@ -1424,6 +1529,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (userSetupComplete != null && Boolean.toString(true).equals(userSetupComplete)) { policy.mUserSetupComplete = true; } + policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null, + ATTR_DELEGATED_CERT_INSTALLER); type = parser.next(); int outerDepth = parser.getDepth(); @@ -1481,9 +1588,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mActivePasswordNonLetter = Integer.parseInt( parser.getAttributeValue(null, "nonletter")); XmlUtils.skipCurrentTag(parser); - } else if (LOCK_TASK_COMPONENTS_XML.equals(tag)) { + } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) { policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name")); XmlUtils.skipCurrentTag(parser); + } else if (TAG_STATUS_BAR.equals(tag)) { + policy.mStatusBarEnabledState = Boolean.parseBoolean( + parser.getAttributeValue(null, ATTR_ENABLED)); + XmlUtils.skipCurrentTag(parser); + } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) { + policy.doNotAskCredentialsOnBoot = true; } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -1517,25 +1630,59 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // sufficiently what is currently set. Note that this is only // a sanity check in case the two get out of sync; this should // never normally happen. - LockPatternUtils utils = new LockPatternUtils(mContext); - if (utils.getActivePasswordQuality() < policy.mActivePasswordQuality) { - Slog.w(LOG_TAG, "Active password quality 0x" - + Integer.toHexString(policy.mActivePasswordQuality) - + " does not match actual quality 0x" - + Integer.toHexString(utils.getActivePasswordQuality())); - policy.mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - policy.mActivePasswordLength = 0; - policy.mActivePasswordUpperCase = 0; - policy.mActivePasswordLowerCase = 0; - policy.mActivePasswordLetters = 0; - policy.mActivePasswordNumeric = 0; - policy.mActivePasswordSymbols = 0; - policy.mActivePasswordNonLetter = 0; + final long identity = Binder.clearCallingIdentity(); + try { + LockPatternUtils utils = new LockPatternUtils(mContext); + if (utils.getActivePasswordQuality(userHandle) < policy.mActivePasswordQuality) { + Slog.w(LOG_TAG, "Active password quality 0x" + + Integer.toHexString(policy.mActivePasswordQuality) + + " does not match actual quality 0x" + + Integer.toHexString(utils.getActivePasswordQuality(userHandle))); + policy.mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + policy.mActivePasswordLength = 0; + policy.mActivePasswordUpperCase = 0; + policy.mActivePasswordLowerCase = 0; + policy.mActivePasswordLetters = 0; + policy.mActivePasswordNumeric = 0; + policy.mActivePasswordSymbols = 0; + policy.mActivePasswordNonLetter = 0; + } + } finally { + Binder.restoreCallingIdentity(identity); } validatePasswordOwnerLocked(policy); syncDeviceCapabilitiesLocked(policy); updateMaximumTimeToLockLocked(policy); + addDeviceInitializerToLockTaskPackagesLocked(userHandle); + updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle); + if (!policy.mStatusBarEnabledState) { + setStatusBarEnabledStateInternal(policy.mStatusBarEnabledState, userHandle); + } + } + + private void updateLockTaskPackagesLocked(List<String> packages, int userId) { + IActivityManager am = ActivityManagerNative.getDefault(); + long ident = Binder.clearCallingIdentity(); + try { + am.updateLockTaskPackages(userId, packages.toArray(new String[packages.size()])); + } catch (RemoteException e) { + // Not gonna happen. + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void updateDeviceOwnerLocked() { + IActivityManager am = ActivityManagerNative.getDefault(); + long ident = Binder.clearCallingIdentity(); + try { + am.updateDeviceOwner(getDeviceOwner()); + } catch (RemoteException e) { + // Not gonna happen. + } finally { + Binder.restoreCallingIdentity(ident); + } } static void validateQualityConstant(int quality) { @@ -1578,15 +1725,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { void syncDeviceCapabilitiesLocked(DevicePolicyData policy) { // Ensure the status of the camera is synced down to the system. Interested native services // should monitor this value and act accordingly. - boolean systemState = SystemProperties.getBoolean(SYSTEM_PROP_DISABLE_CAMERA, false); + String cameraPropertyForUser = SYSTEM_PROP_DISABLE_CAMERA_PREFIX + policy.mUserHandle; + boolean systemState = SystemProperties.getBoolean(cameraPropertyForUser, false); boolean cameraDisabled = getCameraDisabled(null, policy.mUserHandle); if (cameraDisabled != systemState) { long token = Binder.clearCallingIdentity(); try { String value = cameraDisabled ? "1" : "0"; if (DBG) Slog.v(LOG_TAG, "Change in camera state [" - + SYSTEM_PROP_DISABLE_CAMERA + "] = " + value); - SystemProperties.set(SYSTEM_PROP_DISABLE_CAMERA, value); + + cameraPropertyForUser + "] = " + value); + SystemProperties.set(cameraPropertyForUser, value); } finally { Binder.restoreCallingIdentity(token); } @@ -1613,7 +1761,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { updateScreenCaptureDisabledInWindowManager(userHandle, getScreenCaptureDisabled(null, userHandle)); } - } private void cleanUpOldUsers() { @@ -1748,7 +1895,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .setContentIntent(notifyIntent) .setPriority(Notification.PRIORITY_HIGH) .setShowWhen(false) - .setColor(mContext.getResources().getColor( + .setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)) .build(); @@ -1897,7 +2044,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } if (admin.getUid() != Binder.getCallingUid()) { - // If trying to remove device owner, refuse when the caller is not the owner. + // Active device owners must remain active admins. if (isDeviceOwner(adminReceiver.getPackageName())) { return; } @@ -1913,17 +2060,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordQuality(ComponentName who, int quality, int userHandle) { + public void setPasswordQuality(ComponentName who, int quality) { if (!mHasFeature) { return; } + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); validateQualityConstant(quality); - enforceCrossUserPermission(userHandle); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.passwordQuality != quality) { @@ -1962,15 +2107,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumLength(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumLength(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.minimumPasswordLength != length) { @@ -2009,15 +2152,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordHistoryLength(ComponentName who, int length, int userHandle) { + public void setPasswordHistoryLength(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.passwordHistoryLength != length) { @@ -2056,18 +2197,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordExpirationTimeout(ComponentName who, long timeout, int userHandle) { + public void setPasswordExpirationTimeout(ComponentName who, long timeout) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } - if (timeout < 0) { - throw new IllegalArgumentException("Timeout must be >= 0 ms"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD); // Calling this API automatically bumps the expiration date @@ -2225,15 +2362,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumUpperCase(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumUpperCase(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.minimumPasswordUpperCase != length) { @@ -2272,12 +2407,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumLowerCase(ComponentName who, int length, int userHandle) { - enforceCrossUserPermission(userHandle); + public void setPasswordMinimumLowerCase(ComponentName who, int length) { + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.minimumPasswordLowerCase != length) { @@ -2316,15 +2449,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumLetters(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumLetters(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.minimumPasswordLetters != length) { @@ -2354,6 +2485,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); + if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) { + continue; + } if (length < admin.minimumPasswordLetters) { length = admin.minimumPasswordLetters; } @@ -2363,15 +2497,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumNumeric(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumNumeric(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.minimumPasswordNumeric != length) { @@ -2401,6 +2533,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = policy.mAdminList.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); + if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) { + continue; + } if (length < admin.minimumPasswordNumeric) { length = admin.minimumPasswordNumeric; } @@ -2410,15 +2545,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumSymbols(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumSymbols(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.minimumPasswordSymbols != length) { @@ -2448,6 +2581,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); + if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) { + continue; + } if (length < admin.minimumPasswordSymbols) { length = admin.minimumPasswordSymbols; } @@ -2457,15 +2593,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setPasswordMinimumNonLetter(ComponentName who, int length, int userHandle) { + public void setPasswordMinimumNonLetter(ComponentName who, int length) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (ap.minimumPasswordNonLetter != length) { @@ -2495,6 +2629,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); + if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) { + continue; + } if (length < admin.minimumPasswordNonLetter) { length = admin.minimumPasswordNonLetter; } @@ -2521,8 +2658,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked(null, - DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); if (policy.mActivePasswordQuality < getPasswordQuality(null, userHandle) || policy.mActivePasswordLength < getPasswordMinimumLength(null, userHandle)) { return false; @@ -2555,15 +2691,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, int userHandle) { + public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. getActiveAdminForCallerLocked(who, @@ -2631,11 +2765,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return strictestAdmin; } - public boolean resetPassword(String passwordOrNull, int flags, int userHandle) { + public boolean resetPassword(String passwordOrNull, int flags) { if (!mHasFeature) { return false; } - enforceCrossUserPermission(userHandle); + final int userHandle = UserHandle.getCallingUserId(); enforceNotManagedProfile(userHandle, "reset the password"); String password = passwordOrNull != null ? passwordOrNull : ""; @@ -2738,15 +2872,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } + boolean callerIsDeviceOwnerAdmin = isCallerDeviceOwnerOrInitializer(callingUid); + boolean doNotAskCredentialsOnBoot = + (flags & DevicePolicyManager.DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0; + if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) { + setDoNotAskCredentialsOnBoot(); + } + // Don't do this with the lock held, because it is going to call // back in to the service. long ident = Binder.clearCallingIdentity(); try { LockPatternUtils utils = new LockPatternUtils(mContext); if (!TextUtils.isEmpty(password)) { - utils.saveLockPassword(password, quality, false, userHandle); + utils.saveLockPassword(password, null, quality, userHandle); } else { - utils.clearLock(false, userHandle); + utils.clearLock(userHandle); } boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0; if (requireEntry) { @@ -2766,15 +2907,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return true; } - public void setMaximumTimeToLock(ComponentName who, long timeMs, int userHandle) { + private void setDoNotAskCredentialsOnBoot() { + synchronized (this) { + DevicePolicyData policyData = getUserData(UserHandle.USER_OWNER); + if (!policyData.doNotAskCredentialsOnBoot) { + policyData.doNotAskCredentialsOnBoot = true; + saveSettingsLocked(UserHandle.USER_OWNER); + } + } + } + + public boolean getDoNotAskCredentialsOnBoot() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT, null); + synchronized (this) { + DevicePolicyData policyData = getUserData(UserHandle.USER_OWNER); + return policyData.doNotAskCredentialsOnBoot; + } + } + + public void setMaximumTimeToLock(ComponentName who, long timeMs) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK); if (ap.maximumTimeToUnlock != timeMs) { @@ -2877,7 +3035,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void enforceCanManageCaCerts(ComponentName who) { if (who == null) { - mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null); + if (!isCallerDelegatedCertInstaller()) { + mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null); + } } else { synchronized (this) { getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -2885,6 +3045,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private boolean isCallerDelegatedCertInstaller() { + final int callingUid = Binder.getCallingUid(); + final int userHandle = UserHandle.getUserId(callingUid); + synchronized (this) { + final DevicePolicyData policy = getUserData(userHandle); + if (policy.mDelegatedCertInstallerPackage == null) { + return false; + } + + try { + int uid = mContext.getPackageManager().getPackageUid( + policy.mDelegatedCertInstallerPackage, userHandle); + return uid == callingUid; + } catch (NameNotFoundException e) { + return false; + } + } + } + @Override public boolean installCaCert(ComponentName admin, byte[] certBuffer) throws RemoteException { enforceCanManageCaCerts(admin); @@ -2954,10 +3133,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, String alias) { if (who == null) { - throw new NullPointerException("ComponentName is null"); - } - synchronized (this) { - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (!isCallerDelegatedCertInstaller()) { + throw new SecurityException("who == null, but caller is not cert installer"); + } + } else { + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + } } final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId()); final long id = Binder.clearCallingIdentity(); @@ -2980,7 +3162,88 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } + @Override + public void choosePrivateKeyAlias(final int uid, final String host, int port, final String url, + final String alias, final IBinder response) { + // Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers. + if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) { + return; + } + + final UserHandle caller = Binder.getCallingUserHandle(); + final ComponentName profileOwner = getProfileOwner(caller.getIdentifier()); + + if (profileOwner == null) { + sendPrivateKeyAliasResponse(null, response); + return; + } + + Intent intent = new Intent(DeviceAdminReceiver.ACTION_CHOOSE_PRIVATE_KEY_ALIAS); + intent.setComponent(profileOwner); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, uid); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_HOST, host); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_PORT, port); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URL, url); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias); + intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response); + + final long id = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(intent, caller, null, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String chosenAlias = getResultData(); + sendPrivateKeyAliasResponse(chosenAlias, response); + } + }, null, Activity.RESULT_OK, null, null); + } finally { + Binder.restoreCallingIdentity(id); + } + } + + private void sendPrivateKeyAliasResponse(final String alias, final IBinder responseBinder) { + final IKeyChainAliasCallback keyChainAliasResponse = + IKeyChainAliasCallback.Stub.asInterface(responseBinder); + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + try { + keyChainAliasResponse.alias(alias); + } catch (Exception e) { + // Catch everything (not just RemoteException): caller could throw a + // RuntimeException back across processes. + Log.e(LOG_TAG, "error while responding to callback", e); + } + return null; + } + }.execute(); + } + + @Override + public void setCertInstallerPackage(ComponentName who, String installerPackage) + throws SecurityException { + int userHandle = UserHandle.getCallingUserId(); + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + DevicePolicyData policy = getUserData(userHandle); + policy.mDelegatedCertInstallerPackage = installerPackage; + saveSettingsLocked(userHandle); + } + } + + @Override + public String getCertInstallerPackage(ComponentName who) throws SecurityException { + int userHandle = UserHandle.getCallingUserId(); + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + DevicePolicyData policy = getUserData(userHandle); + return policy.mDelegatedCertInstallerPackage; + } + } + private void wipeDataLocked(boolean wipeExtRequested, String reason) { + // TODO: wipe all public volumes on device + // If the SD card is encrypted and non-removable, we have to force a wipe. boolean forceExtWipe = !Environment.isExternalStorageRemovable() && isExtStorageEncrypted(); @@ -3024,8 +3287,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long ident = Binder.clearCallingIdentity(); try { if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) { + boolean ownsInitialization = isDeviceInitializer(admin.info.getPackageName()) + && !hasUserSetupCompleted(userHandle); if (userHandle != UserHandle.USER_OWNER - || !isDeviceOwner(admin.info.getPackageName())) { + || !(isDeviceOwner(admin.info.getPackageName()) + || ownsInitialization)) { throw new SecurityException( "Only device owner admins can set WIPE_RESET_PROTECTION_DATA"); } @@ -3235,15 +3501,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public ComponentName setGlobalProxy(ComponentName who, String proxySpec, - String exclusionList, int userHandle) { + String exclusionList) { if (!mHasFeature) { return null; } - enforceCrossUserPermission(userHandle); synchronized(this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } + Preconditions.checkNotNull(who, "ComponentName is null"); // Only check if owner has set global proxy. We don't allow other users to set it. DevicePolicyData policy = getUserData(UserHandle.USER_OWNER); @@ -3265,7 +3528,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // If the user is not the owner, don't set the global proxy. Fail silently. if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) { Slog.w(LOG_TAG, "Only the owner is allowed to set the global proxy. User " - + userHandle + " is not permitted."); + + UserHandle.getCallingUserId() + " is not permitted."); return null; } if (proxySpec == null) { @@ -3375,16 +3638,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * Set the storage encryption request for a single admin. Returns the new total request * status (for all admins). */ - public int setStorageEncryption(ComponentName who, boolean encrypt, int userHandle) { + public int setStorageEncryption(ComponentName who, boolean encrypt) { if (!mHasFeature) { return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { // Check for permissions - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } // Only owner can set storage encryption if (userHandle != UserHandle.USER_OWNER || UserHandle.getCallingUserId() != UserHandle.USER_OWNER) { @@ -3479,8 +3740,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Hook to low-levels: Reporting the current status of encryption. - * @return A value such as {@link DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED} or - * {@link DevicePolicyManager#ENCRYPTION_STATUS_INACTIVE} or + * @return A value such as {@link DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED}, + * {@link DevicePolicyManager#ENCRYPTION_STATUS_INACTIVE}, + * {@link DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY}, or * {@link DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE}. */ private int getEncryptionStatus() { @@ -3490,7 +3752,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { return LockPatternUtils.isDeviceEncrypted() ? DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE - : DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE; + : DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY; } finally { Binder.restoreCallingIdentity(token); } @@ -3511,15 +3773,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Set whether the screen capture is disabled for the user managed by the specified admin. */ - public void setScreenCaptureDisabled(ComponentName who, int userHandle, boolean disabled) { + public void setScreenCaptureDisabled(ComponentName who, boolean disabled) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); if (ap.disableScreenCapture != disabled) { @@ -3570,15 +3830,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Set whether auto time is required by the specified admin (must be device owner). */ - public void setAutoTimeRequired(ComponentName who, int userHandle, boolean required) { + public void setAutoTimeRequired(ComponentName who, boolean required) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin admin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); if (admin.requireAutoTime != required) { @@ -3614,22 +3872,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * The system property used to share the state of the camera. The native camera service - * is expected to read this property and act accordingly. + * is expected to read this property and act accordingly. The userId should be appended + * to this key. */ - public static final String SYSTEM_PROP_DISABLE_CAMERA = "sys.secpolicy.camera.disabled"; + public static final String SYSTEM_PROP_DISABLE_CAMERA_PREFIX = "sys.secpolicy.camera.off_"; /** * Disables all device cameras according to the specified admin. */ - public void setCameraDisabled(ComponentName who, boolean disabled, int userHandle) { + public void setCameraDisabled(ComponentName who, boolean disabled) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA); if (ap.disableCamera != disabled) { @@ -3670,16 +3927,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Selectively disable keyguard features. */ - public void setKeyguardDisabledFeatures(ComponentName who, int which, int userHandle) { + public void setKeyguardDisabledFeatures(ComponentName who, int which) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); enforceNotManagedProfile(userHandle, "disable keyguard features"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES); if (ap.disabledKeyguardFeatures != which) { @@ -3728,15 +3983,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + " for device owner"); } synchronized (this) { - if (!allowedToSetDeviceOwnerOnDevice()) { - throw new IllegalStateException( - "Trying to set device owner but device is already provisioned."); - } - - if (mDeviceOwner != null && mDeviceOwner.hasDeviceOwner()) { - throw new IllegalStateException( - "Trying to set device owner but device owner is already set."); - } + enforceCanSetDeviceOwner(); // Shutting down backup manager service permanently. long ident = Binder.clearCallingIdentity(); @@ -3753,14 +4000,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mDeviceOwner == null) { // Device owner is not set and does not exist, set it. mDeviceOwner = DeviceOwner.createWithDeviceOwner(packageName, ownerName); - mDeviceOwner.writeOwnerFile(); - return true; } else { - // Device owner is not set but a profile owner exists, update Device owner state. + // Device owner state already exists, update it. mDeviceOwner.setDeviceOwner(packageName, ownerName); - mDeviceOwner.writeOwnerFile(); - return true; } + mDeviceOwner.writeOwnerFile(); + updateDeviceOwnerLocked(); + return true; } } @@ -3823,9 +4069,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void clearDeviceOwner(String packageName) { - if (packageName == null) { - throw new NullPointerException("packageName is null"); - } + Preconditions.checkNotNull(packageName, "packageName is null"); try { int uid = mContext.getPackageManager().getPackageUid(packageName, 0); if (uid != Binder.getCallingUid()) { @@ -3844,6 +4088,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mDeviceOwner != null) { mDeviceOwner.clearDeviceOwner(); mDeviceOwner.writeOwnerFile(); + updateDeviceOwnerLocked(); } } finally { Binder.restoreCallingIdentity(ident); @@ -3852,47 +4097,143 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) { + public boolean setDeviceInitializer(ComponentName who, ComponentName initializer, + String ownerName) { if (!mHasFeature) { return false; } - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null); + if (initializer == null || !DeviceOwner.isInstalled( + initializer.getPackageName(), mContext.getPackageManager())) { + throw new IllegalArgumentException("Invalid component name " + initializer + + " for device initializer"); + } + synchronized (this) { + enforceCanSetDeviceInitializer(who); - UserInfo info = mUserManager.getUserInfo(userHandle); - if (info == null) { - // User doesn't exist. - throw new IllegalArgumentException( - "Attempted to set profile owner for invalid userId: " + userHandle); + if (mDeviceOwner != null && mDeviceOwner.hasDeviceInitializer()) { + throw new IllegalStateException( + "Trying to set device initializer but device initializer is already set."); + } + + if (mDeviceOwner == null) { + // Device owner state does not exist, create it. + mDeviceOwner = DeviceOwner.createWithDeviceInitializer(initializer, ownerName); + } else { + // Device owner already exists, update it. + mDeviceOwner.setDeviceInitializer(initializer, ownerName); + } + + addDeviceInitializerToLockTaskPackagesLocked(UserHandle.USER_OWNER); + mDeviceOwner.writeOwnerFile(); + return true; } - if (info.isGuest()) { - throw new IllegalStateException("Cannot set a profile owner on a guest"); + } + + private void enforceCanSetDeviceInitializer(ComponentName who) { + if (who == null) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_DEVICE_ADMINS, null); + if (hasUserSetupCompleted(UserHandle.USER_OWNER)) { + throw new IllegalStateException( + "Trying to set device initializer but device is already provisioned."); + } + } else { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + } + + @Override + public boolean isDeviceInitializer(String packageName) { + if (!mHasFeature) { + return false; + } + synchronized (this) { + return mDeviceOwner != null + && mDeviceOwner.hasDeviceInitializer() + && mDeviceOwner.getDeviceInitializerPackageName().equals(packageName); + } + } + + @Override + public String getDeviceInitializer() { + if (!mHasFeature) { + return null; + } + synchronized (this) { + if (mDeviceOwner != null && mDeviceOwner.hasDeviceInitializer()) { + return mDeviceOwner.getDeviceInitializerPackageName(); + } + } + return null; + } + + @Override + public ComponentName getDeviceInitializerComponent() { + if (!mHasFeature) { + return null; + } + synchronized (this) { + if (mDeviceOwner != null && mDeviceOwner.hasDeviceInitializer()) { + return mDeviceOwner.getDeviceInitializerComponent(); + } + } + return null; + } + + @Override + public void clearDeviceInitializer(ComponentName who) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + + ActiveAdmin admin = getActiveAdminUncheckedLocked(who, UserHandle.getCallingUserId()); + + if (admin.getUid() != Binder.getCallingUid()) { + throw new SecurityException("Admin " + who + " is not owned by uid " + + Binder.getCallingUid()); + } + + if (!isDeviceInitializer(admin.info.getPackageName()) + && !isDeviceOwner(admin.info.getPackageName())) { + throw new SecurityException( + "clearDeviceInitializer can only be called by the device initializer/owner"); + } + synchronized (this) { + long ident = Binder.clearCallingIdentity(); + try { + if (mDeviceOwner != null) { + mDeviceOwner.clearDeviceInitializer(); + mDeviceOwner.writeOwnerFile(); + } + } finally { + Binder.restoreCallingIdentity(ident); + } } + } + @Override + public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) { + if (!mHasFeature) { + return false; + } if (who == null || !DeviceOwner.isInstalledForUser(who.getPackageName(), userHandle)) { throw new IllegalArgumentException("Component " + who + " not installed for userId:" + userHandle); } synchronized (this) { - // Only SYSTEM_UID can override the userSetupComplete - if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID - && hasUserSetupCompleted(userHandle)) { - throw new IllegalStateException( - "Trying to set profile owner but user is already set-up."); - } - + enforceCanSetProfileOwner(userHandle); if (mDeviceOwner == null) { // Device owner state does not exist, create it. mDeviceOwner = DeviceOwner.createWithProfileOwner(who, ownerName, userHandle); - mDeviceOwner.writeOwnerFile(); - return true; } else { - // Device owner already exists, update it. + // Device owner state already exists, update it. mDeviceOwner.setProfileOwner(who, ownerName, userHandle); - mDeviceOwner.writeOwnerFile(); - return true; } + mDeviceOwner.writeOwnerFile(); + return true; } } @@ -3924,7 +4265,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Bundle userRestrictions = mUserManager.getUserRestrictions(); mUserManager.setUserRestrictions(new Bundle(), userHandle); if (userRestrictions.getBoolean(UserManager.DISALLOW_ADJUST_VOLUME)) { - audioManager.setMasterMute(false); + audioManager.setMasterMute(false, 0); } if (userRestrictions.getBoolean(UserManager.DISALLOW_UNMUTE_MICROPHONE)) { audioManager.setMicrophoneMute(false); @@ -3946,16 +4287,58 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setProfileEnabled(ComponentName who) { + public boolean setUserEnabled(ComponentName who) { if (!mHasFeature) { - return; + return false; } - final int userHandle = UserHandle.getCallingUserId(); synchronized (this) { - // Check for permissions if (who == null) { throw new NullPointerException("ComponentName is null"); } + int userId = UserHandle.getCallingUserId(); + + ActiveAdmin activeAdmin = + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (!isDeviceInitializer(activeAdmin.info.getPackageName())) { + throw new SecurityException( + "This method can only be called by device initializers"); + } + + long id = Binder.clearCallingIdentity(); + try { + if (!isDeviceOwner(activeAdmin.info.getPackageName())) { + IPackageManager ipm = AppGlobals.getPackageManager(); + ipm.setComponentEnabledSetting(who, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP, userId); + + removeActiveAdmin(who, userId); + } + + if (userId == UserHandle.USER_OWNER) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 1); + } + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 1, userId); + } catch (RemoteException e) { + Log.i(LOG_TAG, "Can't talk to package manager", e); + return false; + } finally { + restoreCallingIdentity(id); + } + return true; + } + } + + @Override + public void setProfileEnabled(ComponentName who) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + final int userHandle = UserHandle.getCallingUserId(); + synchronized (this) { // Check if this is the profile owner who is calling getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); int userId = UserHandle.getCallingUserId(); @@ -3977,12 +4360,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setProfileName(ComponentName who, String profileName) { + Preconditions.checkNotNull(who, "ComponentName is null"); int userId = UserHandle.getCallingUserId(); - - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } - // Check if this is the profile owner (includes device owner). getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -4044,15 +4423,76 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * Device owner can only be set on an unprovisioned device, unless it was initiated by "adb", in - * which case we allow it if no account is associated with the device. + * The profile owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS + * permission. + * The profile owner can only be set before the user setup phase has completed, + * except for: + * - SYSTEM_UID + * - adb if there are not accounts. */ - private boolean allowedToSetDeviceOwnerOnDevice() { - int callingId = Binder.getCallingUid(); - if (callingId == Process.SHELL_UID || callingId == Process.ROOT_UID) { - return AccountManager.get(mContext).getAccounts().length == 0; - } else { - return !hasUserSetupCompleted(UserHandle.USER_OWNER); + private void enforceCanSetProfileOwner(int userHandle) { + UserInfo info = mUserManager.getUserInfo(userHandle); + if (info == null) { + // User doesn't exist. + throw new IllegalArgumentException( + "Attempted to set profile owner for invalid userId: " + userHandle); + } + if (info.isGuest()) { + throw new IllegalStateException("Cannot set a profile owner on a guest"); + } + if (getProfileOwner(userHandle) != null) { + throw new IllegalStateException("Trying to set the profile owner, but profile owner " + + "is already set."); + } + int callingUid = Binder.getCallingUid(); + if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { + if (hasUserSetupCompleted(userHandle) && + AccountManager.get(mContext).getAccountsAsUser(userHandle).length > 0) { + throw new IllegalStateException("Not allowed to set the profile owner because " + + "there are already some accounts on the profile"); + } + return; + } + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null); + if (hasUserSetupCompleted(userHandle) + && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID) { + throw new IllegalStateException("Cannot set the profile owner on a user which is " + + "already set-up"); + } + } + + /** + * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS + * permission. + * The device owner can only be set before the setup phase of the primary user has completed, + * except for adb if no accounts or additional users are present on the device. + */ + private void enforceCanSetDeviceOwner() { + if (mDeviceOwner != null && mDeviceOwner.hasDeviceOwner()) { + throw new IllegalStateException("Trying to set the device owner, but device owner " + + "is already set."); + } + int callingUid = Binder.getCallingUid(); + if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { + if (!hasUserSetupCompleted(UserHandle.USER_OWNER)) { + return; + } + if (mUserManager.getUserCount() > 1) { + throw new IllegalStateException("Not allowed to set the device owner because there " + + "are already several users on the device"); + } + if (AccountManager.get(mContext).getAccounts().length > 0) { + throw new IllegalStateException("Not allowed to set the device owner because there " + + "are already some accounts on the device"); + } + return; + } + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null); + if (hasUserSetupCompleted(UserHandle.USER_OWNER)) { + throw new IllegalStateException("Cannot set the device owner if the device is " + + "already set-up"); } } @@ -4130,7 +4570,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { p.println("Current Device Policy Manager state:"); - + if (mDeviceOwner != null) { + mDeviceOwner.dump(" ", pw); + } int userCount = mUserData.size(); for (int u = 0; u < userCount; u++) { DevicePolicyData policy = getUserData(mUserData.keyAt(u)); @@ -4158,12 +4600,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void addPersistentPreferredActivity(ComponentName who, IntentFilter filter, ComponentName activity) { + Preconditions.checkNotNull(who, "ComponentName is null"); final int userHandle = UserHandle.getCallingUserId(); - synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); IPackageManager pm = AppGlobals.getPackageManager(); @@ -4180,12 +4619,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) { + Preconditions.checkNotNull(who, "ComponentName is null"); final int userHandle = UserHandle.getCallingUserId(); - synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); IPackageManager pm = AppGlobals.getPackageManager(); @@ -4202,12 +4638,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) { + Preconditions.checkNotNull(who, "ComponentName is null"); final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId()); - synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); long id = Binder.clearCallingIdentity(); @@ -4220,19 +4653,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, - PersistableBundle args, int userHandle) { + PersistableBundle args) { if (!mHasFeature) { return; } - enforceCrossUserPermission(userHandle); + Preconditions.checkNotNull(admin, "admin is null"); + Preconditions.checkNotNull(agent, "agent is null"); + final int userHandle = UserHandle.getCallingUserId(); enforceNotManagedProfile(userHandle, "set trust agent configuration"); synchronized (this) { - if (admin == null) { - throw new NullPointerException("admin is null"); - } - if (agent == null) { - throw new NullPointerException("agent is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES); ap.trustAgentInfos.put(agent.flattenToString(), new TrustAgentInfo(args)); @@ -4246,10 +4675,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return null; } + Preconditions.checkNotNull(agent, "agent null"); enforceCrossUserPermission(userHandle); - if (agent == null) { - throw new NullPointerException("agent is null"); - } synchronized (this) { final String componentName = agent.flattenToString(); @@ -4302,10 +4729,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) { + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); int userHandle = UserHandle.getCallingUserId(); @@ -4327,23 +4752,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) { + Preconditions.checkNotNull(who, "ComponentName is null"); int callingUserId = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); IPackageManager pm = AppGlobals.getPackageManager(); long id = Binder.clearCallingIdentity(); try { if ((flags & DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED) != 0) { - pm.addCrossProfileIntentFilter(filter, who.getPackageName(), - mContext.getUserId(), callingUserId, UserHandle.USER_OWNER, 0); + pm.addCrossProfileIntentFilter(filter, who.getPackageName(), callingUserId, + UserHandle.USER_OWNER, 0); } if ((flags & DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT) != 0) { pm.addCrossProfileIntentFilter(filter, who.getPackageName(), - mContext.getUserId(), UserHandle.USER_OWNER, callingUserId, 0); + UserHandle.USER_OWNER, callingUserId, 0); } } catch (RemoteException re) { // Shouldn't happen @@ -4354,21 +4777,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void clearCrossProfileIntentFilters(ComponentName who) { + Preconditions.checkNotNull(who, "ComponentName is null"); int callingUserId = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); IPackageManager pm = AppGlobals.getPackageManager(); long id = Binder.clearCallingIdentity(); try { - pm.clearCrossProfileIntentFilters(callingUserId, who.getPackageName(), - callingUserId); + // Removing those that go from the managed profile to the primary user. + pm.clearCrossProfileIntentFilters(callingUserId, who.getPackageName()); + // And those that go from the primary user to the managed profile. // If we want to support multiple managed profiles, we will have to only remove // those that have callingUserId as their target. - pm.clearCrossProfileIntentFilters(UserHandle.USER_OWNER, who.getPackageName(), - callingUserId); + pm.clearCrossProfileIntentFilters(UserHandle.USER_OWNER, who.getPackageName()); } catch (RemoteException re) { // Shouldn't happen } finally { @@ -4399,8 +4820,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { ApplicationInfo applicationInfo = pm.getApplicationInfo(enabledPackage, PackageManager.GET_UNINSTALLED_PACKAGES, userIdToCheck); - systemService = (applicationInfo.flags - & ApplicationInfo.FLAG_SYSTEM) != 0; + systemService = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } catch (RemoteException e) { Log.i(LOG_TAG, "Can't talk to package managed", e); } @@ -4429,9 +4849,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return false; } - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } + Preconditions.checkNotNull(who, "ComponentName is null"); if (packageList != null) { int userId = UserHandle.getCallingUserId(); @@ -4476,10 +4894,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return null; } - - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { ActiveAdmin admin = getActiveAdminForCallerLocked(who, @@ -4534,15 +4949,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { IPackageManager pm = AppGlobals.getPackageManager(); if (installedServices != null) { for (AccessibilityServiceInfo service : installedServices) { - String packageName = service.getResolveInfo().serviceInfo.packageName; - try { - ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, - PackageManager.GET_UNINSTALLED_PACKAGES, userId); - if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - result.add(packageName); - } - } catch (RemoteException e) { - Log.i(LOG_TAG, "Accessibility service in missing package", e); + ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; + ApplicationInfo applicationInfo = serviceInfo.applicationInfo; + if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + result.add(serviceInfo.packageName); } } } @@ -4589,9 +4999,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return false; } - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } + Preconditions.checkNotNull(who, "ComponentName is null"); // TODO When InputMethodManager supports per user calls remove // this restriction. @@ -4634,10 +5042,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return null; } - - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { ActiveAdmin admin = getActiveAdminForCallerLocked(who, @@ -4693,16 +5098,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { IPackageManager pm = AppGlobals.getPackageManager(); if (imes != null) { for (InputMethodInfo ime : imes) { - String packageName = ime.getPackageName(); - try { - ApplicationInfo applicationInfo = pm.getApplicationInfo( - packageName, PackageManager.GET_UNINSTALLED_PACKAGES, - userId); - if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - result.add(packageName); - } - } catch (RemoteException e) { - Log.i(LOG_TAG, "Input method for missing package", e); + ServiceInfo serviceInfo = ime.getServiceInfo(); + ApplicationInfo applicationInfo = serviceInfo.applicationInfo; + if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + result.add(serviceInfo.packageName); } } } @@ -4716,10 +5115,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public UserHandle createUser(ComponentName who, String name) { + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); long id = Binder.clearCallingIdentity(); @@ -4748,20 +5145,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final IPackageManager ipm = AppGlobals.getPackageManager(); IActivityManager activityManager = ActivityManagerNative.getDefault(); + final int userHandle = user.getIdentifier(); try { // Install the profile owner if not present. - if (!ipm.isPackageAvailable(profileOwnerPkg, user.getIdentifier())) { - ipm.installExistingPackageAsUser(profileOwnerPkg, user.getIdentifier()); + if (!ipm.isPackageAvailable(profileOwnerPkg, userHandle)) { + ipm.installExistingPackageAsUser(profileOwnerPkg, userHandle); } // Start user in background. - activityManager.startUserInBackground(user.getIdentifier()); + activityManager.startUserInBackground(userHandle); } catch (RemoteException e) { Slog.e(LOG_TAG, "Failed to make remote calls for configureUser", e); } - setActiveAdmin(profileOwnerComponent, true, user.getIdentifier(), adminExtras); - setProfileOwner(profileOwnerComponent, ownerName, user.getIdentifier()); + setActiveAdmin(profileOwnerComponent, true, userHandle, adminExtras); + setProfileOwner(profileOwnerComponent, ownerName, userHandle); return user; } finally { restoreCallingIdentity(id); @@ -4770,10 +5168,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean removeUser(ComponentName who, UserHandle userHandle) { + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); long id = Binder.clearCallingIdentity(); @@ -4787,10 +5183,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean switchUser(ComponentName who, UserHandle userHandle) { + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); long id = Binder.clearCallingIdentity(); @@ -4811,17 +5205,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public Bundle getApplicationRestrictions(ComponentName who, String packageName) { + Preconditions.checkNotNull(who, "ComponentName is null"); final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId()); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); long id = Binder.clearCallingIdentity(); try { - return mUserManager.getApplicationRestrictions(packageName, userHandle); + Bundle bundle = mUserManager.getApplicationRestrictions(packageName, userHandle); + // if no restrictions were saved, mUserManager.getApplicationRestrictions + // returns null, but DPM method should return an empty Bundle as per JavaDoc + return bundle != null ? bundle : Bundle.EMPTY; } finally { restoreCallingIdentity(id); } @@ -4830,12 +5225,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setUserRestriction(ComponentName who, String key, boolean enabled) { + Preconditions.checkNotNull(who, "ComponentName is null"); final UserHandle user = new UserHandle(UserHandle.getCallingUserId()); final int userHandle = user.getIdentifier(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); boolean isDeviceOwner = isDeviceOwner(activeAdmin.info.getPackageName()); @@ -4843,6 +5236,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) { throw new SecurityException("Profile owners cannot set user restriction " + key); } + if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) { + throw new SecurityException("User restriction " + key + " cannot be changed"); + } boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user); IAudioService iAudioService = null; @@ -4857,7 +5253,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { iAudioService.setMicrophoneMute(true, who.getPackageName()); } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - iAudioService.setMasterMute(true, 0, who.getPackageName(), null); + iAudioService.setMasterMute(true, 0, who.getPackageName()); } } catch (RemoteException re) { Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); @@ -4922,7 +5318,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (UserManager.DISALLOW_UNMUTE_MICROPHONE.equals(key)) { iAudioService.setMicrophoneMute(false, who.getPackageName()); } else if (UserManager.DISALLOW_ADJUST_VOLUME.equals(key)) { - iAudioService.setMasterMute(false, 0, who.getPackageName(), null); + iAudioService.setMasterMute(false, 0, who.getPackageName()); } } catch (RemoteException re) { Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); @@ -4935,11 +5331,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean setApplicationHidden(ComponentName who, String packageName, boolean hidden) { + Preconditions.checkNotNull(who, "ComponentName is null"); int callingUserId = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); long id = Binder.clearCallingIdentity(); @@ -4958,11 +5352,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isApplicationHidden(ComponentName who, String packageName) { + Preconditions.checkNotNull(who, "ComponentName is null"); int callingUserId = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); long id = Binder.clearCallingIdentity(); @@ -4981,11 +5373,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void enableSystemApp(ComponentName who, String packageName) { + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } - // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -5026,11 +5415,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int enableSystemAppWithIntent(ComponentName who, Intent intent) { + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } - // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -5058,15 +5444,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (activitiesToEnable != null) { for (ResolveInfo info : activitiesToEnable) { if (info.activityInfo != null) { - - if (!isSystemApp(pm, info.activityInfo.packageName, primaryUser.id)) { - throw new IllegalArgumentException( - "Only system apps can be enabled this way."); + String packageName = info.activityInfo.packageName; + if (isSystemApp(pm, packageName, primaryUser.id)) { + numberOfAppsInstalled++; + pm.installExistingPackageAsUser(packageName, userId); + } else { + Slog.d(LOG_TAG, "Not enabling " + packageName + " since is not a" + + " system app"); } - - - numberOfAppsInstalled++; - pm.installExistingPackageAsUser(info.activityInfo.packageName, userId); } } } @@ -5085,7 +5470,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throws RemoteException { ApplicationInfo appInfo = pm.getApplicationInfo(packageName, GET_UNINSTALLED_PACKAGES, userId); - return (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) > 0; + if (appInfo == null) { + throw new IllegalArgumentException("The application " + packageName + + " is not present on this device"); + } + return (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } @Override @@ -5094,10 +5483,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin ap = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); if (disabled) { @@ -5135,12 +5522,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setUninstallBlocked(ComponentName who, String packageName, boolean uninstallBlocked) { + Preconditions.checkNotNull(who, "ComponentName is null"); final int userId = UserHandle.getCallingUserId(); - synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); long id = Binder.clearCallingIdentity(); @@ -5187,10 +5571,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin admin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); if (admin.disableCallerId != disabled) { @@ -5205,12 +5587,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return false; } - + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } - ActiveAdmin admin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); return admin.disableCallerId; @@ -5227,50 +5605,143 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public void startManagedQuickContact(String actualLookupKey, long actualContactId, + Intent originalIntent) { + final Intent intent = QuickContact.rebuildManagedQuickContactsIntent( + actualLookupKey, actualContactId, originalIntent); + final int callingUserId = UserHandle.getCallingUserId(); + + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (this) { + final int managedUserId = getManagedUserId(callingUserId); + if (managedUserId < 0) { + return; + } + if (getCrossProfileCallerIdDisabledForUser(managedUserId)) { + if (VERBOSE_LOG) { + Log.v(LOG_TAG, + "Cross-profile contacts access disabled for user " + managedUserId); + } + return; + } + ContactsInternal.startQuickContactWithErrorToastForUser( + mContext, intent, new UserHandle(managedUserId)); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** + * @return the user ID of the managed user that is linked to the current user, if any. + * Otherwise -1. + */ + public int getManagedUserId(int callingUserId) { + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "getManagedUserId: callingUserId=" + callingUserId); + } + + for (UserInfo ui : mUserManager.getProfiles(callingUserId)) { + if (ui.id == callingUserId || !ui.isManagedProfile()) { + continue; // Caller user self, or not a managed profile. Skip. + } + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Managed user=" + ui.id); + } + return ui.id; + } + if (VERBOSE_LOG) { + Log.v(LOG_TAG, "Managed user not found."); + } + return -1; + } + + @Override + public void setBluetoothContactSharingDisabled(ComponentName who, boolean disabled) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + synchronized (this) { + ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (admin.disableBluetoothContactSharing != disabled) { + admin.disableBluetoothContactSharing = disabled; + saveSettingsLocked(UserHandle.getCallingUserId()); + } + } + } + + @Override + public boolean getBluetoothContactSharingDisabled(ComponentName who) { + if (!mHasFeature) { + return false; + } + Preconditions.checkNotNull(who, "ComponentName is null"); + synchronized (this) { + ActiveAdmin admin = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + return admin.disableBluetoothContactSharing; + } + } + + @Override + public boolean getBluetoothContactSharingDisabledForUser(int userId) { + // TODO: Should there be a check to make sure this relationship is + // within a profile group? + // enforceSystemProcess("getCrossProfileCallerIdDisabled can only be called by system"); + synchronized (this) { + ActiveAdmin admin = getProfileOwnerAdmin(userId); + return (admin != null) ? admin.disableBluetoothContactSharing : false; + } + } + /** * Sets which packages may enter lock task mode. * * This function can only be called by the device owner. - * @param components The list of components allowed to enter lock task mode. + * @param packages The list of packages allowed to enter lock task mode. */ - public void setLockTaskPackages(ComponentName who, String[] packages) throws SecurityException { + public void setLockTaskPackages(ComponentName who, String[] packages) + throws SecurityException { + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); int userHandle = Binder.getCallingUserHandle().getIdentifier(); - DevicePolicyData policy = getUserData(userHandle); - policy.mLockTaskPackages.clear(); - if (packages != null) { - for (int j = 0; j < packages.length; j++) { - String pkg = packages[j]; - policy.mLockTaskPackages.add(pkg); - } - } - - // Store the settings persistently. - saveSettingsLocked(userHandle); + setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages))); } } + private void setLockTaskPackagesLocked(int userHandle, List<String> packages) { + DevicePolicyData policy = getUserData(userHandle); + policy.mLockTaskPackages = packages; + + // Store the settings persistently. + saveSettingsLocked(userHandle); + updateLockTaskPackagesLocked(packages, userHandle); + } + /** * This function returns the list of components allowed to start the task lock mode. */ public String[] getLockTaskPackages(ComponentName who) { + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); - int userHandle = Binder.getCallingUserHandle().getIdentifier(); - DevicePolicyData policy = getUserData(userHandle); - return policy.mLockTaskPackages.toArray(new String[0]); + final List<String> packages = getLockTaskPackagesLocked(userHandle); + return packages.toArray(new String[packages.size()]); } } + private List<String> getLockTaskPackagesLocked(int userHandle) { + final DevicePolicyData policy = getUserData(userHandle); + return policy.mLockTaskPackages; + } + /** * This function lets the caller know whether the given package is allowed to start the * lock task mode. @@ -5323,16 +5794,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setGlobalSetting(ComponentName who, String setting, String value) { final ContentResolver contentResolver = mContext.getContentResolver(); + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); if (!GLOBAL_SETTINGS_WHITELIST.contains(setting)) { - throw new SecurityException(String.format( - "Permission denial: device owners cannot update %1$s", setting)); + // BLUETOOTH_ON and WIFI_ON used to be supported but not any more. We do not want to + // throw a SecurityException not to break apps. + if (!Settings.Global.BLUETOOTH_ON.equals(setting) + && !Settings.Global.WIFI_ON.equals(setting)) { + throw new SecurityException(String.format( + "Permission denial: device owners cannot update %1$s", setting)); + } + } + + if (Settings.Global.STAY_ON_WHILE_PLUGGED_IN.equals(setting)) { + // ignore if it contradicts an existing policy + long timeMs = getMaximumTimeToLock(who, UserHandle.getCallingUserId()); + if (timeMs > 0 && timeMs < Integer.MAX_VALUE) { + return; + } } long id = Binder.clearCallingIdentity(); @@ -5346,13 +5828,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setSecureSetting(ComponentName who, String setting, String value) { + Preconditions.checkNotNull(who, "ComponentName is null"); int callingUserId = UserHandle.getCallingUserId(); final ContentResolver contentResolver = mContext.getContentResolver(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); @@ -5377,18 +5857,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setMasterVolumeMuted(ComponentName who, boolean on) { - final ContentResolver contentResolver = mContext.getContentResolver(); - + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); IAudioService iAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); - try{ - iAudioService.setMasterMute(on, 0, who.getPackageName(), null); + try { + iAudioService.setMasterMute(on, 0, who.getPackageName()); } catch (RemoteException re) { Slog.e(LOG_TAG, "Failed to setMasterMute", re); } @@ -5397,12 +5873,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isMasterVolumeMuted(ComponentName who) { - final ContentResolver contentResolver = mContext.getContentResolver(); - + Preconditions.checkNotNull(who, "ComponentName is null"); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); - } getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); AudioManager audioManager = @@ -5411,6 +5883,108 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public void setUserIcon(ComponentName who, Bitmap icon) { + synchronized (this) { + Preconditions.checkNotNull(who, "ComponentName is null"); + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + + int userId = UserHandle.getCallingUserId(); + long id = Binder.clearCallingIdentity(); + try { + mUserManager.setUserIcon(userId, icon); + } finally { + restoreCallingIdentity(id); + } + } + } + + @Override + public void sendDeviceInitializerStatus(int statusCode, String description) { + synchronized (this) { + String packageName = getDeviceInitializer(); + if (packageName == null) { + throw new SecurityException("No device initializers"); + } + UserHandle callingUser = Binder.getCallingUserHandle(); + int deviceInitializerUid = -1; + try { + deviceInitializerUid = mContext.getPackageManager().getPackageUid( + packageName, callingUser.getIdentifier()); + } catch (NameNotFoundException e) { + throw new SecurityException(e); + } + if (Binder.getCallingUid() != deviceInitializerUid) { + throw new SecurityException("Caller must be a device initializer"); + } + long id = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent( + DevicePolicyManager.ACTION_SEND_DEVICE_INITIALIZER_STATUS); + intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_INITIALIZER_STATUS_CODE, + statusCode); + intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_INITIALIZER_STATUS_DESCRIPTION, + description); + mContext.sendBroadcastAsUser(intent, callingUser, + android.Manifest.permission.RECEIVE_DEVICE_INITIALIZER_STATUS); + } finally { + restoreCallingIdentity(id); + } + } + } + + @Override + public boolean setKeyguardEnabledState(ComponentName who, boolean enabled) { + Preconditions.checkNotNull(who, "ComponentName is null"); + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + } + final int userId = UserHandle.getCallingUserId(); + LockPatternUtils utils = new LockPatternUtils(mContext); + + long ident = Binder.clearCallingIdentity(); + try { + // disallow disabling the keyguard if a password is currently set + if (!enabled && utils.isSecure(userId)) { + return false; + } + utils.setLockScreenDisabled(!enabled, userId); + } finally { + Binder.restoreCallingIdentity(ident); + } + return true; + } + + @Override + public void setStatusBarEnabledState(ComponentName who, boolean enabled) { + int userId = UserHandle.getCallingUserId(); + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + DevicePolicyData policy = getUserData(userId); + if (policy.mStatusBarEnabledState != enabled) { + policy.mStatusBarEnabledState = enabled; + setStatusBarEnabledStateInternal(enabled, userId); + saveSettingsLocked(userId); + } + } + } + + private void setStatusBarEnabledStateInternal(boolean enabled, int userId) { + long ident = Binder.clearCallingIdentity(); + try { + IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.checkService(Context.STATUS_BAR_SERVICE)); + if (statusBarService != null) { + int flags = enabled ? StatusBarManager.DISABLE_NONE : STATUS_BAR_DISABLE_MASK; + statusBarService.disableForUser(flags, mToken, mContext.getPackageName(), userId); + } + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Failed to disable the status bar", e); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * We need to update the internal state of whether a user has completed setup once. After * that, we ignore any changes that reset the Settings.Secure.USER_SETUP_COMPLETE changes @@ -5431,6 +6005,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!policy.mUserSetupComplete) { policy.mUserSetupComplete = true; synchronized (this) { + // The DeviceInitializer was whitelisted but now should be removed. + removeDeviceInitializerFromLockTaskPackages(userHandle); saveSettingsLocked(userHandle); } } @@ -5438,6 +6014,35 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void addDeviceInitializerToLockTaskPackagesLocked(int userHandle) { + if (hasUserSetupCompleted(userHandle)) { + return; + } + + final String deviceInitializerPackage = getDeviceInitializer(); + if (deviceInitializerPackage == null) { + return; + } + + final List<String> packages = getLockTaskPackagesLocked(userHandle); + if (!packages.contains(deviceInitializerPackage)) { + packages.add(deviceInitializerPackage); + setLockTaskPackagesLocked(userHandle, packages); + } + } + + private void removeDeviceInitializerFromLockTaskPackages(int userHandle) { + final String deviceInitializerPackage = getDeviceInitializer(); + if (deviceInitializerPackage == null) { + return; + } + + List<String> packages = getLockTaskPackagesLocked(userHandle); + if (packages.remove(deviceInitializerPackage)) { + setLockTaskPackagesLocked(userHandle, packages); + } + } + private class SetupContentObserver extends ContentObserver { private final Uri mUserSetupComplete = Settings.Secure.getUriFor( @@ -5498,6 +6103,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public boolean isActiveAdminWithPolicy(int uid, int reqPolicy) { + final int userId = UserHandle.getUserId(uid); + synchronized(DevicePolicyManagerService.this) { + return getActiveAdminWithPolicyForUidLocked(null, reqPolicy, uid) != null; + } + } + private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) { final List<OnCrossProfileWidgetProvidersChangeListener> listeners; synchronized (DevicePolicyManagerService.this) { @@ -5510,4 +6123,92 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + + /** + * Returns true if specified admin is allowed to limit passwords and has a + * {@code passwordQuality} of at least {@code minPasswordQuality} + */ + private static boolean isLimitPasswordAllowed(ActiveAdmin admin, int minPasswordQuality) { + if (admin.passwordQuality < minPasswordQuality) { + return false; + } + return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + } + + @Override + public void setSystemUpdatePolicy(ComponentName who, PersistableBundle policy) { + synchronized (this) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + if (policy == null) { + mDeviceOwner.clearSystemUpdatePolicy(); + } else { + mDeviceOwner.setSystemUpdatePolicy(policy); + } + mDeviceOwner.writeOwnerFile(); + } + mContext.sendBroadcastAsUser( + new Intent(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED), + UserHandle.OWNER); + } + + @Override + public PersistableBundle getSystemUpdatePolicy() { + synchronized (this) { + return mDeviceOwner.getSystemUpdatePolicy(); + } + } + + /** + * Checks if the caller of the method is the device owner app or device initialization app. + * + * @param callerUid UID of the caller. + * @return true if the caller is the device owner app or device initializer. + */ + private boolean isCallerDeviceOwnerOrInitializer(int callerUid) { + String[] pkgs = mContext.getPackageManager().getPackagesForUid(callerUid); + for (String pkg : pkgs) { + if (isDeviceOwner(pkg) || isDeviceInitializer(pkg)) { + return true; + } + } + return false; + } + + @Override + public void notifyPendingSystemUpdate(long updateReceivedTime) { + mContext.enforceCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE, + "Only the system update service can broadcast update information"); + + if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) { + Slog.w(LOG_TAG, "Only the system update service in the primary user" + + "can broadcast update information."); + return; + } + Intent intent = new Intent(DeviceAdminReceiver.ACTION_NOTIFY_PENDING_SYSTEM_UPDATE); + intent.putExtra(DeviceAdminReceiver.EXTRA_SYSTEM_UPDATE_RECEIVED_TIME, + updateReceivedTime); + + synchronized (this) { + String deviceOwnerPackage = getDeviceOwner(); + if (deviceOwnerPackage == null) { + return; + } + + try { + ActivityInfo[] receivers = mContext.getPackageManager().getPackageInfo( + deviceOwnerPackage, PackageManager.GET_RECEIVERS).receivers; + if (receivers != null) { + for (int i = 0; i < receivers.length; i++) { + if (permission.BIND_DEVICE_ADMIN.equals(receivers[i].permission)) { + intent.setComponent(new ComponentName(deviceOwnerPackage, + receivers[i].name)); + mContext.sendBroadcastAsUser(intent, UserHandle.OWNER); + } + } + } + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Cannot find device owner package", e); + } + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0705fbd..593853c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -26,16 +26,11 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.media.AudioService; -import android.media.tv.TvInputManager; import android.os.Build; import android.os.Environment; import android.os.FactoryTest; -import android.os.Handler; -import android.os.IBinder; import android.os.IPowerManager; import android.os.Looper; import android.os.RemoteException; @@ -44,22 +39,21 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; -import android.service.dreams.DreamService; +import android.os.storage.IMountService; import android.util.DisplayMetrics; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import android.view.WindowManager; import android.webkit.WebViewFactory; import com.android.internal.R; import com.android.internal.os.BinderInternal; -import com.android.internal.os.Zygote; import com.android.internal.os.SamplingProfilerIntegration; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accounts.AccountManagerService; import com.android.server.am.ActivityManagerService; -import com.android.server.am.BatteryStatsService; +import com.android.server.audio.AudioService; +import com.android.server.camera.CameraService; import com.android.server.clipboard.ClipboardService; import com.android.server.content.ContentService; import com.android.server.devicepolicy.DevicePolicyManagerService; @@ -69,7 +63,6 @@ import com.android.server.fingerprint.FingerprintService; import com.android.server.hdmi.HdmiControlService; import com.android.server.input.InputManagerService; import com.android.server.job.JobSchedulerService; -import com.android.server.lights.LightsManager; import com.android.server.lights.LightsService; import com.android.server.media.MediaRouterService; import com.android.server.media.MediaSessionService; @@ -83,6 +76,7 @@ import com.android.server.pm.Installer; import com.android.server.pm.LauncherAppsService; import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerService; +import com.android.server.power.DeviceIdleController; import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; import com.android.server.restrictions.RestrictionsManagerService; @@ -131,6 +125,8 @@ public final class SystemServer { "com.android.server.print.PrintManagerService"; private static final String USB_SERVICE_CLASS = "com.android.server.usb.UsbService$Lifecycle"; + private static final String MIDI_SERVICE_CLASS = + "com.android.server.midi.MidiService$Lifecycle"; private static final String WIFI_SERVICE_CLASS = "com.android.server.wifi.WifiService"; private static final String WIFI_P2P_SERVICE_CLASS = @@ -139,6 +135,8 @@ public final class SystemServer { "com.android.server.ethernet.EthernetService"; private static final String JOB_SCHEDULER_SERVICE_CLASS = "com.android.server.job.JobSchedulerService"; + private static final String MOUNT_SERVICE_CLASS = + "com.android.server.MountService$Lifecycle"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; private final int mFactoryTestMode; @@ -327,6 +325,9 @@ public final class SystemServer { // initialize power management features. mActivityManagerService.initPowerManagement(); + // Manages LEDs and display backlight so we need it to bring up the display. + mSystemServiceManager.startService(LightsService.class); + // Display manager is needed to provide display metrics before package manager // starts up. mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class); @@ -365,9 +366,6 @@ public final class SystemServer { * Starts some essential services that are not tangled up in the bootstrap process. */ private void startCoreServices() { - // Manages LEDs and display backlight. - mSystemServiceManager.startService(LightsService.class); - // Tracks the battery level. Requires LightService. mSystemServiceManager.startService(BatteryService.class); @@ -392,7 +390,7 @@ public final class SystemServer { ContentService contentService = null; VibratorService vibrator = null; IAlarmManager alarm = null; - MountService mountService = null; + IMountService mountService = null; NetworkManagementService networkManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; @@ -410,11 +408,11 @@ public final class SystemServer { ConsumerIrService consumerIr = null; AudioService audioService = null; MmsServiceBroker mmsService = null; + EntropyMixer entropyMixer = null; + CameraService cameraService = null; 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); @@ -436,10 +434,13 @@ public final class SystemServer { ServiceManager.addService("telephony.registry", telephonyRegistry); Slog.i(TAG, "Entropy Mixer"); - ServiceManager.addService("entropy", new EntropyMixer(context)); + entropyMixer = new EntropyMixer(context); mContentResolver = context.getContentResolver(); + Slog.i(TAG, "Camera Service"); + mSystemServiceManager.startService(CameraService.class); + // The AccountManager must come before the ContentService try { // TODO: seems like this should be disable-able, but req'd by ContentService @@ -526,23 +527,20 @@ public final class SystemServer { // Bring up services needed for UI. if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) { - //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, "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); } } @@ -560,15 +558,19 @@ public final class SystemServer { * NotificationManagerService is dependant on MountService, * (for media / usb notifications) so we must start MountService first. */ - Slog.i(TAG, "Mount Service"); - mountService = new MountService(context); - ServiceManager.addService("mount", mountService); + mSystemServiceManager.startService(MOUNT_SERVICE_CLASS); + mountService = IMountService.Stub.asInterface( + ServiceManager.getService("mount")); } catch (Throwable e) { reportWtf("starting Mount Service", e); } } } + // We start this here so that we update our configuration to set watch or television + // as appropriate. + mSystemServiceManager.startService(UiModeManagerService.class); + try { mPackageManagerService.performBootDexOpt(); } catch (Throwable e) { @@ -719,7 +721,10 @@ public final class SystemServer { * first before continuing. */ if (mountService != null && !mOnlyCore) { - mountService.waitForAsecScan(); + try { + mountService.waitForAsecScan(); + } catch (RemoteException ignored) { + } } try { @@ -790,32 +795,33 @@ public final class SystemServer { } } - if (!disableMedia && !"0".equals(SystemProperties.get("system_init.startaudioservice"))) { - try { - Slog.i(TAG, "Audio Service"); - audioService = new AudioService(context); - ServiceManager.addService(Context.AUDIO_SERVICE, audioService); - } catch (Throwable e) { - reportWtf("starting Audio Service", e); - } + try { + Slog.i(TAG, "Audio Service"); + audioService = new AudioService(context); + ServiceManager.addService(Context.AUDIO_SERVICE, audioService); + } catch (Throwable e) { + reportWtf("starting Audio Service", e); } if (!disableNonCoreServices) { mSystemServiceManager.startService(DockObserver.class); } - 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, "Wired Accessory Manager"); + // Listen for wired headset changes + inputManager.setWiredAccessoryCallbacks( + new WiredAccessoryManager(context, inputManager)); + } catch (Throwable e) { + reportWtf("starting WiredAccessoryManager", e); } if (!disableNonCoreServices) { + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) { + // Start MIDI Manager service + mSystemServiceManager.startService(MIDI_SERVICE_CLASS); + } + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST) || mPackageManager.hasSystemFeature( PackageManager.FEATURE_USB_ACCESSORY)) { @@ -835,8 +841,6 @@ public final class SystemServer { mSystemServiceManager.startService(TwilightService.class); - mSystemServiceManager.startService(UiModeManagerService.class); - mSystemServiceManager.startService(JobSchedulerService.class); if (!disableNonCoreServices) { @@ -881,14 +885,12 @@ public final class SystemServer { } } - 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, "CommonTimeManagementService"); + commonTimeMgmtService = new CommonTimeManagementService(context); + ServiceManager.addService("commontime_management", commonTimeMgmtService); + } catch (Throwable e) { + reportWtf("starting CommonTimeManagementService service", e); } if (!disableNetwork) { @@ -915,6 +917,11 @@ public final class SystemServer { } } + if (!disableNonCoreServices) { + ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE, + new GraphicsStatsService(context)); + } + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) { mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS); } @@ -958,6 +965,7 @@ public final class SystemServer { if (!disableNonCoreServices) { mSystemServiceManager.startService(MediaProjectionManagerService.class); + mSystemServiceManager.startService(DeviceIdleController.class); } // Before things start rolling, be sure we have decided whether @@ -1036,7 +1044,6 @@ public final class SystemServer { } // These are needed to propagate to the runnable below. - final MountService mountServiceF = mountService; final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; final NetworkPolicyManagerService networkPolicyF = networkPolicy; @@ -1084,11 +1091,6 @@ public final class SystemServer { reportWtf("starting System UI", e); } try { - if (mountServiceF != null) mountServiceF.systemReady(); - } catch (Throwable e) { - reportWtf("making Mount Service ready", e); - } - try { if (networkScoreF != null) networkScoreF.systemReady(); } catch (Throwable e) { reportWtf("making Network Score Service ready", e); diff --git a/services/midi/Android.mk b/services/midi/Android.mk new file mode 100644 index 0000000..faac01c --- /dev/null +++ b/services/midi/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := services.midi + +LOCAL_SRC_FILES += \ + $(call all-java-files-under,java) + +LOCAL_JAVA_LIBRARIES := services.core + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java new file mode 100644 index 0000000..c1c5c56 --- /dev/null +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2014 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 an + * limitations under the License. + */ + +package com.android.server.midi; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.XmlResourceParser; +import android.media.midi.IMidiDeviceListener; +import android.media.midi.IMidiDeviceServer; +import android.media.midi.IMidiManager; +import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceService; +import android.media.midi.MidiDeviceStatus; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.content.PackageMonitor; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.XmlUtils; +import com.android.server.SystemService; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class MidiService extends IMidiManager.Stub { + + public static class Lifecycle extends SystemService { + private MidiService mMidiService; + + public Lifecycle(Context context) { + super(context); + } + + @Override + public void onStart() { + mMidiService = new MidiService(getContext()); + publishBinderService(Context.MIDI_SERVICE, mMidiService); + } + } + + private static final String TAG = "MidiService"; + + private final Context mContext; + + // list of all our clients, keyed by Binder token + private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>(); + + // list of all devices, keyed by MidiDeviceInfo + private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo + = new HashMap<MidiDeviceInfo, Device>(); + + // list of all devices, keyed by IMidiDeviceServer + private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>(); + + // used for assigning IDs to MIDI devices + private int mNextDeviceId = 1; + + private final PackageManager mPackageManager; + + // PackageMonitor for listening to package changes + private final PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public void onPackageAdded(String packageName, int uid) { + addPackageDeviceServers(packageName); + } + + @Override + public void onPackageModified(String packageName) { + removePackageDeviceServers(packageName); + addPackageDeviceServers(packageName); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + removePackageDeviceServers(packageName); + } + }; + + private final class Client implements IBinder.DeathRecipient { + // Binder token for this client + private final IBinder mToken; + // This client's UID + private final int mUid; + // This client's PID + private final int mPid; + // List of all receivers for this client + private final ArrayList<IMidiDeviceListener> mListeners + = new ArrayList<IMidiDeviceListener>(); + + public Client(IBinder token) { + mToken = token; + mUid = Binder.getCallingUid(); + mPid = Binder.getCallingPid(); + } + + public int getUid() { + return mUid; + } + + public void addListener(IMidiDeviceListener listener) { + mListeners.add(listener); + } + + public void removeListener(IMidiDeviceListener listener) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + removeClient(mToken); + } + } + + public void deviceAdded(Device device) { + // ignore private devices that our client cannot access + if (!device.isUidAllowed(mUid)) return; + + MidiDeviceInfo deviceInfo = device.getDeviceInfo(); + try { + for (IMidiDeviceListener listener : mListeners) { + listener.onDeviceAdded(deviceInfo); + } + } catch (RemoteException e) { + Log.e(TAG, "remote exception", e); + } + } + + public void deviceRemoved(Device device) { + // ignore private devices that our client cannot access + if (!device.isUidAllowed(mUid)) return; + + MidiDeviceInfo deviceInfo = device.getDeviceInfo(); + try { + for (IMidiDeviceListener listener : mListeners) { + listener.onDeviceRemoved(deviceInfo); + } + } catch (RemoteException e) { + Log.e(TAG, "remote exception", e); + } + } + + public void deviceStatusChanged(Device device, MidiDeviceStatus status) { + // ignore private devices that our client cannot access + if (!device.isUidAllowed(mUid)) return; + + try { + for (IMidiDeviceListener listener : mListeners) { + listener.onDeviceStatusChanged(status); + } + } catch (RemoteException e) { + Log.e(TAG, "remote exception", e); + } + } + + public void binderDied() { + removeClient(mToken); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Client: UID: "); + sb.append(mUid); + sb.append(" PID: "); + sb.append(mPid); + sb.append(" listener count: "); + sb.append(mListeners.size()); + return sb.toString(); + } + } + + private Client getClient(IBinder token) { + synchronized (mClients) { + Client client = mClients.get(token); + if (client == null) { + client = new Client(token); + + try { + token.linkToDeath(client, 0); + } catch (RemoteException e) { + return null; + } + mClients.put(token, client); + } + return client; + } + } + + private void removeClient(IBinder token) { + mClients.remove(token); + } + + private final class Device implements IBinder.DeathRecipient { + private final IMidiDeviceServer mServer; + private final MidiDeviceInfo mDeviceInfo; + private MidiDeviceStatus mDeviceStatus; + private IBinder mDeviceStatusToken; + // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only) + private final ServiceInfo mServiceInfo; + // UID of device implementation + private final int mUid; + + public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo, + ServiceInfo serviceInfo, int uid) { + mServer = server; + mDeviceInfo = deviceInfo; + mServiceInfo = serviceInfo; + mUid = uid; + } + + public MidiDeviceInfo getDeviceInfo() { + return mDeviceInfo; + } + + public MidiDeviceStatus getDeviceStatus() { + return mDeviceStatus; + } + + public void setDeviceStatus(IBinder token, MidiDeviceStatus status) { + mDeviceStatus = status; + + if (mDeviceStatusToken == null && token != null) { + // register a death recipient so we can clear the status when the device dies + try { + token.linkToDeath(new IBinder.DeathRecipient() { + @Override + public void binderDied() { + // reset to default status and clear the token + mDeviceStatus = new MidiDeviceStatus(mDeviceInfo); + mDeviceStatusToken = null; + notifyDeviceStatusChanged(Device.this, mDeviceStatus); + } + }, 0); + mDeviceStatusToken = token; + } catch (RemoteException e) { + // reset to default status + mDeviceStatus = new MidiDeviceStatus(mDeviceInfo); + } + } + } + + public IMidiDeviceServer getDeviceServer() { + return mServer; + } + + public ServiceInfo getServiceInfo() { + return mServiceInfo; + } + + public String getPackageName() { + return (mServiceInfo == null ? null : mServiceInfo.packageName); + } + + public int getUid() { + return mUid; + } + + public boolean isUidAllowed(int uid) { + return (!mDeviceInfo.isPrivate() || mUid == uid); + } + + public void binderDied() { + synchronized (mDevicesByInfo) { + if (mDevicesByInfo.remove(mDeviceInfo) != null) { + removeDeviceLocked(this); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Device: "); + sb.append(mDeviceInfo); + sb.append(" UID: "); + sb.append(mUid); + return sb.toString(); + } + } + + public MidiService(Context context) { + mContext = context; + mPackageManager = context.getPackageManager(); + mPackageMonitor.register(context, null, true); + + Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE); + List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent, + PackageManager.GET_META_DATA); + if (resolveInfos != null) { + int count = resolveInfos.size(); + for (int i = 0; i < count; i++) { + ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo; + if (serviceInfo != null) { + addPackageDeviceServer(serviceInfo); + } + } + } + } + + @Override + public void registerListener(IBinder token, IMidiDeviceListener listener) { + Client client = getClient(token); + if (client == null) return; + client.addListener(listener); + } + + @Override + public void unregisterListener(IBinder token, IMidiDeviceListener listener) { + Client client = getClient(token); + if (client == null) return; + client.removeListener(listener); + } + + private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0]; + + public MidiDeviceInfo[] getDeviceList() { + ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>(); + int uid = Binder.getCallingUid(); + + synchronized (mDevicesByInfo) { + for (Device device : mDevicesByInfo.values()) { + if (device.isUidAllowed(uid)) { + deviceInfos.add(device.getDeviceInfo()); + } + } + } + + return deviceInfos.toArray(EMPTY_DEVICE_INFO_ARRAY); + } + + @Override + public IMidiDeviceServer openDevice(IBinder token, MidiDeviceInfo deviceInfo) { + Device device = mDevicesByInfo.get(deviceInfo); + if (device == null) { + Log.e(TAG, "device not found in openDevice: " + deviceInfo); + return null; + } + + if (!device.isUidAllowed(Binder.getCallingUid())) { + throw new SecurityException("Attempt to open private device with wrong UID"); + } + + return device.getDeviceServer(); + } + + @Override + public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts, + int numOutputPorts, String[] inputPortNames, String[] outputPortNames, + Bundle properties, int type) { + int uid = Binder.getCallingUid(); + if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) { + throw new SecurityException("only system can create USB devices"); + } + + synchronized (mDevicesByInfo) { + return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames, + outputPortNames, properties, server, null, false, uid); + } + } + + @Override + public void unregisterDeviceServer(IMidiDeviceServer server) { + synchronized (mDevicesByInfo) { + Device device = mDevicesByServer.get(server.asBinder()); + if (device != null) { + mDevicesByInfo.remove(device.getDeviceInfo()); + removeDeviceLocked(device); + } + } + } + + @Override + public MidiDeviceInfo getServiceDeviceInfo(String packageName, String className) { + synchronized (mDevicesByInfo) { + for (Device device : mDevicesByInfo.values()) { + ServiceInfo serviceInfo = device.getServiceInfo(); + if (serviceInfo != null && + packageName.equals(serviceInfo.packageName) && + className.equals(serviceInfo.name)) { + return device.getDeviceInfo(); + } + } + return null; + } + } + + @Override + public MidiDeviceStatus getDeviceStatus(MidiDeviceInfo deviceInfo) { + Device device = mDevicesByInfo.get(deviceInfo); + if (device == null) { + throw new IllegalArgumentException("no such device for " + deviceInfo); + } + return device.getDeviceStatus(); + } + + @Override + public void setDeviceStatus(IBinder token, MidiDeviceStatus status) { + MidiDeviceInfo deviceInfo = status.getDeviceInfo(); + Device device = mDevicesByInfo.get(deviceInfo); + if (device == null) { + // Just return quietly here if device no longer exists + return; + } + if (Binder.getCallingUid() != device.getUid()) { + throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid() + + " does not match device's UID " + device.getUid()); + } + device.setDeviceStatus(token, status); + notifyDeviceStatusChanged(device, status); + } + + private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) { + synchronized (mClients) { + for (Client c : mClients.values()) { + c.deviceStatusChanged(device, status); + } + } + } + + // synchronize on mDevicesByInfo + private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts, + String[] inputPortNames, String[] outputPortNames, Bundle properties, + IMidiDeviceServer server, ServiceInfo serviceInfo, + boolean isPrivate, int uid) { + + int id = mNextDeviceId++; + MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, + inputPortNames, outputPortNames, properties, isPrivate); + Device device = new Device(server, deviceInfo, serviceInfo, uid); + + if (server != null) { + IBinder binder = server.asBinder(); + try { + binder.linkToDeath(device, 0); + } catch (RemoteException e) { + return null; + } + mDevicesByServer.put(binder, device); + } + mDevicesByInfo.put(deviceInfo, device); + + synchronized (mClients) { + for (Client c : mClients.values()) { + c.deviceAdded(device); + } + } + + return deviceInfo; + } + + // synchronize on mDevicesByInfo + private void removeDeviceLocked(Device device) { + IMidiDeviceServer server = device.getDeviceServer(); + if (server != null) { + mDevicesByServer.remove(server); + } + + synchronized (mClients) { + for (Client c : mClients.values()) { + c.deviceRemoved(device); + } + } + } + + private void addPackageDeviceServers(String packageName) { + PackageInfo info; + + try { + info = mPackageManager.getPackageInfo(packageName, + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e); + return; + } + + ServiceInfo[] services = info.services; + if (services == null) return; + for (int i = 0; i < services.length; i++) { + addPackageDeviceServer(services[i]); + } + } + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + private void addPackageDeviceServer(ServiceInfo serviceInfo) { + XmlResourceParser parser = null; + + try { + parser = serviceInfo.loadXmlMetaData(mPackageManager, + MidiDeviceService.SERVICE_INTERFACE); + if (parser == null) return; + + Bundle properties = null; + int numInputPorts = 0; + int numOutputPorts = 0; + boolean isPrivate = false; + ArrayList<String> inputPortNames = new ArrayList<String>(); + ArrayList<String> outputPortNames = new ArrayList<String>(); + + while (true) { + int eventType = parser.next(); + if (eventType == XmlPullParser.END_DOCUMENT) { + break; + } else if (eventType == XmlPullParser.START_TAG) { + String tagName = parser.getName(); + if ("device".equals(tagName)) { + if (properties != null) { + Log.w(TAG, "nested <device> elements in metadata for " + + serviceInfo.packageName); + continue; + } + properties = new Bundle(); + properties.putParcelable(MidiDeviceInfo.PROPERTY_SERVICE_INFO, serviceInfo); + numInputPorts = 0; + numOutputPorts = 0; + isPrivate = false; + + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + String name = parser.getAttributeName(i); + String value = parser.getAttributeValue(i); + if ("private".equals(name)) { + isPrivate = "true".equals(value); + } else { + properties.putString(name, value); + } + } + } else if ("input-port".equals(tagName)) { + if (properties == null) { + Log.w(TAG, "<input-port> outside of <device> in metadata for " + + serviceInfo.packageName); + continue; + } + numInputPorts++; + + String portName = null; + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + String name = parser.getAttributeName(i); + String value = parser.getAttributeValue(i); + if ("name".equals(name)) { + portName = value; + break; + } + } + inputPortNames.add(portName); + } else if ("output-port".equals(tagName)) { + if (properties == null) { + Log.w(TAG, "<output-port> outside of <device> in metadata for " + + serviceInfo.packageName); + continue; + } + numOutputPorts++; + + String portName = null; + int count = parser.getAttributeCount(); + for (int i = 0; i < count; i++) { + String name = parser.getAttributeName(i); + String value = parser.getAttributeValue(i); + if ("name".equals(name)) { + portName = value; + break; + } + } + outputPortNames.add(portName); + } + } else if (eventType == XmlPullParser.END_TAG) { + String tagName = parser.getName(); + if ("device".equals(tagName)) { + if (properties != null) { + if (numInputPorts == 0 && numOutputPorts == 0) { + Log.w(TAG, "<device> with no ports in metadata for " + + serviceInfo.packageName); + continue; + } + + int uid; + try { + ApplicationInfo appInfo = mPackageManager.getApplicationInfo( + serviceInfo.packageName, 0); + uid = appInfo.uid; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "could not fetch ApplicationInfo for " + + serviceInfo.packageName); + continue; + } + + synchronized (mDevicesByInfo) { + addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL, + numInputPorts, numOutputPorts, + inputPortNames.toArray(EMPTY_STRING_ARRAY), + outputPortNames.toArray(EMPTY_STRING_ARRAY), + properties, null, serviceInfo, isPrivate, uid); + } + // setting properties to null signals that we are no longer + // processing a <device> + properties = null; + inputPortNames.clear(); + outputPortNames.clear(); + } + } + } + } + } catch (Exception e) { + Log.w(TAG, "Unable to load component info " + serviceInfo.toString(), e); + } finally { + if (parser != null) parser.close(); + } + } + + private void removePackageDeviceServers(String packageName) { + synchronized (mDevicesByInfo) { + Iterator<Device> iterator = mDevicesByInfo.values().iterator(); + while (iterator.hasNext()) { + Device device = iterator.next(); + if (packageName.equals(device.getPackageName())) { + iterator.remove(); + removeDeviceLocked(device); + } + } + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + + pw.println("MIDI Manager State:"); + pw.increaseIndent(); + + pw.println("Devices:"); + pw.increaseIndent(); + synchronized (mDevicesByInfo) { + for (Device device : mDevicesByInfo.values()) { + pw.println(device.toString()); + } + } + pw.decreaseIndent(); + + pw.println("Clients:"); + pw.increaseIndent(); + synchronized (mClients) { + for (Client client : mClients.values()) { + pw.println(client.toString()); + } + } + pw.decreaseIndent(); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index e9203a4..ab56493 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -92,6 +92,7 @@ public class DhcpClient extends BaseDhcpStateMachine { private static final boolean DBG = true; private static final boolean STATE_DBG = false; private static final boolean MSG_DBG = false; + private static final boolean PACKET_DBG = true; // Timers and timeouts. private static final int SECONDS = 1000; @@ -329,6 +330,9 @@ public class DhcpClient extends BaseDhcpStateMachine { if (packet != null) { maybeLog("Received packet: " + packet); sendMessage(CMD_RECEIVED_PACKET, packet); + } else if (PACKET_DBG) { + Log.d(TAG, + "Can't parse packet" + HexDump.dumpHexString(mPacket, 0, length)); } } catch (IOException|ErrnoException e) { if (!stopped) { diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/services/net/java/android/net/dhcp/DhcpDeclinePacket.java index 9d985ac..4a22b65 100644 --- a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java +++ b/services/net/java/android/net/dhcp/DhcpDeclinePacket.java @@ -53,6 +53,9 @@ class DhcpDeclinePacket extends DhcpPacket { * Adds optional parameters to the DECLINE packet. */ void finishPacket(ByteBuffer buffer) { - // None needed + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DECLINE); + addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId()); + // RFC 2131 says we MUST NOT include our common client TLVs or the parameter request list. + addTlvEnd(buffer); } } diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java index a031080..ed0fdc6 100644 --- a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java +++ b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java @@ -52,6 +52,7 @@ class DhcpDiscoverPacket extends DhcpPacket { */ void finishPacket(ByteBuffer buffer) { addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DISCOVER); + addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId()); addCommonClientTlvs(buffer); addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); addTlvEnd(buffer); diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/services/net/java/android/net/dhcp/DhcpInformPacket.java index 8bc7cdd..2434fc9 100644 --- a/services/net/java/android/net/dhcp/DhcpInformPacket.java +++ b/services/net/java/android/net/dhcp/DhcpInformPacket.java @@ -53,12 +53,9 @@ class DhcpInformPacket extends DhcpPacket { * Adds additional parameters to the INFORM packet. */ void finishPacket(ByteBuffer buffer) { - byte[] clientId = new byte[7]; - - clientId[0] = CLIENT_ID_ETHER; - System.arraycopy(mClientMac, 0, clientId, 1, 6); - - addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST); + addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_INFORM); + addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId()); + addCommonClientTlvs(buffer); addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); addTlvEnd(buffer); } diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index d41629d..a64ee6f 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -285,6 +285,16 @@ abstract class DhcpPacket { } /** + * Returns the client ID. This follows RFC 2132 and is based on the hardware address. + */ + public byte[] getClientId() { + byte[] clientId = new byte[mClientMac.length + 1]; + clientId[0] = CLIENT_ID_ETHER; + System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length); + return clientId; + } + + /** * Creates a new L3 packet (including IP header) containing the * DHCP udp packet. This method relies upon the delegated method * finishPacket() to insert the per-packet contents. diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/services/net/java/android/net/dhcp/DhcpRequestPacket.java index 42b7b0c..5d378b8 100644 --- a/services/net/java/android/net/dhcp/DhcpRequestPacket.java +++ b/services/net/java/android/net/dhcp/DhcpRequestPacket.java @@ -56,20 +56,14 @@ class DhcpRequestPacket extends DhcpPacket { * Adds the optional parameters to the client-generated REQUEST packet. */ void finishPacket(ByteBuffer buffer) { - byte[] clientId = new byte[7]; - - // assemble client identifier - clientId[0] = CLIENT_ID_ETHER; - System.arraycopy(mClientMac, 0, clientId, 1, 6); - addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST); + addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId()); if (!INADDR_ANY.equals(mRequestedIp)) { addTlv(buffer, DHCP_REQUESTED_IP, mRequestedIp); } if (!INADDR_ANY.equals(mServerIdentifier)) { addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier); } - addTlv(buffer, DHCP_CLIENT_IDENTIFIER, clientId); addCommonClientTlvs(buffer); addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams); addTlvEnd(buffer); diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index 6785cb8..34347cf 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -761,7 +761,7 @@ public final class PrintManagerService extends SystemService { .setWhen(System.currentTimeMillis()) .setAutoCancel(true) .setShowWhen(true) - .setColor(mContext.getResources().getColor( + .setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)); NotificationManager notificationManager = (NotificationManager) mContext diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java index 33edb11..ae19dac 100644 --- a/services/print/java/com/android/server/print/UserState.java +++ b/services/print/java/com/android/server/print/UserState.java @@ -204,8 +204,8 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { IntentSender intentSender = PendingIntent.getActivityAsUser( mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(mUserId)) - .getIntentSender(); + | PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, + null, new UserHandle(mUserId)) .getIntentSender(); Bundle result = new Bundle(); result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob); diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java index 218f899..946d28e 100644 --- a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java +++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java @@ -16,35 +16,20 @@ package com.android.server.restrictions; -import android.Manifest; -import android.accounts.IAccountAuthenticator; import android.app.AppGlobals; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.admin.IDevicePolicyManager; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.IRestrictionsManager; import android.content.RestrictionsManager; -import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; -import android.database.ContentObserver; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; -import android.os.IBinder; import android.os.IUserManager; import android.os.PersistableBundle; -import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; -import android.os.UserManager; import android.util.Log; import com.android.internal.util.ArrayUtils; diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index c198900..48d8ffb 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -16,7 +16,7 @@ package com.android.server; -import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; +import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; @@ -157,7 +157,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { // mMobile.link.addRoute(MOBILE_ROUTE_V6); // mMobile.doReturnDefaults(); // -// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE); +// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION); // mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); // nextConnBroadcast.get(); // @@ -177,7 +177,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { // mMobile.link.addRoute(MOBILE_ROUTE_V6); // mMobile.doReturnDefaults(); // -// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE); +// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION); // mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); // nextConnBroadcast.get(); // @@ -193,7 +193,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { // // expect that mobile will be torn down // doReturn(true).when(mMobile.tracker).teardown(); // -// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE); +// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION); // mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget(); // nextConnBroadcast.get(); // @@ -212,7 +212,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { // mMobile.link.clear(); // mMobile.doReturnDefaults(); // -// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE); +// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION); // mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); // nextConnBroadcast.get(); // diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java index bf0e75d..dae8447 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java @@ -219,31 +219,31 @@ public class LockSettingsStorageTests extends AndroidTestCase { public void testPassword_Write() { mStorage.writePasswordHash("thepassword".getBytes(), 0); - assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0)); + assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0).hash); mStorage.clearCache(); - assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0)); + assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0).hash); } public void testPassword_WriteProfileWritesParent() { mStorage.writePasswordHash("parentpasswordd".getBytes(), 1); mStorage.writePasswordHash("profilepassword".getBytes(), 2); - assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1)); - assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2)); + assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1).hash); + assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash); mStorage.clearCache(); - assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1)); - assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2)); + assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1).hash); + assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2).hash); } public void testPassword_WriteParentWritesProfile() { mStorage.writePasswordHash("profilepassword".getBytes(), 2); mStorage.writePasswordHash("parentpasswordd".getBytes(), 1); - assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1)); - assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2)); + assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1).hash); + assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2).hash); mStorage.clearCache(); - assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1)); - assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2)); + assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1).hash); + assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2).hash); } public void testPattern_Default() { @@ -253,31 +253,31 @@ public class LockSettingsStorageTests extends AndroidTestCase { public void testPattern_Write() { mStorage.writePatternHash("thepattern".getBytes(), 0); - assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0)); + assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0).hash); mStorage.clearCache(); - assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0)); + assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0).hash); } public void testPattern_WriteProfileWritesParent() { mStorage.writePatternHash("parentpatternn".getBytes(), 1); mStorage.writePatternHash("profilepattern".getBytes(), 2); - assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1)); - assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2)); + assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1).hash); + assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2).hash); mStorage.clearCache(); - assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1)); - assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2)); + assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1).hash); + assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2).hash); } public void testPattern_WriteParentWritesProfile() { mStorage.writePatternHash("profilepattern".getBytes(), 2); mStorage.writePatternHash("parentpatternn".getBytes(), 1); - assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1)); - assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2)); + assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1).hash); + assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2).hash); mStorage.clearCache(); - assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1)); - assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2)); + assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1).hash); + assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2).hash); } public void testPrefetch() { @@ -289,8 +289,8 @@ public class LockSettingsStorageTests extends AndroidTestCase { mStorage.prefetchUser(0); assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0)); - assertArrayEquals("pattern".getBytes(), mStorage.readPatternHash(0)); - assertArrayEquals("password".getBytes(), mStorage.readPasswordHash(0)); + assertArrayEquals("pattern".getBytes(), mStorage.readPatternHash(0).hash); + assertArrayEquals("password".getBytes(), mStorage.readPasswordHash(0).hash); } public void testFileLocation_Owner() { diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 0b4d42e..72a458b 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -18,7 +18,7 @@ package com.android.server; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.EXTRA_UID; -import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; +import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.WARNING_DISABLED; @@ -597,7 +597,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { future = expectMeteredIfacesChanged(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); future.get(); verifyAndReset(); @@ -708,7 +708,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { future = expectMeteredIfacesChanged(TEST_IFACE); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); future.get(); verifyAndReset(); } diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java index 7383478..90b4f43 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java @@ -18,7 +18,7 @@ package com.android.server; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.EXTRA_UID; -import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; +import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIMAX; @@ -39,6 +39,7 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS; import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL; +import static org.easymock.EasyMock.anyInt; import static org.easymock.EasyMock.anyLong; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; @@ -191,7 +192,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); // verify service has empty history for wifi assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); @@ -245,7 +246,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); // verify service has empty history for wifi assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); @@ -336,7 +337,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); verifyAndReset(); // modify some number on wifi, and trigger poll event @@ -388,7 +389,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); verifyAndReset(); // create some traffic on first network @@ -430,7 +431,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL)); verifyAndReset(); @@ -476,7 +477,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); verifyAndReset(); // create some traffic @@ -545,7 +546,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); verifyAndReset(); // create some traffic @@ -579,7 +580,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL)); verifyAndReset(); @@ -616,7 +617,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); verifyAndReset(); // create some traffic for two apps @@ -682,7 +683,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); verifyAndReset(); // create some initial traffic @@ -747,7 +748,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); verifyAndReset(); // create some tethering traffic @@ -787,7 +788,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectNetworkStatsPoll(); replay(); - mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE)); + mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); verifyAndReset(); // create some traffic, but only for DEV, and across 1.5 buckets @@ -879,7 +880,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectLastCall().anyTimes(); mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(), - isA(PendingIntent.class), isA(WorkSource.class), + anyInt(), isA(PendingIntent.class), isA(WorkSource.class), isA(AlarmManager.AlarmClockInfo.class)); expectLastCall().atLeastOnce(); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java index 8e8e4e6..ca270e7 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/ApplicationRestrictionsTest.java @@ -65,7 +65,7 @@ public class ApplicationRestrictionsTest extends AndroidTestCase { sDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0); - sDpm.setProfileOwner(context.getPackageName(), "Test", UserHandle.myUserId()); + sDpm.setProfileOwner(sAdminReceiver, "Test", UserHandle.myUserId()); Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1); // Remove the admin if already registered. It's async, so add it back @@ -132,4 +132,4 @@ public class ApplicationRestrictionsTest extends AndroidTestCase { Bundle returned = sDpm.getApplicationRestrictions(sAdminReceiver, RESTRICTED_APP); assertEquals(returned.getString("KEY_FANCY_TEXT"), fancyText); } -}
\ No newline at end of file +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java index f913b97..7c3014c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DeviceOwnerTest.java @@ -16,6 +16,7 @@ package com.android.server.devicepolicy; +import android.content.ComponentName; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; @@ -32,7 +33,7 @@ import java.io.ByteArrayOutputStream; public class DeviceOwnerTest extends AndroidTestCase { private ByteArrayInputStream mInputStreamForTest; - private ByteArrayOutputStream mOutputStreamForTest = new ByteArrayOutputStream(); + private final ByteArrayOutputStream mOutputStreamForTest = new ByteArrayOutputStream(); @SmallTest public void testDeviceOwnerOnly() throws Exception { @@ -46,13 +47,15 @@ public class DeviceOwnerTest extends AndroidTestCase { assertEquals("some.device.owner.package", in.getDeviceOwnerPackageName()); assertEquals("owner", in.getDeviceOwnerName()); - assertNull(in.getProfileOwnerPackageName(1)); + assertNull(in.getProfileOwnerComponent(1)); } @SmallTest public void testProfileOwnerOnly() throws Exception { DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest); - out.setProfileOwner("some.profile.owner.package", "some-company", 1); + ComponentName admin = new ComponentName( + "some.profile.owner.package", "some.profile.owner.package.Class"); + out.setProfileOwner(admin, "some-company", 1); out.writeOwnerFile(); mInputStreamForTest = new ByteArrayInputStream(mOutputStreamForTest.toByteArray()); @@ -61,16 +64,24 @@ public class DeviceOwnerTest extends AndroidTestCase { assertNull(in.getDeviceOwnerPackageName()); assertNull(in.getDeviceOwnerName()); - assertEquals("some.profile.owner.package", in.getProfileOwnerPackageName(1)); + assertEquals(admin, in.getProfileOwnerComponent(1)); assertEquals("some-company", in.getProfileOwnerName(1)); } @SmallTest public void testDeviceAndProfileOwners() throws Exception { DeviceOwner out = new DeviceOwner(null, mOutputStreamForTest); + ComponentName profileAdmin = new ComponentName( + "some.profile.owner.package", "some.profile.owner.package.Class"); + ComponentName otherProfileAdmin = new ComponentName( + "some.other.profile.owner", "some.other.profile.owner.OtherClass"); + // Old code used package name rather than component name, so the class + // bit could be empty. + ComponentName legacyComponentName = new ComponentName("legacy.profile.owner.package", ""); out.setDeviceOwner("some.device.owner.package", "owner"); - out.setProfileOwner("some.profile.owner.package", "some-company", 1); - out.setProfileOwner("some.other.profile.owner", "some-other-company", 2); + out.setProfileOwner(profileAdmin, "some-company", 1); + out.setProfileOwner(otherProfileAdmin, "some-other-company", 2); + out.setProfileOwner(legacyComponentName, "legacy-company", 3); out.writeOwnerFile(); mInputStreamForTest = new ByteArrayInputStream(mOutputStreamForTest.toByteArray()); @@ -80,9 +91,10 @@ public class DeviceOwnerTest extends AndroidTestCase { assertEquals("some.device.owner.package", in.getDeviceOwnerPackageName()); assertEquals("owner", in.getDeviceOwnerName()); - assertEquals("some.profile.owner.package", in.getProfileOwnerPackageName(1)); + assertEquals(profileAdmin, in.getProfileOwnerComponent(1)); assertEquals("some-company", in.getProfileOwnerName(1)); - assertEquals("some.other.profile.owner", in.getProfileOwnerPackageName(2)); + assertEquals(otherProfileAdmin, in.getProfileOwnerComponent(2)); assertEquals("some-other-company", in.getProfileOwnerName(2)); + assertEquals(legacyComponentName, in.getProfileOwnerComponent(3)); } -}
\ No newline at end of file +} diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index b631331..a3f3a5d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -32,7 +32,6 @@ import java.io.FileOutputStream; import java.io.IOException; public class PackageManagerSettingsTests extends AndroidTestCase { - private static final String PACKAGE_NAME_2 = "com.google.app2"; private static final String PACKAGE_NAME_3 = "com.android.app3"; private static final String PACKAGE_NAME_1 = "com.google.app1"; @@ -56,7 +55,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase { writeFile(new File(getContext().getFilesDir(), "system/packages.xml"), ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" + "<packages>" - + "<last-platform-version internal=\"15\" external=\"0\" />" + + "<last-platform-version internal=\"15\" external=\"0\" fingerprint=\"foo\" />" + "<permission-trees>" + "<item name=\"com.google.android.permtree\" package=\"com.google.android.permpackage\" />" + "</permission-trees>" @@ -110,28 +109,32 @@ public class PackageManagerSettingsTests extends AndroidTestCase { .getBytes()); } - @Override - protected void setUp() throws Exception { - super.setUp(); + private void deleteSystemFolder() { + File systemFolder = new File(getContext().getFilesDir(), "system"); + deleteFolder(systemFolder); + } + + private static void deleteFolder(File folder) { + File[] files = folder.listFiles(); + if (files != null) { + for (File file : files) { + deleteFolder(file); + } + } + folder.delete(); } private void writeOldFiles() { + deleteSystemFolder(); writePackagesXml(); writeStoppedPackagesXml(); writePackagesList(); } - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - public void testSettingsReadOld() { - // Debug.waitForDebugger(); - // Write the package files and make sure they're parsed properly the first time writeOldFiles(); - Settings settings = new Settings(getContext(), getContext().getFilesDir()); + Settings settings = new Settings(getContext().getFilesDir(), new Object()); assertEquals(true, settings.readLPw(null, null, 0, false)); assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_3)); assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_1)); @@ -149,11 +152,12 @@ public class PackageManagerSettingsTests extends AndroidTestCase { public void testNewPackageRestrictionsFile() { // Write the package files and make sure they're parsed properly the first time writeOldFiles(); - Settings settings = new Settings(getContext(), getContext().getFilesDir()); + Settings settings = new Settings(getContext().getFilesDir(), new Object()); assertEquals(true, settings.readLPw(null, null, 0, false)); + settings.writeLPr(); // Create Settings again to make it read from the new files - settings = new Settings(getContext(), getContext().getFilesDir()); + settings = new Settings(getContext().getFilesDir(), new Object()); assertEquals(true, settings.readLPw(null, null, 0, false)); PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_2); @@ -164,7 +168,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase { public void testEnableDisable() { // Write the package files and make sure they're parsed properly the first time writeOldFiles(); - Settings settings = new Settings(getContext(), getContext().getFilesDir()); + Settings settings = new Settings(getContext().getFilesDir(), new Object()); assertEquals(true, settings.readLPw(null, null, 0, false)); // Enable/Disable a package diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java new file mode 100644 index 0000000..eb7eb15 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 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.os.Bundle; +import android.os.FileUtils; +import android.os.Parcelable; +import android.test.AndroidTestCase; +import android.util.AtomicFile; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +public class UserManagerServiceTest extends AndroidTestCase { + private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["}; + private File restrictionsFile; + + @Override + protected void setUp() throws Exception { + super.setUp(); + restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml"); + restrictionsFile.delete(); + } + + @Override + protected void tearDown() throws Exception { + restrictionsFile.delete(); + super.tearDown(); + } + + public void testWriteReadApplicationRestrictions() throws IOException { + AtomicFile atomicFile = new AtomicFile(restrictionsFile); + Bundle bundle = createBundle(); + UserManagerService.writeApplicationRestrictionsLocked(bundle, atomicFile); + assertTrue(atomicFile.getBaseFile().exists()); + String s = FileUtils.readTextFile(restrictionsFile, 10000, ""); + System.out.println("restrictionsFile: " + s); + bundle = UserManagerService.readApplicationRestrictionsLocked(atomicFile); + System.out.println("readApplicationRestrictionsLocked bundle: " + bundle); + assertBundle(bundle); + } + + private Bundle createBundle() { + Bundle result = new Bundle(); + // Tests for 6 allowed types: Integer, Boolean, String, String[], Bundle and Parcelable[] + result.putBoolean("boolean_0", false); + result.putBoolean("boolean_1", true); + result.putInt("integer", 100); + result.putString("empty", ""); + result.putString("string", "text"); + result.putStringArray("string[]", STRING_ARRAY); + + Bundle bundle = new Bundle(); + bundle.putString("bundle_string", "bundle_string"); + bundle.putInt("bundle_int", 1); + result.putBundle("bundle", bundle); + + Bundle[] bundleArray = new Bundle[2]; + bundleArray[0] = new Bundle(); + bundleArray[0].putString("bundle_array_string", "bundle_array_string"); + bundleArray[0].putBundle("bundle_array_bundle", bundle); + bundleArray[1] = new Bundle(); + bundleArray[1].putString("bundle_array_string2", "bundle_array_string2"); + result.putParcelableArray("bundle_array", bundleArray); + return result; + } + + private void assertBundle(Bundle bundle) { + assertFalse(bundle.getBoolean("boolean_0")); + assertTrue(bundle.getBoolean("boolean_1")); + assertEquals(100, bundle.getInt("integer")); + assertEquals("", bundle.getString("empty")); + assertEquals("text", bundle.getString("string")); + assertEquals(Arrays.asList(STRING_ARRAY), Arrays.asList(bundle.getStringArray("string[]"))); + Parcelable[] bundle_array = bundle.getParcelableArray("bundle_array"); + assertEquals(2, bundle_array.length); + Bundle bundle1 = (Bundle) bundle_array[0]; + assertEquals("bundle_array_string", bundle1.getString("bundle_array_string")); + assertNotNull(bundle1.getBundle("bundle_array_bundle")); + Bundle bundle2 = (Bundle) bundle_array[1]; + assertEquals("bundle_array_string2", bundle2.getString("bundle_array_string2")); + Bundle childBundle = bundle.getBundle("bundle"); + assertEquals("bundle_string", childBundle.getString("bundle_string")); + assertEquals(1, childBundle.getInt("bundle_int")); + } + +} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index a0b8c94..2d47c24 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -32,8 +32,9 @@ import java.util.List; /** Test {@link UserManager} functionality. */ public class UserManagerTest extends AndroidTestCase { - UserManager mUserManager = null; - Object mUserLock = new Object(); + private UserManager mUserManager = null; + private final Object mUserLock = new Object(); + private List<Integer> usersToRemove; @Override public void setUp() throws Exception { @@ -49,11 +50,19 @@ public class UserManagerTest extends AndroidTestCase { }, filter); removeExistingUsers(); + usersToRemove = new ArrayList<>(); + } + + @Override + protected void tearDown() throws Exception { + for (Integer userId : usersToRemove) { + removeUser(userId); + } + super.tearDown(); } private void removeExistingUsers() { List<UserInfo> list = mUserManager.getUsers(); - boolean found = false; for (UserInfo user : list) { if (user.id != UserHandle.USER_OWNER) { removeUser(user.id); @@ -66,7 +75,7 @@ public class UserManagerTest extends AndroidTestCase { } public void testAddUser() throws Exception { - UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST); + UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); assertTrue(userInfo != null); List<UserInfo> list = mUserManager.getUsers(); @@ -83,12 +92,11 @@ public class UserManagerTest extends AndroidTestCase { } } assertTrue(found); - removeUser(userInfo.id); } public void testAdd2Users() throws Exception { - UserInfo user1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST); - UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_ADMIN); + UserInfo user1 = createUser("Guest 1", UserInfo.FLAG_GUEST); + UserInfo user2 = createUser("User 2", UserInfo.FLAG_ADMIN); assertTrue(user1 != null); assertTrue(user2 != null); @@ -96,41 +104,67 @@ public class UserManagerTest extends AndroidTestCase { assertTrue(findUser(0)); assertTrue(findUser(user1.id)); assertTrue(findUser(user2.id)); - removeUser(user1.id); - removeUser(user2.id); } public void testRemoveUser() throws Exception { - UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST); + UserInfo userInfo = createUser("Guest 1", UserInfo.FLAG_GUEST); removeUser(userInfo.id); assertFalse(findUser(userInfo.id)); } public void testAddGuest() throws Exception { - UserInfo userInfo1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST); - UserInfo userInfo2 = mUserManager.createUser("Guest 2", UserInfo.FLAG_GUEST); + UserInfo userInfo1 = createUser("Guest 1", UserInfo.FLAG_GUEST); + UserInfo userInfo2 = createUser("Guest 2", UserInfo.FLAG_GUEST); assertNotNull(userInfo1); assertNull(userInfo2); - - // Cleanup - removeUser(userInfo1.id); } // Make sure only one managed profile can be created public void testAddManagedProfile() throws Exception { - UserInfo userInfo1 = mUserManager.createProfileForUser("Managed 1", + UserInfo userInfo1 = createProfileForUser("Managed 1", UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER); - UserInfo userInfo2 = mUserManager.createProfileForUser("Managed 2", + UserInfo userInfo2 = createProfileForUser("Managed 2", UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER); assertNotNull(userInfo1); assertNull(userInfo2); // Verify that current user is not a managed profile assertFalse(mUserManager.isManagedProfile()); - // Cleanup - removeUser(userInfo1.id); } + public void testGetUserCreationTime() throws Exception { + UserInfo profile = createProfileForUser("Managed 1", + UserInfo.FLAG_MANAGED_PROFILE, UserHandle.USER_OWNER); + assertNotNull(profile); + assertTrue("creationTime must be set when the profile is created", + profile.creationTime > 0); + assertEquals(profile.creationTime, mUserManager.getUserCreationTime( + new UserHandle(profile.id))); + + long ownerCreationTime = mUserManager.getUserInfo(UserHandle.USER_OWNER).creationTime; + assertEquals(ownerCreationTime, mUserManager.getUserCreationTime( + new UserHandle(UserHandle.USER_OWNER))); + + try { + int noSuchUserId = 100500; + mUserManager.getUserCreationTime(new UserHandle(noSuchUserId)); + fail("SecurityException should be thrown for nonexistent user"); + } catch (Exception e) { + assertTrue("SecurityException should be thrown for nonexistent user, but was: " + e, + e instanceof SecurityException); + } + + UserInfo user = createUser("User 1", 0); + try { + mUserManager.getUserCreationTime(new UserHandle(user.id)); + fail("SecurityException should be thrown for other user"); + } catch (Exception e) { + assertTrue("SecurityException should be thrown for other user, but was: " + e, + e instanceof SecurityException); + } + } + + private boolean findUser(int id) { List<UserInfo> list = mUserManager.getUsers(); @@ -143,40 +177,29 @@ public class UserManagerTest extends AndroidTestCase { } public void testSerialNumber() { - UserInfo user1 = mUserManager.createUser("User 1", UserInfo.FLAG_RESTRICTED); + UserInfo user1 = createUser("User 1", UserInfo.FLAG_RESTRICTED); int serialNumber1 = user1.serialNumber; assertEquals(serialNumber1, mUserManager.getUserSerialNumber(user1.id)); assertEquals(user1.id, mUserManager.getUserHandle(serialNumber1)); - removeUser(user1.id); - UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_RESTRICTED); + UserInfo user2 = createUser("User 2", UserInfo.FLAG_RESTRICTED); int serialNumber2 = user2.serialNumber; assertFalse(serialNumber1 == serialNumber2); assertEquals(serialNumber2, mUserManager.getUserSerialNumber(user2.id)); assertEquals(user2.id, mUserManager.getUserHandle(serialNumber2)); - removeUser(user2.id); } public void testMaxUsers() { int N = UserManager.getMaxSupportedUsers(); int count = mUserManager.getUsers().size(); - List<UserInfo> created = new ArrayList<UserInfo>(); // Create as many users as permitted and make sure creation passes while (count < N) { - UserInfo ui = mUserManager.createUser("User " + count, 0); + UserInfo ui = createUser("User " + count, 0); assertNotNull(ui); - created.add(ui); count++; } // Try to create one more user and make sure it fails - UserInfo extra = null; - assertNull(extra = mUserManager.createUser("One more", 0)); - if (extra != null) { - removeUser(extra.id); - } - while (!created.isEmpty()) { - UserInfo user = created.remove(0); - removeUser(user.id); - } + UserInfo extra = createUser("One more", 0); + assertNull(extra); } public void testRestrictions() { @@ -198,11 +221,27 @@ public class UserManagerTest extends AndroidTestCase { mUserManager.removeUser(userId); while (mUserManager.getUserInfo(userId) != null) { try { - mUserLock.wait(1000); + mUserLock.wait(500); } catch (InterruptedException ie) { } } } } + private UserInfo createUser(String name, int flags) { + UserInfo user = mUserManager.createUser(name, flags); + if (user != null) { + usersToRemove.add(user.id); + } + return user; + } + + private UserInfo createProfileForUser(String name, int flags, int userHandle) { + UserInfo profile = mUserManager.createProfileForUser(name, flags, userHandle); + if (profile != null) { + usersToRemove.add(profile.id); + } + return profile; + } + } diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java index 5f639ab..869d6e1 100644 --- a/services/usage/java/com/android/server/usage/IntervalStats.java +++ b/services/usage/java/com/android/server/usage/IntervalStats.java @@ -81,6 +81,17 @@ class IntervalStats { return event; } + private boolean isStatefulEvent(int eventType) { + switch (eventType) { + case UsageEvents.Event.MOVE_TO_FOREGROUND: + case UsageEvents.Event.MOVE_TO_BACKGROUND: + case UsageEvents.Event.END_OF_DAY: + case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: + return true; + } + return false; + } + void update(String packageName, long timeStamp, int eventType) { UsageStats usageStats = getOrCreateUsageStats(packageName); @@ -93,7 +104,11 @@ class IntervalStats { usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed; } } - usageStats.mLastEvent = eventType; + + if (isStatefulEvent(eventType)) { + usageStats.mLastEvent = eventType; + } + usageStats.mLastTimeUsed = timeStamp; usageStats.mEndTimeStamp = timeStamp; diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index 26ced03..4498b84 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -18,6 +18,7 @@ package com.android.server.usage; import android.app.usage.TimeSparseArray; import android.app.usage.UsageStatsManager; +import android.os.Build; import android.util.AtomicFile; import android.util.Slog; @@ -35,7 +36,7 @@ import java.util.List; * Provides an interface to query for UsageStat data from an XML database. */ class UsageStatsDatabase { - private static final int CURRENT_VERSION = 2; + private static final int CURRENT_VERSION = 3; private static final String TAG = "UsageStatsDatabase"; private static final boolean DEBUG = UsageStatsService.DEBUG; @@ -47,6 +48,8 @@ class UsageStatsDatabase { private final TimeSparseArray<AtomicFile>[] mSortedStatFiles; private final UnixCalendar mCal; private final File mVersionFile; + private boolean mFirstUpdate; + private boolean mNewUpdate; public UsageStatsDatabase(File dir) { mIntervalDirs = new File[] { @@ -73,7 +76,7 @@ class UsageStatsDatabase { } } - checkVersionLocked(); + checkVersionAndBuildLocked(); indexFilesLocked(); // Delete files that are in the future. @@ -194,10 +197,35 @@ class UsageStatsDatabase { } } - private void checkVersionLocked() { + /** + * Is this the first update to the system from L to M? + */ + boolean isFirstUpdate() { + return mFirstUpdate; + } + + /** + * Is this a system update since we started tracking build fingerprint in the version file? + */ + boolean isNewUpdate() { + return mNewUpdate; + } + + private void checkVersionAndBuildLocked() { int version; + String buildFingerprint; + String currentFingerprint = getBuildFingerprint(); + mFirstUpdate = true; + mNewUpdate = true; try (BufferedReader reader = new BufferedReader(new FileReader(mVersionFile))) { version = Integer.parseInt(reader.readLine()); + buildFingerprint = reader.readLine(); + if (buildFingerprint != null) { + mFirstUpdate = false; + } + if (currentFingerprint.equals(buildFingerprint)) { + mNewUpdate = false; + } } catch (NumberFormatException | IOException e) { version = 0; } @@ -205,9 +233,15 @@ class UsageStatsDatabase { if (version != CURRENT_VERSION) { Slog.i(TAG, "Upgrading from version " + version + " to " + CURRENT_VERSION); doUpgradeLocked(version); + } + if (version != CURRENT_VERSION || mNewUpdate) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(mVersionFile))) { writer.write(Integer.toString(CURRENT_VERSION)); + writer.write("\n"); + writer.write(currentFingerprint); + writer.write("\n"); + writer.flush(); } catch (IOException e) { Slog.e(TAG, "Failed to write new version"); throw new RuntimeException(e); @@ -215,6 +249,12 @@ class UsageStatsDatabase { } } + private String getBuildFingerprint() { + return Build.VERSION.RELEASE + ";" + + Build.VERSION.CODENAME + ";" + + Build.VERSION.INCREMENTAL; + } + private void doUpgradeLocked(int thisVersion) { if (thisVersion < 2) { // Delete all files if we are version 0. This is a pre-release version, @@ -378,26 +418,27 @@ class UsageStatsDatabase { } } - try { - IntervalStats stats = new IntervalStats(); - ArrayList<T> results = new ArrayList<>(); - for (int i = startIndex; i <= endIndex; i++) { - final AtomicFile f = intervalStats.valueAt(i); + final IntervalStats stats = new IntervalStats(); + final ArrayList<T> results = new ArrayList<>(); + for (int i = startIndex; i <= endIndex; i++) { + final AtomicFile f = intervalStats.valueAt(i); - if (DEBUG) { - Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath()); - } + if (DEBUG) { + Slog.d(TAG, "Reading stat file " + f.getBaseFile().getAbsolutePath()); + } + try { UsageStatsXml.read(f, stats); if (beginTime < stats.endTime) { combiner.combine(stats, false, results); } + } catch (IOException e) { + Slog.e(TAG, "Failed to read usage stats file", e); + // We continue so that we return results that are not + // corrupt. } - return results; - } catch (IOException e) { - Slog.e(TAG, "Failed to read usage stats file", e); - return null; } + return results; } } @@ -450,6 +491,10 @@ class UsageStatsDatabase { mCal.addDays(-7); pruneFilesOlderThan(mIntervalDirs[UsageStatsManager.INTERVAL_DAILY], mCal.getTimeInMillis()); + + // We must re-index our file list or we will be trying to read + // deleted files. + indexFilesLocked(); } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 485b2a2..3d54dfb 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -21,8 +21,10 @@ import android.app.AppOpsManager; import android.app.usage.ConfigurationStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManagerInternal; +import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -32,6 +34,8 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.res.Configuration; +import android.database.ContentObserver; +import android.net.Uri; import android.os.Binder; import android.os.Environment; import android.os.Handler; @@ -42,17 +46,20 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.SystemConfig; import com.android.server.SystemService; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -74,6 +81,7 @@ public class UsageStatsService extends SystemService implements static final int MSG_REPORT_EVENT = 0; static final int MSG_FLUSH_TO_DISK = 1; static final int MSG_REMOVE_USER = 2; + static final int MSG_INFORM_LISTENERS = 3; private final Object mLock = new Object(); Handler mHandler; @@ -85,6 +93,12 @@ public class UsageStatsService extends SystemService implements long mRealTimeSnapshot; long mSystemTimeSnapshot; + private static final long DEFAULT_APP_IDLE_THRESHOLD_MILLIS = 1L * 24 * 60 * 60 * 1000; // 1 day + private long mAppIdleDurationMillis; + + private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener> + mPackageAccessListeners = new ArrayList<>(); + public UsageStatsService(Context context) { super(context); } @@ -112,11 +126,24 @@ public class UsageStatsService extends SystemService implements mRealTimeSnapshot = SystemClock.elapsedRealtime(); mSystemTimeSnapshot = System.currentTimeMillis(); + // Look at primary user's secure setting for this. TODO: Maybe apply different + // thresholds for different users. + mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(), + Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS, + UserHandle.USER_OWNER); publishLocalService(UsageStatsManagerInternal.class, new LocalService()); publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService()); } + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + // Observe changes to the threshold + new SettingsObserver(mHandler).registerObserver(); + } + } + private class UserRemovedReceiver extends BroadcastReceiver { @Override @@ -235,7 +262,19 @@ public class UsageStatsService extends SystemService implements final UserUsageStatsService service = getUserDataAndInitializeIfNeededLocked(userId, timeNow); + final long lastUsed = service.getLastPackageAccessTime(event.mPackage); + final boolean previouslyIdle = hasPassedIdleDuration(lastUsed); service.reportEvent(event); + // Inform listeners if necessary + if ((event.mEventType == Event.MOVE_TO_FOREGROUND + || event.mEventType == Event.MOVE_TO_BACKGROUND + || event.mEventType == Event.INTERACTION)) { + if (previouslyIdle) { + // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage); + mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId, + /* idle = */ 0, event.mPackage)); + } + } } } @@ -308,6 +347,56 @@ public class UsageStatsService extends SystemService implements } } + /** + * Called by LocalService stub. + */ + long getLastPackageAccessTime(String packageName, int userId) { + synchronized (mLock) { + final long timeNow = checkAndGetTimeLocked(); + // android package is always considered non-idle. + // TODO: Add a generic whitelisting mechanism + if (packageName.equals("android")) { + return timeNow; + } + final UserUsageStatsService service = + getUserDataAndInitializeIfNeededLocked(userId, timeNow); + return service.getLastPackageAccessTime(packageName); + } + } + + void addListener(AppIdleStateChangeListener listener) { + synchronized (mLock) { + if (!mPackageAccessListeners.contains(listener)) { + mPackageAccessListeners.add(listener); + } + } + } + + void removeListener(AppIdleStateChangeListener listener) { + synchronized (mLock) { + mPackageAccessListeners.remove(listener); + } + } + + private boolean hasPassedIdleDuration(long lastUsed) { + final long now = System.currentTimeMillis(); + return lastUsed < now - mAppIdleDurationMillis; + } + + boolean isAppIdle(String packageName, int userId) { + if (SystemConfig.getInstance().getAllowInPowerSave().contains(packageName)) { + return false; + } + final long lastUsed = getLastPackageAccessTime(packageName, userId); + return hasPassedIdleDuration(lastUsed); + } + + void informListeners(String packageName, int userId, boolean isIdle) { + for (AppIdleStateChangeListener listener : mPackageAccessListeners) { + listener.onAppIdleStateChanged(packageName, userId, isIdle); + } + } + private static boolean validRange(long currentTime, long beginTime, long endTime) { return beginTime <= currentTime && beginTime < endTime; } @@ -366,6 +455,10 @@ public class UsageStatsService extends SystemService implements removeUser(msg.arg1); break; + case MSG_INFORM_LISTENERS: + informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1); + break; + default: super.handleMessage(msg); break; @@ -373,6 +466,29 @@ public class UsageStatsService extends SystemService implements } } + /** + * Observe settings changes for Settings.Secure.APP_IDLE_DURATION. + */ + private class SettingsObserver extends ContentObserver { + + SettingsObserver(Handler handler) { + super(handler); + } + + void registerObserver() { + getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.APP_IDLE_DURATION), false, this, UserHandle.USER_OWNER); + } + + @Override + public void onChange(boolean selfChange, Uri uri, int userId) { + mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(), + Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS, + UserHandle.USER_OWNER); + // TODO: Check if we need to update idle states of all the apps + } + } + private class BinderService extends IUsageStatsManager.Stub { private boolean hasPermission(String callingPackage) { @@ -488,6 +604,23 @@ public class UsageStatsService extends SystemService implements } @Override + public void reportEvent(String packageName, int userId, int eventType) { + if (packageName == null) { + Slog.w(TAG, "Event reported without a package name"); + return; + } + + UsageEvents.Event event = new UsageEvents.Event(); + event.mPackage = packageName; + + // This will later be converted to system time. + event.mTimeStamp = SystemClock.elapsedRealtime(); + + event.mEventType = eventType; + mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget(); + } + + @Override public void reportConfigurationChange(Configuration config, int userId) { if (config == null) { Slog.w(TAG, "Configuration event reported with a null config"); @@ -506,11 +639,32 @@ public class UsageStatsService extends SystemService implements } @Override + public boolean isAppIdle(String packageName, int userId) { + return UsageStatsService.this.isAppIdle(packageName, userId); + } + + @Override + public long getLastPackageAccessTime(String packageName, int userId) { + return UsageStatsService.this.getLastPackageAccessTime(packageName, userId); + } + + @Override public void prepareShutdown() { // This method *WILL* do IO work, but we must block until it is finished or else // we might not shutdown cleanly. This is ok to do with the 'am' lock held, because // we are shutting down. shutdown(); } + + @Override + public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) { + UsageStatsService.this.addListener(listener); + } + + @Override + public void removeAppIdleStateChangeListener( + AppIdleStateChangeListener listener) { + UsageStatsService.this.removeListener(listener); + } } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java index ef95a7b..bfb71c5 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java @@ -191,7 +191,7 @@ final class UsageStatsXmlV1 { statsOut.events.clear(); } - statsOut.endTime = XmlUtils.readLongAttribute(parser, END_TIME_ATTR); + statsOut.endTime = statsOut.beginTime + XmlUtils.readLongAttribute(parser, END_TIME_ATTR); int eventCode; int outerDepth = parser.getDepth(); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 6596781..0a9481a 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -19,8 +19,11 @@ package com.android.server.usage; import android.app.usage.ConfigurationStats; import android.app.usage.TimeSparseArray; import android.app.usage.UsageEvents; +import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.SystemClock; import android.content.Context; @@ -60,18 +63,21 @@ class UserUsageStatsService { private final UnixCalendar mDailyExpiryDate; private final StatsUpdatedListener mListener; private final String mLogPrefix; + private final int mUserId; interface StatsUpdatedListener { void onStatsUpdated(); } - UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener) { + UserUsageStatsService(Context context, int userId, File usageStatsDir, + StatsUpdatedListener listener) { mContext = context; mDailyExpiryDate = new UnixCalendar(0); mDatabase = new UsageStatsDatabase(usageStatsDir); mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT]; mListener = listener; mLogPrefix = "User[" + Integer.toString(userId) + "] "; + mUserId = userId; } void init(final long currentTimeMillis) { @@ -127,6 +133,35 @@ class UserUsageStatsService { stat.updateConfigurationStats(null, stat.lastTimeSaved); } + + if (mDatabase.isNewUpdate()) { + initializeDefaultsForApps(currentTimeMillis, mDatabase.isFirstUpdate()); + } + } + + /** + * If any of the apps don't have a last-used entry, add one now. + * @param currentTimeMillis the current time + * @param firstUpdate if it is the first update, touch all installed apps, otherwise only + * touch the system apps + */ + private void initializeDefaultsForApps(long currentTimeMillis, boolean firstUpdate) { + PackageManager pm = mContext.getPackageManager(); + List<PackageInfo> packages = pm.getInstalledPackages(0, mUserId); + final int packageCount = packages.size(); + for (int i = 0; i < packageCount; i++) { + final PackageInfo pi = packages.get(i); + String packageName = pi.packageName; + if (pi.applicationInfo != null && (firstUpdate || pi.applicationInfo.isSystemApp()) + && getLastPackageAccessTime(packageName) == -1) { + for (IntervalStats stats : mCurrentStats) { + stats.update(packageName, currentTimeMillis, Event.INTERACTION); + mStatsChanged = true; + } + } + } + // Persist the new OTA-related access stats. + persistActiveStats(); } void onTimeChanged(long oldTime, long newTime) { @@ -161,7 +196,9 @@ class UserUsageStatsService { if (currentDailyStats.events == null) { currentDailyStats.events = new TimeSparseArray<>(); } - currentDailyStats.events.put(event.mTimeStamp, event); + if (event.mEventType != UsageEvents.Event.INTERACTION) { + currentDailyStats.events.put(event.mTimeStamp, event); + } for (IntervalStats stats : mCurrentStats) { if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { @@ -328,6 +365,16 @@ class UserUsageStatsService { return new UsageEvents(results, table); } + long getLastPackageAccessTime(String packageName) { + final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY]; + UsageStats packageUsage; + if ((packageUsage = yearly.packageStats.get(packageName)) == null) { + return -1; + } else { + return packageUsage.getLastTimeUsed(); + } + } + void persistActiveStats() { if (mStatsChanged) { Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); @@ -570,6 +617,8 @@ class UserUsageStatsService { return "CONTINUE_PREVIOUS_DAY"; case UsageEvents.Event.CONFIGURATION_CHANGE: return "CONFIGURATION_CHANGE"; + case UsageEvents.Event.INTERACTION: + return "INTERACTION"; default: return "UNKNOWN"; } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java new file mode 100644 index 0000000..8f0c6c8 --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2014 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 an + * limitations under the License. + */ + +package com.android.server.usb; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbInterface; +import android.media.AudioSystem; +import android.media.IAudioService; +import android.media.midi.MidiDeviceInfo; +import android.os.FileObserver; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Slog; + +import com.android.internal.alsa.AlsaCardsParser; +import com.android.internal.alsa.AlsaDevicesParser; +import com.android.server.audio.AudioService; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.ArrayList; + +/** + * UsbAlsaManager manages USB audio and MIDI devices. + */ +public final class UsbAlsaManager { + private static final String TAG = UsbAlsaManager.class.getSimpleName(); + private static final boolean DEBUG = true; + + private static final String ALSA_DIRECTORY = "/dev/snd/"; + + private final Context mContext; + private IAudioService mAudioService; + private final boolean mHasMidiFeature; + + private final AlsaCardsParser mCardsParser = new AlsaCardsParser(); + private final AlsaDevicesParser mDevicesParser = new AlsaDevicesParser(); + + // this is needed to map USB devices to ALSA Audio Devices, especially to remove an + // ALSA device when we are notified that its associated USB device has been removed. + + private final HashMap<UsbDevice,UsbAudioDevice> + mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>(); + + private final HashMap<UsbDevice,UsbMidiDevice> + mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>(); + + private final HashMap<String,AlsaDevice> + mAlsaDevices = new HashMap<String,AlsaDevice>(); + + private UsbAudioDevice mAccessoryAudioDevice = null; + + // UsbMidiDevice for USB peripheral mode (gadget) device + private UsbMidiDevice mPeripheralMidiDevice = null; + + private final class AlsaDevice { + public static final int TYPE_UNKNOWN = 0; + public static final int TYPE_PLAYBACK = 1; + public static final int TYPE_CAPTURE = 2; + public static final int TYPE_MIDI = 3; + + public int mCard; + public int mDevice; + public int mType; + + public AlsaDevice(int type, int card, int device) { + mType = type; + mCard = card; + mDevice = device; + } + + public boolean equals(Object obj) { + if (! (obj instanceof AlsaDevice)) { + return false; + } + AlsaDevice other = (AlsaDevice)obj; + return (mType == other.mType && mCard == other.mCard && mDevice == other.mDevice); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("AlsaDevice: [card: " + mCard); + sb.append(", device: " + mDevice); + sb.append(", type: " + mType); + sb.append("]"); + return sb.toString(); + } + } + + private final FileObserver mAlsaObserver = new FileObserver(ALSA_DIRECTORY, + FileObserver.CREATE | FileObserver.DELETE) { + public void onEvent(int event, String path) { + switch (event) { + case FileObserver.CREATE: + alsaFileAdded(path); + break; + case FileObserver.DELETE: + alsaFileRemoved(path); + break; + } + } + }; + + /* package */ UsbAlsaManager(Context context) { + mContext = context; + mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); + + // initial scan + mCardsParser.scan(); + } + + public void systemReady() { + mAudioService = IAudioService.Stub.asInterface( + ServiceManager.getService(Context.AUDIO_SERVICE)); + + mAlsaObserver.startWatching(); + + // add existing alsa devices + File[] files = new File(ALSA_DIRECTORY).listFiles(); + for (int i = 0; i < files.length; i++) { + alsaFileAdded(files[i].getName()); + } + } + + // Notifies AudioService when a device is added or removed + // audioDevice - the AudioDevice that was added or removed + // enabled - if true, we're connecting a device (it's arrived), else disconnecting + private void notifyDeviceState(UsbAudioDevice audioDevice, boolean enabled) { + if (DEBUG) { + Slog.d(TAG, "notifyDeviceState " + enabled + " " + audioDevice); + } + + if (mAudioService == null) { + Slog.e(TAG, "no AudioService"); + return; + } + + // FIXME Does not yet handle the case where the setting is changed + // after device connection. Ideally we should handle the settings change + // in SettingsObserver. Here we should log that a USB device is connected + // and disconnected with its address (card , device) and force the + // connection or disconnection when the setting changes. + int isDisabled = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USB_AUDIO_AUTOMATIC_ROUTING_DISABLED, 0); + if (isDisabled != 0) { + return; + } + + int state = (enabled ? 1 : 0); + int alsaCard = audioDevice.mCard; + int alsaDevice = audioDevice.mDevice; + if (alsaCard < 0 || alsaDevice < 0) { + Slog.e(TAG, "Invalid alsa card or device alsaCard: " + alsaCard + + " alsaDevice: " + alsaDevice); + return; + } + + String address = AudioService.makeAlsaAddressString(alsaCard, alsaDevice); + try { + // Playback Device + if (audioDevice.mHasPlayback) { + int device = (audioDevice == mAccessoryAudioDevice ? + AudioSystem.DEVICE_OUT_USB_ACCESSORY : + AudioSystem.DEVICE_OUT_USB_DEVICE); + if (DEBUG) { + Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) + + " addr:" + address + " name:" + audioDevice.mDeviceName); + } + mAudioService.setWiredDeviceConnectionState( + device, state, address, audioDevice.mDeviceName, TAG); + } + + // Capture Device + if (audioDevice.mHasCapture) { + int device = (audioDevice == mAccessoryAudioDevice ? + AudioSystem.DEVICE_IN_USB_ACCESSORY : + AudioSystem.DEVICE_IN_USB_DEVICE); + mAudioService.setWiredDeviceConnectionState( + device, state, address, audioDevice.mDeviceName, TAG); + } + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState"); + } + } + + private AlsaDevice waitForAlsaDevice(int card, int device, int type) { + AlsaDevice testDevice = new AlsaDevice(type, card, device); + + // This value was empirically determined. + final int kWaitTime = 2500; // ms + + synchronized(mAlsaDevices) { + long timeout = SystemClock.elapsedRealtime() + kWaitTime; + do { + if (mAlsaDevices.values().contains(testDevice)) { + return testDevice; + } + long waitTime = timeout - SystemClock.elapsedRealtime(); + if (waitTime > 0) { + try { + mAlsaDevices.wait(waitTime); + } catch (InterruptedException e) { + Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file."); + } + } + } while (timeout > SystemClock.elapsedRealtime()); + } + + Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice); + return null; + } + + private void alsaFileAdded(String name) { + int type = AlsaDevice.TYPE_UNKNOWN; + int card = -1, device = -1; + + if (name.startsWith("pcmC")) { + if (name.endsWith("p")) { + type = AlsaDevice.TYPE_PLAYBACK; + } else if (name.endsWith("c")) { + type = AlsaDevice.TYPE_CAPTURE; + } + } else if (name.startsWith("midiC")) { + type = AlsaDevice.TYPE_MIDI; + } + + if (type != AlsaDevice.TYPE_UNKNOWN) { + try { + int c_index = name.indexOf('C'); + int d_index = name.indexOf('D'); + int end = name.length(); + if (type == AlsaDevice.TYPE_PLAYBACK || type == AlsaDevice.TYPE_CAPTURE) { + // skip trailing 'p' or 'c' + end--; + } + card = Integer.parseInt(name.substring(c_index + 1, d_index)); + device = Integer.parseInt(name.substring(d_index + 1, end)); + } catch (Exception e) { + Slog.e(TAG, "Could not parse ALSA file name " + name, e); + return; + } + synchronized(mAlsaDevices) { + if (mAlsaDevices.get(name) == null) { + AlsaDevice alsaDevice = new AlsaDevice(type, card, device); + Slog.d(TAG, "Adding ALSA device " + alsaDevice); + mAlsaDevices.put(name, alsaDevice); + mAlsaDevices.notifyAll(); + } + } + } + } + + private void alsaFileRemoved(String path) { + synchronized(mAlsaDevices) { + AlsaDevice device = mAlsaDevices.remove(path); + if (device != null) { + Slog.d(TAG, "ALSA device removed: " + device); + } + } + } + + /* + * Select the default device of the specified card. + */ + /* package */ UsbAudioDevice selectAudioCard(int card) { + if (DEBUG) { + Slog.d(TAG, "selectAudioCard() card:" + card); + } + if (!mCardsParser.isCardUsb(card)) { + // Don't. AudioPolicyManager has logic for falling back to internal devices. + return null; + } + + mDevicesParser.scan(); + int device = mDevicesParser.getDefaultDeviceNum(card); + + boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card); + boolean hasCapture = mDevicesParser.hasCaptureDevices(card); + int deviceClass = + (mCardsParser.isCardUsb(card) + ? UsbAudioDevice.kAudioDeviceClass_External + : UsbAudioDevice.kAudioDeviceClass_Internal) | + UsbAudioDevice.kAudioDeviceMeta_Alsa; + + // Playback device file needed/present? + if (hasPlayback && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_PLAYBACK) == null)) { + return null; + } + + // Capture device file needed/present? + if (hasCapture && (waitForAlsaDevice(card, device, AlsaDevice.TYPE_CAPTURE) == null)) { + return null; + } + + if (DEBUG) { + Slog.d(TAG, "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); + } + + UsbAudioDevice audioDevice = + new UsbAudioDevice(card, device, hasPlayback, hasCapture, deviceClass); + AlsaCardsParser.AlsaCardRecord cardRecord = mCardsParser.getCardRecordFor(card); + audioDevice.mDeviceName = cardRecord.mCardName; + audioDevice.mDeviceDescription = cardRecord.mCardDescription; + + notifyDeviceState(audioDevice, true); + + return audioDevice; + } + + /* package */ UsbAudioDevice selectDefaultDevice() { + if (DEBUG) { + Slog.d(TAG, "UsbAudioManager.selectDefaultDevice()"); + } + mCardsParser.scan(); + return selectAudioCard(mCardsParser.getDefaultCard()); + } + + /* package */ void usbDeviceAdded(UsbDevice usbDevice) { + if (DEBUG) { + Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() + + "nm:" + usbDevice.getProductName()); + } + + // Is there an audio interface in there? + boolean isAudioDevice = false; + + // FIXME - handle multiple configurations? + int interfaceCount = usbDevice.getInterfaceCount(); + for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount; + ntrfaceIndex++) { + UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex); + if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) { + isAudioDevice = true; + } + } + if (!isAudioDevice) { + return; + } + + ArrayList<AlsaCardsParser.AlsaCardRecord> prevScanRecs = mCardsParser.getScanRecords(); + mCardsParser.scan(); + + int addedCard = -1; + ArrayList<AlsaCardsParser.AlsaCardRecord> + newScanRecs = mCardsParser.getNewCardRecords(prevScanRecs); + if (newScanRecs.size() > 0) { + // This is where we select the just connected device + // NOTE - to switch to prefering the first-connected device, just always + // take the else clause below. + addedCard = newScanRecs.get(0).mCardNum; + } else { + addedCard = mCardsParser.getDefaultUsbCard(); + } + + // If the default isn't a USB device, let the existing "select internal mechanism" + // handle the selection. + if (mCardsParser.isCardUsb(addedCard)) { + UsbAudioDevice audioDevice = selectAudioCard(addedCard); + if (audioDevice != null) { + mAudioDevices.put(usbDevice, audioDevice); + } + + // look for MIDI devices + + // Don't need to call mDevicesParser.scan() because selectAudioCard() does this above. + // Uncomment this next line if that behavior changes in the fugure. + // mDevicesParser.scan() + + boolean hasMidi = mDevicesParser.hasMIDIDevices(addedCard); + if (hasMidi && mHasMidiFeature) { + int device = mDevicesParser.getDefaultDeviceNum(addedCard); + AlsaDevice alsaDevice = waitForAlsaDevice(addedCard, device, AlsaDevice.TYPE_MIDI); + if (alsaDevice != null) { + Bundle properties = new Bundle(); + String manufacturer = usbDevice.getManufacturerName(); + String product = usbDevice.getProductName(); + String name; + if (manufacturer == null || manufacturer.isEmpty()) { + name = product; + } else if (product == null || product.isEmpty()) { + name = manufacturer; + } else { + name = manufacturer + " " + product; + } + properties.putString(MidiDeviceInfo.PROPERTY_NAME, name); + properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer); + properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product); + properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER, + usbDevice.getSerialNumber()); + properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, alsaDevice.mCard); + properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, alsaDevice.mDevice); + properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice); + + UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties, + alsaDevice.mCard, alsaDevice.mDevice); + if (usbMidiDevice != null) { + mMidiDevices.put(usbDevice, usbMidiDevice); + } + } + } + } + } + + /* package */ void usbDeviceRemoved(UsbDevice usbDevice) { + if (DEBUG) { + Slog.d(TAG, "deviceRemoved(): " + usbDevice.getManufacturerName() + + " " + usbDevice.getProductName()); + } + + UsbAudioDevice audioDevice = mAudioDevices.remove(usbDevice); + if (audioDevice != null) { + if (audioDevice.mHasPlayback || audioDevice.mHasPlayback) { + notifyDeviceState(audioDevice, false); + + // if there any external devices left, select one of them + selectDefaultDevice(); + } + } + UsbMidiDevice usbMidiDevice = mMidiDevices.remove(usbDevice); + if (usbMidiDevice != null) { + IoUtils.closeQuietly(usbMidiDevice); + } + } + + /* package */ void setAccessoryAudioState(boolean enabled, int card, int device) { + if (DEBUG) { + Slog.d(TAG, "setAccessoryAudioState " + enabled + " " + card + " " + device); + } + if (enabled) { + mAccessoryAudioDevice = new UsbAudioDevice(card, device, true, false, + UsbAudioDevice.kAudioDeviceClass_External); + notifyDeviceState(mAccessoryAudioDevice, true); + } else if (mAccessoryAudioDevice != null) { + notifyDeviceState(mAccessoryAudioDevice, false); + mAccessoryAudioDevice = null; + } + } + + /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) { + if (!mHasMidiFeature) { + return; + } + + if (enabled && mPeripheralMidiDevice == null) { + Bundle properties = new Bundle(); + Resources r = mContext.getResources(); + properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString( + com.android.internal.R.string.usb_midi_peripheral_name)); + properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString( + com.android.internal.R.string.usb_midi_peripheral_manufacturer_name)); + properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString( + com.android.internal.R.string.usb_midi_peripheral_product_name)); + properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card); + properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device); + mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device); + } else if (!enabled && mPeripheralMidiDevice != null) { + IoUtils.closeQuietly(mPeripheralMidiDevice); + mPeripheralMidiDevice = null; + } + } + + // + // Devices List + // + public ArrayList<UsbAudioDevice> getConnectedDevices() { + ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size()); + for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { + devices.add(entry.getValue()); + } + return devices; + } + + // + // Logging + // + public void dump(FileDescriptor fd, PrintWriter pw) { + pw.println(" USB Audio Devices:"); + for (UsbDevice device : mAudioDevices.keySet()) { + pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device)); + } + pw.println(" USB MIDI Devices:"); + for (UsbDevice device : mMidiDevices.keySet()) { + pw.println(" " + device.getDeviceName() + ": " + mMidiDevices.get(device)); + } + } + + public void logDevicesList(String title) { + if (DEBUG) { + for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { + Slog.i(TAG, "UsbDevice-------------------"); + Slog.i(TAG, "" + (entry != null ? entry.getKey() : "[none]")); + Slog.i(TAG, "UsbAudioDevice--------------"); + Slog.i(TAG, "" + entry.getValue()); + } + } + } + + // This logs a more terse (and more readable) version of the devices list + public void logDevices(String title) { + if (DEBUG) { + Slog.i(TAG, title); + for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { + Slog.i(TAG, entry.getValue().toShortString()); + } + } + } +} diff --git a/services/usb/java/com/android/server/usb/UsbAudioDevice.java b/services/usb/java/com/android/server/usb/UsbAudioDevice.java new file mode 100644 index 0000000..bdd28e4 --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbAudioDevice.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 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 an + * limitations under the License. + */ + +package com.android.server.usb; + +public final class UsbAudioDevice { + private static final String TAG = "UsbAudioDevice"; + protected static final boolean DEBUG = false; + + public int mCard; + public int mDevice; + public boolean mHasPlayback; + public boolean mHasCapture; + + // Device "class" flags + public static final int kAudioDeviceClassMask = 0x00FFFFFF; + public static final int kAudioDeviceClass_Undefined = 0x00000000; + public static final int kAudioDeviceClass_Internal = 0x00000001; + public static final int kAudioDeviceClass_External = 0x00000002; + // Device meta-data flags + public static final int kAudioDeviceMetaMask = 0xFF000000; + public static final int kAudioDeviceMeta_Alsa = 0x80000000; + // This member is a combination of the above bit-flags + public int mDeviceClass; + + public String mDeviceName = ""; + public String mDeviceDescription = ""; + + public UsbAudioDevice(int card, int device, + boolean hasPlayback, boolean hasCapture, int deviceClass) { + mCard = card; + mDevice = device; + mHasPlayback = hasPlayback; + mHasCapture = hasCapture; + mDeviceClass = deviceClass; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("UsbAudioDevice: [card: " + mCard); + sb.append(", device: " + mDevice); + sb.append(", name: " + mDeviceName); + sb.append(", hasPlayback: " + mHasPlayback); + sb.append(", hasCapture: " + mHasCapture); + sb.append(", class: 0x" + Integer.toHexString(mDeviceClass) + "]"); + return sb.toString(); + } + + public String toShortString() { + return "[card:" + mCard + " device:" + mDevice + " " + mDeviceName + "]"; + } +} + diff --git a/services/usb/java/com/android/server/usb/UsbAudioManager.java b/services/usb/java/com/android/server/usb/UsbAudioManager.java deleted file mode 100644 index bb45ee8..0000000 --- a/services/usb/java/com/android/server/usb/UsbAudioManager.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2014 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 an - * limitations under the License. - */ - -package com.android.server.usb; - -import android.alsa.AlsaCardsParser; -import android.alsa.AlsaDevicesParser; -import android.content.Context; -import android.content.Intent; -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbInterface; -import android.media.AudioManager; -import android.os.UserHandle; -import android.util.Slog; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.HashMap; - -/** - * UsbAudioManager manages USB audio devices. - */ -public class UsbAudioManager { - private static final String TAG = UsbAudioManager.class.getSimpleName(); - private static final boolean DEBUG = false; - - private final Context mContext; - - private final class AudioDevice { - public int mCard; - public int mDevice; - public boolean mHasPlayback; - public boolean mHasCapture; - public boolean mHasMIDI; - - public AudioDevice(int card, int device, - boolean hasPlayback, boolean hasCapture, boolean hasMidi) { - mCard = card; - mDevice = device; - mHasPlayback = hasPlayback; - mHasCapture = hasCapture; - mHasMIDI = hasMidi; - } - - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("AudioDevice: [card: " + mCard); - sb.append(", device: " + mDevice); - sb.append(", hasPlayback: " + mHasPlayback); - sb.append(", hasCapture: " + mHasCapture); - sb.append(", hasMidi: " + mHasMIDI); - sb.append("]"); - return sb.toString(); - } - } - - private final HashMap<UsbDevice,AudioDevice> mAudioDevices - = new HashMap<UsbDevice,AudioDevice>(); - - /* package */ UsbAudioManager(Context context) { - mContext = context; - } - - // Broadcasts the arrival/departure of a USB audio interface - // audioDevice - the AudioDevice that was added or removed - // enabled - if true, we're connecting a device (it's arrived), else disconnecting - private void sendDeviceNotification(AudioDevice audioDevice, boolean enabled) { - // send a sticky broadcast containing current USB state - Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_DEVICE_PLUG); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - intent.putExtra("state", enabled ? 1 : 0); - intent.putExtra("card", audioDevice.mCard); - intent.putExtra("device", audioDevice.mDevice); - intent.putExtra("hasPlayback", audioDevice.mHasPlayback); - intent.putExtra("hasCapture", audioDevice.mHasCapture); - intent.putExtra("hasMIDI", audioDevice.mHasMIDI); - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - } - - private boolean waitForAlsaFile(int card, int device, boolean capture) { - // These values were empirically determined. - final int kNumRetries = 5; - final int kSleepTime = 500; // ms - String alsaDevPath = "/dev/snd/pcmC" + card + "D" + device + (capture ? "c" : "p"); - File alsaDevFile = new File(alsaDevPath); - boolean exists = false; - for (int retry = 0; !exists && retry < kNumRetries; retry++) { - exists = alsaDevFile.exists(); - if (!exists) { - try { - Thread.sleep(kSleepTime); - } catch (IllegalThreadStateException ex) { - Slog.d(TAG, "usb: IllegalThreadStateException while waiting for ALSA file."); - } catch (java.lang.InterruptedException ex) { - Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file."); - } - } - } - - return exists; - } - - /* package */ void deviceAdded(UsbDevice usbDevice) { - // Is there an audio interface in there? - boolean isAudioDevice = false; - - // FIXME - handle multiple configurations? - int interfaceCount = usbDevice.getInterfaceCount(); - for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < interfaceCount; - ntrfaceIndex++) { - UsbInterface ntrface = usbDevice.getInterface(ntrfaceIndex); - if (ntrface.getInterfaceClass() == UsbConstants.USB_CLASS_AUDIO) { - isAudioDevice = true; - } - } - if (!isAudioDevice) { - return; - } - - //TODO(pmclean) The "Parser" objects inspect files in "/proc/asound" which we presume is - // present, unlike the waitForAlsaFile() which waits on a file in /dev/snd. It is not - // clear why this works, or that it can be relied on going forward. Needs further - // research. - AlsaCardsParser cardsParser = new AlsaCardsParser(); - cardsParser.scan(); - // cardsParser.Log(); - - // But we need to parse the device to determine its capabilities. - AlsaDevicesParser devicesParser = new AlsaDevicesParser(); - devicesParser.scan(); - // devicesParser.Log(); - - // The protocol for now will be to select the last-connected (highest-numbered) - // Alsa Card. - int card = cardsParser.getNumCardRecords() - 1; - int device = 0; - - boolean hasPlayback = devicesParser.hasPlaybackDevices(card); - boolean hasCapture = devicesParser.hasCaptureDevices(card); - boolean hasMidi = devicesParser.hasMIDIDevices(card); - - // Playback device file needed/present? - if (hasPlayback && - !waitForAlsaFile(card, device, false)) { - return; - } - - // Capture device file needed/present? - if (hasCapture && - !waitForAlsaFile(card, device, true)) { - return; - } - - if (DEBUG) { - Slog.d(TAG, - "usb: hasPlayback:" + hasPlayback + " hasCapture:" + hasCapture); - } - - AudioDevice audioDevice = new AudioDevice(card, device, hasPlayback, hasCapture, hasMidi); - mAudioDevices.put(usbDevice, audioDevice); - sendDeviceNotification(audioDevice, true); - } - - /* package */ void deviceRemoved(UsbDevice device) { - if (DEBUG) { - Slog.d(TAG, "deviceRemoved(): " + device); - } - - AudioDevice audioDevice = mAudioDevices.remove(device); - if (audioDevice != null) { - sendDeviceNotification(audioDevice, false); - } - } - - public void dump(FileDescriptor fd, PrintWriter pw) { - pw.println(" USB AudioDevices:"); - for (UsbDevice device : mAudioDevices.keySet()) { - pw.println(" " + device.getDeviceName() + ": " + mAudioDevices.get(device)); - } - } -} diff --git a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java index 1cf00d2..8849acd 100644 --- a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java +++ b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java @@ -21,7 +21,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.net.LocalSocket; import android.net.LocalSocketAddress; @@ -31,8 +30,11 @@ import android.os.FileUtils; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; import android.util.Slog; import android.util.Base64; + import com.android.server.FgThread; import java.lang.Thread; @@ -46,7 +48,7 @@ import java.io.PrintWriter; import java.security.MessageDigest; import java.util.Arrays; -public class UsbDebuggingManager implements Runnable { +public class UsbDebuggingManager { private static final String TAG = "UsbDebuggingManager"; private static final boolean DEBUG = false; @@ -57,86 +59,135 @@ public class UsbDebuggingManager implements Runnable { private final Context mContext; private final Handler mHandler; - private Thread mThread; + private UsbDebuggingThread mThread; private boolean mAdbEnabled = false; private String mFingerprints; - private LocalSocket mSocket = null; - private OutputStream mOutputStream = null; public UsbDebuggingManager(Context context) { mHandler = new UsbDebuggingHandler(FgThread.get().getLooper()); mContext = context; } - private void listenToSocket() throws IOException { - try { - byte[] buffer = new byte[BUFFER_SIZE]; - LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET, - LocalSocketAddress.Namespace.RESERVED); - InputStream inputStream = null; - - mSocket = new LocalSocket(); - mSocket.connect(address); + class UsbDebuggingThread extends Thread { + private boolean mStopped; + private LocalSocket mSocket; + private OutputStream mOutputStream; + private InputStream mInputStream; - mOutputStream = mSocket.getOutputStream(); - inputStream = mSocket.getInputStream(); + UsbDebuggingThread() { + super(TAG); + } + @Override + public void run() { + if (DEBUG) Slog.d(TAG, "Entering thread"); while (true) { - int count = inputStream.read(buffer); - if (count < 0) { - break; - } - - if (buffer[0] == 'P' && buffer[1] == 'K') { - String key = new String(Arrays.copyOfRange(buffer, 2, count)); - Slog.d(TAG, "Received public key: " + key); - Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_CONFIRM); - msg.obj = key; - mHandler.sendMessage(msg); + synchronized (this) { + if (mStopped) { + if (DEBUG) Slog.d(TAG, "Exiting thread"); + return; + } + try { + openSocketLocked(); + } catch (Exception e) { + /* Don't loop too fast if adbd dies, before init restarts it */ + SystemClock.sleep(1000); + } } - else { - Slog.e(TAG, "Wrong message: " + (new String(Arrays.copyOfRange(buffer, 0, 2)))); - break; + try { + listenToSocket(); + } catch (Exception e) { + /* Don't loop too fast if adbd dies, before init restarts it */ + SystemClock.sleep(1000); } } - } finally { - closeSocket(); } - } - @Override - public void run() { - while (mAdbEnabled) { + private void openSocketLocked() throws IOException { try { - listenToSocket(); - } catch (Exception e) { - /* Don't loop too fast if adbd dies, before init restarts it */ - SystemClock.sleep(1000); + LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET, + LocalSocketAddress.Namespace.RESERVED); + mInputStream = null; + + if (DEBUG) Slog.d(TAG, "Creating socket"); + mSocket = new LocalSocket(); + mSocket.connect(address); + + mOutputStream = mSocket.getOutputStream(); + mInputStream = mSocket.getInputStream(); + } catch (IOException ioe) { + closeSocketLocked(); + throw ioe; } } - } - private void closeSocket() { - try { - mOutputStream.close(); - } catch (IOException e) { - Slog.e(TAG, "Failed closing output stream: " + e); - } + private void listenToSocket() throws IOException { + try { + byte[] buffer = new byte[BUFFER_SIZE]; + while (true) { + int count = mInputStream.read(buffer); + if (count < 0) { + break; + } - try { - mSocket.close(); - } catch (IOException ex) { - Slog.e(TAG, "Failed closing socket: " + ex); + if (buffer[0] == 'P' && buffer[1] == 'K') { + String key = new String(Arrays.copyOfRange(buffer, 2, count)); + Slog.d(TAG, "Received public key: " + key); + Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_CONFIRM); + msg.obj = key; + mHandler.sendMessage(msg); + } else { + Slog.e(TAG, "Wrong message: " + + (new String(Arrays.copyOfRange(buffer, 0, 2)))); + break; + } + } + } finally { + synchronized (this) { + closeSocketLocked(); + } + } } - } - private void sendResponse(String msg) { - if (mOutputStream != null) { + private void closeSocketLocked() { + if (DEBUG) Slog.d(TAG, "Closing socket"); + try { + if (mOutputStream != null) { + mOutputStream.close(); + mOutputStream = null; + } + } catch (IOException e) { + Slog.e(TAG, "Failed closing output stream: " + e); + } + try { - mOutputStream.write(msg.getBytes()); + if (mSocket != null) { + mSocket.close(); + mSocket = null; + } + } catch (IOException ex) { + Slog.e(TAG, "Failed closing socket: " + ex); + } + } + + /** Call to stop listening on the socket and exit the thread. */ + void stopListening() { + synchronized (this) { + mStopped = true; + closeSocketLocked(); } - catch (IOException ex) { - Slog.e(TAG, "Failed to write response:", ex); + } + + void sendResponse(String msg) { + synchronized (this) { + if (!mStopped && mOutputStream != null) { + try { + mOutputStream.write(msg.getBytes()); + } + catch (IOException ex) { + Slog.e(TAG, "Failed to write response:", ex); + } + } } } } @@ -161,7 +212,7 @@ public class UsbDebuggingManager implements Runnable { mAdbEnabled = true; - mThread = new Thread(UsbDebuggingManager.this, TAG); + mThread = new UsbDebuggingThread(); mThread.start(); break; @@ -171,16 +222,12 @@ public class UsbDebuggingManager implements Runnable { break; mAdbEnabled = false; - closeSocket(); - try { - mThread.join(); - } catch (Exception ex) { + if (mThread != null) { + mThread.stopListening(); + mThread = null; } - mThread = null; - mOutputStream = null; - mSocket = null; break; case MESSAGE_ADB_ALLOW: { @@ -197,19 +244,33 @@ public class UsbDebuggingManager implements Runnable { writeKey(key); } - sendResponse("OK"); + if (mThread != null) { + mThread.sendResponse("OK"); + } break; } case MESSAGE_ADB_DENY: - sendResponse("NO"); + if (mThread != null) { + mThread.sendResponse("NO"); + } break; case MESSAGE_ADB_CONFIRM: { + if ("trigger_restart_min_framework".equals( + SystemProperties.get("vold.decrypt"))) { + Slog.d(TAG, "Deferring adb confirmation until after vold decrypt"); + if (mThread != null) { + mThread.sendResponse("NO"); + } + break; + } String key = (String)msg.obj; String fingerprints = getFingerprints(key); if ("".equals(fingerprints)) { - sendResponse("NO"); + if (mThread != null) { + mThread.sendResponse("NO"); + } break; } mFingerprints = fingerprints; @@ -279,7 +340,7 @@ public class UsbDebuggingManager implements Runnable { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) { try { - mContext.startActivity(intent); + mContext.startActivityAsUser(intent, UserHandle.OWNER); return true; } catch (ActivityNotFoundException e) { Slog.e(TAG, "unable to start adb whitelist activity: " + componentName, e); @@ -379,7 +440,7 @@ public class UsbDebuggingManager implements Runnable { public void dump(FileDescriptor fd, PrintWriter pw) { pw.println(" USB Debugging State:"); - pw.println(" Connected to adbd: " + (mOutputStream != null)); + pw.println(" Connected to adbd: " + (mThread != null)); pw.println(" Last key received: " + mFingerprints); pw.println(" User keys:"); try { diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index f3fa747..6adb8be 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -30,7 +30,6 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; -import android.media.AudioManager; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; @@ -85,6 +84,8 @@ public class UsbDeviceManager { "/sys/class/android_usb/android0/f_rndis/ethaddr"; private static final String AUDIO_SOURCE_PCM_PATH = "/sys/class/android_usb/android0/f_audio_source/pcm"; + private static final String MIDI_ALSA_PATH = + "/sys/class/android_usb/android0/f_midi/alsa"; private static final int MSG_UPDATE_STATE = 0; private static final int MSG_ENABLE_ADB = 1; @@ -124,9 +125,13 @@ public class UsbDeviceManager { private boolean mUseUsbNotification; private boolean mAdbEnabled; private boolean mAudioSourceEnabled; + private boolean mMidiEnabled; + private int mMidiCard; + private int mMidiDevice; private Map<String, List<Pair<String, String>>> mOemModeMap; private String[] mAccessoryStrings; private UsbDebuggingManager mDebuggingManager; + private final UsbAlsaManager mUsbAlsaManager; private class AdbSettingsObserver extends ContentObserver { public AdbSettingsObserver() { @@ -159,8 +164,9 @@ public class UsbDeviceManager { } }; - public UsbDeviceManager(Context context) { + public UsbDeviceManager(Context context, UsbAlsaManager alsaManager) { mContext = context; + mUsbAlsaManager = alsaManager; mContentResolver = context.getContentResolver(); PackageManager pm = mContext.getPackageManager(); mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); @@ -359,18 +365,6 @@ public class UsbDeviceManager { updateState(state); mAdbEnabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB); - // Upgrade step for previous versions that used persist.service.adb.enable - String value = SystemProperties.get("persist.service.adb.enable", ""); - if (value.length() > 0) { - char enable = value.charAt(0); - if (enable == '1') { - setAdbEnabled(true); - } else if (enable == '0') { - setAdbEnabled(false); - } - SystemProperties.set("persist.service.adb.enable", ""); - } - // register observer to listen for settings changes mContentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.ADB_ENABLED), @@ -594,19 +588,15 @@ public class UsbDeviceManager { boolean enabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_AUDIO_SOURCE); if (enabled != mAudioSourceEnabled) { - // send a sticky broadcast containing current USB state - Intent intent = new Intent(AudioManager.ACTION_USB_AUDIO_ACCESSORY_PLUG); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - intent.putExtra("state", (enabled ? 1 : 0)); + int card = -1; + int device = -1; + if (enabled) { Scanner scanner = null; try { scanner = new Scanner(new File(AUDIO_SOURCE_PCM_PATH)); - int card = scanner.nextInt(); - int device = scanner.nextInt(); - intent.putExtra("card", card); - intent.putExtra("device", device); + card = scanner.nextInt(); + device = scanner.nextInt(); } catch (FileNotFoundException e) { Slog.e(TAG, "could not open audio source PCM file", e); } finally { @@ -615,11 +605,34 @@ public class UsbDeviceManager { } } } - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + mUsbAlsaManager.setAccessoryAudioState(enabled, card, device); mAudioSourceEnabled = enabled; } } + private void updateMidiFunction() { + boolean enabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MIDI); + if (enabled != mMidiEnabled) { + if (enabled) { + Scanner scanner = null; + try { + scanner = new Scanner(new File(MIDI_ALSA_PATH)); + mMidiCard = scanner.nextInt(); + mMidiDevice = scanner.nextInt(); + } catch (FileNotFoundException e) { + Slog.e(TAG, "could not open MIDI PCM file", e); + enabled = false; + } finally { + if (scanner != null) { + scanner.close(); + } + } + } + mMidiEnabled = enabled; + } + mUsbAlsaManager.setPeripheralMidiState(mMidiEnabled && mConfigured, mMidiCard, mMidiDevice); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -638,6 +651,7 @@ public class UsbDeviceManager { if (mBootCompleted) { updateUsbState(); updateAudioSourceFunction(); + updateMidiFunction(); } break; case MSG_ENABLE_ADB: @@ -653,6 +667,7 @@ public class UsbDeviceManager { updateAdbNotification(); updateUsbState(); updateAudioSourceFunction(); + updateMidiFunction(); break; case MSG_BOOT_COMPLETED: mBootCompleted = true; @@ -703,6 +718,8 @@ public class UsbDeviceManager { id = com.android.internal.R.string.usb_mtp_notification_title; } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP)) { id = com.android.internal.R.string.usb_ptp_notification_title; + } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MIDI)) { + id = com.android.internal.R.string.usb_midi_notification_title; } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MASS_STORAGE)) { id = com.android.internal.R.string.usb_cd_installer_notification_title; @@ -742,7 +759,7 @@ public class UsbDeviceManager { "com.android.settings.UsbSettings")); PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); - notification.color = mContext.getResources().getColor( + notification.color = mContext.getColor( com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(mContext, title, message, pi); notification.visibility = Notification.VISIBILITY_PUBLIC; @@ -780,7 +797,7 @@ public class UsbDeviceManager { "com.android.settings.DevelopmentSettings")); PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); - notification.color = mContext.getResources().getColor( + notification.color = mContext.getColor( com.android.internal.R.color.system_notification_accent_color); notification.setLatestEventInfo(mContext, title, message, pi); notification.visibility = Notification.VISIBILITY_PUBLIC; diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index e769bda..5b58051 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -17,7 +17,6 @@ package com.android.server.usb; import android.content.Context; -import android.content.Intent; import android.hardware.usb.UsbConfiguration; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; @@ -25,13 +24,11 @@ import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.os.Bundle; import android.os.ParcelFileDescriptor; -import android.os.Parcelable; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.io.FileDescriptor; -import java.io.FileNotFoundException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; @@ -60,16 +57,16 @@ public class UsbHostManager { private ArrayList<UsbInterface> mNewInterfaces; private ArrayList<UsbEndpoint> mNewEndpoints; - private UsbAudioManager mUsbAudioManager; + private final UsbAlsaManager mUsbAlsaManager; @GuardedBy("mLock") private UsbSettingsManager mCurrentSettings; - public UsbHostManager(Context context) { + public UsbHostManager(Context context, UsbAlsaManager alsaManager) { mContext = context; mHostBlacklist = context.getResources().getStringArray( com.android.internal.R.array.config_usbHostBlacklist); - mUsbAudioManager = new UsbAudioManager(context); + mUsbAlsaManager = alsaManager; } public void setCurrentSettings(UsbSettingsManager settings) { @@ -222,7 +219,7 @@ public class UsbHostManager { mDevices.put(mNewDevice.getDeviceName(), mNewDevice); Slog.d(TAG, "Added device " + mNewDevice); getCurrentSettings().deviceAttached(mNewDevice); - mUsbAudioManager.deviceAdded(mNewDevice); + mUsbAlsaManager.usbDeviceAdded(mNewDevice); } else { Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded"); } @@ -238,7 +235,7 @@ public class UsbHostManager { synchronized (mLock) { UsbDevice device = mDevices.remove(deviceName); if (device != null) { - mUsbAudioManager.deviceRemoved(device); + mUsbAlsaManager.usbDeviceRemoved(device); getCurrentSettings().deviceDetached(device); } } @@ -290,7 +287,6 @@ public class UsbHostManager { pw.println(" " + name + ": " + mDevices.get(name)); } } - mUsbAudioManager.dump(fd, pw); } private native void monitorUsbHostBus(); diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java new file mode 100644 index 0000000..671cf01 --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2014 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 an + * limitations under the License. + */ + +package com.android.server.usb; + +import android.content.Context; +import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceServer; +import android.media.midi.MidiManager; +import android.media.midi.MidiReceiver; +import android.media.midi.MidiSender; +import android.os.Bundle; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.system.StructPollfd; +import android.util.Log; + +import com.android.internal.midi.MidiEventScheduler; +import com.android.internal.midi.MidiEventScheduler.MidiEvent; + +import libcore.io.IoUtils; + +import java.io.Closeable; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public final class UsbMidiDevice implements Closeable { + private static final String TAG = "UsbMidiDevice"; + + private MidiDeviceServer mServer; + + // event schedulers for each output port + private final MidiEventScheduler[] mEventSchedulers; + + private static final int BUFFER_SIZE = 512; + + private final FileDescriptor[] mFileDescriptors; + + // for polling multiple FileDescriptors for MIDI events + private final StructPollfd[] mPollFDs; + // streams for reading from ALSA driver + private final FileInputStream[] mInputStreams; + // streams for writing to ALSA driver + private final FileOutputStream[] mOutputStreams; + + public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { + // FIXME - support devices with different number of input and output ports + int subDevices = nativeGetSubdeviceCount(card, device); + if (subDevices <= 0) { + Log.e(TAG, "nativeGetSubdeviceCount failed"); + return null; + } + + // FIXME - support devices with different number of input and output ports + FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices); + if (fileDescriptors == null) { + Log.e(TAG, "nativeOpen failed"); + return null; + } + + UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors); + if (!midiDevice.register(context, properties)) { + IoUtils.closeQuietly(midiDevice); + Log.e(TAG, "createDeviceServer failed"); + return null; + } + return midiDevice; + } + + private UsbMidiDevice(FileDescriptor[] fileDescriptors) { + mFileDescriptors = fileDescriptors; + int inputCount = fileDescriptors.length; + int outputCount = fileDescriptors.length; + + mPollFDs = new StructPollfd[inputCount]; + mInputStreams = new FileInputStream[inputCount]; + for (int i = 0; i < inputCount; i++) { + FileDescriptor fd = fileDescriptors[i]; + StructPollfd pollfd = new StructPollfd(); + pollfd.fd = fd; + pollfd.events = (short)OsConstants.POLLIN; + mPollFDs[i] = pollfd; + mInputStreams[i] = new FileInputStream(fd); + } + + mOutputStreams = new FileOutputStream[outputCount]; + mEventSchedulers = new MidiEventScheduler[outputCount]; + for (int i = 0; i < outputCount; i++) { + mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); + mEventSchedulers[i] = new MidiEventScheduler(); + } + } + + private boolean register(Context context, Bundle properties) { + MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); + if (midiManager == null) { + Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); + return false; + } + + int inputCount = mInputStreams.length; + int outputCount = mOutputStreams.length; + MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount]; + for (int port = 0; port < inputCount; port++) { + inputPortReceivers[port] = mEventSchedulers[port].getReceiver(); + } + + mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount, + null, null, properties, MidiDeviceInfo.TYPE_USB, null); + if (mServer == null) { + return false; + } + final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); + + // Create input thread which will read from all input ports + new Thread("UsbMidiDevice input thread") { + @Override + public void run() { + byte[] buffer = new byte[BUFFER_SIZE]; + try { + boolean done = false; + while (!done) { + // look for a readable FileDescriptor + for (int index = 0; index < mPollFDs.length; index++) { + StructPollfd pfd = mPollFDs[index]; + if ((pfd.revents & OsConstants.POLLIN) != 0) { + // clear readable flag + pfd.revents = 0; + + int count = mInputStreams[index].read(buffer); + outputReceivers[index].send(buffer, 0, count); + } else if ((pfd.revents & (OsConstants.POLLERR + | OsConstants.POLLHUP)) != 0) { + done = true; + } + } + + // wait until we have a readable port + Os.poll(mPollFDs, -1 /* infinite timeout */); + } + } catch (IOException e) { + Log.d(TAG, "reader thread exiting"); + } catch (ErrnoException e) { + Log.d(TAG, "reader thread exiting"); + } + Log.d(TAG, "input thread exit"); + } + }.start(); + + // Create output thread for each output port + for (int port = 0; port < outputCount; port++) { + final MidiEventScheduler eventSchedulerF = mEventSchedulers[port]; + final FileOutputStream outputStreamF = mOutputStreams[port]; + final int portF = port; + + new Thread("UsbMidiDevice output thread " + port) { + @Override + public void run() { + while (true) { + MidiEvent event; + try { + event = (MidiEvent)eventSchedulerF.waitNextEvent(); + } catch (InterruptedException e) { + // try again + continue; + } + if (event == null) { + break; + } + try { + outputStreamF.write(event.data, 0, event.count); + } catch (IOException e) { + Log.e(TAG, "write failed for port " + portF); + } + eventSchedulerF.addEventToPool(event); + } + Log.d(TAG, "output thread exit"); + } + }.start(); + } + + return true; + } + + @Override + public void close() throws IOException { + for (int i = 0; i < mEventSchedulers.length; i++) { + mEventSchedulers[i].close(); + } + + if (mServer != null) { + mServer.close(); + } + + for (int i = 0; i < mInputStreams.length; i++) { + mInputStreams[i].close(); + } + for (int i = 0; i < mOutputStreams.length; i++) { + mOutputStreams[i].close(); + } + nativeClose(mFileDescriptors); + } + + private static native int nativeGetSubdeviceCount(int card, int device); + private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); + private static native void nativeClose(FileDescriptor[] fileDescriptors); +} diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index fd83f92..fda8076 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -73,6 +73,7 @@ public class UsbService extends IUsbManager.Stub { private UsbDeviceManager mDeviceManager; private UsbHostManager mHostManager; + private final UsbAlsaManager mAlsaManager; private final Object mLock = new Object(); @@ -95,12 +96,14 @@ public class UsbService extends IUsbManager.Stub { public UsbService(Context context) { mContext = context; + mAlsaManager = new UsbAlsaManager(context); + final PackageManager pm = mContext.getPackageManager(); if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) { - mHostManager = new UsbHostManager(context); + mHostManager = new UsbHostManager(context, mAlsaManager); } if (new File("/sys/class/android_usb").exists()) { - mDeviceManager = new UsbDeviceManager(context); + mDeviceManager = new UsbDeviceManager(context, mAlsaManager); } setCurrentUser(UserHandle.USER_OWNER); @@ -137,6 +140,8 @@ public class UsbService extends IUsbManager.Stub { } public void systemReady() { + mAlsaManager.systemReady(); + if (mDeviceManager != null) { mDeviceManager.systemReady(); } @@ -305,6 +310,7 @@ public class UsbService extends IUsbManager.Stub { if (mHostManager != null) { mHostManager.dump(fd, pw); } + mAlsaManager.dump(fd, pw); synchronized (mLock) { for (int i = 0; i < mSettingsByUser.size(); i++) { diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java index 37b5c51..bfd1f13 100644 --- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java @@ -27,7 +27,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index f5d4867..7dce83e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -39,7 +39,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; -import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionService; @@ -51,6 +50,7 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; @@ -130,12 +130,14 @@ public class VoiceInteractionManagerService extends SystemService { } public void initForUser(int userHandle) { - if (DEBUG) Slog.i(TAG, "initForUser user=" + userHandle); + if (DEBUG) Slog.d(TAG, "**************** initForUser user=" + userHandle); String curInteractorStr = Settings.Secure.getStringForUser( mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE, userHandle); ComponentName curRecognizer = getCurRecognizer(userHandle); VoiceInteractionServiceInfo curInteractorInfo = null; + if (DEBUG) Slog.d(TAG, "curInteractorStr=" + curInteractorStr + + " curRecognizer=" + curRecognizer); if (curInteractorStr == null && curRecognizer != null && !ActivityManager.isLowRamDeviceStatic()) { // If there is no interactor setting, that means we are upgrading @@ -149,6 +151,8 @@ public class VoiceInteractionManagerService extends SystemService { // Looks good! We'll apply this one. To make it happen, we clear the // recognizer so that we don't think we have anything set and will // re-apply the settings. + if (DEBUG) Slog.d(TAG, "No set interactor, found avail: " + + curInteractorInfo.getServiceInfo().name); curRecognizer = null; } } @@ -157,6 +161,7 @@ public class VoiceInteractionManagerService extends SystemService { // enabled; if it is, turn it off. if (ActivityManager.isLowRamDeviceStatic() && curInteractorStr != null) { if (!TextUtils.isEmpty(curInteractorStr)) { + if (DEBUG) Slog.d(TAG, "Svelte device; disabling interactor"); setCurInteractor(null, userHandle); curInteractorStr = ""; } @@ -179,8 +184,11 @@ public class VoiceInteractionManagerService extends SystemService { } // If the apps for the currently set components still exist, then all is okay. if (recognizerInfo != null && (curInteractor == null || interactorInfo != null)) { + if (DEBUG) Slog.d(TAG, "Current interactor/recognizer okay, done!"); return; } + if (DEBUG) Slog.d(TAG, "Bad recognizer (" + recognizerInfo + ") or interactor (" + + interactorInfo + ")"); } // Initializing settings, look for an interactor first (but only on non-svelte). @@ -317,7 +325,7 @@ public class VoiceInteractionManagerService extends SystemService { if (TextUtils.isEmpty(curInteractor)) { return null; } - if (DEBUG) Slog.i(TAG, "getCurInteractor curInteractor=" + curInteractor + if (DEBUG) Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor + " user=" + userHandle); return ComponentName.unflattenFromString(curInteractor); } @@ -326,7 +334,7 @@ public class VoiceInteractionManagerService extends SystemService { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.VOICE_INTERACTION_SERVICE, comp != null ? comp.flattenToShortString() : "", userHandle); - if (DEBUG) Slog.i(TAG, "setCurInteractor comp=" + comp + if (DEBUG) Slog.d(TAG, "setCurInteractor comp=" + comp + " user=" + userHandle); } @@ -364,7 +372,7 @@ public class VoiceInteractionManagerService extends SystemService { if (TextUtils.isEmpty(curRecognizer)) { return null; } - if (DEBUG) Slog.i(TAG, "getCurRecognizer curRecognizer=" + curRecognizer + if (DEBUG) Slog.d(TAG, "getCurRecognizer curRecognizer=" + curRecognizer + " user=" + userHandle); return ComponentName.unflattenFromString(curRecognizer); } @@ -373,23 +381,21 @@ public class VoiceInteractionManagerService extends SystemService { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.VOICE_RECOGNITION_SERVICE, comp != null ? comp.flattenToShortString() : "", userHandle); - if (DEBUG) Slog.i(TAG, "setCurRecognizer comp=" + comp + if (DEBUG) Slog.d(TAG, "setCurRecognizer comp=" + comp + " user=" + userHandle); } @Override - public void startSession(IVoiceInteractionService service, Bundle args) { + public void showSession(IVoiceInteractionService service, Bundle args, int flags) { synchronized (this) { if (mImpl == null || mImpl.mService == null || service.asBinder() != mImpl.mService.asBinder()) { throw new SecurityException( "Caller is not the current voice interaction service"); } - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - mImpl.startSessionLocked(callingPid, callingUid, args); + mImpl.showSessionLocked(args, flags, null /* showCallback */); } finally { Binder.restoreCallingIdentity(caller); } @@ -417,6 +423,40 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public boolean showSessionFromSession(IBinder token, Bundle sessionArgs, int flags) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "showSessionFromSession without running voice interaction service"); + return false; + } + final long caller = Binder.clearCallingIdentity(); + try { + return mImpl.showSessionLocked(sessionArgs, flags, null /* showCallback */); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public boolean hideSessionFromSession(IBinder token) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "hideSessionFromSession without running voice interaction service"); + return false; + } + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + return mImpl.hideSessionLocked(callingPid, callingUid); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override public int startVoiceActivity(IBinder token, Intent intent, String resolvedType) { synchronized (this) { if (mImpl == null) { @@ -426,9 +466,6 @@ public class VoiceInteractionManagerService extends SystemService { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); - if (!SystemProperties.getBoolean("persist.test.voice_interaction", false)) { - throw new SecurityException("Voice interaction not supported"); - } try { return mImpl.startVoiceActivityLocked(callingPid, callingUid, token, intent, resolvedType); @@ -439,34 +476,44 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public void finish(IBinder token) { + public void setKeepAwake(IBinder token, boolean keepAwake) { synchronized (this) { if (mImpl == null) { - Slog.w(TAG, "finish without running voice interaction service"); + Slog.w(TAG, "setKeepAwake without running voice interaction service"); return; } final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - mImpl.finishLocked(callingPid, callingUid, token); + mImpl.setKeepAwakeLocked(callingPid, callingUid, token, keepAwake); } finally { Binder.restoreCallingIdentity(caller); } } } - //----------------- Model management APIs --------------------------------// - @Override - public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) { + public void finish(IBinder token) { synchronized (this) { - if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold the permission " - + Manifest.permission.MANAGE_VOICE_KEYPHRASES); + if (mImpl == null) { + Slog.w(TAG, "finish without running voice interaction service"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.finishLocked(token); + } finally { + Binder.restoreCallingIdentity(caller); } } + } + + //----------------- Model management APIs --------------------------------// + + @Override + public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) { + enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES); if (bcp47Locale == null) { throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel"); @@ -483,15 +530,9 @@ public class VoiceInteractionManagerService extends SystemService { @Override public int updateKeyphraseSoundModel(KeyphraseSoundModel model) { - synchronized (this) { - if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold the permission " - + Manifest.permission.MANAGE_VOICE_KEYPHRASES); - } - if (model == null) { - throw new IllegalArgumentException("Model must not be null"); - } + enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES); + if (model == null) { + throw new IllegalArgumentException("Model must not be null"); } final long caller = Binder.clearCallingIdentity(); @@ -514,13 +555,7 @@ public class VoiceInteractionManagerService extends SystemService { @Override public int deleteKeyphraseSoundModel(int keyphraseId, String bcp47Locale) { - synchronized (this) { - if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold the permission " - + Manifest.permission.MANAGE_VOICE_KEYPHRASES); - } - } + enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES); if (bcp47Locale == null) { throw new IllegalArgumentException( @@ -649,6 +684,52 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public ComponentName getActiveServiceComponentName() { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + return mImpl != null ? mImpl.mComponent : null; + } + } + + @Override + public void showSessionForActiveService(IVoiceInteractionSessionShowCallback showCallback) { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "showSessionForActiveService without running voice interaction" + + "service"); + return; + } + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.showSessionLocked(new Bundle() /* sessionArgs */, + VoiceInteractionService.START_SOURCE_ASSIST_GESTURE + | VoiceInteractionService.START_WITH_ASSIST + | VoiceInteractionService.START_WITH_SCREENSHOT, + showCallback); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public boolean isSessionRunning() { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + return mImpl != null && mImpl.mActiveSession != null; + } + } + + @Override + public boolean activeServiceSupportsAssistGesture() { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + return mImpl != null && mImpl.mInfo.getSupportsAssistGesture(); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -668,6 +749,12 @@ public class VoiceInteractionManagerService extends SystemService { mSoundTriggerHelper.dump(fd, pw, args); } + private void enforceCallingPermission(String permission) { + if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Caller does not hold the permission " + permission); + } + } + class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { super(handler); @@ -696,7 +783,7 @@ public class VoiceInteractionManagerService extends SystemService { @Override public void onSomePackagesChanged() { int userHandle = getChangingUserId(); - if (DEBUG) Slog.i(TAG, "onSomePackagesChanged user=" + userHandle); + if (DEBUG) Slog.d(TAG, "onSomePackagesChanged user=" + userHandle); ComponentName curInteractor = getCurInteractor(userHandle); ComponentName curRecognizer = getCurRecognizer(userHandle); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index b36b611..61ec162 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; -import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -35,19 +34,18 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; -import android.service.voice.IVoiceInteractionSessionService; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; import android.util.Slog; import android.view.IWindowManager; -import android.view.WindowManager; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import java.io.FileDescriptor; import java.io.PrintWriter; -class VoiceInteractionManagerServiceImpl { +class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback { final static String TAG = "VoiceInteractionServiceManager"; final boolean mValid; @@ -64,7 +62,7 @@ class VoiceInteractionManagerServiceImpl { boolean mBound = false; IVoiceInteractionService mService; - SessionConnection mActiveSession; + VoiceInteractionSessionConnection mActiveSession; final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -100,91 +98,6 @@ class VoiceInteractionManagerServiceImpl { } }; - final class SessionConnection implements ServiceConnection { - final IBinder mToken = new Binder(); - final Bundle mArgs; - boolean mBound; - IVoiceInteractionSessionService mService; - IVoiceInteractionSession mSession; - IVoiceInteractor mInteractor; - - SessionConnection(Bundle args) { - mArgs = args; - Intent serviceIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); - serviceIntent.setComponent(mSessionComponentName); - mBound = mContext.bindServiceAsUser(serviceIntent, this, - Context.BIND_AUTO_CREATE, new UserHandle(mUser)); - if (mBound) { - try { - mIWindowManager.addWindowToken(mToken, - WindowManager.LayoutParams.TYPE_VOICE_INTERACTION); - } catch (RemoteException e) { - Slog.w(TAG, "Failed adding window token", e); - } - } else { - Slog.w(TAG, "Failed binding to voice interaction session service " + mComponent); - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mLock) { - mService = IVoiceInteractionSessionService.Stub.asInterface(service); - if (mActiveSession == this) { - try { - mService.newSession(mToken, mArgs); - } catch (RemoteException e) { - Slog.w(TAG, "Failed adding window token", e); - } - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - } - - public void cancel() { - if (mBound) { - if (mSession != null) { - try { - mSession.destroy(); - } catch (RemoteException e) { - Slog.w(TAG, "Voice interation session already dead"); - } - } - if (mSession != null) { - try { - mAm.finishVoiceTask(mSession); - } catch (RemoteException e) { - } - } - mContext.unbindService(this); - try { - mIWindowManager.removeWindowToken(mToken); - } catch (RemoteException e) { - Slog.w(TAG, "Failed removing window token", e); - } - mBound = false; - mService = null; - mSession = null; - mInteractor = null; - } - } - - public void dump(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mToken="); pw.println(mToken); - pw.print(prefix); pw.print("mArgs="); pw.println(mArgs); - pw.print(prefix); pw.print("mBound="); pw.println(mBound); - if (mBound) { - pw.print(prefix); pw.print("mService="); pw.println(mService); - pw.print(prefix); pw.print("mSession="); pw.println(mSession); - pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor); - } - } - }; - VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock, int userHandle, ComponentName service) { mContext = context; @@ -222,12 +135,20 @@ class VoiceInteractionManagerServiceImpl { mContext.registerReceiver(mBroadcastReceiver, filter, null, handler); } - public void startSessionLocked(int callingPid, int callingUid, Bundle args) { + public boolean showSessionLocked(Bundle args, int flags, + IVoiceInteractionSessionShowCallback showCallback) { + if (mActiveSession == null) { + mActiveSession = new VoiceInteractionSessionConnection(mLock, mSessionComponentName, + mUser, mContext, this, mInfo.getServiceInfo().applicationInfo.uid); + } + return mActiveSession.showLocked(args, flags, showCallback); + } + + public boolean hideSessionLocked(int callingPid, int callingUid) { if (mActiveSession != null) { - mActiveSession.cancel(); - mActiveSession = null; + return mActiveSession.hideLocked(); } - mActiveSession = new SessionConnection(args); + return false; } public boolean deliverNewSessionLocked(int callingPid, int callingUid, IBinder token, @@ -236,8 +157,7 @@ class VoiceInteractionManagerServiceImpl { Slog.w(TAG, "deliverNewSession does not match active session"); return false; } - mActiveSession.mSession = session; - mActiveSession.mInteractor = interactor; + mActiveSession.deliverNewSessionLocked(session, interactor); return true; } @@ -248,6 +168,10 @@ class VoiceInteractionManagerServiceImpl { Slog.w(TAG, "startVoiceActivity does not match active session"); return ActivityManager.START_CANCELED; } + if (!mActiveSession.mShown) { + Slog.w(TAG, "startVoiceActivity not allowed on hidden session"); + return ActivityManager.START_CANCELED; + } intent = new Intent(intent); intent.addCategory(Intent.CATEGORY_VOICE); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); @@ -259,8 +183,20 @@ class VoiceInteractionManagerServiceImpl { } } + public void setKeepAwakeLocked(int callingPid, int callingUid, IBinder token, + boolean keepAwake) { + try { + if (mActiveSession == null || token != mActiveSession.mToken) { + Slog.w(TAG, "setKeepAwake does not match active session"); + return; + } + mAm.setVoiceKeepAwake(mActiveSession.mSession, keepAwake); + } catch (RemoteException e) { + throw new IllegalStateException("Unexpected remote error", e); + } + } - public void finishLocked(int callingPid, int callingUid, IBinder token) { + public void finishLocked(IBinder token) { if (mActiveSession == null || token != mActiveSession.mToken) { Slog.w(TAG, "finish does not match active session"); return; @@ -327,4 +263,11 @@ class VoiceInteractionManagerServiceImpl { Slog.w(TAG, "RemoteException while calling soundModelsChanged", e); } } + + @Override + public void sessionConnectionGone(VoiceInteractionSessionConnection connection) { + synchronized (mLock) { + finishLocked(connection.mToken); + } + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java new file mode 100644 index 0000000..9634ab8 --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2015 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.voiceinteraction; + +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.AppOpsManager; +import android.app.AssistContent; +import android.app.IActivityManager; +import android.content.ClipData; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.service.voice.IVoiceInteractionSession; +import android.service.voice.IVoiceInteractionSessionService; +import android.service.voice.VoiceInteractionService; +import android.util.Slog; +import android.view.IWindowManager; +import android.view.WindowManager; +import com.android.internal.app.IAssistScreenshotReceiver; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.os.IResultReceiver; + +import java.io.PrintWriter; +import java.util.ArrayList; + +final class VoiceInteractionSessionConnection implements ServiceConnection { + final static String TAG = "VoiceInteractionServiceManager"; + + final IBinder mToken = new Binder(); + final Object mLock; + final ComponentName mSessionComponentName; + final Intent mBindIntent; + final int mUser; + final Context mContext; + final Callback mCallback; + final int mCallingUid; + final IActivityManager mAm; + final IWindowManager mIWindowManager; + final AppOpsManager mAppOps; + final IBinder mPermissionOwner; + boolean mShown; + Bundle mShowArgs; + int mShowFlags; + boolean mBound; + boolean mFullyBound; + boolean mCanceled; + IVoiceInteractionSessionService mService; + IVoiceInteractionSession mSession; + IVoiceInteractor mInteractor; + boolean mHaveAssistData; + Bundle mAssistData; + boolean mHaveScreenshot; + Bitmap mScreenshot; + ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>(); + + IVoiceInteractionSessionShowCallback mShowCallback = + new IVoiceInteractionSessionShowCallback.Stub() { + @Override + public void onFailed() throws RemoteException { + synchronized (mLock) { + notifyPendingShowCallbacksFailedLocked(); + } + } + + @Override + public void onShown() throws RemoteException { + synchronized (mLock) { + // TODO: Figure out whether this is good enough or whether we need to hook into + // Window manager to actually wait for the window to be drawn. + notifyPendingShowCallbacksShownLocked(); + } + } + }; + + public interface Callback { + public void sessionConnectionGone(VoiceInteractionSessionConnection connection); + } + + final ServiceConnection mFullConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + } + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + + final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() { + @Override + public void send(int resultCode, Bundle resultData) throws RemoteException { + synchronized (mLock) { + if (mShown) { + mHaveAssistData = true; + mAssistData = resultData; + deliverSessionDataLocked(); + } + } + } + }; + + final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() { + @Override + public void send(Bitmap screenshot) throws RemoteException { + synchronized (mLock) { + if (mShown) { + mHaveScreenshot = true; + mScreenshot = screenshot; + deliverSessionDataLocked(); + } + } + } + }; + + public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user, + Context context, Callback callback, int callingUid) { + mLock = lock; + mSessionComponentName = component; + mUser = user; + mContext = context; + mCallback = callback; + mCallingUid = callingUid; + mAm = ActivityManagerNative.getDefault(); + mIWindowManager = IWindowManager.Stub.asInterface( + ServiceManager.getService(Context.WINDOW_SERVICE)); + mAppOps = context.getSystemService(AppOpsManager.class); + IBinder permOwner = null; + try { + permOwner = mAm.newUriPermissionOwner("voicesession:" + + component.flattenToShortString()); + } catch (RemoteException e) { + Slog.w("voicesession", "AM dead", e); + } + mPermissionOwner = permOwner; + mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); + mBindIntent.setComponent(mSessionComponentName); + mBound = mContext.bindServiceAsUser(mBindIntent, this, + Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY + | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser)); + if (mBound) { + try { + mIWindowManager.addWindowToken(mToken, + WindowManager.LayoutParams.TYPE_VOICE_INTERACTION); + } catch (RemoteException e) { + Slog.w(TAG, "Failed adding window token", e); + } + } else { + Slog.w(TAG, "Failed binding to voice interaction session service " + + mSessionComponentName); + } + } + + public boolean showLocked(Bundle args, int flags, + IVoiceInteractionSessionShowCallback showCallback) { + if (mBound) { + if (!mFullyBound) { + mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection, + Context.BIND_AUTO_CREATE|Context.BIND_TREAT_LIKE_ACTIVITY, + new UserHandle(mUser)); + } + mShown = true; + mShowArgs = args; + mShowFlags = flags; + mHaveAssistData = false; + if ((flags&VoiceInteractionService.START_WITH_ASSIST) != 0) { + if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid, + mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED) { + try { + mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL, + mAssistReceiver); + } catch (RemoteException e) { + } + } else { + mHaveAssistData = true; + mAssistData = null; + } + } else { + mAssistData = null; + } + mHaveScreenshot = false; + if ((flags&VoiceInteractionService.START_WITH_SCREENSHOT) != 0) { + if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid, + mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED) { + try { + mIWindowManager.requestAssistScreenshot(mScreenshotReceiver); + } catch (RemoteException e) { + } + } else { + mHaveScreenshot = true; + mScreenshot = null; + } + } else { + mScreenshot = null; + } + if (mSession != null) { + try { + mSession.show(mShowArgs, mShowFlags, showCallback); + mShowArgs = null; + mShowFlags = 0; + } catch (RemoteException e) { + } + deliverSessionDataLocked(); + } else if (showCallback != null) { + mPendingShowCallbacks.add(showCallback); + } + return true; + } + if (showCallback != null) { + try { + showCallback.onFailed(); + } catch (RemoteException e) { + } + } + return false; + } + + void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) { + if (!"content".equals(uri.getScheme())) { + return; + } + long ident = Binder.clearCallingIdentity(); + try { + // This will throw SecurityException for us. + mAm.checkGrantUriPermission(srcUid, null, ContentProvider.getUriWithoutUserId(uri), + mode, ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(srcUid))); + // No security exception, do the grant. + int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser); + uri = ContentProvider.getUriWithoutUserId(uri); + mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg, + uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser); + } catch (RemoteException e) { + } catch (SecurityException e) { + Slog.w(TAG, "Can't propagate permission", e); + } finally { + Binder.restoreCallingIdentity(ident); + } + + } + + void grantClipDataItemPermission(ClipData.Item item, int mode, int srcUid, int destUid, + String destPkg) { + if (item.getUri() != null) { + grantUriPermission(item.getUri(), mode, srcUid, destUid, destPkg); + } + Intent intent = item.getIntent(); + if (intent != null && intent.getData() != null) { + grantUriPermission(intent.getData(), mode, srcUid, destUid, destPkg); + } + } + + void grantClipDataPermissions(ClipData data, int mode, int srcUid, int destUid, + String destPkg) { + final int N = data.getItemCount(); + for (int i=0; i<N; i++) { + grantClipDataItemPermission(data.getItemAt(i), mode, srcUid, destUid, destPkg); + } + } + + void deliverSessionDataLocked() { + if (mSession == null) { + return; + } + if (mHaveAssistData) { + if (mAssistData != null) { + int uid = mAssistData.getInt(Intent.EXTRA_ASSIST_UID, -1); + if (uid >= 0) { + Bundle assistContext = mAssistData.getBundle(Intent.EXTRA_ASSIST_CONTEXT); + if (assistContext != null) { + AssistContent content = AssistContent.getAssistContent(assistContext); + if (content != null) { + Intent intent = content.getIntent(); + if (intent != null) { + ClipData data = intent.getClipData(); + if (data != null && Intent.isAccessUriMode(intent.getFlags())) { + grantClipDataPermissions(data, intent.getFlags(), uid, + mCallingUid, mSessionComponentName.getPackageName()); + } + } + ClipData data = content.getClipData(); + if (data != null) { + grantClipDataPermissions(data, + Intent.FLAG_GRANT_READ_URI_PERMISSION, + uid, mCallingUid, mSessionComponentName.getPackageName()); + } + } + } + } + } + try { + mSession.handleAssist(mAssistData); + } catch (RemoteException e) { + } + mAssistData = null; + mHaveAssistData = false; + } + if (mHaveScreenshot) { + try { + mSession.handleScreenshot(mScreenshot); + } catch (RemoteException e) { + } + mScreenshot = null; + mHaveScreenshot = false; + } + } + + public boolean hideLocked() { + if (mBound) { + if (mShown) { + mShown = false; + mShowArgs = null; + mShowFlags = 0; + mHaveAssistData = false; + mAssistData = null; + if (mSession != null) { + try { + mSession.hide(); + } catch (RemoteException e) { + } + } + try { + mAm.revokeUriPermissionFromOwner(mPermissionOwner, null, + Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + mUser); + } catch (RemoteException e) { + } + if (mSession != null) { + try { + mAm.finishVoiceTask(mSession); + } catch (RemoteException e) { + } + } + } + if (mFullyBound) { + mContext.unbindService(mFullConnection); + mFullyBound = false; + } + return true; + } + return false; + } + + public boolean deliverNewSessionLocked(IVoiceInteractionSession session, + IVoiceInteractor interactor) { + mSession = session; + mInteractor = interactor; + if (mShown) { + try { + session.show(mShowArgs, mShowFlags, mShowCallback); + mShowArgs = null; + mShowFlags = 0; + } catch (RemoteException e) { + } + deliverSessionDataLocked(); + } + return true; + } + + private void notifyPendingShowCallbacksShownLocked() { + for (int i = 0; i < mPendingShowCallbacks.size(); i++) { + try { + mPendingShowCallbacks.get(i).onShown(); + } catch (RemoteException e) { + } + } + mPendingShowCallbacks.clear(); + } + + private void notifyPendingShowCallbacksFailedLocked() { + for (int i = 0; i < mPendingShowCallbacks.size(); i++) { + try { + mPendingShowCallbacks.get(i).onFailed(); + } catch (RemoteException e) { + } + } + mPendingShowCallbacks.clear(); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mService = IVoiceInteractionSessionService.Stub.asInterface(service); + if (!mCanceled) { + try { + mService.newSession(mToken, mShowArgs, mShowFlags); + } catch (RemoteException e) { + Slog.w(TAG, "Failed adding window token", e); + } + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mCallback.sessionConnectionGone(this); + mService = null; + } + + public void cancel() { + mCanceled = true; + if (mBound) { + if (mSession != null) { + try { + mSession.destroy(); + } catch (RemoteException e) { + Slog.w(TAG, "Voice interation session already dead"); + } + } + if (mSession != null) { + try { + mAm.finishVoiceTask(mSession); + } catch (RemoteException e) { + } + } + mContext.unbindService(this); + try { + mIWindowManager.removeWindowToken(mToken); + } catch (RemoteException e) { + Slog.w(TAG, "Failed removing window token", e); + } + mBound = false; + mService = null; + mSession = null; + mInteractor = null; + } + if (mFullyBound) { + mContext.unbindService(mFullConnection); + mFullyBound = false; + } + } + + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mToken="); pw.println(mToken); + pw.print(prefix); pw.print("mShown="); pw.println(mShown); + pw.print(prefix); pw.print("mShowArgs="); pw.println(mShowArgs); + pw.print(prefix); pw.print("mShowFlags=0x"); pw.println(Integer.toHexString(mShowFlags)); + pw.print(prefix); pw.print("mBound="); pw.println(mBound); + if (mBound) { + pw.print(prefix); pw.print("mService="); pw.println(mService); + pw.print(prefix); pw.print("mSession="); pw.println(mSession); + pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor); + } + pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData); + if (mHaveAssistData) { + pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData); + } + } +}; |