diff options
Diffstat (limited to 'services')
148 files changed, 8475 insertions, 3766 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index bfbf0ac..2781890 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1040,7 +1040,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private void addServiceLocked(Service service, UserState userState) { try { - service.linkToOwnDeathLocked(); + service.onAdded(); userState.mBoundServices.add(service); userState.mComponentNameToServiceMap.put(service.mComponentName, service); } catch (RemoteException re) { @@ -1056,7 +1056,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private void removeServiceLocked(Service service, UserState userState) { userState.mBoundServices.remove(service); userState.mComponentNameToServiceMap.remove(service.mComponentName); - service.unlinkToOwnDeathLocked(); + service.onRemoved(); } /** @@ -1931,6 +1931,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final ResolveInfo mResolveInfo; + final IBinder mOverlayWindowToken = new Binder(); + // the events pending events to be dispatched to this service final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>(); @@ -2112,7 +2114,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mBindingServices.remove(mComponentName); mWasConnectedAndDied = false; try { - mServiceInterface.setConnection(this, mId); + mServiceInterface.init(this, mId, mOverlayWindowToken); onUserStateChangedLocked(userState); } catch (RemoteException re) { Slog.w(LOG_TAG, "Error while setting connection for service: " @@ -2602,6 +2604,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { /* do nothing - #binderDied takes care */ } + public void onAdded() throws RemoteException { + linkToOwnDeathLocked(); + final long identity = Binder.clearCallingIdentity(); + try { + mWindowManagerService.addWindowToken(mOverlayWindowToken, + WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + public void onRemoved() { + final long identity = Binder.clearCallingIdentity(); + try { + mWindowManagerService.removeWindowToken(mOverlayWindowToken, true); + } finally { + Binder.restoreCallingIdentity(identity); + } + unlinkToOwnDeathLocked(); + } + public void linkToOwnDeathLocked() throws RemoteException { mService.linkToDeath(this, 0); } @@ -2614,7 +2637,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { try { // Clear the proxy in the other process so this // IAccessibilityServiceConnection can be garbage collected. - mServiceInterface.setConnection(null, mId); + mServiceInterface.init(null, mId, null); } catch (RemoteException re) { /* ignore */ } @@ -3156,7 +3179,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case WindowManager.LayoutParams.TYPE_STATUS_BAR: case WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL: case WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL: - case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: @@ -3165,6 +3187,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return AccessibilityWindowInfo.TYPE_SYSTEM; } + case WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY: { + return AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY; + } + default: { return -1; } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index fea1a7a..6c2681b 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -153,7 +153,7 @@ import javax.crypto.spec.SecretKeySpec; import libcore.io.IoUtils; -public class BackupManagerService extends IBackupManager.Stub { +public class BackupManagerService { private static final String TAG = "BackupManagerService"; private static final boolean DEBUG = true; @@ -322,8 +322,12 @@ public class BackupManagerService extends IBackupManager.Stub { // Watch the device provisioning operation during setup ContentObserver mProvisionedObserver; - static BackupManagerService sInstance; - static BackupManagerService getInstance() { + // The published binder is actually to a singleton trampoline object that calls + // through to the proper code. This indirection lets us turn down the heavy + // implementation object on the fly without disturbing binders that have been + // cached elsewhere in the system. + static Trampoline sInstance; + static Trampoline getInstance() { // Always constructed during system bringup, so no need to lazy-init return sInstance; } @@ -332,7 +336,7 @@ public class BackupManagerService extends IBackupManager.Stub { public Lifecycle(Context context) { super(context); - sInstance = new BackupManagerService(context); + sInstance = new Trampoline(context); } @Override @@ -342,11 +346,17 @@ public class BackupManagerService extends IBackupManager.Stub { @Override public void onBootPhase(int phase) { - if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + sInstance.initialize(UserHandle.USER_OWNER); + } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { ContentResolver r = sInstance.mContext.getContentResolver(); boolean areEnabled = Settings.Secure.getInt(r, Settings.Secure.BACKUP_ENABLED, 0) != 0; - sInstance.setBackupEnabled(areEnabled); + try { + sInstance.setBackupEnabled(areEnabled); + } catch (RemoteException e) { + // can't happen; it's a local object + } } } } @@ -934,7 +944,7 @@ public class BackupManagerService extends IBackupManager.Stub { // ----- Main service implementation ----- - public BackupManagerService(Context context) { + public BackupManagerService(Context context, Trampoline parent) { mContext = context; mPackageManager = context.getPackageManager(); mPackageManagerBinder = AppGlobals.getPackageManager(); @@ -944,7 +954,7 @@ public class BackupManagerService extends IBackupManager.Stub { mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount")); - mBackupManagerBinder = asInterface(asBinder()); + mBackupManagerBinder = Trampoline.asInterface(parent.asBinder()); // spin up the backup/restore handler thread mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND); @@ -1451,7 +1461,6 @@ public class BackupManagerService extends IBackupManager.Stub { return false; } - @Override public boolean setBackupPassword(String currentPw, String newPw) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "setBackupPassword"); @@ -1532,7 +1541,6 @@ public class BackupManagerService extends IBackupManager.Stub { return false; } - @Override public boolean hasBackupPassword() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "hasBackupPassword"); @@ -8145,7 +8153,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // // This is the variant used by 'adb backup'; it requires on-screen confirmation // by the user because it can be used to offload data over untrusted USB. - @Override public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem, boolean compress, String[] pkgList) { @@ -8217,7 +8224,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } - @Override public void fullTransportBackup(String[] pkgNames) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullTransportBackup"); @@ -8247,7 +8253,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } - @Override public void fullRestore(ParcelFileDescriptor fd) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore"); @@ -8343,7 +8348,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF // Confirm that the previously-requested full backup/restore operation can proceed. This // is used to require a user-facing disclosure about the operation. - @Override public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword, String encPpassword, IFullBackupRestoreObserver observer) { if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token @@ -8391,8 +8395,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } - // Enable/disable the backup service - @Override + // Enable/disable backups public void setBackupEnabled(boolean enable) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "setBackupEnabled"); @@ -8798,7 +8801,6 @@ 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. - @Override public void opComplete(int token) { if (MORE_DEBUG) Slog.v(TAG, "opComplete: " + Integer.toHexString(token)); Operation op = null; @@ -9147,7 +9149,6 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF } } - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java index 601f15e..7ad7657c 100644 --- a/services/backup/java/com/android/server/backup/FullBackupJob.java +++ b/services/backup/java/com/android/server/backup/FullBackupJob.java @@ -59,7 +59,7 @@ public class FullBackupJob extends JobService { @Override public boolean onStartJob(JobParameters params) { mParams = params; - BackupManagerService service = BackupManagerService.getInstance(); + Trampoline service = BackupManagerService.getInstance(); return service.beginFullBackup(this); } @@ -67,7 +67,7 @@ public class FullBackupJob extends JobService { public boolean onStopJob(JobParameters params) { if (mParams != null) { mParams = null; - BackupManagerService service = BackupManagerService.getInstance(); + Trampoline service = BackupManagerService.getInstance(); service.endFullBackup(); } return false; diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java new file mode 100644 index 0000000..8bd7132 --- /dev/null +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -0,0 +1,342 @@ +/* + * 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.backup; + +import android.app.backup.IBackupManager; +import android.app.backup.IFullBackupRestoreObserver; +import android.app.backup.IRestoreSession; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Environment; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.util.Slog; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; + +public class Trampoline extends IBackupManager.Stub { + static final String TAG = "BackupManagerService"; + static final boolean DEBUG_TRAMPOLINE = false; + + // When this file is present, the backup service is inactive + static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress"; + + // Product-level suppression of backup/restore + static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; + + final Context mContext; + final File mSuppressFile; // existence testing & creating synchronized on 'this' + final boolean mGlobalDisable; + volatile BackupManagerService mService; + + public Trampoline(Context context) { + mContext = context; + File dir = new File(Environment.getSecureDataDirectory(), "backup"); + dir.mkdirs(); + mSuppressFile = new File(dir, BACKUP_SUPPRESS_FILENAME); + mGlobalDisable = SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); + } + + // internal control API + public void initialize(final int whichUser) { + // Note that only the owner user is currently involved in backup/restore + if (whichUser == UserHandle.USER_OWNER) { + // Does this product support backup/restore at all? + if (mGlobalDisable) { + Slog.i(TAG, "Backup/restore not supported"); + return; + } + + synchronized (this) { + if (!mSuppressFile.exists()) { + mService = new BackupManagerService(mContext, this); + } else { + Slog.i(TAG, "Backup inactive in user " + whichUser); + } + } + } + } + + public void setBackupServiceActive(final int userHandle, boolean makeActive) { + // Only the DPM should be changing the active state of backup + final int caller = Binder.getCallingUid(); + if (caller != Process.SYSTEM_UID + && caller != Process.ROOT_UID) { + throw new SecurityException("No permission to configure backup activity"); + } + + if (mGlobalDisable) { + Slog.i(TAG, "Backup/restore not supported"); + return; + } + + if (userHandle == UserHandle.USER_OWNER) { + synchronized (this) { + if (makeActive != isBackupServiceActive(userHandle)) { + Slog.i(TAG, "Making backup " + + (makeActive ? "" : "in") + "active in user " + userHandle); + if (makeActive) { + mService = new BackupManagerService(mContext, this); + mSuppressFile.delete(); + } else { + mService = null; + try { + mSuppressFile.createNewFile(); + } catch (IOException e) { + Slog.e(TAG, "Unable to persist backup service inactivity"); + } + } + } + } + } + } + + /** + * Querying activity state of backup service. Calling this method before initialize yields + * undefined result. + * @param userHandle The user in which the activity state of backup service is queried. + * @return true if the service is active. + */ + public boolean isBackupServiceActive(final int userHandle) { + if (userHandle == UserHandle.USER_OWNER) { + synchronized (this) { + return mService != null; + } + } + return false; + } + + // IBackupManager binder API + @Override + public void dataChanged(String packageName) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.dataChanged(packageName); + } + } + + @Override + public void clearBackupData(String transportName, String packageName) + throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.clearBackupData(transportName, packageName); + } + } + + @Override + public void agentConnected(String packageName, IBinder agent) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.agentConnected(packageName, agent); + } + } + + @Override + public void agentDisconnected(String packageName) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.agentDisconnected(packageName); + } + } + + @Override + public void restoreAtInstall(String packageName, int token) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.restoreAtInstall(packageName, token); + } + } + + @Override + public void setBackupEnabled(boolean isEnabled) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.setBackupEnabled(isEnabled); + } + } + + @Override + public void setAutoRestore(boolean doAutoRestore) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.setAutoRestore(doAutoRestore); + } + } + + @Override + public void setBackupProvisioned(boolean isProvisioned) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.setBackupProvisioned(isProvisioned); + } + } + + @Override + public boolean isBackupEnabled() throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.isBackupEnabled() : false; + } + + @Override + public boolean setBackupPassword(String currentPw, String newPw) throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.setBackupPassword(currentPw, newPw) : false; + } + + @Override + public boolean hasBackupPassword() throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.hasBackupPassword() : false; + } + + @Override + public void backupNow() throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.backupNow(); + } + } + + @Override + public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs, + boolean includeShared, boolean doWidgets, boolean allApps, + boolean allIncludesSystem, boolean doCompress, String[] packageNames) + throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.fullBackup(fd, includeApks, includeObbs, includeShared, doWidgets, + allApps, allIncludesSystem, doCompress, packageNames); + } + } + + @Override + public void fullTransportBackup(String[] packageNames) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.fullTransportBackup(packageNames); + } + } + + @Override + public void fullRestore(ParcelFileDescriptor fd) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.fullRestore(fd); + } + } + + @Override + public void acknowledgeFullBackupOrRestore(int token, boolean allow, String curPassword, + String encryptionPassword, IFullBackupRestoreObserver observer) + throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.acknowledgeFullBackupOrRestore(token, allow, + curPassword, encryptionPassword, observer); + } + } + + @Override + public String getCurrentTransport() throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.getCurrentTransport() : null; + } + + @Override + public String[] listAllTransports() throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.listAllTransports() : null; + } + + @Override + public String selectBackupTransport(String transport) throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.selectBackupTransport(transport) : null; + } + + @Override + public Intent getConfigurationIntent(String transport) throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.getConfigurationIntent(transport) : null; + } + + @Override + public String getDestinationString(String transport) throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.getDestinationString(transport) : null; + } + + @Override + public Intent getDataManagementIntent(String transport) throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.getDataManagementIntent(transport) : null; + } + + @Override + public String getDataManagementLabel(String transport) throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.getDataManagementLabel(transport) : null; + } + + @Override + public IRestoreSession beginRestoreSession(String packageName, String transportID) + throws RemoteException { + BackupManagerService svc = mService; + return (svc != null) ? svc.beginRestoreSession(packageName, transportID) : null; + } + + @Override + public void opComplete(int token) throws RemoteException { + BackupManagerService svc = mService; + if (svc != null) { + svc.opComplete(token); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + BackupManagerService svc = mService; + if (svc != null) { + svc.dump(fd, pw, args); + } else { + pw.println("Inactive"); + } + } + + // Full backup/restore entry points - non-Binder; called directly + // by the full-backup scheduled job + /* package */ boolean beginFullBackup(FullBackupJob scheduledJob) { + BackupManagerService svc = mService; + return (svc != null) ? svc.beginFullBackup(scheduledJob) : false; + } + + /* package */ void endFullBackup() { + BackupManagerService svc = mService; + if (svc != null) { + svc.endFullBackup(); + } + } +} diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 8b524dd..831af85 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -75,6 +75,12 @@ class AlarmManagerService extends SystemService { // warning message. The time duration is in milliseconds. private static final long LATE_ALARM_THRESHOLD = 10 * 1000; + // Minimum futurity of a new alarm + private static final long MIN_FUTURITY = 5 * 1000; // 5 seconds, in millis + + // Minimum alarm recurrence interval + private static final long MIN_INTERVAL = 60 * 1000; // one minute, in millis + private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP; private static final int RTC_MASK = 1 << RTC; private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << ELAPSED_REALTIME_WAKEUP; @@ -696,6 +702,15 @@ class AlarmManagerService extends SystemService { windowLength = AlarmManager.INTERVAL_HOUR; } + // Sanity check the recurrence interval. This will catch people who supply + // seconds when the API expects milliseconds. + if (interval > 0 && interval < MIN_INTERVAL) { + Slog.w(TAG, "Suspiciously short interval " + interval + + " millis; expanding to " + (int)(MIN_INTERVAL/1000) + + " seconds"); + interval = MIN_INTERVAL; + } + if (type < RTC_WAKEUP || type > ELAPSED_REALTIME) { throw new IllegalArgumentException("Invalid alarm type " + type); } @@ -709,7 +724,11 @@ class AlarmManagerService extends SystemService { } final long nowElapsed = SystemClock.elapsedRealtime(); - final long triggerElapsed = convertToElapsed(triggerAtTime, type); + final long nominalTrigger = convertToElapsed(triggerAtTime, type); + // Try to prevent spamming by making sure we aren't firing alarms in the immediate future + final long minTrigger = nowElapsed + MIN_FUTURITY; + final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger; + final long maxElapsed; if (windowLength == AlarmManager.WINDOW_EXACT) { maxElapsed = triggerElapsed; @@ -1105,7 +1124,7 @@ class AlarmManagerService extends SystemService { if (DEBUG_ALARM_CLOCK) { Log.v(TAG, "Found AlarmClockInfo at " + - formatNextAlarm(getContext(), a.alarmClock) + + formatNextAlarm(getContext(), a.alarmClock, userId) + " for user " + userId); } @@ -1143,7 +1162,7 @@ class AlarmManagerService extends SystemService { if (alarmClock != null) { if (DEBUG_ALARM_CLOCK) { Log.v(TAG, "Next AlarmClockInfoForUser(" + userId + "): " + - formatNextAlarm(getContext(), alarmClock)); + formatNextAlarm(getContext(), alarmClock, userId)); } mNextAlarmClockForUser.put(userId, alarmClock); } else { @@ -1185,7 +1204,7 @@ class AlarmManagerService extends SystemService { AlarmManager.AlarmClockInfo alarmClock = pendingUsers.valueAt(i); Settings.System.putStringForUser(getContext().getContentResolver(), Settings.System.NEXT_ALARM_FORMATTED, - formatNextAlarm(getContext(), alarmClock), + formatNextAlarm(getContext(), alarmClock, userId), userId); getContext().sendBroadcastAsUser(NEXT_ALARM_CLOCK_CHANGED_INTENT, @@ -1196,8 +1215,9 @@ class AlarmManagerService extends SystemService { /** * Formats an alarm like platform/packages/apps/DeskClock used to. */ - private static String formatNextAlarm(final Context context, AlarmManager.AlarmClockInfo info) { - String skeleton = DateFormat.is24HourFormat(context) ? "EHm" : "Ehma"; + private static String formatNextAlarm(final Context context, AlarmManager.AlarmClockInfo info, + int userId) { + String skeleton = DateFormat.is24HourFormat(context, userId) ? "EHm" : "Ehma"; String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton); return (info == null) ? "" : DateFormat.format(pattern, info.getTriggerTime()).toString(); diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 636228b..ebdd386 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -44,6 +44,10 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + class BluetoothManagerService extends IBluetoothManager.Stub { private static final String TAG = "BluetoothManagerService"; private static final boolean DBG = true; @@ -1282,4 +1286,21 @@ class BluetoothManagerService extends IBluetoothManager.Stub { // todo: notify user to power down and power up phone to make bluetooth work. } } + + @Override + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println("enabled: " + mEnable); + writer.println("state: " + mState); + writer.println("address: " + mAddress); + writer.println("name: " + mName); + if (mBluetooth == null) { + writer.println("Bluetooth Service not connected"); + } else { + try { + writer.println(mBluetooth.dump()); + } catch (RemoteException re) { + writer.println("RemoteException while calling Bluetooth Service"); + } + } + } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 6a6dcaf..8b3739d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -23,18 +23,19 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; import static android.net.ConnectivityManager.TYPE_BLUETOOTH; import static android.net.ConnectivityManager.TYPE_DUMMY; import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; -import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; +import static android.net.ConnectivityManager.TYPE_MOBILE_CBS; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; -import static android.net.ConnectivityManager.TYPE_MOBILE_IMS; -import static android.net.ConnectivityManager.TYPE_MOBILE_CBS; -import static android.net.ConnectivityManager.TYPE_MOBILE_IA; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +import static android.net.ConnectivityManager.TYPE_MOBILE_IA; +import static android.net.ConnectivityManager.TYPE_MOBILE_IMS; +import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; +import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.TYPE_PROXY; +import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIMAX; -import static android.net.ConnectivityManager.TYPE_PROXY; import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; @@ -64,7 +65,6 @@ import android.net.INetworkStatsService; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LinkProperties.CompareResult; -import android.net.LinkQualityInfo; import android.net.MobileDataStateTracker; import android.net.Network; import android.net.NetworkAgent; @@ -184,7 +184,8 @@ import javax.net.ssl.SSLSession; /** * @hide */ -public class ConnectivityService extends IConnectivityManager.Stub { +public class ConnectivityService extends IConnectivityManager.Stub + implements PendingIntent.OnFinished { private static final String TAG = "ConnectivityService"; private static final boolean DBG = true; @@ -238,8 +239,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean mLockdownEnabled; private LockdownVpnTracker mLockdownTracker; - private Nat464Xlat mClat; - /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */ private Object mRulesLock = new Object(); /** Currently active network rules by UID. */ @@ -256,7 +255,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Context mContext; private int mNetworkPreference; - private int mActiveDefaultNetwork = TYPE_NONE; // 0 is full bad, 100 is full good private int mDefaultInetConditionPublished = 0; @@ -384,6 +382,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private static final int EVENT_SYSTEM_READY = 25; + /** + * used to add a network request with a pending intent + * includes a NetworkRequestInfo + */ + private static final int EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT = 26; + + /** + * used to remove a pending intent and its associated network request. + * arg1 = UID of caller + * obj = PendingIntent + */ + private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27; + /** Handler used for internal events. */ final private InternalHandler mHandler; @@ -397,6 +408,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private String mNetTransitionWakeLockCausedBy = ""; private int mNetTransitionWakeLockSerialNumber; private int mNetTransitionWakeLockTimeout; + private final PowerManager.WakeLock mPendingIntentWakeLock; private InetAddress mDefaultDns; @@ -651,6 +663,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mNetTransitionWakeLockTimeout = mContext.getResources().getInteger( com.android.internal.R.integer.config_networkTransitionTimeout); + mPendingIntentWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mNetTrackers = new NetworkStateTracker[ ConnectivityManager.MAX_NETWORK_TYPE+1]; @@ -689,6 +702,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { // ignore it - leave the entry null } } + + // Forcibly add TYPE_VPN as a supported type, if it has not already been added via config. + if (mNetConfigs[TYPE_VPN] == null) { + // mNetConfigs is used only for "restore time", which isn't applicable to VPNs, so we + // don't need to add TYPE_VPN to mNetConfigs. + mLegacyTypeTracker.addSupportedType(TYPE_VPN); + mNetworksDefined++; // used only in the log() statement below. + } + if (VDBG) log("mNetworksDefined=" + mNetworksDefined); mProtectedNetworks = new ArrayList<Integer>(); @@ -715,12 +737,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { intentFilter.addAction(Intent.ACTION_USER_STOPPING); mContext.registerReceiverAsUser( mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); - mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler); try { mNetd.registerObserver(mTethering); mNetd.registerObserver(mDataActivityObserver); - mNetd.registerObserver(mClat); } catch (RemoteException e) { loge("Error registering observer :" + e); } @@ -797,20 +817,85 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** - * Check if UID should be blocked from using the network represented by the given networkType. - * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type. - */ - private boolean isNetworkBlocked(int networkType, int uid) { - return isNetworkWithLinkPropertiesBlocked(getLinkPropertiesForType(networkType), uid); + private NetworkState getFilteredNetworkState(int networkType, int uid) { + NetworkInfo info = null; + LinkProperties lp = null; + NetworkCapabilities nc = null; + Network network = null; + + if (mLegacyTypeTracker.isTypeSupported(networkType)) { + NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); + if (nai != null) { + synchronized (nai) { + info = new NetworkInfo(nai.networkInfo); + lp = new LinkProperties(nai.linkProperties); + nc = new NetworkCapabilities(nai.networkCapabilities); + network = new Network(nai.network); + } + info.setType(networkType); + } else { + info = new NetworkInfo(networkType, 0, getNetworkTypeName(networkType), ""); + info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null); + info.setIsAvailable(true); + lp = new LinkProperties(); + nc = new NetworkCapabilities(); + network = null; + } + info = getFilteredNetworkInfo(info, lp, uid); + } + + return new NetworkState(info, lp, nc, network); } - /** - * Check if UID should be blocked from using the network represented by the given - * NetworkAgentInfo. - */ - private boolean isNetworkBlocked(NetworkAgentInfo nai, int uid) { - return isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid); + private NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) { + if (network == null) { + return null; + } + synchronized (mNetworkForNetId) { + return mNetworkForNetId.get(network.netId); + } + }; + + private NetworkState getUnfilteredActiveNetworkState(int uid) { + NetworkInfo info = null; + LinkProperties lp = null; + NetworkCapabilities nc = null; + Network network = null; + + NetworkAgentInfo nai = mNetworkForRequestId.get(mDefaultRequest.requestId); + + if (!mLockdownEnabled) { + int user = UserHandle.getUserId(uid); + synchronized (mVpns) { + Vpn vpn = mVpns.get(user); + if (vpn != null && vpn.appliesToUid(uid)) { + // getUnderlyingNetworks() returns: + // null => the VPN didn't specify anything, so we use the default. + // empty array => the VPN explicitly said "no default network". + // non-empty array => the VPN specified one or more default networks; we use the + // first one. + Network[] networks = vpn.getUnderlyingNetworks(); + if (networks != null) { + if (networks.length > 0) { + nai = getNetworkAgentInfoForNetwork(networks[0]); + } else { + nai = null; + } + } + } + } + } + + if (nai != null) { + synchronized (nai) { + info = new NetworkInfo(nai.networkInfo); + lp = new LinkProperties(nai.linkProperties); + nc = new NetworkCapabilities(nai.networkCapabilities); + network = new Network(nai.network); + } + } + + return new NetworkState(info, lp, nc, network); } /** @@ -837,40 +922,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { /** * Return a filtered {@link NetworkInfo}, potentially marked * {@link DetailedState#BLOCKED} based on - * {@link #isNetworkBlocked}. - * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type. + * {@link #isNetworkWithLinkPropertiesBlocked}. */ - private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) { - NetworkInfo info = getNetworkInfoForType(networkType); - return getFilteredNetworkInfo(info, networkType, uid); - } - - /* - * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type. - */ - private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, int networkType, int uid) { - if (isNetworkBlocked(networkType, uid)) { - // network is blocked; clone and override state - info = new NetworkInfo(info); - info.setDetailedState(DetailedState.BLOCKED, null, null); - if (VDBG) log("returning Blocked NetworkInfo"); - } - if (mLockdownTracker != null) { - info = mLockdownTracker.augmentNetworkInfo(info); - if (VDBG) log("returning Locked NetworkInfo"); - } - return info; - } - - private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid) { - NetworkInfo info = nai.networkInfo; - if (isNetworkBlocked(nai, uid)) { + private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, LinkProperties lp, int uid) { + if (info != null && isNetworkWithLinkPropertiesBlocked(lp, uid)) { // network is blocked; clone and override state info = new NetworkInfo(info); info.setDetailedState(DetailedState.BLOCKED, null, null); if (DBG) log("returning Blocked NetworkInfo"); } - if (mLockdownTracker != null) { + if (info != null && mLockdownTracker != null) { info = mLockdownTracker.augmentNetworkInfo(info); if (DBG) log("returning Locked NetworkInfo"); } @@ -888,7 +949,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { public NetworkInfo getActiveNetworkInfo() { enforceAccessPermission(); final int uid = Binder.getCallingUid(); - return getNetworkInfo(mActiveDefaultNetwork, uid); + NetworkState state = getUnfilteredActiveNetworkState(uid); + return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid); } /** @@ -923,8 +985,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkInfo provNi = getProvisioningNetworkInfo(); if (provNi == null) { - final int uid = Binder.getCallingUid(); - provNi = getNetworkInfo(mActiveDefaultNetwork, uid); + provNi = getActiveNetworkInfo(); } if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi); return provNi; @@ -932,62 +993,50 @@ public class ConnectivityService extends IConnectivityManager.Stub { public NetworkInfo getActiveNetworkInfoUnfiltered() { enforceAccessPermission(); - if (isNetworkTypeValid(mActiveDefaultNetwork)) { - return getNetworkInfoForType(mActiveDefaultNetwork); - } - return null; + final int uid = Binder.getCallingUid(); + NetworkState state = getUnfilteredActiveNetworkState(uid); + return state.networkInfo; } @Override public NetworkInfo getActiveNetworkInfoForUid(int uid) { enforceConnectivityInternalPermission(); - return getNetworkInfo(mActiveDefaultNetwork, uid); + NetworkState state = getUnfilteredActiveNetworkState(uid); + return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid); } @Override public NetworkInfo getNetworkInfo(int networkType) { enforceAccessPermission(); final int uid = Binder.getCallingUid(); - return getNetworkInfo(networkType, uid); - } - - private NetworkInfo getNetworkInfo(int networkType, int uid) { - NetworkInfo info = null; - if (isNetworkTypeValid(networkType)) { - if (getNetworkInfoForType(networkType) != null) { - info = getFilteredNetworkInfo(networkType, uid); - } - } - return info; + NetworkState state = getFilteredNetworkState(networkType, uid); + return state.networkInfo; } @Override public NetworkInfo getNetworkInfoForNetwork(Network network) { enforceAccessPermission(); - if (network == null) return null; - final int uid = Binder.getCallingUid(); - NetworkAgentInfo nai = null; - synchronized (mNetworkForNetId) { - nai = mNetworkForNetId.get(network.netId); - } - if (nai == null) return null; - synchronized (nai) { - if (nai.networkInfo == null) return null; - - return getFilteredNetworkInfo(nai, uid); + NetworkInfo info = null; + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai != null) { + synchronized (nai) { + info = new NetworkInfo(nai.networkInfo); + info = getFilteredNetworkInfo(info, nai.linkProperties, uid); + } } + return info; } @Override public NetworkInfo[] getAllNetworkInfo() { enforceAccessPermission(); - final int uid = Binder.getCallingUid(); final ArrayList<NetworkInfo> result = Lists.newArrayList(); for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE; networkType++) { - if (getNetworkInfoForType(networkType) != null) { - result.add(getFilteredNetworkInfo(networkType, uid)); + NetworkInfo info = getNetworkInfo(networkType); + if (info != null) { + result.add(info); } } return result.toArray(new NetworkInfo[result.size()]); @@ -997,11 +1046,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { public Network getNetworkForType(int networkType) { enforceAccessPermission(); final int uid = Binder.getCallingUid(); - if (isNetworkBlocked(networkType, uid)) { - return null; + NetworkState state = getFilteredNetworkState(networkType, uid); + if (!isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid)) { + return state.network; } - NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - return (nai == null) ? null : nai.network; + return null; } @Override @@ -1019,7 +1068,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public boolean isNetworkSupported(int networkType) { enforceAccessPermission(); - return (isNetworkTypeValid(networkType) && (getNetworkInfoForType(networkType) != null)); + return mLegacyTypeTracker.isTypeSupported(networkType); } /** @@ -1032,14 +1081,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ @Override public LinkProperties getActiveLinkProperties() { - return getLinkPropertiesForType(mActiveDefaultNetwork); + enforceAccessPermission(); + final int uid = Binder.getCallingUid(); + NetworkState state = getUnfilteredActiveNetworkState(uid); + return state.linkProperties; } @Override public LinkProperties getLinkPropertiesForType(int networkType) { enforceAccessPermission(); - if (isNetworkTypeValid(networkType)) { - return getLinkPropertiesForTypeInternal(networkType); + NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); + if (nai != null) { + synchronized (nai) { + return new LinkProperties(nai.linkProperties); + } } return null; } @@ -1048,11 +1103,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public LinkProperties getLinkProperties(Network network) { enforceAccessPermission(); - NetworkAgentInfo nai = null; - synchronized (mNetworkForNetId) { - nai = mNetworkForNetId.get(network.netId); - } - + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai != null) { synchronized (nai) { return new LinkProperties(nai.linkProperties); @@ -1064,10 +1115,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public NetworkCapabilities getNetworkCapabilities(Network network) { enforceAccessPermission(); - NetworkAgentInfo nai = null; - synchronized (mNetworkForNetId) { - nai = mNetworkForNetId.get(network.netId); - } + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai != null) { synchronized (nai) { return new NetworkCapabilities(nai.networkCapabilities); @@ -1083,36 +1131,22 @@ public class ConnectivityService extends IConnectivityManager.Stub { final ArrayList<NetworkState> result = Lists.newArrayList(); for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE; networkType++) { - if (getNetworkInfoForType(networkType) != null) { - final NetworkInfo info = getFilteredNetworkInfo(networkType, uid); - final LinkProperties lp = getLinkPropertiesForTypeInternal(networkType); - final NetworkCapabilities netcap = getNetworkCapabilitiesForType(networkType); - result.add(new NetworkState(info, lp, netcap)); + NetworkState state = getFilteredNetworkState(networkType, uid); + if (state.networkInfo != null) { + result.add(state); } } return result.toArray(new NetworkState[result.size()]); } - private NetworkState getNetworkStateUnchecked(int networkType) { - if (isNetworkTypeValid(networkType)) { - NetworkInfo info = getNetworkInfoForType(networkType); - if (info != null) { - return new NetworkState(info, - getLinkPropertiesForTypeInternal(networkType), - getNetworkCapabilitiesForType(networkType)); - } - } - return null; - } - @Override public NetworkQuotaInfo getActiveNetworkQuotaInfo() { enforceAccessPermission(); - + final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork); - if (state != null) { + final NetworkState state = getUnfilteredActiveNetworkState(uid); + if (state.networkInfo != null) { try { return mPolicyManager.getNetworkQuotaInfo(state); } catch (RemoteException e) { @@ -1127,17 +1161,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public boolean isActiveNetworkMetered() { enforceAccessPermission(); + final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { - return isNetworkMeteredUnchecked(mActiveDefaultNetwork); + return isActiveNetworkMeteredUnchecked(uid); } finally { Binder.restoreCallingIdentity(token); } } - private boolean isNetworkMeteredUnchecked(int networkType) { - final NetworkState state = getNetworkStateUnchecked(networkType); - if (state != null) { + private boolean isActiveNetworkMeteredUnchecked(int uid) { + final NetworkState state = getUnfilteredActiveNetworkState(uid); + if (state.networkInfo != null) { try { return mPolicyManager.isNetworkMetered(state); } catch (RemoteException e) { @@ -1308,16 +1343,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { // kick off connectivity change broadcast for active network, since // global background policy change is radical. - final int networkType = mActiveDefaultNetwork; - if (isNetworkTypeValid(networkType)) { - final NetworkStateTracker tracker = mNetTrackers[networkType]; - if (tracker != null) { - final NetworkInfo info = tracker.getNetworkInfo(); - if (info != null && info.isConnected()) { - sendConnectedBroadcast(info); - } - } - } + // TODO: Dead code; remove. + // + // final int networkType = mActiveDefaultNetwork; + // if (isNetworkTypeValid(networkType)) { + // final NetworkStateTracker tracker = mNetTrackers[networkType]; + // if (tracker != null) { + // final NetworkInfo info = tracker.getNetworkInfo(); + // if (info != null && info.isConnected()) { + // sendConnectedBroadcast(info); + // } + // } + // } } }; @@ -1744,16 +1781,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { pw.println(); pw.decreaseIndent(); - pw.print("mActiveDefaultNetwork: " + mActiveDefaultNetwork); - if (mActiveDefaultNetwork != TYPE_NONE) { - NetworkInfo activeNetworkInfo = getActiveNetworkInfo(); - if (activeNetworkInfo != null) { - pw.print(" " + activeNetworkInfo.getState() + - "/" + activeNetworkInfo.getDetailedState()); - } - } - pw.println(); - pw.println("mLegacyTypeTracker:"); pw.increaseIndent(); mLegacyTypeTracker.dump(pw); @@ -1782,10 +1809,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean isLiveNetworkAgent(NetworkAgentInfo nai, String msg) { if (nai.network == null) return false; - final NetworkAgentInfo officialNai; - synchronized (mNetworkForNetId) { - officialNai = mNetworkForNetId.get(nai.network.netId); - } + final NetworkAgentInfo officialNai = getNetworkAgentInfoForNetwork(nai.network); if (officialNai != null && officialNai.equals(nai)) return true; if (officialNai != null || VDBG) { loge(msg + " - isLiveNetworkAgent found mismatched netId: " + officialNai + @@ -1981,7 +2005,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { * to the link that may have incorrectly setup by the lower * levels. */ - LinkProperties lp = getLinkPropertiesForTypeInternal(info.getType()); + LinkProperties lp = getLinkPropertiesForType(info.getType()); if (DBG) { log("EVENT_STATE_CHANGED: connected to provisioning network, lp=" + lp); } @@ -2123,7 +2147,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (nai.networkRequests.get(mDefaultRequest.requestId) != null) { removeDataActivityTracking(nai); - mActiveDefaultNetwork = ConnectivityManager.TYPE_NONE; notifyLockdownVpn(nai); requestNetworkTransitionWakelock(nai.name()); } @@ -2135,11 +2158,40 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + // If this method proves to be too slow then we can maintain a separate + // pendingIntent => NetworkRequestInfo map. + // This method assumes that every non-null PendingIntent maps to exactly 1 NetworkRequestInfo. + private NetworkRequestInfo findExistingNetworkRequestInfo(PendingIntent pendingIntent) { + Intent intent = pendingIntent.getIntent(); + for (Map.Entry<NetworkRequest, NetworkRequestInfo> entry : mNetworkRequests.entrySet()) { + PendingIntent existingPendingIntent = entry.getValue().mPendingIntent; + if (existingPendingIntent != null && + existingPendingIntent.getIntent().filterEquals(intent)) { + return entry.getValue(); + } + } + return null; + } + + private void handleRegisterNetworkRequestWithIntent(Message msg) { + final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj); + + NetworkRequestInfo existingRequest = findExistingNetworkRequestInfo(nri.mPendingIntent); + if (existingRequest != null) { // remove the existing request. + if (DBG) log("Replacing " + existingRequest.request + " with " + + nri.request + " because their intents matched."); + handleReleaseNetworkRequest(existingRequest.request, getCallingUid()); + } + handleRegisterNetworkRequest(msg); + } + private void handleRegisterNetworkRequest(Message msg) { final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj); final NetworkCapabilities newCap = nri.request.networkCapabilities; int score = 0; + mNetworkRequests.put(nri.request, nri); + // Check for the best currently alive network that satisfies this request NetworkAgentInfo bestNetwork = null; for (NetworkAgentInfo network : mNetworkAgentInfos.values()) { @@ -2177,7 +2229,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork); } } - mNetworkRequests.put(nri.request, nri); + if (nri.isRequest) { if (DBG) log("sending new NetworkRequest to factories"); for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { @@ -2187,6 +2239,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent, + int callingUid) { + NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent); + if (nri != null) { + handleReleaseNetworkRequest(nri.request, callingUid); + } + } + private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) { NetworkRequestInfo nri = mNetworkRequests.get(request); if (nri != null) { @@ -2222,11 +2282,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - // Maintain the illusion. When this request arrived, we might have preteneded + // Maintain the illusion. When this request arrived, we might have pretended // that a network connected to serve it, even though the network was already // connected. Now that this request has gone away, we might have to pretend // that the network disconnected. LegacyTypeTracker will generate that - // phatom disconnect for this type. + // phantom disconnect for this type. NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); if (nai != null) { mNetworkForRequestId.remove(nri.request.requestId); @@ -2257,7 +2317,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public void handleMessage(Message msg) { - NetworkInfo info; switch (msg.what) { case EVENT_EXPIRE_NET_TRANSITION_WAKELOCK: case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: { @@ -2338,6 +2397,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleRegisterNetworkRequest(msg); break; } + case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT: { + handleRegisterNetworkRequestWithIntent(msg); + break; + } + case EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT: { + handleReleaseNetworkRequestWithIntent((PendingIntent) msg.obj, msg.arg1); + break; + } case EVENT_RELEASE_NETWORK_REQUEST: { handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1); break; @@ -2490,10 +2557,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (network == null) return; final int uid = Binder.getCallingUid(); - NetworkAgentInfo nai = null; - synchronized (mNetworkForNetId) { - nai = mNetworkForNetId.get(network.netId); - } + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return; if (DBG) log("reportBadNetwork(" + nai.name() + ") by " + uid); synchronized (nai) { @@ -2501,7 +2565,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // which isn't meant to work on uncreated networks. if (!nai.created) return; - if (isNetworkBlocked(nai, uid)) return; + if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid)) return; nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid); } @@ -2532,7 +2596,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { String exclList = ""; String pacFileUrl = ""; if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) || - (proxyProperties.getPacFileUrl() != null))) { + !Uri.EMPTY.equals(proxyProperties.getPacFileUrl()))) { if (!proxyProperties.isValid()) { if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); @@ -2542,7 +2606,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); exclList = mGlobalProxy.getExclusionListAsString(); - if (proxyProperties.getPacFileUrl() != null) { + if (!Uri.EMPTY.equals(proxyProperties.getPacFileUrl())) { pacFileUrl = proxyProperties.getPacFileUrl().toString(); } } else { @@ -2604,7 +2668,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void handleApplyDefaultProxy(ProxyInfo proxy) { if (proxy != null && TextUtils.isEmpty(proxy.getHost()) - && (proxy.getPacFileUrl() == null)) { + && Uri.EMPTY.equals(proxy.getPacFileUrl())) { proxy = null; } synchronized (mProxyLock) { @@ -2620,7 +2684,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { // global (to get the correct local port), and send a broadcast. // TODO: Switch PacManager to have its own message to send back rather than // reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy. - if ((mGlobalProxy != null) && (proxy != null) && (proxy.getPacFileUrl() != null) + if ((mGlobalProxy != null) && (proxy != null) + && (!Uri.EMPTY.equals(proxy.getPacFileUrl())) && proxy.getPacFileUrl().equals(mGlobalProxy.getPacFileUrl())) { mGlobalProxy = proxy; sendProxyBroadcast(mGlobalProxy); @@ -2703,42 +2768,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { Slog.e(TAG, s); } - int convertFeatureToNetworkType(int networkType, String feature) { - int usedNetworkType = networkType; - - if(networkType == ConnectivityManager.TYPE_MOBILE) { - if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) || - TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_FOTA; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_IMS; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_CBS; - } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_EMERGENCY)) { - usedNetworkType = ConnectivityManager.TYPE_MOBILE_EMERGENCY; - } else { - Slog.e(TAG, "Can't match any mobile netTracker!"); - } - } else if (networkType == ConnectivityManager.TYPE_WIFI) { - if (TextUtils.equals(feature, "p2p")) { - usedNetworkType = ConnectivityManager.TYPE_WIFI_P2P; - } else { - Slog.e(TAG, "Can't match any wifi netTracker!"); - } - } else { - Slog.e(TAG, "Unexpected network type"); - } - return usedNetworkType; - } - private static <T> T checkNotNull(T value, String message) { if (value == null) { throw new NullPointerException(message); @@ -2747,9 +2776,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** - * Prepare for a VPN application. This method is used by VpnDialogs - * and not available in ConnectivityManager. Permissions are checked - * in Vpn class. + * Prepare for a VPN application. + * Permissions are checked in Vpn class. * @hide */ @Override @@ -2763,8 +2791,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { /** * Set whether the current VPN package has the ability to launch VPNs without - * user intervention. This method is used by system UIs and not available - * in ConnectivityManager. Permissions are checked in Vpn class. + * user intervention. This method is used by system-privileged apps. + * Permissions are checked in Vpn class. * @hide */ @Override @@ -3181,7 +3209,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0); Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); intent.putExtra("state", enable); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } finally { Binder.restoreCallingIdentity(ident); } @@ -3225,43 +3253,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } }; - @Override - public LinkQualityInfo getLinkQualityInfo(int networkType) { - enforceAccessPermission(); - if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) { - return mNetTrackers[networkType].getLinkQualityInfo(); - } else { - return null; - } - } - - @Override - public LinkQualityInfo getActiveLinkQualityInfo() { - enforceAccessPermission(); - if (isNetworkTypeValid(mActiveDefaultNetwork) && - mNetTrackers[mActiveDefaultNetwork] != null) { - return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo(); - } else { - return null; - } - } - - @Override - public LinkQualityInfo[] getAllLinkQualityInfo() { - enforceAccessPermission(); - final ArrayList<LinkQualityInfo> result = Lists.newArrayList(); - for (NetworkStateTracker tracker : mNetTrackers) { - if (tracker != null) { - LinkQualityInfo li = tracker.getLinkQualityInfo(); - if (li != null) { - result.add(li); - } - } - } - - return result.toArray(new LinkQualityInfo[result.size()]); - } - /* Infrastructure for network sampling */ private void handleNetworkSamplingTimeout() { @@ -3351,12 +3342,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { static final boolean LISTEN = false; final NetworkRequest request; - IBinder mBinder; + final PendingIntent mPendingIntent; + private final IBinder mBinder; final int mPid; final int mUid; final Messenger messenger; final boolean isRequest; + NetworkRequestInfo(NetworkRequest r, PendingIntent pi, boolean isRequest) { + request = r; + mPendingIntent = pi; + messenger = null; + mBinder = null; + mPid = getCallingPid(); + mUid = getCallingUid(); + this.isRequest = isRequest; + } + NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, boolean isRequest) { super(); messenger = m; @@ -3365,6 +3367,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mPid = getCallingPid(); mUid = getCallingUid(); this.isRequest = isRequest; + mPendingIntent = null; try { mBinder.linkToDeath(this, 0); @@ -3374,7 +3377,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } void unlinkDeathRecipient() { - mBinder.unlinkToDeath(this, 0); + if (mBinder != null) { + mBinder.unlinkToDeath(this, 0); + } } public void binderDied() { @@ -3385,22 +3390,46 @@ public class ConnectivityService extends IConnectivityManager.Stub { public String toString() { return (isRequest ? "Request" : "Listen") + " from uid/pid:" + mUid + "/" + - mPid + " for " + request; + mPid + " for " + request + + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent); } } @Override public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities, Messenger messenger, int timeoutMs, IBinder binder, int legacyType) { + networkCapabilities = new NetworkCapabilities(networkCapabilities); + enforceNetworkRequestPermissions(networkCapabilities); + enforceMeteredApnPolicy(networkCapabilities); + + if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) { + throw new IllegalArgumentException("Bad timeout specified"); + } + + NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, + nextNetworkRequestId()); + if (DBG) log("requestNetwork for " + networkRequest); + NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, + NetworkRequestInfo.REQUEST); + + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri)); + if (timeoutMs > 0) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST, + nri), timeoutMs); + } + return networkRequest; + } + + private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities) { if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) == false) { enforceConnectivityInternalPermission(); } else { enforceChangePermission(); } + } - networkCapabilities = new NetworkCapabilities(networkCapabilities); - + 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) == false) { @@ -3415,29 +3444,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); } } + } - if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) { - throw new IllegalArgumentException("Bad timeout specified"); - } - NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, + @Override + public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities, + PendingIntent operation) { + checkNotNull(operation, "PendingIntent cannot be null."); + networkCapabilities = new NetworkCapabilities(networkCapabilities); + enforceNetworkRequestPermissions(networkCapabilities); + enforceMeteredApnPolicy(networkCapabilities); + + NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE, nextNetworkRequestId()); - if (DBG) log("requestNetwork for " + networkRequest); - NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder, + if (DBG) log("pendingRequest for " + networkRequest + " to trigger " + operation); + NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation, NetworkRequestInfo.REQUEST); - - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri)); - if (timeoutMs > 0) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST, - nri), timeoutMs); - } + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT, + nri)); return networkRequest; } @Override - public NetworkRequest pendingRequestForNetwork(NetworkCapabilities networkCapabilities, - PendingIntent operation) { - // TODO - return null; + public void releasePendingNetworkRequest(PendingIntent operation) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT, + getCallingUid(), 0, operation)); } @Override @@ -3511,6 +3541,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos = new HashMap<Messenger, NetworkAgentInfo>(); + // Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated. private final NetworkRequest mDefaultRequest; private boolean isDefaultNetwork(NetworkAgentInfo nai) { @@ -3522,10 +3553,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { int currentScore, NetworkMisc networkMisc) { enforceConnectivityInternalPermission(); + // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network + // satisfies mDefaultRequest. NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), new NetworkInfo(networkInfo), new LinkProperties(linkProperties), new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler, - new NetworkMisc(networkMisc)); + new NetworkMisc(networkMisc), mDefaultRequest); synchronized (this) { nai.networkMonitor.systemReady = mSystemReady; } @@ -3549,7 +3582,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { // The NetworkAgentInfo does not know whether clatd is running on its network or not. Before // we do anything else, make sure its LinkProperties are accurate. - mClat.fixupLinkProperties(networkAgent, oldLp); + if (networkAgent.clatd != null) { + networkAgent.clatd.fixupLinkProperties(oldLp); + } updateInterfaces(newLp, oldLp, netId); updateMtu(newLp, oldLp); @@ -3568,15 +3603,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private void updateClat(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo na) { - final boolean wasRunningClat = mClat.isRunningClat(na); - final boolean shouldRunClat = Nat464Xlat.requiresClat(na); + private void updateClat(LinkProperties newLp, LinkProperties oldLp, NetworkAgentInfo nai) { + final boolean wasRunningClat = nai.clatd != null && nai.clatd.isStarted(); + final boolean shouldRunClat = Nat464Xlat.requiresClat(nai); if (!wasRunningClat && shouldRunClat) { - // Start clatd. If it's already been started but is not running yet, this is a no-op. - mClat.startClat(na); + nai.clatd = new Nat464Xlat(mContext, mNetd, mTrackerHandler, nai); + nai.clatd.start(); } else if (wasRunningClat && !shouldRunClat) { - mClat.stopClat(); + nai.clatd.stop(); } } @@ -3703,12 +3738,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void updateCapabilities(NetworkAgentInfo networkAgent, NetworkCapabilities networkCapabilities) { - // TODO - turn this on in MR1 when we have more dogfooding time. - // rematchAllNetworksAndRequests(); if (!Objects.equals(networkAgent.networkCapabilities, networkCapabilities)) { synchronized (networkAgent) { networkAgent.networkCapabilities = networkCapabilities; } + rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore()); notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED); } } @@ -3730,6 +3764,38 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent, + int notificationType) { + if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE) { + Intent intent = new Intent(); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK, nri.request); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, networkAgent.network); + sendIntent(nri.mPendingIntent, intent); + } + // else not handled + } + + private void sendIntent(PendingIntent pendingIntent, Intent intent) { + mPendingIntentWakeLock.acquire(); + try { + if (DBG) log("Sending " + pendingIntent); + pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */); + } catch (PendingIntent.CanceledException e) { + if (DBG) log(pendingIntent + " was not sent, it had been canceled."); + mPendingIntentWakeLock.release(); + releasePendingNetworkRequest(pendingIntent); + } + // ...otherwise, mPendingIntentWakeLock.release() gets called by onSendFinished() + } + + @Override + public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, + String resultData, Bundle resultExtras) { + if (DBG) log("Finished sending " + pendingIntent); + mPendingIntentWakeLock.release(); + releasePendingNetworkRequest(pendingIntent); + } + private void callCallbackForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent, int notificationType) { if (nri.messenger == null) return; // Default request has no msgr @@ -3793,7 +3859,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void makeDefault(NetworkAgentInfo newNetwork) { if (DBG) log("Switching to new default network: " + newNetwork); - mActiveDefaultNetwork = newNetwork.networkInfo.getType(); setupDataActivityTracking(newNetwork); try { mNetd.setDefaultNetId(newNetwork.network.netId); @@ -3835,7 +3900,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // another higher scoring network by another call to rematchNetworkAndRequests() // and this other call also lingered newNetwork. private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, boolean nascent) { - if (!newNetwork.created) loge("ERROR: uncreated network being rematched."); + if (!newNetwork.created) return; if (nascent && !newNetwork.validated) loge("ERROR: nascent network not validated."); boolean keep = newNetwork.isVPN(); boolean isNewDefault = false; @@ -3898,7 +3963,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mDefaultRequest.requestId == nri.request.requestId) { isNewDefault = true; // TODO: Remove following line. It's redundant with makeDefault call. - mActiveDefaultNetwork = newNetwork.networkInfo.getType(); if (newNetwork.linkProperties != null) { updateTcpBufferSizes(newNetwork); setDefaultDnsSystemProperties( @@ -3982,6 +4046,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { } notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_AVAILABLE); + + // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above, + // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest + // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the + // newNetwork to the tracker explicitly (it's a no-op if it has already been added). + if (newNetwork.isVPN()) { + mLegacyTypeTracker.add(TYPE_VPN, newNetwork); + } } else if (nascent) { // Only tear down newly validated networks here. Leave unvalidated to either become // validated (and get evaluated against peers, one losing here) or @@ -4126,7 +4198,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { final int oldScore = nai.getCurrentScore(); nai.setCurrentScore(score); - if (nai.created) rematchAllNetworksAndRequests(nai, oldScore); + rematchAllNetworksAndRequests(nai, oldScore); sendUpdatedScoreToFactories(nai); } @@ -4140,7 +4212,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { // } else if (nai.networkMonitor.isEvaluating()) { // notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType); // } - callCallbackForRequest(nri, nai, notifyType); + if (nri.mPendingIntent == null) { + callCallbackForRequest(nri, nai, notifyType); + } else { + sendPendingIntentForRequest(nri, nai, notifyType); + } } private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) { @@ -4199,7 +4275,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkRequest nr = networkAgent.networkRequests.valueAt(i); NetworkRequestInfo nri = mNetworkRequests.get(nr); if (VDBG) log(" sending notification for " + nr); - callCallbackForRequest(nri, networkAgent, notifyType); + if (nri.mPendingIntent == null) { + callCallbackForRequest(nri, networkAgent, notifyType); + } else { + sendPendingIntentForRequest(nri, networkAgent, notifyType); + } } } @@ -4217,44 +4297,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { return "UNKNOWN"; } - private LinkProperties getLinkPropertiesForTypeInternal(int networkType) { - NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - if (nai != null) { - synchronized (nai) { - return new LinkProperties(nai.linkProperties); - } - } - return new LinkProperties(); - } - - private NetworkInfo getNetworkInfoForType(int networkType) { - if (!mLegacyTypeTracker.isTypeSupported(networkType)) - return null; - - NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - if (nai != null) { - NetworkInfo result = new NetworkInfo(nai.networkInfo); - result.setType(networkType); - return result; - } else { - NetworkInfo result = new NetworkInfo( - networkType, 0, ConnectivityManager.getNetworkTypeName(networkType), ""); - result.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null); - result.setIsAvailable(true); - return result; - } - } - - private NetworkCapabilities getNetworkCapabilitiesForType(int networkType) { - NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); - if (nai != null) { - synchronized (nai) { - return new NetworkCapabilities(nai.networkCapabilities); - } - } - return new NetworkCapabilities(); - } - @Override public boolean addVpnAddress(String address, int prefixLength) { throwIfLockdownEnabled(); @@ -4272,4 +4314,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mVpns.get(user).removeAddress(address, prefixLength); } } + + @Override + public boolean setUnderlyingNetworksForVpn(Network[] networks) { + throwIfLockdownEnabled(); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized (mVpns) { + return mVpns.get(user).setUnderlyingNetworks(networks); + } + } } diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index d05c280..41ce25d 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -65,11 +65,15 @@ final class DockObserver extends SystemService { private boolean mUpdatesStopped; + private final boolean mAllowTheaterModeWakeFromDock; + public DockObserver(Context context) { super(context); mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mAllowTheaterModeWakeFromDock = context.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromDock); init(); // set initial status @@ -126,8 +130,12 @@ final class DockObserver extends SystemService { if (newState != mReportedDockState) { mReportedDockState = newState; if (mSystemReady) { - // Wake up immediately when docked or undocked. - mPowerManager.wakeUp(SystemClock.uptimeMillis()); + // Wake up immediately when docked or undocked except in theater mode. + if (mAllowTheaterModeWakeFromDock + || Settings.Global.getInt(getContext().getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0) == 0) { + mPowerManager.wakeUp(SystemClock.uptimeMillis()); + } updateLocked(); } } diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index 29b04da..a44cb72 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -408,9 +408,14 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { if (!newline) out.append("\n"); } else { String text = dbe.getText(70); - boolean truncated = (text.length() == 70); - out.append(" ").append(text.trim().replace('\n', '/')); - if (truncated) out.append(" ..."); + out.append(" "); + if (text == null) { + out.append("[null]"); + } else { + boolean truncated = (text.length() == 70); + out.append(text.trim().replace('\n', '/')); + if (truncated) out.append(" ..."); + } out.append("\n"); } } catch (IOException e) { diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 99a1254..8417ccc 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -52,7 +52,7 @@ option java_package com.android.server # NotificationManagerService.java # --------------------------- # when a NotificationManager.notify is called -2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3) +2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3),(update|1) # when someone tries to cancel a notification, the notification manager sometimes # calls this with flags too 2751 notification_cancel (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(required_flags|1),(forbidden_flags|1),(reason|1|5),(listener|3) @@ -69,6 +69,10 @@ option java_package com.android.server 27511 notification_expansion (key|3),(user_action|1),(expanded|1) # when a notification has been clicked 27520 notification_clicked (key|3) +# 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) # --------------------------- # Watchdog.java @@ -165,6 +169,12 @@ option java_package com.android.server # --------------------------- +# WallpaperManagerService.java +# --------------------------- +33000 wp_wallpaper_crashed (component|3) + + +# --------------------------- # ConnectivityService.java # --------------------------- # Connectivity state changed diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index edea274..4d0868a 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -66,6 +66,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; +import android.graphics.drawable.Drawable; import android.inputmethodservice.InputMethodService; import android.net.Uri; import android.os.Binder; @@ -2830,23 +2831,30 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } } - final Context themedContext = new ContextThemeWrapper(context, - android.R.style.Theme_DeviceDefault_Settings); - mDialogBuilder = new AlertDialog.Builder(themedContext); - final TypedArray a = themedContext.obtainStyledAttributes(null, - com.android.internal.R.styleable.DialogPreference, - com.android.internal.R.attr.alertDialogStyle, 0); - mDialogBuilder.setIcon(a.getDrawable( - com.android.internal.R.styleable.DialogPreference_dialogIcon)); - a.recycle(); + + final Context settingsContext = new ContextThemeWrapper(context, + com.android.internal.R.style.Theme_DeviceDefault_Settings); + + mDialogBuilder = new AlertDialog.Builder(settingsContext); mDialogBuilder.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { hideInputMethodMenu(); } }); - final LayoutInflater inflater = - (LayoutInflater)themedContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + final Context dialogContext = mDialogBuilder.getContext(); + final TypedArray a = dialogContext.obtainStyledAttributes(null, + com.android.internal.R.styleable.DialogPreference, + com.android.internal.R.attr.alertDialogStyle, 0); + final Drawable dialogIcon = a.getDrawable( + com.android.internal.R.styleable.DialogPreference_dialogIcon); + a.recycle(); + + mDialogBuilder.setIcon(dialogIcon); + + final LayoutInflater inflater = (LayoutInflater) dialogContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); final View tv = inflater.inflate( com.android.internal.R.layout.input_method_switch_dialog_title, null); mDialogBuilder.setCustomTitle(tv); @@ -2857,7 +2865,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub .findViewById(com.android.internal.R.id.hard_keyboard_section) .setVisibility(mWindowManagerService.isHardKeyboardAvailable() ? View.VISIBLE : View.GONE); - final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById( + final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById( com.android.internal.R.id.hard_keyboard_switch); hardKeySwitch.setChecked(mShowImeWithHardKeyboard); hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { @@ -2870,7 +2878,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } }); - final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(themedContext, + final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext, com.android.internal.R.layout.input_method_switch_item, imList, checkedItem); final OnClickListener choiceListener = new OnClickListener() { @Override @@ -2926,10 +2934,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public ImeSubtypeListAdapter(Context context, int textViewResourceId, List<ImeSubtypeListItem> itemsList, int checkedItem) { super(context, textViewResourceId, itemsList); + mTextViewResourceId = textViewResourceId; mItemsList = itemsList; mCheckedItem = checkedItem; - mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index 07cc864..cea1ebe 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -21,16 +21,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; import android.net.Uri; import android.util.FastImmutableArraySet; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; +import android.util.MutableInt; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.LogPrinter; @@ -213,36 +213,65 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } boolean dumpMap(PrintWriter out, String titlePrefix, String title, - String prefix, Map<String, F[]> map, String packageName, - boolean printFilter) { - String eprefix = prefix + " "; - String fprefix = prefix + " "; + String prefix, ArrayMap<String, F[]> map, String packageName, + boolean printFilter, boolean collapseDuplicates) { + final String eprefix = prefix + " "; + final String fprefix = prefix + " "; + final ArrayMap<Object, MutableInt> found = new ArrayMap<>(); boolean printedSomething = false; Printer printer = null; - for (Map.Entry<String, F[]> e : map.entrySet()) { - F[] a = e.getValue(); + for (int mapi=0; mapi<map.size(); mapi++) { + F[] a = map.valueAt(mapi); final int N = a.length; boolean printedHeader = false; F filter; - for (int i=0; i<N && (filter=a[i]) != null; i++) { - if (packageName != null && !isPackageForFilter(packageName, filter)) { - continue; - } - if (title != null) { - out.print(titlePrefix); out.println(title); - title = null; + if (collapseDuplicates) { + found.clear(); + for (int i=0; i<N && (filter=a[i]) != null; i++) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { + continue; + } + Object label = filterToLabel(filter); + int index = found.indexOfKey(label); + if (index < 0) { + found.put(label, new MutableInt(1)); + } else { + found.valueAt(index).value++; + } } - if (!printedHeader) { - out.print(eprefix); out.print(e.getKey()); out.println(":"); - printedHeader = true; + for (int i=0; i<found.size(); i++) { + if (title != null) { + out.print(titlePrefix); out.println(title); + title = null; + } + if (!printedHeader) { + out.print(eprefix); out.print(map.keyAt(mapi)); out.println(":"); + printedHeader = true; + } + printedSomething = true; + dumpFilterLabel(out, fprefix, found.keyAt(i), found.valueAt(i).value); } - printedSomething = true; - dumpFilter(out, fprefix, filter); - if (printFilter) { - if (printer == null) { - printer = new PrintWriterPrinter(out); + } else { + for (int i=0; i<N && (filter=a[i]) != null; i++) { + if (packageName != null && !isPackageForFilter(packageName, filter)) { + continue; + } + if (title != null) { + out.print(titlePrefix); out.println(title); + title = null; + } + if (!printedHeader) { + out.print(eprefix); out.print(map.keyAt(mapi)); out.println(":"); + printedHeader = true; + } + printedSomething = true; + dumpFilter(out, fprefix, filter); + if (printFilter) { + if (printer == null) { + printer = new PrintWriterPrinter(out); + } + filter.dump(printer, fprefix + " "); } - filter.dump(printer, fprefix + " "); } } } @@ -250,32 +279,32 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } public boolean dump(PrintWriter out, String title, String prefix, String packageName, - boolean printFilter) { + boolean printFilter, boolean collapseDuplicates) { String innerPrefix = prefix + " "; String sepPrefix = "\n" + prefix; String curPrefix = title + "\n" + prefix; if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix, - mTypeToFilter, packageName, printFilter)) { + mTypeToFilter, packageName, printFilter, collapseDuplicates)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix, - mBaseTypeToFilter, packageName, printFilter)) { + mBaseTypeToFilter, packageName, printFilter, collapseDuplicates)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix, - mWildTypeToFilter, packageName, printFilter)) { + mWildTypeToFilter, packageName, printFilter, collapseDuplicates)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "Schemes:", innerPrefix, - mSchemeToFilter, packageName, printFilter)) { + mSchemeToFilter, packageName, printFilter, collapseDuplicates)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix, - mActionToFilter, packageName, printFilter)) { + mActionToFilter, packageName, printFilter, collapseDuplicates)) { curPrefix = sepPrefix; } if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix, - mTypedActionToFilter, packageName, printFilter)) { + mTypedActionToFilter, packageName, printFilter, collapseDuplicates)) { curPrefix = sepPrefix; } return curPrefix == sepPrefix; @@ -479,6 +508,14 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { out.print(prefix); out.println(filter); } + protected Object filterToLabel(F filter) { + return "IntentFilter"; + } + + protected void dumpFilterLabel(PrintWriter out, String prefix, Object label, int count) { + out.print(prefix); out.print(label); out.print(": "); out.println(count); + } + private final void addFilter(ArrayMap<String, F[]> map, String name, F filter) { F[] array = map.get(name); if (array == null) { @@ -736,7 +773,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { /** * All filters that have been registered. */ - private final HashSet<F> mFilters = new HashSet<F>(); + private final ArraySet<F> mFilters = new ArraySet<F>(); /** * All of the MIME types that have been registered, such as "image/jpeg", diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 9eb1a18..eca6ec7 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -60,6 +60,8 @@ 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; @@ -1795,9 +1797,6 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean addGpsStatusListener(IGpsStatusListener listener, String packageName) { - if (mGpsStatusProvider == null) { - return false; - } int allowedResolutionLevel = getCallerAllowedResolutionLevel(); checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, LocationManager.GPS_PROVIDER); @@ -1812,6 +1811,10 @@ public class LocationManagerService extends ILocationManager.Stub { Binder.restoreCallingIdentity(ident); } + if (mGpsStatusProvider == null) { + return false; + } + try { mGpsStatusProvider.addGpsStatusListener(listener); } catch (RemoteException e) { @@ -1857,8 +1860,8 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean removeGpsMeasurementsListener(IGpsMeasurementsListener listener) { - return mGpsMeasurementsProvider.removeListener(listener); + public void removeGpsMeasurementsListener(IGpsMeasurementsListener listener) { + mGpsMeasurementsProvider.removeListener(listener); } @Override @@ -1886,8 +1889,8 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean removeGpsNavigationMessageListener(IGpsNavigationMessageListener listener) { - return mGpsNavigationMessageProvider.removeListener(listener); + public void removeGpsNavigationMessageListener(IGpsNavigationMessageListener listener) { + mGpsNavigationMessageProvider.removeListener(listener); } @Override diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index b708c3f..77662cc 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -18,49 +18,36 @@ package com.android.server; import android.content.BroadcastReceiver; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.content.Context.USER_SERVICE; import static android.Manifest.permission.READ_PROFILE; -import android.database.Cursor; + import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteStatement; import android.os.Binder; -import android.os.Environment; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.storage.IMountService; import android.os.ServiceManager; -import android.os.storage.StorageManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; -import android.security.KeyChain; -import android.security.KeyChain.KeyChainConnection; import android.security.KeyStore; import android.text.TextUtils; -import android.util.Log; import android.util.Slog; -import com.android.internal.os.BackgroundThread; import com.android.internal.widget.ILockSettings; -import com.android.internal.widget.ILockSettingsObserver; import com.android.internal.widget.LockPatternUtils; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -73,43 +60,40 @@ import java.util.List; */ public class LockSettingsService extends ILockSettings.Stub { - private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"; - - private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; + private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE; - private final DatabaseHelper mOpenHelper; private static final String TAG = "LockSettingsService"; - private static final String TABLE = "locksettings"; - private static final String COLUMN_KEY = "name"; - private static final String COLUMN_USERID = "user"; - private static final String COLUMN_VALUE = "value"; - - private static final String[] COLUMNS_FOR_QUERY = { - COLUMN_VALUE - }; + private final Context mContext; - 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 final LockSettingsStorage mStorage; - private final Context mContext; private LockPatternUtils mLockPatternUtils; private boolean mFirstCallToVold; - private final ArrayList<LockSettingsObserver> mObservers = new ArrayList<>(); - public LockSettingsService(Context context) { mContext = context; // Open the database - mOpenHelper = new DatabaseHelper(mContext); mLockPatternUtils = new LockPatternUtils(context); mFirstCallToVold = true; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); + filter.addAction(Intent.ACTION_USER_STARTING); mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); + + mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() { + @Override + public void initialize(SQLiteDatabase db) { + // Get the lockscreen default from a system property, if available + boolean lockScreenDisable = SystemProperties.getBoolean( + "ro.lockscreen.disable.default", false); + if (lockScreenDisable) { + mStorage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0); + } + } + }); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -130,12 +114,16 @@ public class LockSettingsService extends ILockSettings.Stub { final int parentSysUid = UserHandle.getUid(parentInfo.id, Process.SYSTEM_UID); ks.syncUid(parentSysUid, userSysUid); } + } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) { + final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + mStorage.prefetchUser(userHandle); } } }; public void systemReady() { migrateOldData(); + mStorage.prefetchUser(UserHandle.USER_OWNER); } private void migrateOldData() { @@ -220,29 +208,30 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void setBoolean(String key, boolean value, int userId) throws RemoteException { checkWritePermission(userId); - - writeToDb(key, value ? "1" : "0", userId); + setStringUnchecked(key, userId, value ? "1" : "0"); } @Override public void setLong(String key, long value, int userId) throws RemoteException { checkWritePermission(userId); - - writeToDb(key, Long.toString(value), userId); + setStringUnchecked(key, userId, Long.toString(value)); } @Override public void setString(String key, String value, int userId) throws RemoteException { checkWritePermission(userId); + setStringUnchecked(key, userId, value); + } - writeToDb(key, value, userId); + private void setStringUnchecked(String key, int userId, String value) { + mStorage.writeKeyValue(key, value, userId); } @Override public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { checkReadPermission(key, userId); - String value = readFromDb(key, null, userId); + String value = mStorage.readKeyValue(key, null, userId); return TextUtils.isEmpty(value) ? defaultValue : (value.equals("1") || value.equals("true")); } @@ -251,7 +240,7 @@ public class LockSettingsService extends ILockSettings.Stub { public long getLong(String key, long defaultValue, int userId) throws RemoteException { checkReadPermission(key, userId); - String value = readFromDb(key, null, userId); + String value = mStorage.readKeyValue(key, null, userId); return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value); } @@ -259,106 +248,21 @@ public class LockSettingsService extends ILockSettings.Stub { public String getString(String key, String defaultValue, int userId) throws RemoteException { checkReadPermission(key, userId); - return readFromDb(key, defaultValue, userId); - } - - @Override - public void registerObserver(ILockSettingsObserver remote) throws RemoteException { - synchronized (mObservers) { - for (int i = 0; i < mObservers.size(); i++) { - if (mObservers.get(i).remote.asBinder() == remote.asBinder()) { - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); - if (isDebuggable) { - throw new IllegalStateException("Observer was already registered."); - } else { - Log.e(TAG, "Observer was already registered."); - return; - } - } - } - LockSettingsObserver o = new LockSettingsObserver(); - o.remote = remote; - o.remote.asBinder().linkToDeath(o, 0); - mObservers.add(o); - } - } - - @Override - public void unregisterObserver(ILockSettingsObserver remote) throws RemoteException { - synchronized (mObservers) { - for (int i = 0; i < mObservers.size(); i++) { - if (mObservers.get(i).remote.asBinder() == remote.asBinder()) { - mObservers.remove(i); - return; - } - } - } - } - - public void notifyObservers(String key, int userId) { - synchronized (mObservers) { - for (int i = 0; i < mObservers.size(); i++) { - try { - mObservers.get(i).remote.onLockSettingChanged(key, userId); - } catch (RemoteException e) { - // The stack trace is not really helpful here. - Log.e(TAG, "Failed to notify ILockSettingsObserver: " + e); - } - } - } - } - - private int getUserParentOrSelfId(int userId) { - if (userId != 0) { - final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); - final UserInfo pi = um.getProfileParent(userId); - if (pi != null) { - return pi.id; - } - } - return userId; - } - - private String getLockPatternFilename(int userId) { - String dataSystemDirectory = - android.os.Environment.getDataDirectory().getAbsolutePath() + - SYSTEM_DIRECTORY; - userId = getUserParentOrSelfId(userId); - if (userId == 0) { - // Leave it in the same place for user 0 - return dataSystemDirectory + LOCK_PATTERN_FILE; - } else { - return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE) - .getAbsolutePath(); - } - } - - private String getLockPasswordFilename(int userId) { - userId = getUserParentOrSelfId(userId); - String dataSystemDirectory = - android.os.Environment.getDataDirectory().getAbsolutePath() + - SYSTEM_DIRECTORY; - if (userId == 0) { - // Leave it in the same place for user 0 - return dataSystemDirectory + LOCK_PASSWORD_FILE; - } else { - return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE) - .getAbsolutePath(); - } + return mStorage.readKeyValue(key, defaultValue, userId); } @Override public boolean havePassword(int userId) throws RemoteException { // Do we need a permissions check here? - return new File(getLockPasswordFilename(userId)).length() > 0; + return mStorage.hasPassword(userId); } @Override public boolean havePattern(int userId) throws RemoteException { // Do we need a permissions check here? - return new File(getLockPatternFilename(userId)).length() > 0; + return mStorage.hasPattern(userId); } private void maybeUpdateKeystore(String password, int userHandle) { @@ -394,7 +298,7 @@ public class LockSettingsService extends ILockSettings.Stub { final byte[] hash = LockPatternUtils.patternToHash( LockPatternUtils.stringToPattern(pattern)); - writeFile(getLockPatternFilename(userId), hash); + mStorage.writePatternHash(hash, userId); } @Override @@ -403,68 +307,46 @@ public class LockSettingsService extends ILockSettings.Stub { maybeUpdateKeystore(password, userId); - writeFile(getLockPasswordFilename(userId), - mLockPatternUtils.passwordToHash(password, userId)); + mStorage.writePasswordHash(mLockPatternUtils.passwordToHash(password, userId), userId); } @Override public boolean checkPattern(String pattern, int userId) throws RemoteException { checkPasswordReadPermission(userId); - try { - // Read all the bytes from the file - RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r"); - final byte[] stored = new byte[(int) raf.length()]; - int got = raf.read(stored, 0, stored.length); - raf.close(); - if (got <= 0) { - return true; - } - // Compare the hash from the file with the entered pattern's hash - final byte[] hash = LockPatternUtils.patternToHash( - LockPatternUtils.stringToPattern(pattern)); - final boolean matched = Arrays.equals(stored, hash); - if (matched && !TextUtils.isEmpty(pattern)) { - maybeUpdateKeystore(pattern, userId); - } - return matched; - } catch (FileNotFoundException fnfe) { - Slog.e(TAG, "Cannot read file " + fnfe); - } catch (IOException ioe) { - Slog.e(TAG, "Cannot read file " + ioe); + byte[] hash = LockPatternUtils.patternToHash(LockPatternUtils.stringToPattern(pattern)); + byte[] storedHash = mStorage.readPatternHash(userId); + + if (storedHash == null) { + return true; + } + + boolean matched = Arrays.equals(hash, storedHash); + if (matched && !TextUtils.isEmpty(pattern)) { + maybeUpdateKeystore(pattern, userId); } - return true; + return matched; } @Override public boolean checkPassword(String password, int userId) throws RemoteException { checkPasswordReadPermission(userId); - try { - // Read all the bytes from the file - RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r"); - final byte[] stored = new byte[(int) raf.length()]; - int got = raf.read(stored, 0, stored.length); - raf.close(); - if (got <= 0) { - return true; - } - // Compare the hash from the file with the entered password's hash - final byte[] hash = mLockPatternUtils.passwordToHash(password, userId); - final boolean matched = Arrays.equals(stored, hash); - if (matched && !TextUtils.isEmpty(password)) { - maybeUpdateKeystore(password, userId); - } - return matched; - } catch (FileNotFoundException fnfe) { - Slog.e(TAG, "Cannot read file " + fnfe); - } catch (IOException ioe) { - Slog.e(TAG, "Cannot read file " + ioe); + byte[] hash = mLockPatternUtils.passwordToHash(password, userId); + byte[] storedHash = mStorage.readPasswordHash(userId); + + if (storedHash == null) { + return true; + } + + boolean matched = Arrays.equals(hash, storedHash); + if (matched && !TextUtils.isEmpty(password)) { + maybeUpdateKeystore(password, userId); } - return true; + return matched; } @Override - public boolean checkVoldPassword(int userId) throws RemoteException { + public boolean checkVoldPassword(int userId) throws RemoteException { if (!mFirstCallToVold) { return false; } @@ -512,166 +394,13 @@ public class LockSettingsService extends ILockSettings.Stub { public void removeUser(int userId) { checkWritePermission(userId); - SQLiteDatabase db = mOpenHelper.getWritableDatabase(); - try { - final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); - final UserInfo parentInfo = um.getProfileParent(userId); - if (parentInfo == null) { - // This user owns its lock settings files - safe to delete them - File file = new File(getLockPasswordFilename(userId)); - if (file.exists()) { - file.delete(); - } - file = new File(getLockPatternFilename(userId)); - if (file.exists()) { - file.delete(); - } - } - - db.beginTransaction(); - db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } + mStorage.removeUser(userId); final KeyStore ks = KeyStore.getInstance(); final int userUid = UserHandle.getUid(userId, Process.SYSTEM_UID); ks.resetUid(userUid); } - private void writeFile(String name, byte[] hash) { - try { - // Write the hash to file - RandomAccessFile raf = new RandomAccessFile(name, "rw"); - // Truncate the file if pattern is null, to clear the lock - if (hash == null || hash.length == 0) { - raf.setLength(0); - } else { - raf.write(hash, 0, hash.length); - } - raf.close(); - } catch (IOException ioe) { - Slog.e(TAG, "Error writing to file " + ioe); - } - } - - private void writeToDb(String key, String value, int userId) { - writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId); - notifyObservers(key, userId); - } - - private void writeToDb(SQLiteDatabase db, String key, String value, int userId) { - ContentValues cv = new ContentValues(); - cv.put(COLUMN_KEY, key); - cv.put(COLUMN_USERID, userId); - cv.put(COLUMN_VALUE, value); - - db.beginTransaction(); - try { - db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", - new String[] {key, Integer.toString(userId)}); - db.insert(TABLE, null, cv); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - private String readFromDb(String key, String defaultValue, int userId) { - Cursor cursor; - String result = defaultValue; - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); - if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, - COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", - new String[] { Integer.toString(userId), key }, - null, null, null)) != null) { - if (cursor.moveToFirst()) { - result = cursor.getString(0); - } - cursor.close(); - } - return result; - } - - class DatabaseHelper extends SQLiteOpenHelper { - private static final String TAG = "LockSettingsDB"; - private static final String DATABASE_NAME = "locksettings.db"; - - private static final int DATABASE_VERSION = 2; - - public DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - setWriteAheadLoggingEnabled(true); - } - - private void createTable(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE + " (" + - "_id INTEGER PRIMARY KEY AUTOINCREMENT," + - COLUMN_KEY + " TEXT," + - COLUMN_USERID + " INTEGER," + - COLUMN_VALUE + " TEXT" + - ");"); - } - - @Override - public void onCreate(SQLiteDatabase db) { - createTable(db); - initializeDefaults(db); - } - - private void initializeDefaults(SQLiteDatabase db) { - // Get the lockscreen default from a system property, if available - boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default", - false); - if (lockScreenDisable) { - writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0); - } - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { - int upgradeVersion = oldVersion; - if (upgradeVersion == 1) { - // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED} - // during upgrade based on whether each user previously had widgets in keyguard. - maybeEnableWidgetSettingForUsers(db); - upgradeVersion = 2; - } - - if (upgradeVersion != DATABASE_VERSION) { - Log.w(TAG, "Failed to upgrade database!"); - } - } - - private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) { - final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); - final ContentResolver cr = mContext.getContentResolver(); - final List<UserInfo> users = um.getUsers(); - for (int i = 0; i < users.size(); i++) { - final int userId = users.get(i).id; - final boolean enabled = mLockPatternUtils.hasWidgetsEnabledInKeyguard(userId); - Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled=" - + enabled + ", w[]=" + mLockPatternUtils.getAppWidgets()); - loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled); - } - } - - private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) { - SQLiteStatement stmt = null; - try { - stmt = db.compileStatement( - "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);"); - stmt.bindString(1, key); - stmt.bindLong(2, userId); - stmt.bindLong(3, value ? 1 : 0); - stmt.execute(); - } finally { - if (stmt != null) stmt.close(); - } - } - } - private static final String[] VALID_SETTINGS = new String[] { LockPatternUtils.LOCKOUT_PERMANENT_KEY, LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, @@ -704,13 +433,4 @@ public class LockSettingsService extends ILockSettings.Stub { } return null; } - - private class LockSettingsObserver implements DeathRecipient { - ILockSettingsObserver remote; - - @Override - public void binderDied() { - mObservers.remove(this); - } - } } diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java new file mode 100644 index 0000000..c03bb58 --- /dev/null +++ b/services/core/java/com/android/server/LockSettingsStorage.java @@ -0,0 +1,506 @@ +/* + * 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; + +import com.android.internal.annotations.VisibleForTesting; + +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.UserInfo; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Environment; +import android.os.UserManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +import static android.content.Context.USER_SERVICE; + +/** + * Storage for the lock settings service. + */ +class LockSettingsStorage { + + private static final String TAG = "LockSettingsStorage"; + private static final String TABLE = "locksettings"; + + private static final String COLUMN_KEY = "name"; + private static final String COLUMN_USERID = "user"; + private static final String COLUMN_VALUE = "value"; + + private static final String[] COLUMNS_FOR_QUERY = { + COLUMN_VALUE + }; + private static final String[] COLUMNS_FOR_PREFETCH = { + COLUMN_KEY, COLUMN_VALUE + }; + + 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 Object DEFAULT = new Object(); + + private final DatabaseHelper mOpenHelper; + private final Context mContext; + private final Cache mCache = new Cache(); + private final Object mFileWriteLock = new Object(); + + public LockSettingsStorage(Context context, Callback callback) { + mContext = context; + mOpenHelper = new DatabaseHelper(context, callback); + } + + public void writeKeyValue(String key, String value, int userId) { + writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId); + } + + public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) { + ContentValues cv = new ContentValues(); + cv.put(COLUMN_KEY, key); + cv.put(COLUMN_USERID, userId); + cv.put(COLUMN_VALUE, value); + + db.beginTransaction(); + try { + db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", + new String[] {key, Integer.toString(userId)}); + db.insert(TABLE, null, cv); + db.setTransactionSuccessful(); + mCache.putKeyValue(key, value, userId); + } finally { + db.endTransaction(); + } + + } + + public String readKeyValue(String key, String defaultValue, int userId) { + int version; + synchronized (mCache) { + if (mCache.hasKeyValue(key, userId)) { + return mCache.peekKeyValue(key, defaultValue, userId); + } + version = mCache.getVersion(); + } + + Cursor cursor; + Object result = DEFAULT; + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, + COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", + new String[] { Integer.toString(userId), key }, + null, null, null)) != null) { + if (cursor.moveToFirst()) { + result = cursor.getString(0); + } + cursor.close(); + } + mCache.putKeyValueIfUnchanged(key, result, userId, version); + return result == DEFAULT ? defaultValue : (String) result; + } + + public void prefetchUser(int userId) { + int version; + synchronized (mCache) { + if (mCache.isFetched(userId)) { + return; + } + mCache.setFetched(userId); + version = mCache.getVersion(); + } + + Cursor cursor; + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH, + COLUMN_USERID + "=?", + new String[] { Integer.toString(userId) }, + null, null, null)) != null) { + while (cursor.moveToNext()) { + String key = cursor.getString(0); + String value = cursor.getString(1); + mCache.putKeyValueIfUnchanged(key, value, userId, version); + } + cursor.close(); + } + + // Populate cache by reading the password and pattern files. + readPasswordHash(userId); + readPatternHash(userId); + } + + public byte[] readPasswordHash(int userId) { + final byte[] stored = readFile(getLockPasswordFilename(userId)); + if (stored != null && stored.length > 0) { + return stored; + } + return null; + } + + public byte[] readPatternHash(int userId) { + final byte[] stored = readFile(getLockPatternFilename(userId)); + if (stored != null && stored.length > 0) { + return stored; + } + return null; + } + + public boolean hasPassword(int userId) { + return hasFile(getLockPasswordFilename(userId)); + } + + public boolean hasPattern(int userId) { + return hasFile(getLockPatternFilename(userId)); + } + + private boolean hasFile(String name) { + byte[] contents = readFile(name); + return contents != null && contents.length > 0; + } + + private byte[] readFile(String name) { + int version; + synchronized (mCache) { + if (mCache.hasFile(name)) { + return mCache.peekFile(name); + } + version = mCache.getVersion(); + } + + RandomAccessFile raf = null; + byte[] stored = null; + try { + raf = new RandomAccessFile(name, "r"); + stored = new byte[(int) raf.length()]; + raf.readFully(stored, 0, stored.length); + raf.close(); + } catch (IOException e) { + Slog.e(TAG, "Cannot read file " + e); + } finally { + if (raf != null) { + try { + raf.close(); + } catch (IOException e) { + Slog.e(TAG, "Error closing file " + e); + } + } + } + mCache.putFileIfUnchanged(name, stored, version); + return stored; + } + + private void writeFile(String name, byte[] hash) { + synchronized (mFileWriteLock) { + RandomAccessFile raf = null; + try { + // Write the hash to file + raf = new RandomAccessFile(name, "rw"); + // Truncate the file if pattern is null, to clear the lock + if (hash == null || hash.length == 0) { + raf.setLength(0); + } else { + raf.write(hash, 0, hash.length); + } + raf.close(); + } catch (IOException e) { + Slog.e(TAG, "Error writing to file " + e); + } finally { + if (raf != null) { + try { + raf.close(); + } catch (IOException e) { + Slog.e(TAG, "Error closing file " + e); + } + } + } + mCache.putFile(name, hash); + } + } + + public void writePatternHash(byte[] hash, int userId) { + writeFile(getLockPatternFilename(userId), hash); + } + + public void writePasswordHash(byte[] hash, int userId) { + writeFile(getLockPasswordFilename(userId), hash); + } + + + @VisibleForTesting + String getLockPatternFilename(int userId) { + return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE); + } + + @VisibleForTesting + String getLockPasswordFilename(int userId) { + return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE); + } + + private String getLockCredentialFilePathForUser(int userId, String basename) { + userId = getUserParentOrSelfId(userId); + String dataSystemDirectory = + android.os.Environment.getDataDirectory().getAbsolutePath() + + SYSTEM_DIRECTORY; + if (userId == 0) { + // Leave it in the same place for user 0 + return dataSystemDirectory + basename; + } else { + return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath(); + } + } + + private int getUserParentOrSelfId(int userId) { + if (userId != 0) { + final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); + final UserInfo pi = um.getProfileParent(userId); + if (pi != null) { + return pi.id; + } + } + 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 + String name = getLockPasswordFilename(userId); + File file = new File(name); + if (file.exists()) { + file.delete(); + mCache.putFile(name, null); + } + name = getLockPatternFilename(userId); + file = new File(name); + if (file.exists()) { + file.delete(); + mCache.putFile(name, null); + } + } + } + + try { + db.beginTransaction(); + db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); + db.setTransactionSuccessful(); + mCache.removeUser(userId); + } finally { + db.endTransaction(); + } + } + + @VisibleForTesting + void closeDatabase() { + mOpenHelper.close(); + } + + @VisibleForTesting + void clearCache() { + mCache.clear(); + } + + public interface Callback { + void initialize(SQLiteDatabase db); + } + + class DatabaseHelper extends SQLiteOpenHelper { + private static final String TAG = "LockSettingsDB"; + private static final String DATABASE_NAME = "locksettings.db"; + + private static final int DATABASE_VERSION = 2; + + private final Callback mCallback; + + public DatabaseHelper(Context context, Callback callback) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + setWriteAheadLoggingEnabled(true); + mCallback = callback; + } + + private void createTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE + " (" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + + COLUMN_KEY + " TEXT," + + COLUMN_USERID + " INTEGER," + + COLUMN_VALUE + " TEXT" + + ");"); + } + + @Override + public void onCreate(SQLiteDatabase db) { + createTable(db); + mCallback.initialize(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { + int upgradeVersion = oldVersion; + if (upgradeVersion == 1) { + // Previously migrated lock screen widget settings. Now defunct. + upgradeVersion = 2; + } + + if (upgradeVersion != DATABASE_VERSION) { + Log.w(TAG, "Failed to upgrade database!"); + } + } + } + + /** + * Cache consistency model: + * - Writes to storage write directly to the cache, but this MUST happen within the atomic + * section either provided by the database transaction or mWriteLock, such that writes to the + * cache and writes to the backing storage are guaranteed to occur in the same order + * + * - Reads can populate the cache, but because they are no strong ordering guarantees with + * respect to writes this precaution is taken: + * - The cache is assigned a version number that increases every time the cache is modified. + * Reads from backing storage can only populate the cache if the backing storage + * has not changed since the load operation has begun. + * This guarantees that no read operation can shadow a write to the cache that happens + * after it had begun. + */ + private static class Cache { + private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>(); + private final CacheKey mCacheKey = new CacheKey(); + private int mVersion = 0; + + String peekKeyValue(String key, String defaultValue, int userId) { + Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId); + return cached == DEFAULT ? defaultValue : (String) cached; + } + + boolean hasKeyValue(String key, int userId) { + return contains(CacheKey.TYPE_KEY_VALUE, key, userId); + } + + void putKeyValue(String key, String value, int userId) { + put(CacheKey.TYPE_KEY_VALUE, key, value, userId); + } + + void putKeyValueIfUnchanged(String key, Object value, int userId, int version) { + putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version); + } + + byte[] peekFile(String fileName) { + return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */); + } + + boolean hasFile(String fileName) { + return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */); + } + + void putFile(String key, byte[] value) { + put(CacheKey.TYPE_FILE, key, value, -1 /* userId */); + } + + void putFileIfUnchanged(String key, byte[] value, int version) { + putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version); + } + + void setFetched(int userId) { + put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId); + } + + boolean isFetched(int userId) { + return contains(CacheKey.TYPE_FETCHED, "", userId); + } + + + private synchronized void put(int type, String key, Object value, int userId) { + // Create a new CachKey here because it may be saved in the map if the key is absent. + mCache.put(new CacheKey().set(type, key, userId), value); + mVersion++; + } + + private synchronized void putIfUnchanged(int type, String key, Object value, int userId, + int version) { + if (!contains(type, key, userId) && mVersion == version) { + put(type, key, value, userId); + } + } + + private synchronized boolean contains(int type, String key, int userId) { + return mCache.containsKey(mCacheKey.set(type, key, userId)); + } + + private synchronized Object peek(int type, String key, int userId) { + return mCache.get(mCacheKey.set(type, key, userId)); + } + + private synchronized int getVersion() { + return mVersion; + } + + synchronized void removeUser(int userId) { + for (int i = mCache.size() - 1; i >= 0; i--) { + if (mCache.keyAt(i).userId == userId) { + mCache.removeAt(i); + } + } + + // Make sure in-flight loads can't write to cache. + mVersion++; + } + + synchronized void clear() { + mCache.clear(); + mVersion++; + } + + private static final class CacheKey { + static final int TYPE_KEY_VALUE = 0; + static final int TYPE_FILE = 1; + static final int TYPE_FETCHED = 2; + + String key; + int userId; + int type; + + public CacheKey set(int type, String key, int userId) { + this.type = type; + this.key = key; + this.userId = userId; + return this; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof CacheKey)) + return false; + CacheKey o = (CacheKey) obj; + return userId == o.userId && type == o.type && key.equals(o.key); + } + + @Override + public int hashCode() { + return key.hashCode() ^ userId ^ type; + } + } + } +} diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java index 926235f..b1c4955 100644 --- a/services/core/java/com/android/server/MmsServiceBroker.java +++ b/services/core/java/com/android/server/MmsServiceBroker.java @@ -16,12 +16,11 @@ package com.android.server; -import com.android.internal.telephony.IMms; - import android.Manifest; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.Intent; @@ -35,9 +34,15 @@ import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Telephony; import android.telephony.TelephonyManager; import android.util.Slog; +import com.android.internal.telephony.IMms; + +import java.util.List; + /** * This class is a proxy for MmsService APIs. We need this because MmsService runs * in phone process and may crash anytime. This manages a connection to the actual @@ -118,7 +123,7 @@ public class MmsServiceBroker extends SystemService { } public void systemRunning() { - tryConnecting(); + Slog.i(TAG, "Delay connecting to MmsService until an API is called"); } private void tryConnecting() { @@ -206,7 +211,7 @@ public class MmsServiceBroker extends SystemService { * Throws a security exception unless the caller has carrier privilege. */ private void enforceCarrierPrivilege() { - String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + final String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); for (String pkg : packages) { if (getTelephonyManager().checkCarrierPrivilegesForPackage(pkg) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { @@ -216,31 +221,50 @@ public class MmsServiceBroker extends SystemService { throw new SecurityException("No carrier privilege"); } + private String getCallingPackageName() { + final String[] packages = getPackageManager().getPackagesForUid(Binder.getCallingUid()); + if (packages != null && packages.length > 0) { + return packages[0]; + } + return "unknown"; + } + // Service API calls implementation, proxied to the real MmsService in "com.android.mms.service" private final class BinderService extends IMms.Stub { + private static final String PHONE_PACKAGE_NAME = "com.android.phone"; + @Override - public void sendMessage(long subId, String callingPkg, Uri contentUri, + public void sendMessage(int subId, String callingPkg, Uri contentUri, String locationUrl, Bundle configOverrides, PendingIntent sentIntent) throws RemoteException { + Slog.d(TAG, "sendMessage() by " + callingPkg); mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { return; } + contentUri = adjustUriForUserAndGrantPermission(contentUri, + Telephony.Mms.Intents.MMS_SEND_ACTION, + Intent.FLAG_GRANT_READ_URI_PERMISSION); getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl, configOverrides, sentIntent); } @Override - public void downloadMessage(long subId, String callingPkg, String locationUrl, + public void downloadMessage(int subId, String callingPkg, String locationUrl, Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent) throws RemoteException { + Slog.d(TAG, "downloadMessage() by " + callingPkg); mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS, "Download MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_RECEIVE_MMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { return; } + contentUri = adjustUriForUserAndGrantPermission(contentUri, + Telephony.Mms.Intents.MMS_DOWNLOAD_ACTION, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, contentUri, configOverrides, downloadedIntent); } @@ -259,7 +283,8 @@ public class MmsServiceBroker extends SystemService { } @Override - public Bundle getCarrierConfigValues(long subId) throws RemoteException { + public Bundle getCarrierConfigValues(int subId) throws RemoteException { + Slog.d(TAG, "getCarrierConfigValues() by " + getCallingPackageName()); return getServiceGuarded().getCarrierConfigValues(subId); } @@ -360,7 +385,7 @@ public class MmsServiceBroker extends SystemService { } @Override - public void sendStoredMessage(long subId, String callingPkg, Uri messageUri, + 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"); @@ -386,5 +411,40 @@ public class MmsServiceBroker extends SystemService { public boolean getAutoPersisting() throws RemoteException { return getServiceGuarded().getAutoPersisting(); } + + /** + * Modifies the Uri to contain the caller's userId, if necessary. + * Grants the phone package on primary user permission to access the contentUri, + * even if the caller is not in the primary user. + * + * @param contentUri The Uri to adjust + * @param action The intent action used to find the associated carrier app + * @param permission The permission to add + * @return The adjusted Uri containing the calling userId. + */ + private Uri adjustUriForUserAndGrantPermission(Uri contentUri, String action, + int permission) { + final int callingUserId = UserHandle.getCallingUserId(); + if (callingUserId != UserHandle.USER_OWNER) { + contentUri = ContentProvider.maybeAddUserId(contentUri, callingUserId); + } + long token = Binder.clearCallingIdentity(); + try { + mContext.grantUriPermission(PHONE_PACKAGE_NAME, contentUri, permission); + + // Grant permission for the carrier app. + Intent intent = new Intent(action); + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + List<String> carrierPackages = telephonyManager.getCarrierPackageNamesForIntent( + intent); + if (carrierPackages != null && carrierPackages.size() == 1) { + mContext.grantUriPermission(carrierPackages.get(0), contentUri, permission); + } + } finally { + Binder.restoreCallingIdentity(token); + } + return contentUri; + } } } diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 24d81a0..e400fb6 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -826,7 +826,9 @@ class MountService extends IMountService.Stub // On an encrypted device we can't see system properties yet, so pull // the system locale out of the mount service. - copyLocaleFromMountService(); + if ("".equals(SystemProperties.get("vold.encrypt_progress"))) { + copyLocaleFromMountService(); + } // Let package manager load internal ASECs. mPms.scanAvailableAsecs(); diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java index 96f9ab0..8c3b020 100644 --- a/services/core/java/com/android/server/NativeDaemonConnector.java +++ b/services/core/java/com/android/server/NativeDaemonConnector.java @@ -176,7 +176,6 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo if (buffer[i] == 0) { final String rawEvent = new String( buffer, start, i - start, StandardCharsets.UTF_8); - log("RCV <- {" + rawEvent + "}"); boolean releaseWl = false; try { @@ -197,7 +196,6 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo mResponseQueue.add(event.getCmdNumber(), event); } } catch (IllegalArgumentException e) { - log("Problem parsing message: " + rawEvent + " - " + e); } finally { if (releaseWl) { mWakeLock.acquire(); @@ -209,7 +207,6 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo } if (start == 0) { final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8); - log("RCV incomplete <- {" + rawEvent + "}"); } // We should end at the amount we read. If not, compact then diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 020c951..5fe0d1c 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -239,7 +239,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub mDaemonHandler = new Handler(FgThread.get().getLooper()); - mPhoneStateListener = new PhoneStateListener(SubscriptionManager.DEFAULT_SUB_ID, + mPhoneStateListener = new PhoneStateListener(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mDaemonHandler.getLooper()) { @Override public void onDataConnectionRealTimeInfoChanged( @@ -940,6 +940,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override + public void setInterfaceIpv6NdOffload(String iface, boolean enable) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute( + "interface", "ipv6ndoffload", iface, (enable ? "enable" : "disable")); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override public void addRoute(int netId, RouteInfo route) { modifyRoute("add", "" + netId, route); } @@ -1854,23 +1865,23 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void stopClatd() throws IllegalStateException { + public void stopClatd(String interfaceName) throws IllegalStateException { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute("clatd", "stop"); + mConnector.execute("clatd", "stop", interfaceName); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } } @Override - public boolean isClatdStarted() { + public boolean isClatdStarted(String interfaceName) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); final NativeDaemonEvent event; try { - event = mConnector.execute("clatd", "status"); + event = mConnector.execute("clatd", "status", interfaceName); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java index 395e365..738917f 100644 --- a/services/core/java/com/android/server/NetworkScoreService.java +++ b/services/core/java/com/android/server/NetworkScoreService.java @@ -115,10 +115,10 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public boolean clearScores() { - // Only the active scorer or the system (who can broadcast BROADCAST_SCORE_NETWORKS) should - // be allowed to flush all scores. + // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED) + // should be allowed to flush all scores. if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) || - mContext.checkCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS) == + mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) == PackageManager.PERMISSION_GRANTED) { clearInternal(); return true; @@ -130,16 +130,25 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public boolean setActiveScorer(String packageName) { - mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG); + // TODO: For now, since SCORE_NETWORKS requires an app to be privileged, we allow such apps + // to directly set the scorer app rather than having to use the consent dialog. The + // assumption is that anyone bundling a scorer app with the system is trusted by the OEM to + // do the right thing and not enable this feature without explaining it to the user. + // In the future, should this API be opened to 3p apps, we will need to lock this down and + // figure out another way to streamline the UX. + + // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); + mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG); + return setScorerInternal(packageName); } @Override public void disableScoring() { - // Only the active scorer or the system (who can broadcast BROADCAST_SCORE_NETOWRKS) should - // be allowed to disable scoring. + // Only the active scorer or the system (who can broadcast BROADCAST_NETWORK_PRIVILEGED) + // should be allowed to disable scoring. if (NetworkScorerAppManager.isCallerActiveScorer(mContext, getCallingUid()) || - mContext.checkCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS) == + mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) == PackageManager.PERMISSION_GRANTED) { // The return value is discarded here because at this point, the call should always // succeed. The only reason for failure is if the new package is not a valid scorer, but @@ -188,7 +197,7 @@ public class NetworkScoreService extends INetworkScoreService.Stub { @Override public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) { - mContext.enforceCallingOrSelfPermission(permission.BROADCAST_SCORE_NETWORKS, TAG); + mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG); synchronized (mScoreCaches) { if (mScoreCaches.containsKey(networkType)) { throw new IllegalArgumentException( diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 6f378fd..9d4cd99 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -41,6 +41,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; /** * Service for reading and writing blocks to a persistent partition. @@ -63,22 +66,16 @@ public class PersistentDataBlockService extends SystemService { private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; private static final int HEADER_SIZE = 8; // Magic number to mark block device as adhering to the format consumed by this service - private static final int PARTITION_TYPE_MARKER = 0x1990; + private static final int PARTITION_TYPE_MARKER = 0x19901873; // Limit to 100k as blocks larger than this might cause strain on Binder. - // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100; + public static final int DIGEST_SIZE_BYTES = 32; private final Context mContext; private final String mDataBlockFile; private final Object mLock = new Object(); - private int mAllowedAppId = -1; - /* - * Separate lock for OEM unlock related operations as they can happen in parallel with regular - * block operations. - */ - private final Object mOemLock = new Object(); - + private int mAllowedUid = -1; private long mBlockDeviceSize; public PersistentDataBlockService(Context context) { @@ -86,11 +83,10 @@ public class PersistentDataBlockService extends SystemService { mContext = context; mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP); mBlockDeviceSize = -1; // Load lazily - mAllowedAppId = getAllowedAppId(UserHandle.USER_OWNER); + mAllowedUid = getAllowedUid(UserHandle.USER_OWNER); } - - private int getAllowedAppId(int userHandle) { + private int getAllowedUid(int userHandle) { String allowedPackage = mContext.getResources() .getString(R.string.config_persistentDataPackageName); PackageManager pm = mContext.getPackageManager(); @@ -101,11 +97,12 @@ public class PersistentDataBlockService extends SystemService { // not expected Slog.e(TAG, "not able to find package " + allowedPackage, e); } - return UserHandle.getAppId(allowedUid); + return allowedUid; } @Override public void onStart() { + enforceChecksumValidity(); publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService); } @@ -116,12 +113,21 @@ public class PersistentDataBlockService extends SystemService { } private void enforceUid(int callingUid) { - if (UserHandle.getAppId(callingUid) != mAllowedAppId) { + if (callingUid != mAllowedUid) { throw new SecurityException("uid " + callingUid + " not allowed to access PST"); } } + private void enforceIsOwner() { + if (!Binder.getCallingUserHandle().isOwner()) { + 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); + int totalDataSize; int blockId = inputStream.readInt(); if (blockId == PARTITION_TYPE_MARKER) { @@ -142,6 +148,143 @@ public class PersistentDataBlockService extends SystemService { return mBlockDeviceSize; } + private boolean enforceChecksumValidity() { + byte[] storedDigest = new byte[DIGEST_SIZE_BYTES]; + + synchronized (mLock) { + byte[] digest = computeDigestLocked(storedDigest); + if (digest == null || !Arrays.equals(storedDigest, digest)) { + Slog.i(TAG, "Formatting FRP partition..."); + formatPartitionLocked(); + return false; + } + } + + return true; + } + + private boolean computeAndWriteDigestLocked() { + byte[] digest = computeDigestLocked(null); + if (digest != null) { + DataOutputStream outputStream; + try { + outputStream = new DataOutputStream( + new FileOutputStream(new File(mDataBlockFile))); + } catch (FileNotFoundException e) { + Slog.e(TAG, "partition not available?", e); + return false; + } + + try { + outputStream.write(digest, 0, DIGEST_SIZE_BYTES); + outputStream.flush(); + } catch (IOException e) { + Slog.e(TAG, "failed to write block checksum", e); + return false; + } finally { + IoUtils.closeQuietly(outputStream); + } + return true; + } else { + return false; + } + } + + private byte[] computeDigestLocked(byte[] storedDigest) { + DataInputStream inputStream; + try { + inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); + } catch (FileNotFoundException e) { + Slog.e(TAG, "partition not available?", e); + return null; + } + + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + // won't ever happen -- every implementation is required to support SHA-256 + Slog.e(TAG, "SHA-256 not supported?", e); + IoUtils.closeQuietly(inputStream); + return null; + } + + try { + if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) { + inputStream.read(storedDigest); + } else { + inputStream.skipBytes(DIGEST_SIZE_BYTES); + } + + int read; + byte[] data = new byte[1024]; + md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest + while ((read = inputStream.read(data)) != -1) { + md.update(data, 0, read); + } + } catch (IOException e) { + Slog.e(TAG, "failed to read partition", e); + return null; + } finally { + IoUtils.closeQuietly(inputStream); + } + + return md.digest(); + } + + private void formatPartitionLocked() { + DataOutputStream outputStream; + try { + outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); + } catch (FileNotFoundException e) { + Slog.e(TAG, "partition not available?", e); + return; + } + + byte[] data = new byte[DIGEST_SIZE_BYTES]; + try { + outputStream.write(data, 0, DIGEST_SIZE_BYTES); + outputStream.writeInt(PARTITION_TYPE_MARKER); + outputStream.writeInt(0); // data size + outputStream.flush(); + } catch (IOException e) { + Slog.e(TAG, "failed to format block", e); + return; + } finally { + IoUtils.closeQuietly(outputStream); + } + + doSetOemUnlockEnabledLocked(false); + computeAndWriteDigestLocked(); + } + + private void doSetOemUnlockEnabledLocked(boolean enabled) { + FileOutputStream outputStream; + try { + outputStream = new FileOutputStream(new File(mDataBlockFile)); + } catch (FileNotFoundException e) { + Slog.e(TAG, "partition not available", e); + return; + } + + try { + FileChannel channel = outputStream.getChannel(); + + channel.position(getBlockDeviceSize() - 1); + + ByteBuffer data = ByteBuffer.allocate(1); + data.put(enabled ? (byte) 1 : (byte) 0); + data.flip(); + channel.write(data); + outputStream.flush(); + } catch (IOException e) { + Slog.e(TAG, "unable to access persistent partition", e); + return; + } finally { + IoUtils.closeQuietly(outputStream); + } + } + private native long nativeGetBlockDeviceSize(String path); private native int nativeWipe(String path); @@ -170,19 +313,23 @@ public class PersistentDataBlockService extends SystemService { headerAndData.putInt(data.length); headerAndData.put(data); - try { - synchronized (mLock) { - outputStream.write(headerAndData.array()); - return data.length; - } - } catch (IOException e) { - Slog.e(TAG, "failed writing to the persistent data block", e); - return -1; - } finally { + synchronized (mLock) { try { - outputStream.close(); + byte[] checksum = new byte[DIGEST_SIZE_BYTES]; + outputStream.write(checksum, 0, DIGEST_SIZE_BYTES); + outputStream.write(headerAndData.array()); + outputStream.flush(); } catch (IOException e) { - Slog.e(TAG, "failed closing output stream", e); + Slog.e(TAG, "failed writing to the persistent data block", e); + return -1; + } finally { + IoUtils.closeQuietly(outputStream); + } + + if (computeAndWriteDigestLocked()) { + return data.length; + } else { + return -1; } } } @@ -190,6 +337,9 @@ public class PersistentDataBlockService extends SystemService { @Override public byte[] read() { enforceUid(Binder.getCallingUid()); + if (!enforceChecksumValidity()) { + return new byte[0]; + } DataInputStream inputStream; try { @@ -249,30 +399,11 @@ public class PersistentDataBlockService extends SystemService { return; } enforceOemUnlockPermission(); - FileOutputStream outputStream; - try { - outputStream = new FileOutputStream(new File(mDataBlockFile)); - } catch (FileNotFoundException e) { - Slog.e(TAG, "parition not available", e); - return; - } - - try { - FileChannel channel = outputStream.getChannel(); - - channel.position(getBlockDeviceSize() - 1); + enforceIsOwner(); - ByteBuffer data = ByteBuffer.allocate(1); - data.put(enabled ? (byte) 1 : (byte) 0); - data.flip(); - - synchronized (mOemLock) { - channel.write(data); - } - } catch (IOException e) { - Slog.e(TAG, "unable to access persistent partition", e); - } finally { - IoUtils.closeQuietly(outputStream); + synchronized (mLock) { + doSetOemUnlockEnabledLocked(enabled); + computeAndWriteDigestLocked(); } } @@ -288,8 +419,8 @@ public class PersistentDataBlockService extends SystemService { } try { - inputStream.skip(getBlockDeviceSize() - 1); - synchronized (mOemLock) { + synchronized (mLock) { + inputStream.skip(getBlockDeviceSize() - 1); return inputStream.readByte() != 0; } } catch (IOException e) { @@ -302,7 +433,10 @@ public class PersistentDataBlockService extends SystemService { @Override public int getDataBlockSize() { - enforceUid(Binder.getCallingUid()); + if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE) + != PackageManager.PERMISSION_GRANTED) { + enforceUid(Binder.getCallingUid()); + } DataInputStream inputStream; try { @@ -329,6 +463,5 @@ public class PersistentDataBlockService extends SystemService { long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1; return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE; } - }; } diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index cf2a49f..92fbc1e 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -16,6 +16,7 @@ package com.android.server; +import android.app.ActivityManager; import android.content.pm.FeatureInfo; import android.os.*; import android.os.Process; @@ -32,8 +33,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; import static com.android.internal.util.ArrayUtils.appendInt; @@ -50,7 +49,7 @@ public class SystemConfig { // These are the built-in uid -> permission mappings that were read from the // system configuration files. - final SparseArray<HashSet<String>> mSystemPermissions = new SparseArray<>(); + final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>(); // These are the built-in shared libraries that were read from the // system configuration files. Keys are the library names; strings are the @@ -59,7 +58,7 @@ public class SystemConfig { // These are the features this devices supports that were read from the // system configuration files. - final HashMap<String, FeatureInfo> mAvailableFeatures = new HashMap<>(); + final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>(); public static final class PermissionEntry { public final String name; @@ -94,7 +93,7 @@ public class SystemConfig { return mGlobalGids; } - public SparseArray<HashSet<String>> getSystemPermissions() { + public SparseArray<ArraySet<String>> getSystemPermissions() { return mSystemPermissions; } @@ -102,7 +101,7 @@ public class SystemConfig { return mSharedLibraries; } - public HashMap<String, FeatureInfo> getAvailableFeatures() { + public ArrayMap<String, FeatureInfo> getAvailableFeatures() { return mAvailableFeatures; } @@ -179,6 +178,8 @@ public class SystemConfig { return; } + final boolean lowRam = ActivityManager.isLowRamDeviceStatic(); + try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(permReader); @@ -252,9 +253,9 @@ public class SystemConfig { continue; } perm = perm.intern(); - HashSet<String> perms = mSystemPermissions.get(uid); + ArraySet<String> perms = mSystemPermissions.get(uid); if (perms == null) { - perms = new HashSet<String>(); + perms = new ArraySet<String>(); mSystemPermissions.put(uid, perms); } perms.add(perm); @@ -278,10 +279,17 @@ public class SystemConfig { } else if ("feature".equals(name)) { String fname = parser.getAttributeValue(null, "name"); + boolean allowed; + if (!lowRam) { + allowed = true; + } else { + String notLowRam = parser.getAttributeValue(null, "notLowRam"); + allowed = !"true".equals(notLowRam); + } if (fname == null) { Slog.w(TAG, "<feature> without name at " + parser.getPositionDescription()); - } else { + } else if (allowed) { //Log.i(TAG, "Got feature " + fname); FeatureInfo fi = new FeatureInfo(); fi.name = fname; diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index fe005ec..34da901 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -41,7 +41,6 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.CellInfo; import android.telephony.VoLteServiceState; -import android.telephony.TelephonyManager; import android.telephony.DisconnectCause; import android.telephony.PreciseCallState; import android.telephony.PreciseDataConnectionState; @@ -50,12 +49,12 @@ import android.text.TextUtils; import android.text.format.Time; import java.util.ArrayList; -import java.util.Calendar; import java.util.List; import java.io.FileDescriptor; import java.io.PrintWriter; import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.DefaultPhoneNotifier; @@ -72,7 +71,7 @@ import com.android.server.am.BatteryStatsService; * and 15973975 by saving the phoneId of the registrant and then using the * phoneId when deciding to to make a callback. This is necessary because * a subId changes from to a dummy value when a SIM is removed and thus won't - * compare properly. Because SubscriptionManager.getPhoneId(long subId) handles + * compare properly. Because SubscriptionManager.getPhoneId(int subId) handles * the dummy value conversion we properly do the callbacks. * * Eventually we may want to remove the notion of dummy value but for now this @@ -90,19 +89,31 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { IBinder binder; IPhoneStateListener callback; + IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback; int callerUid; int events; - long subId; + int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + int phoneId = SubscriptionManager.INVALID_PHONE_INDEX; - int phoneId; + boolean matchPhoneStateListenerEvent(int events) { + return (callback != null) && ((events & this.events) != 0); + } + + boolean matchOnSubscriptionsChangedListener() { + return (onSubscriptionsChangedListenerCallback != null); + } @Override public String toString() { - return "{pkgForDebug=" + pkgForDebug + " callerUid=" + callerUid + " subId=" + subId + - " phoneId=" + phoneId + " events=" + Integer.toHexString(events) + "}"; + return "{pkgForDebug=" + pkgForDebug + " binder=" + binder + " callback=" + callback + + " onSubscriptionsChangedListenererCallback=" + + onSubscriptionsChangedListenerCallback + + " callerUid=" + callerUid + " subId=" + subId + " phoneId=" + phoneId + + " events=" + Integer.toHexString(events) + "}"; } } @@ -114,6 +125,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private final IBatteryStats mBatteryStats; + private boolean hasNotifySubscriptionInfoChangedOccurred = false; + private int mNumPhones; private int[] mCallState; @@ -154,6 +167,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private VoLteServiceState mVoLteServiceState = new VoLteServiceState(); + private int mDefaultSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + + private int mDefaultPhoneId = SubscriptionManager.INVALID_PHONE_INDEX; + private DataConnectionRealTimeInfo mDcRtInfo = new DataConnectionRealTimeInfo(); private int mRingingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE; @@ -195,8 +212,28 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } break; } - case MSG_UPDATE_DEFAULT_SUB: {// do nothing - if (VDBG) log(TAG + "MSG_UPDATE_DEFAULT_SUB"); + case MSG_UPDATE_DEFAULT_SUB: { + int newDefaultPhoneId = msg.arg1; + int newDefaultSubId = (Integer)(msg.obj); + if (VDBG) { + log("MSG_UPDATE_DEFAULT_SUB:current mDefaultSubId=" + mDefaultSubId + + " current mDefaultPhoneId=" + mDefaultPhoneId + " newDefaultSubId= " + + newDefaultSubId + " newDefaultPhoneId=" + newDefaultPhoneId); + } + + //Due to possible risk condition,(notify call back using the new + //defaultSubId comes before new defaultSubId update) we need to recall all + //possible missed notify callback + synchronized (mRecords) { + for (Record r : mRecords) { + if(r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { + checkPossibleMissNotify(r, newDefaultPhoneId); + } + } + handleRemoveListLocked(); + } + mDefaultSubId = newDefaultSubId; + mDefaultPhoneId = newDefaultPhoneId; } } } @@ -212,10 +249,21 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (DBG) log("onReceive: userHandle=" + userHandle); mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED, userHandle, 0)); } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED)) { + Integer newDefaultSubIdObj = new Integer(intent.getIntExtra( + PhoneConstants.SUBSCRIPTION_KEY, SubscriptionManager.getDefaultSubId())); + int newDefaultPhoneId = intent.getIntExtra(PhoneConstants.SLOT_KEY, + SubscriptionManager.getPhoneId(mDefaultSubId)); if (DBG) { - log(TAG + "onReceive: ACTION_DEFAULT_SUBSCRIPTION_CHANGED"); + log("onReceive:current mDefaultSubId=" + mDefaultSubId + + " current mDefaultPhoneId=" + mDefaultPhoneId + " newDefaultSubId= " + + newDefaultSubIdObj + " newDefaultPhoneId=" + newDefaultPhoneId); + } + + if(validatePhoneId(newDefaultPhoneId) && (newDefaultSubIdObj.equals(mDefaultSubId) + || (newDefaultPhoneId != mDefaultPhoneId))) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_DEFAULT_SUB, + newDefaultPhoneId, 0, newDefaultSubIdObj)); } - mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_DEFAULT_SUB, 0, 0)); } } }; @@ -290,20 +338,114 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } @Override + public void registerOnSubscriptionsChangedListener(String pkgForDebug, + IOnSubscriptionsChangedListener callback) { + int callerUid = UserHandle.getCallingUserId(); + int myUid = UserHandle.myUserId(); + if (VDBG) { + log("listen oscl: E pkg=" + pkgForDebug + " myUid=" + myUid + + " callerUid=" + callerUid + " callback=" + callback + + " callback.asBinder=" + callback.asBinder()); + } + + /* Checks permission and throws Security exception */ + checkOnSubscriptionsChangedListenerPermission(); + Record r = null; + + synchronized (mRecords) { + // register + find_and_add: { + IBinder b = callback.asBinder(); + final int N = mRecords.size(); + for (int i = 0; i < N; i++) { + r = mRecords.get(i); + if (b == r.binder) { + break find_and_add; + } + } + r = new Record(); + r.binder = b; + mRecords.add(r); + if (DBG) log("listen oscl: add new record"); + } + + r.onSubscriptionsChangedListenerCallback = callback; + r.pkgForDebug = pkgForDebug; + r.callerUid = callerUid; + r.events = 0; + if (DBG) { + log("listen oscl: Register r=" + r); + } + // Always notify when registration occurs if there has been a notification. + if (hasNotifySubscriptionInfoChangedOccurred) { + try { + if (VDBG) log("listen oscl: send to r=" + r); + r.onSubscriptionsChangedListenerCallback.onSubscriptionsChanged(); + if (VDBG) log("listen oscl: sent to r=" + r); + } catch (RemoteException e) { + if (VDBG) log("listen oscl: remote exception sending to r=" + r + " e=" + e); + remove(r.binder); + } + } else { + log("listen oscl: hasNotifySubscriptionInfoChangedOccurred==false no callback"); + } + } + } + + @Override + public void unregisterOnSubscriptionsChangedListener(String pkgForDebug, + IOnSubscriptionsChangedListener callback) { + if (DBG) log("listen oscl: Unregister"); + remove(callback.asBinder()); + } + + private void checkOnSubscriptionsChangedListenerPermission() { + mContext.enforceCallingOrSelfPermission( + SubscriptionManager.OnSubscriptionsChangedListener + .PERMISSION_ON_SUBSCRIPTIONS_CHANGED, null); + } + + @Override + public void notifySubscriptionInfoChanged() { + if (VDBG) log("notifySubscriptionInfoChanged:"); + synchronized (mRecords) { + if (!hasNotifySubscriptionInfoChangedOccurred) { + log("notifySubscriptionInfoChanged: first invocation mRecords.size=" + + mRecords.size()); + } + hasNotifySubscriptionInfoChangedOccurred = true; + mRemoveList.clear(); + for (Record r : mRecords) { + if (r.matchOnSubscriptionsChangedListener()) { + try { + if (VDBG) log("notifySubscriptionInfoChanged: call osc to r=" + r); + r.onSubscriptionsChangedListenerCallback.onSubscriptionsChanged(); + if (VDBG) log("notifySubscriptionInfoChanged: done osc to r=" + r); + } catch (RemoteException ex) { + if (VDBG) log("notifySubscriptionInfoChanged: RemoteException r=" + r); + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); + } + } + + @Override public void listen(String pkgForDebug, IPhoneStateListener callback, int events, boolean notifyNow) { - listenForSubscriber(SubscriptionManager.DEFAULT_SUB_ID, pkgForDebug, callback, events, - notifyNow); + listenForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, pkgForDebug, callback, + events, notifyNow); } @Override - public void listenForSubscriber(long subId, String pkgForDebug, IPhoneStateListener callback, + public void listenForSubscriber(int subId, String pkgForDebug, IPhoneStateListener callback, int events, boolean notifyNow) { listen(pkgForDebug, callback, events, notifyNow, subId); } private void listen(String pkgForDebug, IPhoneStateListener callback, int events, - boolean notifyNow, long subId) { + boolean notifyNow, int subId) { int callerUid = UserHandle.getCallingUserId(); int myUid = UserHandle.myUserId(); if (VDBG) { @@ -311,10 +453,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { + " notifyNow=" + notifyNow + " subId=" + subId + " myUid=" + myUid + " callerUid=" + callerUid); } - if (events != 0) { + + if (events != PhoneStateListener.LISTEN_NONE) { /* Checks permission and throws Security exception */ checkListenerPermission(events); - synchronized (mRecords) { // register Record r = null; @@ -329,26 +471,26 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } r = new Record(); r.binder = b; - r.callback = callback; - r.pkgForDebug = pkgForDebug; - r.callerUid = callerUid; - // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, - // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID - if (!SubscriptionManager.isValidSubId(subId)) { - r.subId = SubscriptionManager.DEFAULT_SUB_ID; - } else {//APP specify subID - r.subId = subId; - } - r.phoneId = SubscriptionManager.getPhoneId(r.subId); - mRecords.add(r); if (DBG) log("listen: add new record"); } + r.callback = callback; + r.pkgForDebug = pkgForDebug; + r.callerUid = callerUid; + // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, + // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID + if (!SubscriptionManager.isValidSubId(subId)) { + r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + } else {//APP specify subID + r.subId = subId; + } + r.phoneId = SubscriptionManager.getPhoneId(r.subId); + int phoneId = r.phoneId; r.events = events; if (DBG) { - log("listen: r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId); + log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId); } if (VDBG) toStringLogSSC("listen"); if (notifyNow && validatePhoneId(phoneId)) { @@ -468,6 +610,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } } else { + if(DBG) log("listen: Unregister"); remove(callback.asBinder()); } } @@ -477,6 +620,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { final int recordCount = mRecords.size(); for (int i = 0; i < recordCount; i++) { if (mRecords.get(i).binder == binder) { + if (VDBG) log("remove: binder=" + binder); mRecords.remove(i); return; } @@ -495,8 +639,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { for (Record r : mRecords) { - if (((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) && - (r.subId == SubscriptionManager.DEFAULT_SUB_ID)) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) && + (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) { try { r.callback.onCallStateChanged(state, incomingNumber); } catch (RemoteException ex) { @@ -506,10 +650,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } handleRemoveListLocked(); } - broadcastCallStateChanged(state, incomingNumber, SubscriptionManager.DEFAULT_SUB_ID); + broadcastCallStateChanged(state, incomingNumber, + SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); } - public void notifyCallStateForSubscriber(long subId, int state, String incomingNumber) { + public void notifyCallStateForSubscriber(int subId, int state, String incomingNumber) { if (!checkNotifyPermission("notifyCallState()")) { return; } @@ -523,9 +668,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallState[phoneId] = state; mCallIncomingNumber[phoneId] = incomingNumber; for (Record r : mRecords) { - if (((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) && + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) && (r.subId == subId) && - (r.subId != SubscriptionManager.DEFAULT_SUB_ID)) { + (r.subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) { try { r.callback.onCallStateChanged(state, incomingNumber); } catch (RemoteException ex) { @@ -539,7 +684,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { broadcastCallStateChanged(state, incomingNumber, subId); } - public void notifyServiceStateForPhoneId(int phoneId, long subId, ServiceState state) { + public void notifyServiceStateForPhoneId(int phoneId, int subId, ServiceState state) { if (!checkNotifyPermission("notifyServiceState()")){ return; } @@ -559,9 +704,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId + " phoneId=" + phoneId + " state=" + state); } - if (((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) && - ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SERVICE_STATE) && + idMatch(r.subId, subId, phoneId)) { try { if (DBG) { log("notifyServiceStateForSubscriber: callback.onSSC r=" + r @@ -583,10 +727,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifySignalStrength(SignalStrength signalStrength) { - notifySignalStrengthForSubscriber(SubscriptionManager.DEFAULT_SUB_ID, signalStrength); + notifySignalStrengthForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, + signalStrength); } - public void notifySignalStrengthForSubscriber(long subId, SignalStrength signalStrength) { + public void notifySignalStrengthForSubscriber(int subId, SignalStrength signalStrength) { if (!checkNotifyPermission("notifySignalStrength()")) { return; } @@ -605,9 +750,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { log("notifySignalStrengthForSubscriber: r=" + r + " subId=" + subId + " phoneId=" + phoneId + " ss=" + signalStrength); } - if (((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) && - ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) && + idMatch(r.subId, subId, phoneId)) { try { if (DBG) { log("notifySignalStrengthForSubscriber: callback.onSsS r=" + r @@ -619,9 +764,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mRemoveList.add(r.binder); } } - if (((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) && - ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SIGNAL_STRENGTH) && + idMatch(r.subId, subId, phoneId)){ try { int gsmSignalStrength = signalStrength.getGsmSignalStrength(); int ss = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength); @@ -645,10 +789,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCellInfo(List<CellInfo> cellInfo) { - notifyCellInfoForSubscriber(SubscriptionManager.DEFAULT_SUB_ID, cellInfo); + notifyCellInfoForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellInfo); } - public void notifyCellInfoForSubscriber(long subId, List<CellInfo> cellInfo) { + public void notifyCellInfoForSubscriber(int subId, List<CellInfo> cellInfo) { if (!checkNotifyPermission("notifyCellInfo()")) { return; } @@ -663,8 +807,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellInfo.set(phoneId, cellInfo); for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) && - ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r); @@ -706,7 +849,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } @Override - public void notifyMessageWaitingChangedForPhoneId(int phoneId, long subId, boolean mwi) { + public void notifyMessageWaitingChangedForPhoneId(int phoneId, int subId, boolean mwi) { if (!checkNotifyPermission("notifyMessageWaitingChanged()")) { return; } @@ -718,9 +861,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mMessageWaiting[phoneId] = mwi; for (Record r : mRecords) { - if (((r.events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) && - ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) && + idMatch(r.subId, subId, phoneId)) { try { r.callback.onMessageWaitingIndicatorChanged(mwi); } catch (RemoteException ex) { @@ -734,10 +877,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCallForwardingChanged(boolean cfi) { - notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUB_ID, cfi); + notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi); } - public void notifyCallForwardingChangedForSubscriber(long subId, boolean cfi) { + public void notifyCallForwardingChangedForSubscriber(int subId, boolean cfi) { if (!checkNotifyPermission("notifyCallForwardingChanged()")) { return; } @@ -750,9 +893,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (validatePhoneId(phoneId)) { mCallForwarding[phoneId] = cfi; for (Record r : mRecords) { - if (((r.events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) && - ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) && + idMatch(r.subId, subId, phoneId)) { try { r.callback.onCallForwardingIndicatorChanged(cfi); } catch (RemoteException ex) { @@ -766,22 +909,24 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyDataActivity(int state) { - notifyDataActivityForSubscriber(SubscriptionManager.DEFAULT_SUB_ID, state); + notifyDataActivityForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state); } - public void notifyDataActivityForSubscriber(long subId, int state) { + public void notifyDataActivityForSubscriber(int subId, int state) { if (!checkNotifyPermission("notifyDataActivity()" )) { return; } synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); - mDataActivity[phoneId] = state; - for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) { - try { - r.callback.onDataActivity(state); - } catch (RemoteException ex) { - mRemoveList.add(r.binder); + if (validatePhoneId(phoneId)) { + mDataActivity[phoneId] = state; + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_DATA_ACTIVITY)) { + try { + r.callback.onDataActivity(state); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } } } @@ -792,12 +937,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { public void notifyDataConnection(int state, boolean isDataConnectivityPossible, String reason, String apn, String apnType, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int networkType, boolean roaming) { - notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_SUB_ID, state, + notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state, isDataConnectivityPossible,reason, apn, apnType, linkProperties, networkCapabilities, networkType, roaming); } - public void notifyDataConnectionForSubscriber(long subId, int state, + public void notifyDataConnectionForSubscriber(int subId, int state, boolean isDataConnectivityPossible, String reason, String apn, String apnType, LinkProperties linkProperties, NetworkCapabilities networkCapabilities, int networkType, boolean roaming) { @@ -813,66 +958,69 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } synchronized (mRecords) { int phoneId = SubscriptionManager.getPhoneId(subId); - boolean modified = false; - if (state == TelephonyManager.DATA_CONNECTED) { - if (!mConnectedApns.contains(apnType)) { - mConnectedApns.add(apnType); - if (mDataConnectionState[phoneId] != state) { - mDataConnectionState[phoneId] = state; - modified = true; + if (validatePhoneId(phoneId)) { + boolean modified = false; + if (state == TelephonyManager.DATA_CONNECTED) { + if (!mConnectedApns.contains(apnType)) { + mConnectedApns.add(apnType); + if (mDataConnectionState[phoneId] != state) { + mDataConnectionState[phoneId] = state; + modified = true; + } } - } - } else { - if (mConnectedApns.remove(apnType)) { - if (mConnectedApns.isEmpty()) { - mDataConnectionState[phoneId] = state; - modified = true; - } else { - // leave mDataConnectionState as is and - // send out the new status for the APN in question. + } else { + if (mConnectedApns.remove(apnType)) { + if (mConnectedApns.isEmpty()) { + mDataConnectionState[phoneId] = state; + modified = true; + } else { + // leave mDataConnectionState as is and + // send out the new status for the APN in question. + } } } - } - mDataConnectionPossible[phoneId] = isDataConnectivityPossible; - mDataConnectionReason[phoneId] = reason; - mDataConnectionLinkProperties[phoneId] = linkProperties; - mDataConnectionNetworkCapabilities[phoneId] = networkCapabilities; - if (mDataConnectionNetworkType[phoneId] != networkType) { - mDataConnectionNetworkType[phoneId] = networkType; - // need to tell registered listeners about the new network type - modified = true; - } - if (modified) { - if (DBG) { - log("onDataConnectionStateChanged(" + mDataConnectionState[phoneId] - + ", " + mDataConnectionNetworkType[phoneId] + ")"); + mDataConnectionPossible[phoneId] = isDataConnectivityPossible; + mDataConnectionReason[phoneId] = reason; + mDataConnectionLinkProperties[phoneId] = linkProperties; + mDataConnectionNetworkCapabilities[phoneId] = networkCapabilities; + if (mDataConnectionNetworkType[phoneId] != networkType) { + mDataConnectionNetworkType[phoneId] = networkType; + // need to tell registered listeners about the new network type + modified = true; + } + if (modified) { + if (DBG) { + log("onDataConnectionStateChanged(" + mDataConnectionState[phoneId] + + ", " + mDataConnectionNetworkType[phoneId] + ")"); + } + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) && + idMatch(r.subId, subId, phoneId)) { + try { + log("Notify data connection state changed on sub: " + + subId); + r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId], + mDataConnectionNetworkType[phoneId]); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); } + mPreciseDataConnectionState = new PreciseDataConnectionState(state, networkType, + apnType, apn, reason, linkProperties, ""); for (Record r : mRecords) { - if (((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) && - ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) { try { - log("Notify data connection state changed on sub: " + - subId); - r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId], - mDataConnectionNetworkType[phoneId]); + r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState); } catch (RemoteException ex) { mRemoveList.add(r.binder); } } } - handleRemoveListLocked(); - } - mPreciseDataConnectionState = new PreciseDataConnectionState(state, networkType, - apnType, apn, reason, linkProperties, ""); - for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) { - try { - r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState); - } catch (RemoteException ex) { - mRemoveList.add(r.binder); - } - } } handleRemoveListLocked(); } @@ -883,11 +1031,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyDataConnectionFailed(String reason, String apnType) { - notifyDataConnectionFailedForSubscriber(SubscriptionManager.DEFAULT_SUB_ID, + notifyDataConnectionFailedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, reason, apnType); } - public void notifyDataConnectionFailedForSubscriber(long subId, + public void notifyDataConnectionFailedForSubscriber(int subId, String reason, String apnType) { if (!checkNotifyPermission("notifyDataConnectionFailed()")) { return; @@ -901,7 +1049,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, "", reason, null, ""); for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) { try { r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState); } catch (RemoteException ex) { @@ -917,10 +1066,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } public void notifyCellLocation(Bundle cellLocation) { - notifyCellLocationForSubscriber(SubscriptionManager.DEFAULT_SUB_ID, cellLocation); + notifyCellLocationForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cellLocation); } - public void notifyCellLocationForSubscriber(long subId, Bundle cellLocation) { + public void notifyCellLocationForSubscriber(int subId, Bundle cellLocation) { log("notifyCellLocationForSubscriber: subId=" + subId + " cellLocation=" + cellLocation); if (!checkNotifyPermission("notifyCellLocation()")) { @@ -936,8 +1085,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mCellLocation[phoneId] = cellLocation; for (Record r : mRecords) { if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) && - ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + idMatch(r.subId, subId, phoneId)) { try { if (DBG_LOC) { log("notifyCellLocation: cellLocation=" + cellLocation @@ -961,7 +1109,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { mOtaspMode = otaspMode; for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_OTASP_CHANGED)) { try { r.callback.onOtaspChanged(otaspMode); } catch (RemoteException ex) { @@ -987,7 +1135,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID); for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_PRECISE_CALL_STATE)) { try { r.callback.onPreciseCallStateChanged(mPreciseCallState); } catch (RemoteException ex) { @@ -1010,7 +1158,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mPreciseCallState = new PreciseCallState(mRingingCallState, mForegroundCallState, mBackgroundCallState, disconnectCause, preciseDisconnectCause); for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_PRECISE_CALL_STATE)) { try { r.callback.onPreciseCallStateChanged(mPreciseCallState); } catch (RemoteException ex) { @@ -1034,7 +1182,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, apn, reason, null, failCause); for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) { try { r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState); } catch (RemoteException ex) { @@ -1055,7 +1204,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { mVoLteServiceState = lteState; for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_VOLTE_STATE) != 0) { + if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_VOLTE_STATE)) { try { r.callback.onVoLteServiceStateChanged( new VoLteServiceState(mVoLteServiceState)); @@ -1068,7 +1217,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - public void notifyOemHookRawEventForSubscriber(long subId, byte[] rawData) { + public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) { if (!checkNotifyPermission("notifyOemHookRawEventForSubscriber")) { return; } @@ -1078,9 +1227,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (VDBG) { log("notifyOemHookRawEventForSubscriber: r=" + r + " subId=" + subId); } - if (((r.events & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) && + if ((r.matchPhoneStateListenerEvent( + PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT)) && ((r.subId == subId) || - (r.subId == SubscriptionManager.DEFAULT_SUB_ID))) { + (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID))) { try { r.callback.onOemHookRawEvent(rawData); } catch (RemoteException ex) { @@ -1134,7 +1284,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { // the legacy intent broadcasting // - private void broadcastServiceStateChanged(ServiceState state, long subId) { + private void broadcastServiceStateChanged(ServiceState state, int subId) { long ident = Binder.clearCallingIdentity(); try { mBatteryStats.notePhoneState(state.getState()); @@ -1153,7 +1303,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } - private void broadcastSignalStrengthChanged(SignalStrength signalStrength, long subId) { + private void broadcastSignalStrengthChanged(SignalStrength signalStrength, int subId) { long ident = Binder.clearCallingIdentity(); try { mBatteryStats.notePhoneSignalStrength(signalStrength); @@ -1172,7 +1322,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } - private void broadcastCallStateChanged(int state, String incomingNumber, long subId) { + private void broadcastCallStateChanged(int state, String incomingNumber, int subId) { long ident = Binder.clearCallingIdentity(); try { if (state == TelephonyManager.CALL_STATE_IDLE) { @@ -1200,7 +1350,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void broadcastDataConnectionStateChanged(int state, boolean isDataConnectivityPossible, String reason, String apn, String apnType, LinkProperties linkProperties, - NetworkCapabilities networkCapabilities, boolean roaming, long subId) { + NetworkCapabilities networkCapabilities, boolean roaming, int subId) { // Note: not reporting to the battery stats service here, because the // status bar takes care of that after taking into account all of the // required info. @@ -1232,7 +1382,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } private void broadcastDataConnectionFailed(String reason, String apnType, - long subId) { + int subId) { Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); intent.putExtra(PhoneConstants.FAILURE_REASON_KEY, reason); intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType); @@ -1253,14 +1403,17 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } private void broadcastPreciseDataConnectionStateChanged(int state, int networkType, - String apnType, String apn, String reason, LinkProperties linkProperties, String failCause) { + String apnType, String apn, String reason, LinkProperties linkProperties, + String failCause) { Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED); intent.putExtra(PhoneConstants.STATE_KEY, state); intent.putExtra(PhoneConstants.DATA_NETWORK_TYPE_KEY, networkType); if (reason != null) intent.putExtra(PhoneConstants.STATE_CHANGE_REASON_KEY, reason); if (apnType != null) intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType); if (apn != null) intent.putExtra(PhoneConstants.DATA_APN_KEY, apn); - if (linkProperties != null) intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties); + if (linkProperties != null) { + intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY,linkProperties); + } if (failCause != null) intent.putExtra(PhoneConstants.DATA_FAILURE_CAUSE_KEY, failCause); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, @@ -1309,7 +1462,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } private void handleRemoveListLocked() { - if (mRemoveList.size() > 0) { + int size = mRemoveList.size(); + if (VDBG) log("handleRemoveListLocked: mRemoveList.size()=" + size); + if (size > 0) { for (IBinder b: mRemoveList) { remove(b); } @@ -1323,7 +1478,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { boolean valid = false; try { foregroundUser = ActivityManager.getCurrentUser(); - valid = r.callerUid == foregroundUser && (r.events & events) != 0; + valid = r.callerUid == foregroundUser && r.matchPhoneStateListenerEvent(events); if (DBG | DBG_LOC) { log("validateEventsAndUserLocked: valid=" + valid + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser @@ -1348,24 +1503,25 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private static class LogSSC { private Time mTime; private String mS; - private long mSubId; + private int mSubId; private int mPhoneId; private ServiceState mState; - public void set(Time t, String s, long subId, int phoneId, ServiceState state) { + public void set(Time t, String s, int subId, int phoneId, ServiceState state) { mTime = t; mS = s; mSubId = subId; mPhoneId = phoneId; mState = state; } @Override public String toString() { - return mS + " " + mTime.toString() + " " + mSubId + " " + mPhoneId + " " + mState; + return mS + " Time " + mTime.toString() + " mSubId " + mSubId + " mPhoneId " + + mPhoneId + " mState " + mState; } } private LogSSC logSSC [] = new LogSSC[10]; private int next = 0; - private void logServiceStateChanged(String s, long subId, int phoneId, ServiceState state) { + private void logServiceStateChanged(String s, int subId, int phoneId, ServiceState state) { if (logSSC == null || logSSC.length == 0) { return; } @@ -1400,4 +1556,121 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { log(prompt + ": ----------------"); } } + + boolean idMatch(int rSubId, int subId, int phoneId) { + if(rSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) { + if(subId < 0) { + // Invalid case, we need compare phoneId with default one. + return (mDefaultPhoneId == phoneId); + } + return (subId == mDefaultSubId); + } else { + return (rSubId == subId); + } + } + + private void checkPossibleMissNotify(Record r, int phoneId) { + int events = r.events; + + if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) { + try { + if (VDBG) log("checkPossibleMissNotify: onServiceStateChanged state=" + + mServiceState[phoneId]); + r.callback.onServiceStateChanged( + new ServiceState(mServiceState[phoneId])); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + + if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) { + try { + SignalStrength signalStrength = mSignalStrength[phoneId]; + if (DBG) { + log("checkPossibleMissNotify: onSignalStrengthsChanged SS=" + signalStrength); + } + r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + + if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) { + try { + int gsmSignalStrength = mSignalStrength[phoneId] + .getGsmSignalStrength(); + if (DBG) { + log("checkPossibleMissNotify: onSignalStrengthChanged SS=" + + gsmSignalStrength); + } + r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1 + : gsmSignalStrength)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + + if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) { + try { + if (DBG_LOC) { + log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = " + + mCellInfo.get(phoneId)); + } + r.callback.onCellInfoChanged(mCellInfo.get(phoneId)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + + if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) { + try { + if (VDBG) { + log("checkPossibleMissNotify: onMessageWaitingIndicatorChanged phoneId=" + + phoneId + " mwi=" + mMessageWaiting[phoneId]); + } + r.callback.onMessageWaitingIndicatorChanged( + mMessageWaiting[phoneId]); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + + if ((events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) { + try { + if (VDBG) { + log("checkPossibleMissNotify: onCallForwardingIndicatorChanged phoneId=" + + phoneId + " cfi=" + mCallForwarding[phoneId]); + } + r.callback.onCallForwardingIndicatorChanged( + mCallForwarding[phoneId]); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + + if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) { + try { + if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = " + + mCellLocation[phoneId]); + r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId])); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + + if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) { + try { + if (DBG) { + log("checkPossibleMissNotify: onDataConnectionStateChanged(mDataConnectionState" + + "=" + mDataConnectionState[phoneId] + + ", mDataConnectionNetworkType=" + mDataConnectionNetworkType[phoneId] + + ")"); + } + r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId], + mDataConnectionNetworkType[phoneId]); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } } diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java index d4c436f..5add88e 100644 --- a/services/core/java/com/android/server/TextServicesManagerService.java +++ b/services/core/java/com/android/server/TextServicesManagerService.java @@ -16,6 +16,7 @@ package com.android.server; +import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.textservice.ISpellCheckerService; import com.android.internal.textservice.ISpellCheckerSession; @@ -28,14 +29,18 @@ import org.xmlpull.v1.XmlPullParserException; import android.app.ActivityManagerNative; import android.app.AppGlobals; import android.app.IUserSwitchObserver; +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; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -43,6 +48,7 @@ import android.os.IRemoteCallback; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.service.textservice.SpellCheckerService; import android.text.TextUtils; @@ -84,6 +90,12 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { public TextServicesManagerService(Context context) { mSystemReady = false; mContext = context; + + final IntentFilter broadcastFilter = new IntentFilter(); + broadcastFilter.addAction(Intent.ACTION_USER_ADDED); + broadcastFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(new TextServicesBroadcastReceiver(), broadcastFilter); + int userId = UserHandle.USER_OWNER; try { ActivityManagerNative.getDefault().registerUserSwitchObserver( @@ -119,6 +131,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { private void switchUserLocked(int userId) { mSettings.setCurrentUserId(userId); + updateCurrentProfileIds(); unbindServiceLocked(); buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap, mSettings); SpellCheckerInfo sci = getCurrentSpellChecker(null); @@ -133,6 +146,16 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } } + void updateCurrentProfileIds() { + List<UserInfo> profiles = + UserManager.get(mContext).getProfiles(mSettings.getCurrentUserId()); + int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null + for (int i = 0; i < currentProfileIds.length; i++) { + currentProfileIds[i] = profiles.get(i).id; + } + mSettings.setCurrentProfileIds(currentProfileIds); + } + private class TextServicesMonitor extends PackageMonitor { private boolean isChangingPackagesOfCurrentUser() { final int userId = getChangingUserId(); @@ -171,6 +194,19 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { } } + class TextServicesBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_USER_ADDED.equals(action) + || Intent.ACTION_USER_REMOVED.equals(action)) { + updateCurrentProfileIds(); + return; + } + Slog.w(TAG, "Unexpected intent " + intent); + } + } + private static void buildSpellCheckerMapLocked(Context context, ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map, TextServicesSettings settings) { @@ -223,7 +259,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? " + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID + " calling userId = " + userId + ", foreground user id = " - + mSettings.getCurrentUserId()); + + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()); try { final String[] packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid); for (int i = 0; i < packageNames.length; ++i) { @@ -237,10 +273,40 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) { return true; - } else { - Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace()); - return false; } + + // Permits current profile to use TSFM as long as the current text service is the system's + // one. This is a tentative solution and should be replaced with fully functional multiuser + // support. + // TODO: Implement multiuser support in TSMS. + final boolean isCurrentProfile = mSettings.isCurrentProfile(userId); + if (DBG) { + Slog.d(TAG, "--- userId = "+ userId + " isCurrentProfile = " + isCurrentProfile); + } + if (mSettings.isCurrentProfile(userId)) { + final SpellCheckerInfo spellCheckerInfo = getCurrentSpellCheckerWithoutVerification(); + if (spellCheckerInfo != null) { + final ServiceInfo serviceInfo = spellCheckerInfo.getServiceInfo(); + final boolean isSystemSpellChecker = + (serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + if (DBG) { + Slog.d(TAG, "--- current spell checker = "+ spellCheckerInfo.getPackageName() + + " isSystem = " + isSystemSpellChecker); + } + if (isSystemSpellChecker) { + return true; + } + } + } + + // Unlike InputMethodManagerService#calledFromValidUser, INTERACT_ACROSS_USERS_FULL isn't + // taken into account here. Anyway this method is supposed to be removed once multiuser + // support is implemented. + if (DBG) { + Slog.d(TAG, "--- IPC from userId:" + userId + " is being ignored. \n" + + getStackTrace()); + } + return false; } private boolean bindCurrentSpellCheckerService( @@ -292,6 +358,10 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { if (!calledFromValidUser()) { return null; } + return getCurrentSpellCheckerWithoutVerification(); + } + + private SpellCheckerInfo getCurrentSpellCheckerWithoutVerification() { synchronized (mSpellCheckerMap) { final String curSpellCheckerId = mSettings.getSelectedSpellChecker(); if (DBG) { @@ -914,6 +984,10 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { private static class TextServicesSettings { private final ContentResolver mResolver; private int mCurrentUserId; + @GuardedBy("mLock") + private int[] mCurrentProfileIds = new int[0]; + private Object mLock = new Object(); + public TextServicesSettings(ContentResolver resolver, int userId) { mResolver = resolver; mCurrentUserId = userId; @@ -928,6 +1002,22 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { mCurrentUserId = userId; } + public void setCurrentProfileIds(int[] currentProfileIds) { + synchronized (mLock) { + mCurrentProfileIds = currentProfileIds; + } + } + + public boolean isCurrentProfile(int userId) { + synchronized (mLock) { + if (userId == mCurrentUserId) return true; + for (int i = 0; i < mCurrentProfileIds.length; i++) { + if (userId == mCurrentProfileIds[i]) return true; + } + return false; + } + } + public int getCurrentUserId() { return mCurrentUserId; } diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 89e3f49..8e46c4d 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -415,15 +415,9 @@ public class Watchdog extends Thread { dumpKernelStackTraces(); } - // Trigger the kernel to dump all blocked threads to the kernel log - try { - FileWriter sysrq_trigger = new FileWriter("/proc/sysrq-trigger"); - sysrq_trigger.write("w"); - sysrq_trigger.close(); - } catch (IOException e) { - Slog.e(TAG, "Failed to write to /proc/sysrq-trigger"); - Slog.e(TAG, e.getMessage()); - } + // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log + doSysRq('w'); + doSysRq('l'); // Try to add the error to the dropbox, but assuming that the ActivityManager // itself may be deadlocked. (which has happened, causing this statement to @@ -488,6 +482,16 @@ public class Watchdog extends Thread { } } + private void doSysRq(char c) { + try { + FileWriter sysrq_trigger = new FileWriter("/proc/sysrq-trigger"); + sysrq_trigger.write(c); + sysrq_trigger.close(); + } catch (IOException e) { + Slog.w(TAG, "Failed to write to /proc/sysrq-trigger", e); + } + } + private File dumpKernelStackTraces() { String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null); if (tracesPath == null || tracesPath.length() == 0) { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index d480f68..a2f4d56 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -77,7 +77,6 @@ import com.android.internal.R; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.FgThread; - import com.google.android.collect.Lists; import com.google.android.collect.Sets; @@ -487,7 +486,7 @@ public class AccountManagerService for (Account sa : sharedAccounts) { if (ArrayUtils.contains(accounts, sa)) continue; // Account doesn't exist. Copy it now. - copyAccountToUser(sa, UserHandle.USER_OWNER, userId); + copyAccountToUser(null /*no response*/, sa, UserHandle.USER_OWNER, userId); } } @@ -673,16 +672,31 @@ public class AccountManagerService } } - private boolean copyAccountToUser(final Account account, int userFrom, int userTo) { + @Override + public void copyAccountToUser(final IAccountManagerResponse response, final Account account, + int userFrom, int userTo) { + enforceCrossUserPermission(UserHandle.USER_ALL, "Calling copyAccountToUser requires " + + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); final UserAccounts fromAccounts = getUserAccounts(userFrom); final UserAccounts toAccounts = getUserAccounts(userTo); if (fromAccounts == null || toAccounts == null) { - return false; + if (response != null) { + Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); + try { + response.onResult(result); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to report error back to the client." + e); + } + } + return; } + Slog.d(TAG, "Copying account " + account.name + + " from user " + userFrom + " to user " + userTo); long identityToken = clearCallingIdentity(); try { - new Session(fromAccounts, null, account.type, false, + new Session(fromAccounts, response, account.type, false, false /* stripAuthTokenFromResult */) { @Override protected String toDebugString(long now) { @@ -697,12 +711,10 @@ public class AccountManagerService @Override public void onResult(Bundle result) { - if (result != null) { - if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { - // Create a Session for the target user and pass in the bundle - completeCloningAccount(result, account, toAccounts); - } - return; + if (result != null + && result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { + // Create a Session for the target user and pass in the bundle + completeCloningAccount(response, result, account, toAccounts); } else { super.onResult(result); } @@ -711,14 +723,13 @@ public class AccountManagerService } finally { restoreCallingIdentity(identityToken); } - return true; } - void completeCloningAccount(final Bundle result, final Account account, - final UserAccounts targetUser) { + private void completeCloningAccount(IAccountManagerResponse response, + final Bundle accountCredentials, final Account account, final UserAccounts targetUser) { long id = clearCallingIdentity(); try { - new Session(targetUser, null, account.type, false, + new Session(targetUser, response, account.type, false, false /* stripAuthTokenFromResult */) { @Override protected String toDebugString(long now) { @@ -731,10 +742,10 @@ public class AccountManagerService // Confirm that the owner's account still exists before this step. UserAccounts owner = getUserAccounts(UserHandle.USER_OWNER); synchronized (owner.cacheLock) { - Account[] ownerAccounts = getAccounts(UserHandle.USER_OWNER); - for (Account acc : ownerAccounts) { + for (Account acc : getAccounts(UserHandle.USER_OWNER)) { if (acc.equals(account)) { - mAuthenticator.addAccountFromCredentials(this, account, result); + mAuthenticator.addAccountFromCredentials( + this, account, accountCredentials); break; } } @@ -743,17 +754,10 @@ public class AccountManagerService @Override public void onResult(Bundle result) { - if (result != null) { - if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { - // TODO: Anything? - } else { - // TODO: Show error notification - // TODO: Should we remove the shadow account to avoid retries? - } - return; - } else { - super.onResult(result); - } + // TODO: Anything to do if if succedded? + // TODO: If it failed: Show error notification? Should we remove the shadow + // account to avoid retries? + super.onResult(result); } @Override @@ -1043,7 +1047,8 @@ public class AccountManagerService } @Override - public void removeAccount(IAccountManagerResponse response, Account account) { + public void removeAccount(IAccountManagerResponse response, Account account, + boolean expectActivityLaunch) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeAccount: " + account + ", response " + response @@ -1088,7 +1093,7 @@ public class AccountManagerService } try { - new RemoveAccountSession(accounts, response, account).bind(); + new RemoveAccountSession(accounts, response, account, expectActivityLaunch).bind(); } finally { restoreCallingIdentity(identityToken); } @@ -1096,7 +1101,7 @@ public class AccountManagerService @Override public void removeAccountAsUser(IAccountManagerResponse response, Account account, - int userId) { + boolean expectActivityLaunch, int userId) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeAccount: " + account + ", response " + response @@ -1145,7 +1150,30 @@ public class AccountManagerService } try { - new RemoveAccountSession(accounts, response, account).bind(); + new RemoveAccountSession(accounts, response, account, expectActivityLaunch).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + @Override + public boolean removeAccountExplicitly(Account account) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "removeAccountExplicitly: " + account + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid()); + } + if (account == null) throw new IllegalArgumentException("account is null"); + checkAuthenticateAccountsPermission(account); + + UserAccounts accounts = getUserAccountsForCaller(); + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) { + return false; + } + long identityToken = clearCallingIdentity(); + try { + return removeAccountInternal(accounts, account); } finally { restoreCallingIdentity(identityToken); } @@ -1154,8 +1182,8 @@ public class AccountManagerService private class RemoveAccountSession extends Session { final Account mAccount; public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, - Account account) { - super(accounts, response, account.type, false /* expectActivityLaunch */, + Account account, boolean expectActivityLaunch) { + super(accounts, response, account.type, expectActivityLaunch, true /* stripAuthTokenFromResult */); mAccount = account; } @@ -1203,10 +1231,12 @@ public class AccountManagerService removeAccountInternal(getUserAccountsForCaller(), account); } - private void removeAccountInternal(UserAccounts accounts, Account account) { + private boolean removeAccountInternal(UserAccounts accounts, Account account) { + int deleted; synchronized (accounts.cacheLock) { final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); - db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + deleted = db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + + "=?", new String[]{account.name, account.type}); removeAccountFromCacheLocked(accounts, account); sendAccountsChangedBroadcast(accounts.userId); @@ -1226,6 +1256,7 @@ public class AccountManagerService Binder.restoreCallingIdentity(id); } } + return (deleted > 0); } @Override @@ -2714,7 +2745,7 @@ public class AccountManagerService break; case MESSAGE_COPY_SHARED_ACCOUNT: - copyAccountToUser((Account) msg.obj, msg.arg1, msg.arg2); + copyAccountToUser(/*no response*/ null, (Account) msg.obj, msg.arg1, msg.arg2); break; default: diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9185ada..7e17043 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -61,7 +61,6 @@ 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.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.ProcessCpuTracker; @@ -83,6 +82,7 @@ import com.android.server.am.ActivityStack.ActivityState; import com.android.server.firewall.IntentFirewall; import com.android.server.pm.Installer; import com.android.server.pm.UserManagerService; +import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.AppTransition; import com.android.server.wm.WindowManagerService; import com.google.android.collect.Lists; @@ -173,6 +173,7 @@ 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.RemoteCallbackList; import android.os.RemoteException; @@ -200,6 +201,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; + import dalvik.system.VMRuntime; import java.io.BufferedInputStream; @@ -794,7 +796,11 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public int hashCode() { - return toString().hashCode(); + int hashCode = 1; + hashCode = 31 * hashCode + sourceUserId; + hashCode = 31 * hashCode + uri.hashCode(); + hashCode = 31 * hashCode + (prefix ? 1231 : 1237); + return hashCode; } @Override @@ -833,10 +839,12 @@ public final class ActivityManagerService extends ActivityManagerNative * indirect content-provider access. */ private class Identity { - public int pid; - public int uid; + public final IBinder token; + public final int pid; + public final int uid; - Identity(int _pid, int _uid) { + Identity(IBinder _token, int _pid, int _uid) { + token = _token; pid = _pid; uid = _uid; } @@ -951,14 +959,17 @@ public final class ActivityManagerService extends ActivityManagerNative private boolean mRunningVoice = false; /** - * State of external calls telling us if the device is asleep. + * State of external calls telling us if the device is awake or asleep. */ - private boolean mWentToSleep = false; + private int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE; + static final int LOCK_SCREEN_HIDDEN = 0; + static final int LOCK_SCREEN_LEAVING = 1; + static final int LOCK_SCREEN_SHOWN = 2; /** * State of external call telling us if the lock screen is shown. */ - private boolean mLockScreenShown = false; + int mLockScreenShown = LOCK_SCREEN_HIDDEN; /** * Set if we are shutting down the system, similar to sleeping. @@ -1178,7 +1189,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final int SERVICE_TIMEOUT_MSG = 12; static final int UPDATE_TIME_ZONE = 13; static final int SHOW_UID_ERROR_MSG = 14; - static final int IM_FEELING_LUCKY_MSG = 15; + static final int SHOW_FINGERPRINT_ERROR_MSG = 15; static final int PROC_START_TIMEOUT_MSG = 20; static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21; static final int KILL_APPLICATION_MSG = 22; @@ -1207,18 +1218,16 @@ public final class ActivityManagerService extends ActivityManagerNative static final int FINISH_BOOTING_MSG = 45; static final int START_USER_SWITCH_MSG = 46; static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47; + static final int DISMISS_DIALOG_MSG = 48; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; static final int FIRST_COMPAT_MODE_MSG = 300; static final int FIRST_SUPERVISOR_STACK_MSG = 100; - AlertDialog mUidAlert; CompatModeDialog mCompatModeDialog; long mLastMemUsageReportTime = 0; - private LockToAppRequestDialog mLockToAppRequest; - /** * Flag whether the current user is a "monkey", i.e. whether * the UI is driven by a UI automation tool. @@ -1444,27 +1453,27 @@ public final class ActivityManagerService extends ActivityManagerNative } } break; case SHOW_UID_ERROR_MSG: { - String title = "System UIDs Inconsistent"; - String text = "UIDs on the system are inconsistent, you need to wipe your" - + " data partition or your device will be unstable."; - Log.e(TAG, title + ": " + text); if (mShowDialogs) { - // XXX This is a temporary dialog, no need to localize. AlertDialog d = new BaseErrorDialog(mContext); d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); d.setCancelable(false); - d.setTitle(title); - d.setMessage(text); - d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky", - mHandler.obtainMessage(IM_FEELING_LUCKY_MSG)); - mUidAlert = d; + 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 IM_FEELING_LUCKY_MSG: { - if (mUidAlert != null) { - mUidAlert.dismiss(); - mUidAlert = null; + 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: { @@ -1609,192 +1618,7 @@ public final class ActivityManagerService extends ActivityManagerNative final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>)msg.obj; Thread thread = new Thread() { @Override public void run() { - final SparseArray<ProcessMemInfo> infoMap - = new SparseArray<ProcessMemInfo>(memInfos.size()); - for (int i=0, N=memInfos.size(); i<N; i++) { - ProcessMemInfo mi = memInfos.get(i); - infoMap.put(mi.pid, mi); - } - updateCpuStatsNow(); - synchronized (mProcessCpuTracker) { - final int N = mProcessCpuTracker.countStats(); - for (int i=0; i<N; i++) { - ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); - if (st.vsize > 0) { - long pss = Debug.getPss(st.pid, null); - if (pss > 0) { - if (infoMap.indexOfKey(st.pid) < 0) { - ProcessMemInfo mi = new ProcessMemInfo(st.name, st.pid, - ProcessList.NATIVE_ADJ, -1, "native", null); - mi.pss = pss; - memInfos.add(mi); - } - } - } - } - } - - long totalPss = 0; - for (int i=0, N=memInfos.size(); i<N; i++) { - ProcessMemInfo mi = memInfos.get(i); - if (mi.pss == 0) { - mi.pss = Debug.getPss(mi.pid, null); - } - totalPss += mi.pss; - } - Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { - @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { - if (lhs.oomAdj != rhs.oomAdj) { - return lhs.oomAdj < rhs.oomAdj ? -1 : 1; - } - if (lhs.pss != rhs.pss) { - return lhs.pss < rhs.pss ? 1 : -1; - } - return 0; - } - }); - - StringBuilder tag = new StringBuilder(128); - StringBuilder stack = new StringBuilder(128); - tag.append("Low on memory -- "); - appendMemBucket(tag, totalPss, "total", false); - appendMemBucket(stack, totalPss, "total", true); - - StringBuilder logBuilder = new StringBuilder(1024); - logBuilder.append("Low on memory:\n"); - - boolean firstLine = true; - int lastOomAdj = Integer.MIN_VALUE; - for (int i=0, N=memInfos.size(); i<N; i++) { - ProcessMemInfo mi = memInfos.get(i); - - if (mi.oomAdj != ProcessList.NATIVE_ADJ - && (mi.oomAdj < ProcessList.SERVICE_ADJ - || mi.oomAdj == ProcessList.HOME_APP_ADJ - || mi.oomAdj == ProcessList.PREVIOUS_APP_ADJ)) { - if (lastOomAdj != mi.oomAdj) { - lastOomAdj = mi.oomAdj; - if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) { - tag.append(" / "); - } - if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ) { - if (firstLine) { - stack.append(":"); - firstLine = false; - } - stack.append("\n\t at "); - } else { - stack.append("$"); - } - } else { - tag.append(" "); - stack.append("$"); - } - if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) { - appendMemBucket(tag, mi.pss, mi.name, false); - } - appendMemBucket(stack, mi.pss, mi.name, true); - if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ - && ((i+1) >= N || memInfos.get(i+1).oomAdj != lastOomAdj)) { - stack.append("("); - for (int k=0; k<DUMP_MEM_OOM_ADJ.length; k++) { - if (DUMP_MEM_OOM_ADJ[k] == mi.oomAdj) { - stack.append(DUMP_MEM_OOM_LABEL[k]); - stack.append(":"); - stack.append(DUMP_MEM_OOM_ADJ[k]); - } - } - stack.append(")"); - } - } - - logBuilder.append(" "); - logBuilder.append(ProcessList.makeOomAdjString(mi.oomAdj)); - logBuilder.append(' '); - logBuilder.append(ProcessList.makeProcStateString(mi.procState)); - logBuilder.append(' '); - ProcessList.appendRamKb(logBuilder, mi.pss); - logBuilder.append(" kB: "); - logBuilder.append(mi.name); - logBuilder.append(" ("); - logBuilder.append(mi.pid); - logBuilder.append(") "); - logBuilder.append(mi.adjType); - logBuilder.append('\n'); - if (mi.adjReason != null) { - logBuilder.append(" "); - logBuilder.append(mi.adjReason); - logBuilder.append('\n'); - } - } - - logBuilder.append(" "); - ProcessList.appendRamKb(logBuilder, totalPss); - logBuilder.append(" kB: TOTAL\n"); - - long[] infos = new long[Debug.MEMINFO_COUNT]; - Debug.getMemInfo(infos); - logBuilder.append(" MemInfo: "); - logBuilder.append(infos[Debug.MEMINFO_SLAB]).append(" kB slab, "); - logBuilder.append(infos[Debug.MEMINFO_SHMEM]).append(" kB shmem, "); - logBuilder.append(infos[Debug.MEMINFO_BUFFERS]).append(" kB buffers, "); - logBuilder.append(infos[Debug.MEMINFO_CACHED]).append(" kB cached, "); - logBuilder.append(infos[Debug.MEMINFO_FREE]).append(" kB free\n"); - if (infos[Debug.MEMINFO_ZRAM_TOTAL] != 0) { - logBuilder.append(" ZRAM: "); - logBuilder.append(infos[Debug.MEMINFO_ZRAM_TOTAL]); - logBuilder.append(" kB RAM, "); - logBuilder.append(infos[Debug.MEMINFO_SWAP_TOTAL]); - logBuilder.append(" kB swap total, "); - logBuilder.append(infos[Debug.MEMINFO_SWAP_FREE]); - logBuilder.append(" kB swap free\n"); - } - Slog.i(TAG, logBuilder.toString()); - - StringBuilder dropBuilder = new StringBuilder(1024); - /* - StringWriter oomSw = new StringWriter(); - PrintWriter oomPw = new FastPrintWriter(oomSw, false, 256); - StringWriter catSw = new StringWriter(); - PrintWriter catPw = new FastPrintWriter(catSw, false, 256); - String[] emptyArgs = new String[] { }; - dumpApplicationMemoryUsage(null, oomPw, " ", emptyArgs, true, catPw); - oomPw.flush(); - String oomString = oomSw.toString(); - */ - dropBuilder.append(stack); - dropBuilder.append('\n'); - dropBuilder.append('\n'); - dropBuilder.append(logBuilder); - dropBuilder.append('\n'); - /* - dropBuilder.append(oomString); - dropBuilder.append('\n'); - */ - StringWriter catSw = new StringWriter(); - synchronized (ActivityManagerService.this) { - PrintWriter catPw = new FastPrintWriter(catSw, false, 256); - String[] emptyArgs = new String[] { }; - catPw.println(); - dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null); - catPw.println(); - mServices.dumpServicesLocked(null, catPw, emptyArgs, 0, - false, false, null); - catPw.println(); - dumpActivitiesLocked(null, catPw, emptyArgs, 0, false, false, null); - catPw.flush(); - } - dropBuilder.append(catSw.toString()); - addErrorToDropBox("lowmem", null, "system_server", null, - null, tag.toString(), dropBuilder.toString(), null, null); - //Slog.i(TAG, "Sent to dropbox:"); - //Slog.i(TAG, dropBuilder.toString()); - synchronized (ActivityManagerService.this) { - long now = SystemClock.uptimeMillis(); - if (mLastMemUsageReportTime < now) { - mLastMemUsageReportTime = now; - } - } + reportMemUsage(memInfos); } }; thread.start(); @@ -1874,7 +1698,6 @@ public final class ActivityManagerService extends ActivityManagerNative BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START, Integer.toString(msg.arg1), msg.arg1); mSystemServiceManager.switchUser(msg.arg1); - mLockToAppRequest.clearPrompt(); break; } case ENTER_ANIMATION_COMPLETE_MSG: { @@ -1910,6 +1733,11 @@ public final class ActivityManagerService extends ActivityManagerNative } break; } + case DISMISS_DIALOG_MSG: { + final Dialog d = (Dialog) msg.obj; + d.dismiss(); + break; + } } } }; @@ -1955,13 +1783,12 @@ public final class ActivityManagerService extends ActivityManagerNative + (SystemClock.uptimeMillis()-start) + "ms"); mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(), memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(), - memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb() - +memInfo.getSlabSizeKb(), - nativeTotalPss); + memInfo.getKernelUsedSizeKb(), nativeTotalPss); } } - int i=0, num=0; + int i = 0; + int num = 0; long[] tmp = new long[1]; do { ProcessRecord proc; @@ -2011,101 +1838,6 @@ public final class ActivityManagerService extends ActivityManagerNative } }; - /** - * Monitor for package changes and update our internal state. - */ - private final PackageMonitor mPackageMonitor = new PackageMonitor() { - @Override - public void onPackageRemoved(String packageName, int uid) { - // Remove all tasks with activities in the specified package from the list of recent tasks - final int eventUserId = getChangingUserId(); - synchronized (ActivityManagerService.this) { - for (int i = mRecentTasks.size() - 1; i >= 0; i--) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.userId != eventUserId) continue; - - ComponentName cn = tr.intent.getComponent(); - if (cn != null && cn.getPackageName().equals(packageName)) { - // If the package name matches, remove the task and kill the process - removeTaskByIdLocked(tr.taskId, ActivityManager.REMOVE_TASK_KILL_PROCESS); - } - } - } - } - - @Override - public boolean onPackageChanged(String packageName, int uid, String[] components) { - onPackageModified(packageName); - return true; - } - - @Override - public void onPackageModified(String packageName) { - final int eventUserId = getChangingUserId(); - final IPackageManager pm = AppGlobals.getPackageManager(); - final ArrayList<Pair<Intent, Integer>> recentTaskIntents = - new ArrayList<Pair<Intent, Integer>>(); - final HashSet<ComponentName> componentsKnownToExist = new HashSet<ComponentName>(); - final ArrayList<Integer> tasksToRemove = new ArrayList<Integer>(); - // Copy the list of recent tasks so that we don't hold onto the lock on - // ActivityManagerService for long periods while checking if components exist. - synchronized (ActivityManagerService.this) { - for (int i = mRecentTasks.size() - 1; i >= 0; i--) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.userId != eventUserId) continue; - - recentTaskIntents.add(new Pair<Intent, Integer>(tr.intent, tr.taskId)); - } - } - // Check the recent tasks and filter out all tasks with components that no longer exist. - for (int i = recentTaskIntents.size() - 1; i >= 0; i--) { - Pair<Intent, Integer> p = recentTaskIntents.get(i); - ComponentName cn = p.first.getComponent(); - if (cn != null && cn.getPackageName().equals(packageName)) { - if (componentsKnownToExist.contains(cn)) { - // If we know that the component still exists in the package, then skip - continue; - } - try { - ActivityInfo info = pm.getActivityInfo(cn, 0, eventUserId); - if (info != null) { - componentsKnownToExist.add(cn); - } else { - tasksToRemove.add(p.second); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to query activity info for component: " + cn, e); - } - } - } - // Prune all the tasks with removed components from the list of recent tasks - synchronized (ActivityManagerService.this) { - for (int i = tasksToRemove.size() - 1; i >= 0; i--) { - // Remove the task but don't kill the process (since other components in that - // package may still be running and in the background) - removeTaskByIdLocked(tasksToRemove.get(i), 0); - } - } - } - - @Override - public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { - // Force stop the specified packages - int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); - if (packages != null) { - for (String pkg : packages) { - synchronized (ActivityManagerService.this) { - if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, - userId, "finished booting")) { - return true; - } - } - } - } - return false; - } - }; - public void setSystemProcess() { try { ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true); @@ -2351,8 +2083,6 @@ public final class ActivityManagerService extends ActivityManagerNative } }; - mLockToAppRequest = new LockToAppRequestDialog(mContext, this); - Watchdog.getInstance().addMonitor(this); Watchdog.getInstance().addThread(mHandler); } @@ -2548,15 +2278,19 @@ public final class ActivityManagerService extends ActivityManagerNative * process when the bindApplication() IPC is sent to the process. They're * lazily setup to make sure the services are running when they're asked for. */ - private HashMap<String, IBinder> getCommonServicesLocked() { + private HashMap<String, IBinder> getCommonServicesLocked(boolean isolated) { if (mAppBindArgs == null) { - mAppBindArgs = new HashMap<String, IBinder>(); + mAppBindArgs = new HashMap<>(); - // Setup the application init args - mAppBindArgs.put("package", ServiceManager.getService("package")); - mAppBindArgs.put("window", ServiceManager.getService("window")); - mAppBindArgs.put(Context.ALARM_SERVICE, - ServiceManager.getService(Context.ALARM_SERVICE)); + // Isolated processes won't get this optimization, so that we don't + // violate the rules about which services they have access to. + if (!isolated) { + // Setup the application init args + mAppBindArgs.put("package", ServiceManager.getService("package")); + mAppBindArgs.put("window", ServiceManager.getService("window")); + mAppBindArgs.put(Context.ALARM_SERVICE, + ServiceManager.getService(Context.ALARM_SERVICE)); + } } return mAppBindArgs; } @@ -4477,9 +4211,13 @@ public final class ActivityManagerService extends ActivityManagerNative // Keep track of the root activity of the task before we finish it TaskRecord tr = r.task; ActivityRecord rootR = tr.getRootActivity(); + 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; } @@ -4498,6 +4236,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (!resumeOK) { + Slog.i(TAG, "Not finishing activity because controller resumed"); return false; } } @@ -4507,12 +4246,18 @@ public final class ActivityManagerService extends ActivityManagerNative boolean res; if (finishTask && r == rootR) { // If requested, remove the task that is associated to this activity only if it - // was the root activity in the task. The result code and data is ignored because - // we don't support returning them across task boundaries. - res = removeTaskByIdLocked(tr.taskId, 0); + // was the root activity in the task. The result code and data is ignored + // because we don't support returning them across task boundaries. + res = removeTaskByIdLocked(tr.taskId, false); + if (!res) { + Slog.i(TAG, "Removing task failed to finish activity"); + } } else { res = tr.stack.requestFinishActivityLocked(token, resultCode, resultData, "app-request", true); + if (!res) { + Slog.i(TAG, "Failed to finish by app-request"); + } } return res; } finally { @@ -5336,7 +5081,7 @@ public final class ActivityManagerService extends ActivityManagerNative tr.getBaseIntent().getComponent().getPackageName(); if (tr.userId != userId) continue; if (!taskPackageName.equals(packageName)) continue; - removeTaskByIdLocked(tr.taskId, 0); + removeTaskByIdLocked(tr.taskId, false); } } @@ -5916,7 +5661,7 @@ public final class ActivityManagerService extends ActivityManagerNative didSomething = true; it.remove(); pir.canceled = true; - if (pir.key.activity != null) { + if (pir.key.activity != null && pir.key.activity.pendingResults != null) { pir.key.activity.pendingResults.remove(pir.ref); } } @@ -6168,7 +5913,8 @@ public final class ActivityManagerService extends ActivityManagerNative profilerInfo, app.instrumentationArguments, app.instrumentationWatcher, app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent, - new Configuration(mConfiguration), app.compat, getCommonServicesLocked(), + new Configuration(mConfiguration), app.compat, + getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked()); updateLruProcessLocked(app, false, null); app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis(); @@ -6314,9 +6060,9 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { if (DEBUG_LOCKSCREEN) logLockScreen(""); mWindowManager.keyguardWaitingForActivityDrawn(); - if (mLockScreenShown) { - mLockScreenShown = false; - comeOutOfSleepIfNeededLocked(); + if (mLockScreenShown == LOCK_SCREEN_SHOWN) { + mLockScreenShown = LOCK_SCREEN_LEAVING; + updateSleepIfNeededLocked(); } } } finally { @@ -6345,8 +6091,26 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // Register receivers to handle package update events - mPackageMonitor.register(mContext, Looper.getMainLooper(), UserHandle.ALL, false); + IntentFilter pkgFilter = new IntentFilter(); + pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); + pkgFilter.addDataScheme("package"); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); + if (pkgs != null) { + for (String pkg : pkgs) { + synchronized (ActivityManagerService.this) { + if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, + 0, "finished booting")) { + setResultCode(Activity.RESULT_OK); + return; + } + } + } + } + } + }, pkgFilter); // Let system services know. mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED); @@ -6505,7 +6269,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { ActivityStack stack = ActivityRecord.getStackLocked(token); if (stack != null) { - stack.backgroundResourcesReleased(token); + stack.backgroundResourcesReleased(); } } } finally { @@ -6971,21 +6735,9 @@ public final class ActivityManagerService extends ActivityManagerNative */ int checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported) { - // We might be performing an operation on behalf of an indirect binder - // invocation, e.g. via {@link #openContentUri}. Check and adjust the - // client identity accordingly before proceeding. - Identity tlsIdentity = sCallerIdentity.get(); - if (tlsIdentity != null) { - Slog.d(TAG, "checkComponentPermission() adjusting {pid,uid} to {" - + tlsIdentity.pid + "," + tlsIdentity.uid + "}"); - uid = tlsIdentity.uid; - pid = tlsIdentity.pid; - } - if (pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; } - return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported); } @@ -7007,6 +6759,26 @@ public final class ActivityManagerService extends ActivityManagerNative return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true); } + @Override + public int checkPermissionWithToken(String permission, int pid, int uid, IBinder callerToken) { + if (permission == null) { + return PackageManager.PERMISSION_DENIED; + } + + // We might be performing an operation on behalf of an indirect binder + // invocation, e.g. via {@link #openContentUri}. Check and adjust the + // client identity accordingly before proceeding. + Identity tlsIdentity = sCallerIdentity.get(); + if (tlsIdentity != null && tlsIdentity.token == callerToken) { + Slog.d(TAG, "checkComponentPermission() adjusting {pid,uid} to {" + + tlsIdentity.pid + "," + tlsIdentity.uid + "}"); + uid = tlsIdentity.uid; + pid = tlsIdentity.pid; + } + + return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true); + } + /** * Binder IPC calls go through the public entry point. * This can be called with or without the global lock held. @@ -7212,13 +6984,13 @@ public final class ActivityManagerService extends ActivityManagerNative */ @Override public int checkUriPermission(Uri uri, int pid, int uid, - final int modeFlags, int userId) { + final int modeFlags, int userId, IBinder callerToken) { enforceNotIsolatedCaller("checkUriPermission"); // Another redirected-binder-call permissions check as in - // {@link checkComponentPermission}. + // {@link checkPermissionWithToken}. Identity tlsIdentity = sCallerIdentity.get(); - if (tlsIdentity != null) { + if (tlsIdentity != null && tlsIdentity.token == callerToken) { uid = tlsIdentity.uid; pid = tlsIdentity.pid; } @@ -8358,6 +8130,16 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private TaskRecord taskForIdLocked(int id) { + final TaskRecord task = recentTaskForIdLocked(id); + if (task != null) { + return task; + } + + // Don't give up. Sometimes it just hasn't made it to recents yet. + return mStackSupervisor.anyTaskForIdLocked(id); + } + private TaskRecord recentTaskForIdLocked(int id) { final int N = mRecentTasks.size(); for (int i=0; i<N; i++) { @@ -8493,51 +8275,119 @@ public final class ActivityManagerService extends ActivityManagerNative return mTaskPersister.getTaskDescriptionIcon(filename); } - private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) { + @Override + public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) + throws RemoteException { + if (opts.getAnimationType() != ActivityOptions.ANIM_CUSTOM_IN_PLACE || + opts.getCustomInPlaceResId() == 0) { + throw new IllegalArgumentException("Expected in-place ActivityOption " + + "with valid animation"); + } + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_TASK_IN_PLACE, false); + mWindowManager.overridePendingAppTransitionInPlace(opts.getPackageName(), + opts.getCustomInPlaceResId()); + mWindowManager.executeAppTransition(); + } + + private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess) { mRecentTasks.remove(tr); tr.removedFromRecents(mTaskPersister); - final boolean killProcesses = (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0; - Intent baseIntent = new Intent( - tr.intent != null ? tr.intent : tr.affinityIntent); - ComponentName component = baseIntent.getComponent(); + ComponentName component = tr.getBaseIntent().getComponent(); if (component == null) { - Slog.w(TAG, "Now component for base intent of task: " + tr); + Slog.w(TAG, "No component for base intent of task: " + tr); + return; + } + + if (!killProcess) { return; } - // Find any running services associated with this app. - mServices.cleanUpRemovedTaskLocked(tr, component, baseIntent); + // Determine if the process(es) for this task should be killed. + final String pkg = component.getPackageName(); + ArrayList<ProcessRecord> procsToKill = new ArrayList<ProcessRecord>(); + ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap(); + for (int i = 0; i < pmap.size(); i++) { - if (killProcesses) { - // Find any running processes associated with this app. - final String pkg = component.getPackageName(); - ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); - ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap(); - for (int i=0; i<pmap.size(); i++) { - SparseArray<ProcessRecord> uids = pmap.valueAt(i); - for (int j=0; j<uids.size(); j++) { - ProcessRecord proc = uids.valueAt(j); - if (proc.userId != tr.userId) { - continue; - } - if (!proc.pkgList.containsKey(pkg)) { - continue; - } - procs.add(proc); + SparseArray<ProcessRecord> uids = pmap.valueAt(i); + for (int j = 0; j < uids.size(); j++) { + ProcessRecord proc = uids.valueAt(j); + if (proc.userId != tr.userId) { + // Don't kill process for a different user. + continue; } - } - - // Kill the running processes. - for (int i=0; i<procs.size(); i++) { - ProcessRecord pr = procs.get(i); - if (pr == mHomeProcess) { + if (proc == mHomeProcess) { // Don't kill the home process along with tasks from the same package. continue; } - if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { - pr.kill("remove task", true); - } else { - pr.waitingToKill = "remove task"; + if (!proc.pkgList.containsKey(pkg)) { + // Don't kill process that is not associated with this task. + continue; + } + + for (int k = 0; k < proc.activities.size(); k++) { + TaskRecord otherTask = proc.activities.get(k).task; + if (tr.taskId != otherTask.taskId && otherTask.inRecents) { + // Don't kill process(es) that has an activity in a different task that is + // also in recents. + return; + } + } + + // Add process to kill list. + procsToKill.add(proc); + } + } + + // Find any running services associated with this app and stop if needed. + mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent())); + + // Kill the running processes. + for (int i = 0; i < procsToKill.size(); i++) { + ProcessRecord pr = procsToKill.get(i); + if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { + pr.kill("remove task", true); + } else { + pr.waitingToKill = "remove task"; + } + } + } + + private void removeTasksByPackageNameLocked(String packageName, int userId) { + // Remove all tasks with activities in the specified package from the list of recent tasks + for (int i = mRecentTasks.size() - 1; i >= 0; i--) { + TaskRecord tr = mRecentTasks.get(i); + if (tr.userId != userId) continue; + + ComponentName cn = tr.intent.getComponent(); + if (cn != null && cn.getPackageName().equals(packageName)) { + // If the package name matches, remove the task. + removeTaskByIdLocked(tr.taskId, true); + } + } + } + + private void removeTasksByRemovedPackageComponentsLocked(String packageName, int userId) { + final IPackageManager pm = AppGlobals.getPackageManager(); + final HashSet<ComponentName> componentsKnownToExist = new HashSet<ComponentName>(); + + for (int i = mRecentTasks.size() - 1; i >= 0; i--) { + TaskRecord tr = mRecentTasks.get(i); + if (tr.userId != userId) continue; + + ComponentName cn = tr.intent.getComponent(); + if (cn != null && cn.getPackageName().equals(packageName)) { + // Skip if component still exists in the package. + if (componentsKnownToExist.contains(cn)) continue; + + try { + ActivityInfo info = pm.getActivityInfo(cn, 0, userId); + if (info != null) { + componentsKnownToExist.add(cn); + } else { + removeTaskByIdLocked(tr.taskId, false); + } + } catch (RemoteException e) { + Log.e(TAG, "Activity info query failed. component=" + cn, e); } } } @@ -8547,37 +8397,37 @@ public final class ActivityManagerService extends ActivityManagerNative * Removes the task with the specified task id. * * @param taskId Identifier of the task to be removed. - * @param flags Additional operational flags. May be 0 or - * {@link ActivityManager#REMOVE_TASK_KILL_PROCESS}. + * @param killProcess Kill any process associated with the task if possible. * @return Returns true if the given task was found and removed. */ - private boolean removeTaskByIdLocked(int taskId, int flags) { - TaskRecord tr = recentTaskForIdLocked(taskId); + private boolean removeTaskByIdLocked(int taskId, boolean killProcess) { + TaskRecord tr = taskForIdLocked(taskId); if (tr != null) { tr.removeTaskActivitiesLocked(); - cleanUpRemovedTaskLocked(tr, flags); + cleanUpRemovedTaskLocked(tr, killProcess); if (tr.isPersistable) { notifyTaskPersisterLocked(null, true); } return true; } + Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId); return false; } @Override - public boolean removeTask(int taskId, int flags) { + public boolean removeTask(int taskId) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()"); long ident = Binder.clearCallingIdentity(); try { - return removeTaskByIdLocked(taskId, flags); + return removeTaskByIdLocked(taskId, true); } finally { Binder.restoreCallingIdentity(ident); } } } - + /** * TODO: Add mController hook */ @@ -8602,6 +8452,7 @@ public final class ActivityManagerService extends ActivityManagerNative try { final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); if (task == null) { + Slog.d(TAG, "Could not find task for id: "+ taskId); return; } if (mStackSupervisor.isLockTaskModeViolation(task)) { @@ -8626,7 +8477,7 @@ public final class ActivityManagerService extends ActivityManagerNative "moveTaskToBack()"); synchronized(this) { - TaskRecord tr = recentTaskForIdLocked(taskId); + TaskRecord tr = taskForIdLocked(taskId); if (tr != null) { if (tr == mStackSupervisor.mLockTaskModeTask) { mStackSupervisor.showLockTaskToast(); @@ -8818,7 +8669,7 @@ public final class ActivityManagerService extends ActivityManagerNative long ident = Binder.clearCallingIdentity(); try { synchronized (this) { - TaskRecord tr = recentTaskForIdLocked(taskId); + TaskRecord tr = taskForIdLocked(taskId); return tr != null && tr.stack != null && tr.stack.isHomeStack(); } } finally { @@ -8852,13 +8703,11 @@ public final class ActivityManagerService extends ActivityManagerNative } boolean isSystemInitiated = Binder.getCallingUid() == Process.SYSTEM_UID; if (!isSystemInitiated && !isLockTaskAuthorized(pkg)) { - final TaskRecord taskRecord = task; - mHandler.post(new Runnable() { - @Override - public void run() { - mLockToAppRequest.showLockTaskPrompt(taskRecord); - } - }); + StatusBarManagerInternal statusBarManager = LocalServices.getService( + StatusBarManagerInternal.class); + if (statusBarManager != null) { + statusBarManager.showScreenPinningRequest(); + } return; } long ident = Binder.clearCallingIdentity(); @@ -8868,7 +8717,8 @@ public final class ActivityManagerService extends ActivityManagerNative task = mStackSupervisor.anyTaskForIdLocked(task.taskId); if (task != null) { if (!isSystemInitiated - && ((mFocusedActivity == null) || (task != mFocusedActivity.task))) { + && ((mStackSupervisor.getFocusedStack() == null) + || (task != mStackSupervisor.getFocusedStack().topTask()))) { throw new IllegalArgumentException("Invalid task, not in foreground"); } mStackSupervisor.setLockTaskModeLocked(task, !isSystemInitiated); @@ -8919,11 +8769,16 @@ public final class ActivityManagerService extends ActivityManagerNative public void startLockTaskModeOnCurrent() throws RemoteException { enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, "startLockTaskModeOnCurrent"); - ActivityRecord r = null; - synchronized (this) { - r = mStackSupervisor.topRunningActivityLocked(); + long ident = Binder.clearCallingIdentity(); + try { + ActivityRecord r = null; + synchronized (this) { + r = mStackSupervisor.topRunningActivityLocked(); + } + startLockTaskMode(r.task); + } finally { + Binder.restoreCallingIdentity(ident); } - startLockTaskMode(r.task); } @Override @@ -10042,10 +9897,11 @@ public final class ActivityManagerService extends ActivityManagerNative // we do the check against the caller's permissions even though it looks // to the content provider like the Activity Manager itself is making // the request. + Binder token = new Binder(); sCallerIdentity.set(new Identity( - Binder.getCallingPid(), Binder.getCallingUid())); + token, Binder.getCallingPid(), Binder.getCallingUid())); try { - pfd = cph.provider.openFile(null, uri, "r", null); + pfd = cph.provider.openFile(null, uri, "r", null, token); } catch (FileNotFoundException e) { // do nothing; pfd will be returned null } finally { @@ -10071,32 +9927,55 @@ public final class ActivityManagerService extends ActivityManagerNative return mSleeping; } - void goingToSleep() { + void onWakefulnessChanged(int wakefulness) { synchronized(this) { - mWentToSleep = true; - goToSleepIfNeededLocked(); + mWakefulness = wakefulness; + updateSleepIfNeededLocked(); } } void finishRunningVoiceLocked() { if (mRunningVoice) { mRunningVoice = false; - goToSleepIfNeededLocked(); + updateSleepIfNeededLocked(); } } - void goToSleepIfNeededLocked() { - if (mWentToSleep && !mRunningVoice) { - if (!mSleeping) { - mSleeping = true; - mStackSupervisor.goingToSleepLocked(); + void updateSleepIfNeededLocked() { + if (mSleeping && !shouldSleepLocked()) { + mSleeping = false; + mStackSupervisor.comeOutOfSleepIfNeededLocked(); + } else if (!mSleeping && shouldSleepLocked()) { + mSleeping = true; + mStackSupervisor.goingToSleepLocked(); - // Initialize the wake times of all processes. - checkExcessivePowerUsageLocked(false); - mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); - Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); - mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY); - } + // Initialize the wake times of all processes. + checkExcessivePowerUsageLocked(false); + mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY); + } + } + + private boolean shouldSleepLocked() { + // Resume applications while running a voice interactor. + if (mRunningVoice) { + return false; + } + + 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; + case PowerManagerInternal.WAKEFULNESS_ASLEEP: + default: + // If we're asleep then pause applications unconditionally. + return true; } } @@ -10152,32 +10031,26 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(origId); } - void logLockScreen(String msg) { - if (DEBUG_LOCKSCREEN) Slog.d(TAG, Debug.getCallers(2) + ":" + msg + - " mLockScreenShown=" + mLockScreenShown + " mWentToSleep=" + - mWentToSleep + " mSleeping=" + mSleeping); - } - - private void comeOutOfSleepIfNeededLocked() { - if ((!mWentToSleep && !mLockScreenShown) || mRunningVoice) { - if (mSleeping) { - mSleeping = false; - mStackSupervisor.comeOutOfSleepIfNeededLocked(); - } + private String lockScreenShownToString() { + switch (mLockScreenShown) { + case LOCK_SCREEN_HIDDEN: return "LOCK_SCREEN_HIDDEN"; + case LOCK_SCREEN_LEAVING: return "LOCK_SCREEN_LEAVING"; + case LOCK_SCREEN_SHOWN: return "LOCK_SCREEN_SHOWN"; + default: return "Unknown=" + mLockScreenShown; } } - void wakingUp() { - synchronized(this) { - mWentToSleep = false; - comeOutOfSleepIfNeededLocked(); - } + void logLockScreen(String msg) { + if (DEBUG_LOCKSCREEN) Slog.d(TAG, Debug.getCallers(2) + ":" + msg + + " mLockScreenShown=" + lockScreenShownToString() + " mWakefulness=" + + PowerManagerInternal.wakefulnessToString(mWakefulness) + + " mSleeping=" + mSleeping); } void startRunningVoiceLocked() { if (!mRunningVoice) { mRunningVoice = true; - comeOutOfSleepIfNeededLocked(); + updateSleepIfNeededLocked(); } } @@ -10196,8 +10069,8 @@ public final class ActivityManagerService extends ActivityManagerNative long ident = Binder.clearCallingIdentity(); try { if (DEBUG_LOCKSCREEN) logLockScreen(" shown=" + shown); - mLockScreenShown = shown; - comeOutOfSleepIfNeededLocked(); + mLockScreenShown = shown ? LOCK_SCREEN_SHOWN : LOCK_SCREEN_HIDDEN; + updateSleepIfNeededLocked(); } finally { Binder.restoreCallingIdentity(ident); } @@ -11378,13 +11251,18 @@ public final class ActivityManagerService extends ActivityManagerNative try { if (AppGlobals.getPackageManager().hasSystemUidErrors()) { - Message msg = Message.obtain(); - msg.what = SHOW_UID_ERROR_MSG; - mHandler.sendMessage(msg); + 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(); } } catch (RemoteException e) { } + if (!Build.isFingerprintConsistent()) { + Slog.e(TAG, "Build fingerprint is not consistent, warning user"); + mHandler.obtainMessage(SHOW_FINGERPRINT_ERROR_MSG).sendToTarget(); + } + long ident = Binder.clearCallingIdentity(); try { Intent intent = new Intent(Intent.ACTION_USER_STARTED); @@ -12962,13 +12840,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (dumpPackage == null) { - if (mSleeping || mWentToSleep || mLockScreenShown) { - pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep - + " mLockScreenShown " + mLockScreenShown); - } - if (mShuttingDown || mRunningVoice) { - pw.print(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice); - } + pw.println(" mWakefulness=" + + PowerManagerInternal.wakefulnessToString(mWakefulness)); + pw.println(" mSleeping=" + mSleeping + " mLockScreenShown=" + + lockScreenShownToString()); + pw.print(" mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice); } if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { @@ -13356,7 +13232,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (mReceiverResolver.dump(pw, needSep ? "\n Receiver Resolver Table:" : " Receiver Resolver Table:", - " ", dumpPackage, false)) { + " ", dumpPackage, false, false)) { needSep = true; printedAnything = true; } @@ -13942,6 +13818,35 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private static final int KSM_SHARED = 0; + private static final int KSM_SHARING = 1; + private static final int KSM_UNSHARED = 2; + private static final int KSM_VOLATILE = 3; + + private final long[] getKsmInfo() { + long[] longOut = new long[4]; + final int[] SINGLE_LONG_FORMAT = new int[] { + Process.PROC_SPACE_TERM|Process.PROC_OUT_LONG + }; + long[] longTmp = new long[1]; + Process.readProcFile("/sys/kernel/mm/ksm/pages_shared", + SINGLE_LONG_FORMAT, null, longTmp, null); + longOut[KSM_SHARED] = longTmp[0] * ProcessList.PAGE_SIZE / 1024; + longTmp[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_sharing", + SINGLE_LONG_FORMAT, null, longTmp, null); + longOut[KSM_SHARING] = longTmp[0] * ProcessList.PAGE_SIZE / 1024; + longTmp[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_unshared", + SINGLE_LONG_FORMAT, null, longTmp, null); + longOut[KSM_UNSHARED] = longTmp[0] * ProcessList.PAGE_SIZE / 1024; + longTmp[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_volatile", + SINGLE_LONG_FORMAT, null, longTmp, null); + longOut[KSM_VOLATILE] = longTmp[0] * ProcessList.PAGE_SIZE / 1024; + return longOut; + } + final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) { boolean dumpDetails = false; @@ -14061,7 +13966,9 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<MemItem> procMems = new ArrayList<MemItem>(); final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>(); - long nativePss=0, dalvikPss=0, otherPss=0; + long nativePss = 0; + long dalvikPss = 0; + long otherPss = 0; long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length]; @@ -14259,8 +14166,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(), memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(), - memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()+memInfo.getSlabSizeKb(), - nativeProcTotalPss); + memInfo.getKernelUsedSizeKb(), nativeProcTotalPss); } } if (!brief) { @@ -14288,7 +14194,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.print(" Free RAM: "); pw.print(cachedPss + memInfo.getCachedSizeKb() + memInfo.getFreeSizeKb()); pw.print(" kB ("); pw.print(cachedPss); pw.print(" cached pss + "); - pw.print(memInfo.getCachedSizeKb()); pw.print(" cached + "); + pw.print(memInfo.getCachedSizeKb()); pw.print(" cached kernel + "); pw.print(memInfo.getFreeSizeKb()); pw.println(" free)"); } else { pw.print("ram,"); pw.print(memInfo.getTotalSizeKb()); pw.print(","); @@ -14299,16 +14205,12 @@ public final class ActivityManagerService extends ActivityManagerNative } if (!isCompact) { pw.print(" Used RAM: "); pw.print(totalPss - cachedPss - + memInfo.getBuffersSizeKb() + memInfo.getShmemSizeKb() - + memInfo.getSlabSizeKb()); pw.print(" kB ("); + + memInfo.getKernelUsedSizeKb()); pw.print(" kB ("); pw.print(totalPss - cachedPss); pw.print(" used pss + "); - pw.print(memInfo.getBuffersSizeKb()); pw.print(" buffers + "); - pw.print(memInfo.getShmemSizeKb()); pw.print(" shmem + "); - pw.print(memInfo.getSlabSizeKb()); pw.println(" slab)"); + pw.print(memInfo.getKernelUsedSizeKb()); pw.print(" kernel)\n"); pw.print(" Lost RAM: "); pw.print(memInfo.getTotalSizeKb() - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() - - memInfo.getBuffersSizeKb() - memInfo.getShmemSizeKb() - - memInfo.getSlabSizeKb()); pw.println(" kB"); + - memInfo.getKernelUsedSizeKb()); pw.println(" kB"); } if (!brief) { if (memInfo.getZramTotalSizeKb() != 0) { @@ -14326,32 +14228,16 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(memInfo.getSwapFreeSizeKb()); } } - final int[] SINGLE_LONG_FORMAT = new int[] { - Process.PROC_SPACE_TERM|Process.PROC_OUT_LONG - }; - long[] longOut = new long[1]; - Process.readProcFile("/sys/kernel/mm/ksm/pages_shared", - SINGLE_LONG_FORMAT, null, longOut, null); - long shared = longOut[0] * ProcessList.PAGE_SIZE / 1024; - longOut[0] = 0; - Process.readProcFile("/sys/kernel/mm/ksm/pages_sharing", - SINGLE_LONG_FORMAT, null, longOut, null); - long sharing = longOut[0] * ProcessList.PAGE_SIZE / 1024; - longOut[0] = 0; - Process.readProcFile("/sys/kernel/mm/ksm/pages_unshared", - SINGLE_LONG_FORMAT, null, longOut, null); - long unshared = longOut[0] * ProcessList.PAGE_SIZE / 1024; - longOut[0] = 0; - Process.readProcFile("/sys/kernel/mm/ksm/pages_volatile", - SINGLE_LONG_FORMAT, null, longOut, null); - long voltile = longOut[0] * ProcessList.PAGE_SIZE / 1024; + final long[] ksm = getKsmInfo(); if (!isCompact) { - if (sharing != 0 || shared != 0 || unshared != 0 || voltile != 0) { - pw.print(" KSM: "); pw.print(sharing); + if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0 + || ksm[KSM_VOLATILE] != 0) { + pw.print(" KSM: "); pw.print(ksm[KSM_SHARING]); pw.print(" kB saved from shared "); - pw.print(shared); pw.println(" kB"); - pw.print(" "); pw.print(unshared); pw.print(" kB unshared; "); - pw.print(voltile); pw.println(" kB volatile"); + pw.print(ksm[KSM_SHARED]); pw.println(" kB"); + pw.print(" "); pw.print(ksm[KSM_UNSHARED]); + pw.print(" kB unshared; "); + pw.print(ksm[KSM_VOLATILE]); pw.println(" kB volatile"); } pw.print(" Tuning: "); pw.print(ActivityManager.staticGetMemoryClass()); @@ -14371,9 +14257,9 @@ public final class ActivityManagerService extends ActivityManagerNative } pw.println(); } else { - pw.print("ksm,"); pw.print(sharing); pw.print(","); - pw.print(shared); pw.print(","); pw.print(unshared); pw.print(","); - pw.println(voltile); + pw.print("ksm,"); pw.print(ksm[KSM_SHARING]); pw.print(","); + pw.print(ksm[KSM_SHARED]); pw.print(","); pw.print(ksm[KSM_UNSHARED]); + pw.print(","); pw.println(ksm[KSM_VOLATILE]); pw.print("tuning,"); pw.print(ActivityManager.staticGetMemoryClass()); pw.print(','); @@ -14392,6 +14278,265 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss, + String name) { + sb.append(" "); + sb.append(ProcessList.makeOomAdjString(oomAdj)); + sb.append(' '); + sb.append(ProcessList.makeProcStateString(procState)); + sb.append(' '); + ProcessList.appendRamKb(sb, pss); + sb.append(" kB: "); + sb.append(name); + } + + private void appendMemInfo(StringBuilder sb, ProcessMemInfo mi) { + appendBasicMemEntry(sb, mi.oomAdj, mi.procState, mi.pss, mi.name); + sb.append(" ("); + sb.append(mi.pid); + sb.append(") "); + sb.append(mi.adjType); + sb.append('\n'); + if (mi.adjReason != null) { + sb.append(" "); + sb.append(mi.adjReason); + sb.append('\n'); + } + } + + void reportMemUsage(ArrayList<ProcessMemInfo> memInfos) { + final SparseArray<ProcessMemInfo> infoMap = new SparseArray<>(memInfos.size()); + for (int i=0, N=memInfos.size(); i<N; i++) { + ProcessMemInfo mi = memInfos.get(i); + infoMap.put(mi.pid, mi); + } + updateCpuStatsNow(); + synchronized (mProcessCpuTracker) { + final int N = mProcessCpuTracker.countStats(); + for (int i=0; i<N; i++) { + ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); + if (st.vsize > 0) { + long pss = Debug.getPss(st.pid, null); + if (pss > 0) { + if (infoMap.indexOfKey(st.pid) < 0) { + ProcessMemInfo mi = new ProcessMemInfo(st.name, st.pid, + ProcessList.NATIVE_ADJ, -1, "native", null); + mi.pss = pss; + memInfos.add(mi); + } + } + } + } + } + + long totalPss = 0; + for (int i=0, N=memInfos.size(); i<N; i++) { + ProcessMemInfo mi = memInfos.get(i); + if (mi.pss == 0) { + mi.pss = Debug.getPss(mi.pid, null); + } + totalPss += mi.pss; + } + Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { + @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { + if (lhs.oomAdj != rhs.oomAdj) { + return lhs.oomAdj < rhs.oomAdj ? -1 : 1; + } + if (lhs.pss != rhs.pss) { + return lhs.pss < rhs.pss ? 1 : -1; + } + return 0; + } + }); + + StringBuilder tag = new StringBuilder(128); + StringBuilder stack = new StringBuilder(128); + tag.append("Low on memory -- "); + appendMemBucket(tag, totalPss, "total", false); + appendMemBucket(stack, totalPss, "total", true); + + StringBuilder fullNativeBuilder = new StringBuilder(1024); + StringBuilder shortNativeBuilder = new StringBuilder(1024); + StringBuilder fullJavaBuilder = new StringBuilder(1024); + + boolean firstLine = true; + int lastOomAdj = Integer.MIN_VALUE; + long extraNativeRam = 0; + long cachedPss = 0; + for (int i=0, N=memInfos.size(); i<N; i++) { + ProcessMemInfo mi = memInfos.get(i); + + if (mi.oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) { + cachedPss += mi.pss; + } + + if (mi.oomAdj != ProcessList.NATIVE_ADJ + && (mi.oomAdj < ProcessList.SERVICE_ADJ + || mi.oomAdj == ProcessList.HOME_APP_ADJ + || mi.oomAdj == ProcessList.PREVIOUS_APP_ADJ)) { + if (lastOomAdj != mi.oomAdj) { + lastOomAdj = mi.oomAdj; + if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) { + tag.append(" / "); + } + if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ) { + if (firstLine) { + stack.append(":"); + firstLine = false; + } + stack.append("\n\t at "); + } else { + stack.append("$"); + } + } else { + tag.append(" "); + stack.append("$"); + } + if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) { + appendMemBucket(tag, mi.pss, mi.name, false); + } + appendMemBucket(stack, mi.pss, mi.name, true); + if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ + && ((i+1) >= N || memInfos.get(i+1).oomAdj != lastOomAdj)) { + stack.append("("); + for (int k=0; k<DUMP_MEM_OOM_ADJ.length; k++) { + if (DUMP_MEM_OOM_ADJ[k] == mi.oomAdj) { + stack.append(DUMP_MEM_OOM_LABEL[k]); + stack.append(":"); + stack.append(DUMP_MEM_OOM_ADJ[k]); + } + } + stack.append(")"); + } + } + + appendMemInfo(fullNativeBuilder, mi); + if (mi.oomAdj == ProcessList.NATIVE_ADJ) { + // The short form only has native processes that are >= 1MB. + if (mi.pss >= 1000) { + appendMemInfo(shortNativeBuilder, mi); + } else { + extraNativeRam += mi.pss; + } + } else { + // Short form has all other details, but if we have collected RAM + // from smaller native processes let's dump a summary of that. + if (extraNativeRam > 0) { + appendBasicMemEntry(shortNativeBuilder, ProcessList.NATIVE_ADJ, + -1, extraNativeRam, "(Other native)"); + shortNativeBuilder.append('\n'); + extraNativeRam = 0; + } + appendMemInfo(fullJavaBuilder, mi); + } + } + + fullJavaBuilder.append(" "); + ProcessList.appendRamKb(fullJavaBuilder, totalPss); + fullJavaBuilder.append(" kB: TOTAL\n"); + + MemInfoReader memInfo = new MemInfoReader(); + memInfo.readMemInfo(); + final long[] infos = memInfo.getRawInfo(); + + StringBuilder memInfoBuilder = new StringBuilder(1024); + Debug.getMemInfo(infos); + memInfoBuilder.append(" MemInfo: "); + memInfoBuilder.append(infos[Debug.MEMINFO_SLAB]).append(" kB slab, "); + memInfoBuilder.append(infos[Debug.MEMINFO_SHMEM]).append(" kB shmem, "); + memInfoBuilder.append(infos[Debug.MEMINFO_VM_ALLOC_USED]).append(" kB vm alloc, "); + memInfoBuilder.append(infos[Debug.MEMINFO_PAGE_TABLES]).append(" kB page tables "); + memInfoBuilder.append(infos[Debug.MEMINFO_KERNEL_STACK]).append(" kB kernel stack\n"); + memInfoBuilder.append(" "); + memInfoBuilder.append(infos[Debug.MEMINFO_BUFFERS]).append(" kB buffers, "); + memInfoBuilder.append(infos[Debug.MEMINFO_CACHED]).append(" kB cached, "); + memInfoBuilder.append(infos[Debug.MEMINFO_MAPPED]).append(" kB mapped, "); + memInfoBuilder.append(infos[Debug.MEMINFO_FREE]).append(" kB free\n"); + if (infos[Debug.MEMINFO_ZRAM_TOTAL] != 0) { + memInfoBuilder.append(" ZRAM: "); + memInfoBuilder.append(infos[Debug.MEMINFO_ZRAM_TOTAL]); + memInfoBuilder.append(" kB RAM, "); + memInfoBuilder.append(infos[Debug.MEMINFO_SWAP_TOTAL]); + memInfoBuilder.append(" kB swap total, "); + memInfoBuilder.append(infos[Debug.MEMINFO_SWAP_FREE]); + memInfoBuilder.append(" kB swap free\n"); + } + final long[] ksm = getKsmInfo(); + if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0 + || ksm[KSM_VOLATILE] != 0) { + memInfoBuilder.append(" KSM: "); memInfoBuilder.append(ksm[KSM_SHARING]); + memInfoBuilder.append(" kB saved from shared "); + memInfoBuilder.append(ksm[KSM_SHARED]); memInfoBuilder.append(" kB\n"); + memInfoBuilder.append(" "); memInfoBuilder.append(ksm[KSM_UNSHARED]); + memInfoBuilder.append(" kB unshared; "); + memInfoBuilder.append(ksm[KSM_VOLATILE]); memInfoBuilder.append(" kB volatile\n"); + } + memInfoBuilder.append(" Free RAM: "); + memInfoBuilder.append(cachedPss + memInfo.getCachedSizeKb() + + memInfo.getFreeSizeKb()); + memInfoBuilder.append(" kB\n"); + memInfoBuilder.append(" Used RAM: "); + memInfoBuilder.append(totalPss - cachedPss + memInfo.getKernelUsedSizeKb()); + memInfoBuilder.append(" kB\n"); + memInfoBuilder.append(" Lost RAM: "); + memInfoBuilder.append(memInfo.getTotalSizeKb() + - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() + - memInfo.getKernelUsedSizeKb()); + memInfoBuilder.append(" kB\n"); + Slog.i(TAG, "Low on memory:"); + Slog.i(TAG, shortNativeBuilder.toString()); + Slog.i(TAG, fullJavaBuilder.toString()); + Slog.i(TAG, memInfoBuilder.toString()); + + StringBuilder dropBuilder = new StringBuilder(1024); + /* + StringWriter oomSw = new StringWriter(); + PrintWriter oomPw = new FastPrintWriter(oomSw, false, 256); + StringWriter catSw = new StringWriter(); + PrintWriter catPw = new FastPrintWriter(catSw, false, 256); + String[] emptyArgs = new String[] { }; + dumpApplicationMemoryUsage(null, oomPw, " ", emptyArgs, true, catPw); + oomPw.flush(); + String oomString = oomSw.toString(); + */ + dropBuilder.append("Low on memory:"); + dropBuilder.append(stack); + dropBuilder.append('\n'); + dropBuilder.append(fullNativeBuilder); + dropBuilder.append(fullJavaBuilder); + dropBuilder.append('\n'); + dropBuilder.append(memInfoBuilder); + dropBuilder.append('\n'); + /* + dropBuilder.append(oomString); + dropBuilder.append('\n'); + */ + StringWriter catSw = new StringWriter(); + synchronized (ActivityManagerService.this) { + PrintWriter catPw = new FastPrintWriter(catSw, false, 256); + String[] emptyArgs = new String[] { }; + catPw.println(); + dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null); + catPw.println(); + mServices.dumpServicesLocked(null, catPw, emptyArgs, 0, + false, false, null); + catPw.println(); + dumpActivitiesLocked(null, catPw, emptyArgs, 0, false, false, null); + catPw.flush(); + } + dropBuilder.append(catSw.toString()); + addErrorToDropBox("lowmem", null, "system_server", null, + null, tag.toString(), dropBuilder.toString(), null, null); + //Slog.i(TAG, "Sent to dropbox:"); + //Slog.i(TAG, dropBuilder.toString()); + synchronized (ActivityManagerService.this) { + long now = SystemClock.uptimeMillis(); + if (mLastMemUsageReportTime < now) { + mLastMemUsageReportTime = now; + } + } + } + /** * Searches array of arguments for the specified string * @param args array of argument strings @@ -15467,7 +15612,7 @@ public final class ActivityManagerService extends ActivityManagerNative & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) { Slog.w(TAG, "Skipping broadcast of " + intent + ": user " + userId + " is stopped"); - return ActivityManager.BROADCAST_SUCCESS; + return ActivityManager.BROADCAST_FAILED_USER_STOPPED; } } @@ -15522,128 +15667,135 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // Handle special intents: if this broadcast is from the package - // manager about a package being removed, we need to remove all of - // its activities from the history stack. - final boolean uidRemoved = Intent.ACTION_UID_REMOVED.equals( - intent.getAction()); - if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction()) - || Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction()) - || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction()) - || Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction()) - || uidRemoved) { - if (checkComponentPermission( - android.Manifest.permission.BROADCAST_PACKAGE_REMOVED, - callingPid, callingUid, -1, true) - == PackageManager.PERMISSION_GRANTED) { - if (uidRemoved) { - final Bundle intentExtras = intent.getExtras(); - final int uid = intentExtras != null - ? intentExtras.getInt(Intent.EXTRA_UID) : -1; - if (uid >= 0) { - BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics(); - synchronized (bs) { - bs.removeUidStatsLocked(uid); - } - mAppOpsService.uidRemoved(uid); + final String action = intent.getAction(); + if (action != null) { + switch (action) { + case Intent.ACTION_UID_REMOVED: + case Intent.ACTION_PACKAGE_REMOVED: + case Intent.ACTION_PACKAGE_CHANGED: + case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: + case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: + // Handle special intents: if this broadcast is from the package + // manager about a package being removed, we need to remove all of + // its activities from the history stack. + if (checkComponentPermission( + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED, + callingPid, callingUid, -1, true) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: " + intent.getAction() + + " broadcast from " + callerPackage + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " requires " + + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED; + Slog.w(TAG, msg); + throw new SecurityException(msg); } - } else { - // If resources are unavailable just force stop all - // those packages and flush the attribute cache as well. - if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())) { - String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - if (list != null && (list.length > 0)) { - for (String pkg : list) { - forceStopPackageLocked(pkg, -1, false, true, true, false, false, userId, - "storage unmount"); + switch (action) { + case Intent.ACTION_UID_REMOVED: + final Bundle intentExtras = intent.getExtras(); + final int uid = intentExtras != null + ? intentExtras.getInt(Intent.EXTRA_UID) : -1; + if (uid >= 0) { + BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics(); + synchronized (bs) { + bs.removeUidStatsLocked(uid); + } + mAppOpsService.uidRemoved(uid); } - cleanupRecentTasksLocked(UserHandle.USER_ALL); - sendPackageBroadcastLocked( - IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list, userId); - } - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals( - intent.getAction())) { - cleanupRecentTasksLocked(UserHandle.USER_ALL); - } else { - Uri data = intent.getData(); - String ssp; - if (data != null && (ssp=data.getSchemeSpecificPart()) != null) { - boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals( - intent.getAction()); - boolean fullUninstall = removed && - !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); - if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) { - forceStopPackageLocked(ssp, UserHandle.getAppId( - intent.getIntExtra(Intent.EXTRA_UID, -1)), false, true, true, - false, fullUninstall, userId, - removed ? "pkg removed" : "pkg changed"); + break; + case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: + // If resources are unavailable just force stop all those packages + // and flush the attribute cache as well. + String list[] = + intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + if (list != null && list.length > 0) { + for (int i = 0; i < list.length; i++) { + forceStopPackageLocked(list[i], -1, false, true, true, + false, false, userId, "storage unmount"); + } + cleanupRecentTasksLocked(UserHandle.USER_ALL); + sendPackageBroadcastLocked( + IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list, + userId); } - if (removed) { - sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED, - new String[] {ssp}, userId); - if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - mAppOpsService.packageRemoved( - intent.getIntExtra(Intent.EXTRA_UID, -1), ssp); - - // Remove all permissions granted from/to this package - removeUriPermissionsForPackageLocked(ssp, userId, true); + break; + case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: + cleanupRecentTasksLocked(UserHandle.USER_ALL); + break; + case Intent.ACTION_PACKAGE_REMOVED: + case Intent.ACTION_PACKAGE_CHANGED: + Uri data = intent.getData(); + String ssp; + if (data != null && (ssp=data.getSchemeSpecificPart()) != null) { + boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action); + boolean fullUninstall = removed && + !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) { + forceStopPackageLocked(ssp, UserHandle.getAppId( + intent.getIntExtra(Intent.EXTRA_UID, -1)), + false, true, true, false, fullUninstall, userId, + removed ? "pkg removed" : "pkg changed"); + } + if (removed) { + sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED, + new String[] {ssp}, userId); + if (fullUninstall) { + mAppOpsService.packageRemoved( + intent.getIntExtra(Intent.EXTRA_UID, -1), ssp); + + // Remove all permissions granted from/to this package + removeUriPermissionsForPackageLocked(ssp, userId, true); + + removeTasksByPackageNameLocked(ssp, userId); + } + } else { + removeTasksByRemovedPackageComponentsLocked(ssp, userId); } } + break; + } + break; + case Intent.ACTION_PACKAGE_ADDED: + // Special case for adding a package: by default turn on compatibility mode. + Uri data = intent.getData(); + String ssp; + if (data != null && (ssp = data.getSchemeSpecificPart()) != null) { + final boolean replacing = + intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + mCompatModePackages.handlePackageAddedLocked(ssp, replacing); + + if (replacing) { + removeTasksByRemovedPackageComponentsLocked(ssp, userId); } } - } - } else { - String msg = "Permission Denial: " + intent.getAction() - + " broadcast from " + callerPackage + " (pid=" + callingPid - + ", uid=" + callingUid + ")" - + " requires " - + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - - // Special case for adding a package: by default turn on compatibility - // mode. - } else if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { - Uri data = intent.getData(); - String ssp; - if (data != null && (ssp=data.getSchemeSpecificPart()) != null) { - mCompatModePackages.handlePackageAddedLocked(ssp, - intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)); - } - } - - /* - * If this is the time zone changed action, queue up a message that will reset the timezone - * of all currently running processes. This message will get queued up before the broadcast - * happens. - */ - if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { - mHandler.sendEmptyMessage(UPDATE_TIME_ZONE); - } - - /* - * If the user set the time, let all running processes know. - */ - if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) { - final int is24Hour = intent.getBooleanExtra( - Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, false) ? 1 : 0; - mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0)); - BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - stats.noteCurrentTimeChangedLocked(); + break; + case Intent.ACTION_TIMEZONE_CHANGED: + // If this is the time zone changed action, queue up a message that will reset + // the timezone of all currently running processes. This message will get + // queued up before the broadcast happens. + mHandler.sendEmptyMessage(UPDATE_TIME_ZONE); + break; + case Intent.ACTION_TIME_CHANGED: + // If the user set the time, let all running processes know. + final int is24Hour = + intent.getBooleanExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, false) ? 1 + : 0; + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0)); + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + stats.noteCurrentTimeChangedLocked(); + } + break; + case Intent.ACTION_CLEAR_DNS_CACHE: + mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG); + break; + case Proxy.PROXY_CHANGE_ACTION: + ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); + break; } } - if (Intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction())) { - mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG); - } - - if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { - ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO); - mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); - } - // Add to the sticky list if requested. if (sticky) { if (checkPermission(android.Manifest.permission.BROADCAST_STICKY, @@ -19060,13 +19212,8 @@ public final class ActivityManagerService extends ActivityManagerNative private final class LocalService extends ActivityManagerInternal { @Override - public void goingToSleep() { - ActivityManagerService.this.goingToSleep(); - } - - @Override - public void wakingUp() { - ActivityManagerService.this.wakingUp(); + public void onWakefulnessChanged(int wakefulness) { + ActivityManagerService.this.onWakefulnessChanged(wakefulness); } @Override @@ -19105,16 +19252,9 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (ActivityManagerService.this) { long origId = Binder.clearCallingIdentity(); try { - TaskRecord tr = recentTaskForIdLocked(mTaskId); - if (tr == null) { + if (!removeTaskByIdLocked(mTaskId, false)) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } - // Only kill the process if we are not a new document - int flags = tr.getBaseIntent().getFlags(); - boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == - Intent.FLAG_ACTIVITY_NEW_DOCUMENT; - removeTaskByIdLocked(mTaskId, - !isDocument ? ActivityManager.REMOVE_TASK_KILL_PROCESS : 0); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 1a0e45e..c12cadb 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -21,6 +21,7 @@ import android.os.PersistableBundle; import android.os.Trace; import com.android.internal.app.ResolverActivity; +import com.android.internal.content.ReferrerIntent; import com.android.internal.util.XmlUtils; import com.android.server.AttributeCache; import com.android.server.am.ActivityStack.ActivityState; @@ -128,7 +129,7 @@ final class ActivityRecord { final int requestCode; // code given by requester (resultTo) ArrayList<ResultInfo> results; // pending ActivityResult objs we have received HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act - ArrayList<Intent> newIntents; // any pending new intents for single-top mode + ArrayList<ReferrerIntent> newIntents; // any pending new intents for single-top mode ActivityOptions pendingOptions; // most recently given options ActivityOptions returningOptions; // options that are coming back via convertToTranslucent HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold @@ -629,9 +630,9 @@ final class ActivityRecord { } } - void addNewIntentLocked(Intent intent) { + void addNewIntentLocked(ReferrerIntent intent) { if (newIntents == null) { - newIntents = new ArrayList<Intent>(); + newIntents = new ArrayList<>(); } newIntents.add(intent); } @@ -640,7 +641,7 @@ final class ActivityRecord { * Deliver a new Intent to an existing activity, so that its onNewIntent() * method will be called at the proper time. */ - final void deliverNewIntentLocked(int callingUid, Intent intent) { + final void deliverNewIntentLocked(int callingUid, Intent intent, String referrer) { // The activity now gets access to the data associated with this Intent. service.grantUriPermissionFromIntentLocked(callingUid, packageName, intent, getUriPermissionsLocked(), userId); @@ -649,14 +650,14 @@ final class ActivityRecord { // device is sleeping, then all activities are stopped, so in that // case we will deliver it if this is the current top activity on its // stack. + final ReferrerIntent rintent = new ReferrerIntent(intent, referrer); boolean unsent = true; if ((state == ActivityState.RESUMED || (service.isSleeping() && task.stack.topRunningActivityLocked(null) == this)) && app != null && app.thread != null) { try { - ArrayList<Intent> ar = new ArrayList<Intent>(); - intent = new Intent(intent); - ar.add(intent); + ArrayList<ReferrerIntent> ar = new ArrayList<>(1); + ar.add(rintent); app.thread.scheduleNewIntent(ar, appToken); unsent = false; } catch (RemoteException e) { @@ -668,7 +669,7 @@ final class ActivityRecord { } } if (unsent) { - addNewIntentLocked(new Intent(intent)); + addNewIntentLocked(rintent); } } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index e1b8278..ea694ad 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -44,6 +44,7 @@ import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import android.util.ArraySet; import com.android.internal.app.IVoiceInteractor; +import com.android.internal.content.ReferrerIntent; import com.android.internal.os.BatteryStatsImpl; import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService.ItemMatcher; @@ -241,9 +242,6 @@ final class ActivityStack { /** Run all ActivityStacks through this */ final ActivityStackSupervisor mStackSupervisor; - /** Used to keep resumeTopActivityLocked() from being entered recursively */ - private boolean inResumeTopActivity; - 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; @@ -702,6 +700,10 @@ final class ActivityStack { activities.get(activityNdx).setSleeping(false); } } + if (mPausingActivity != null) { + Slog.d(TAG, "awakeFromSleepingLocked: previously pausing activity didn't pause"); + activityPausedLocked(mPausingActivity.appToken, true); + } } /** @@ -835,7 +837,7 @@ final class ActivityStack { prev.task.touchActiveTime(); clearLaunchTime(prev); final ActivityRecord next = mStackSupervisor.topRunningActivityLocked(); - if (mService.mHasRecents && (next == null || next.noDisplay || next.task != prev.task)) { + if (mService.mHasRecents && (next == null || next.noDisplay || next.task != prev.task || uiSleeping)) { prev.updateThumbnail(screenshotActivities(prev), null); } stopFullyDrawnTraceIfNeeded(); @@ -1468,7 +1470,7 @@ final class ActivityStack { } final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { - if (inResumeTopActivity) { + if (mStackSupervisor.inResumeTopActivity) { // Don't even start recursing. return false; } @@ -1476,10 +1478,14 @@ final class ActivityStack { boolean result = false; try { // Protect against recursion. - inResumeTopActivity = true; + mStackSupervisor.inResumeTopActivity = true; + if (mService.mLockScreenShown == ActivityManagerService.LOCK_SCREEN_LEAVING) { + mService.mLockScreenShown = ActivityManagerService.LOCK_SCREEN_HIDDEN; + mService.updateSleepIfNeededLocked(); + } result = resumeTopActivityInnerLocked(prev, options); } finally { - inResumeTopActivity = false; + mStackSupervisor.inResumeTopActivity = false; } return result; } @@ -1536,9 +1542,6 @@ final class ActivityStack { ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Top activity resumed " + next); if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); - - // Make sure to notify Keyguard as well if it is waiting for an activity to be drawn. - mStackSupervisor.notifyActivityDrawnForKeyguard(); return false; } @@ -1641,8 +1644,8 @@ final class ActivityStack { boolean dontWaitForPause = (next.info.flags&ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0; boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, true, dontWaitForPause); if (mResumedActivity != null) { - pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause); if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing " + mResumedActivity); + pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause); } if (pausing) { if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG, @@ -2952,7 +2955,8 @@ final class ActivityStack { parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK || parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP || (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - parent.deliverNewIntentLocked(srec.info.applicationInfo.uid, destIntent); + parent.deliverNewIntentLocked(srec.info.applicationInfo.uid, destIntent, + srec.packageName); } else { try { ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( @@ -3317,17 +3321,18 @@ final class ActivityStack { mHandler.sendEmptyMessageDelayed(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG, 500); } else { Slog.e(TAG, "releaseBackgroundResources: activity " + r + " no longer running"); - backgroundResourcesReleased(r.appToken); + backgroundResourcesReleased(); } } } - final void backgroundResourcesReleased(IBinder token) { + final void backgroundResourcesReleased() { mHandler.removeMessages(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG); final ActivityRecord r = getVisibleBehindActivity(); if (r != null) { mStackSupervisor.mStoppingActivities.add(r); setVisibleBehindActivity(null); + mStackSupervisor.scheduleIdleTimeoutLocked(null); } mStackSupervisor.resumeTopActivitiesLocked(); } @@ -3772,7 +3777,7 @@ final class ActivityStack { private boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume) { List<ResultInfo> results = null; - List<Intent> newIntents = null; + List<ReferrerIntent> newIntents = null; if (andResume) { results = r.results; newIntents = r.newIntents; diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 03dd3c0..38809cb 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -97,6 +97,7 @@ import android.view.InputEvent; import android.view.Surface; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.app.IVoiceInteractor; +import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TransferPipe; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.widget.LockPatternUtils; @@ -274,6 +275,9 @@ public final class ActivityStackSupervisor implements DisplayListener { final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<PendingActivityLaunch>(); + /** Used to keep resumeTopActivityLocked() from being entered recursively */ + boolean inResumeTopActivity; + /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. @@ -444,10 +448,6 @@ public final class ActivityStackSupervisor implements DisplayListener { return mService.startHomeActivityLocked(mCurrentUser); } - void keyguardWaitingForActivityDrawn() { - mWindowManager.keyguardWaitingForActivityDrawn(); - } - TaskRecord anyTaskForIdLocked(int id) { int numDisplays = mActivityDisplays.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { @@ -656,7 +656,6 @@ public final class ActivityStackSupervisor implements DisplayListener { void reportActivityVisibleLocked(ActivityRecord r) { sendWaitingVisibleReportLocked(r); - notifyActivityDrawnForKeyguard(); } void sendWaitingVisibleReportLocked(ActivityRecord r) { @@ -1102,7 +1101,7 @@ public final class ActivityStackSupervisor implements DisplayListener { throw new RemoteException(); } List<ResultInfo> results = null; - List<Intent> newIntents = null; + List<ReferrerIntent> newIntents = null; if (andResume) { results = r.results; newIntents = r.newIntents; @@ -1156,9 +1155,9 @@ public final class ActivityStackSupervisor implements DisplayListener { app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP); app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration), - r.compat, r.task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, - results, newIntents, !andResume, mService.isNextTransitionForward(), - profilerInfo); + r.compat, r.launchedFromPackage, r.task.voiceInteractor, app.repProcState, + r.icicle, r.persistentState, results, newIntents, !andResume, + mService.isNextTransitionForward(), profilerInfo); if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { // This may be a heavy-weight process! Note that the package @@ -1832,6 +1831,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityStack lastStack = getLastStack(); ActivityRecord curTop = lastStack == null? null : lastStack.topRunningNonDelayedActivityLocked(notTop); + boolean movedToFront = false; if (curTop != null && (curTop.task != intentActivity.task || curTop.task != lastStack.topTask())) { r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); @@ -1851,6 +1851,7 @@ public final class ActivityStackSupervisor implements DisplayListener { intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE); } options = null; + movedToFront = true; } } // If the caller has requested that the target task be @@ -1865,6 +1866,12 @@ public final class ActivityStackSupervisor implements DisplayListener { // sure we have correctly resumed the top activity. if (doResume) { resumeTopActivitiesLocked(targetStack, null, options); + + // Make sure to notify Keyguard as well if we are not running an app + // transition later. + if (!movedToFront) { + notifyActivityDrawnForKeyguard(); + } } else { ActivityOptions.abort(options); } @@ -1897,7 +1904,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.deliverNewIntentLocked(callingUid, r.intent); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); } else { // A special case: we need to // start the activity because it is not currently @@ -1923,7 +1930,8 @@ public final class ActivityStackSupervisor implements DisplayListener { if (intentActivity.frontOfTask) { intentActivity.task.setIntent(r); } - intentActivity.deliverNewIntentLocked(callingUid, r.intent); + intentActivity.deliverNewIntentLocked(callingUid, r.intent, + r.launchedFromPackage); } else if (!r.intent.filterEquals(intentActivity.task.intent)) { // In this case we are launching the root activity // of the task, but with a different intent. We @@ -1955,6 +1963,11 @@ public final class ActivityStackSupervisor implements DisplayListener { // sure we have correctly resumed the top activity. if (doResume) { targetStack.resumeTopActivityLocked(null, options); + if (!movedToFront) { + // Make sure to notify Keyguard as well if we are not running an app + // transition later. + notifyActivityDrawnForKeyguard(); + } } else { ActivityOptions.abort(options); } @@ -1996,7 +2009,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // is the case, so this is it! return ActivityManager.START_RETURN_INTENT_TO_CALLER; } - top.deliverNewIntentLocked(callingUid, r.intent); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); return ActivityManager.START_DELIVERED_TO_TOP; } } @@ -2072,7 +2085,7 @@ public final class ActivityStackSupervisor implements DisplayListener { keepCurTransition = true; if (top != null) { ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.deliverNewIntentLocked(callingUid, r.intent); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); // For paranoia, make sure we have correctly // resumed the top activity. targetStack.mLastPausedActivity = null; @@ -2093,7 +2106,7 @@ public final class ActivityStackSupervisor implements DisplayListener { task.moveActivityToFrontLocked(top); ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task); top.updateOptionsLocked(options); - top.deliverNewIntentLocked(callingUid, r.intent); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); targetStack.mLastPausedActivity = null; if (doResume) { targetStack.resumeTopActivityLocked(null); @@ -2133,7 +2146,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // is the case, so this is it! return ActivityManager.START_RETURN_INTENT_TO_CALLER; } - top.deliverNewIntentLocked(callingUid, r.intent); + top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage); return ActivityManager.START_DELIVERED_TO_TOP; } } @@ -3550,9 +3563,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } mLockTaskNotify.show(false); try { - boolean shouldLockKeyguard = Settings.System.getInt( + boolean shouldLockKeyguard = Settings.Secure.getInt( mService.mContext.getContentResolver(), - Settings.System.LOCK_TO_APP_EXIT_LOCKED) != 0; + Settings.Secure.LOCK_TO_APP_EXIT_LOCKED) != 0; if (!mLockTaskIsLocked && shouldLockKeyguard) { mWindowManager.lockNow(null); mWindowManager.dismissKeyguard(); diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java index 5768ddb..b3777ed 100644 --- a/services/core/java/com/android/server/am/LockTaskNotify.java +++ b/services/core/java/com/android/server/am/LockTaskNotify.java @@ -19,6 +19,7 @@ package com.android.server.am; import android.content.Context; import android.os.Handler; import android.os.Message; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; @@ -56,8 +57,7 @@ public class LockTaskNotify { if (mLastToast != null) { mLastToast.cancel(); } - mLastToast = Toast.makeText(mContext, text, Toast.LENGTH_LONG); - mLastToast.show(); + mLastToast = makeAllUserToastAndShow(text); } public void show(boolean starting) { @@ -65,7 +65,15 @@ public class LockTaskNotify { if (starting) { showString = R.string.lock_to_app_start; } - Toast.makeText(mContext, mContext.getString(showString), Toast.LENGTH_LONG).show(); + makeAllUserToastAndShow(mContext.getString(showString)); + } + + private Toast makeAllUserToastAndShow(String text) { + Toast toast = Toast.makeText(mContext, text, Toast.LENGTH_LONG); + toast.getWindowParams().privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + toast.show(); + return toast; } private final class H extends Handler { diff --git a/services/core/java/com/android/server/am/LockToAppRequestDialog.java b/services/core/java/com/android/server/am/LockToAppRequestDialog.java deleted file mode 100644 index a1eb31e..0000000 --- a/services/core/java/com/android/server/am/LockToAppRequestDialog.java +++ /dev/null @@ -1,141 +0,0 @@ - -package com.android.server.am; - -import android.app.AlertDialog; -import android.app.admin.DevicePolicyManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.res.Resources; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.util.Slog; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; -import android.widget.CheckBox; - -import com.android.internal.R; -import com.android.internal.widget.ILockSettings; -import com.android.internal.widget.LockPatternUtils; -import com.android.internal.widget.LockPatternUtilsCache; - -public class LockToAppRequestDialog implements OnClickListener { - private static final String TAG = "ActivityManager"; - - final private Context mContext; - final private ActivityManagerService mService; - - private AlertDialog mDialog; - private TaskRecord mRequestedTask; - - private CheckBox mCheckbox; - - private ILockSettings mLockSettingsService; - - private AccessibilityManager mAccessibilityService; - - public LockToAppRequestDialog(Context context, ActivityManagerService activityManagerService) { - mContext = context; - mAccessibilityService = (AccessibilityManager) - mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); - mService = activityManagerService; - } - - private ILockSettings getLockSettings() { - if (mLockSettingsService == null) { - mLockSettingsService = LockPatternUtilsCache.getInstance( - ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"))); - } - return mLockSettingsService; - } - - private int getLockString(int userId) { - try { - int quality = (int) getLockSettings().getLong(LockPatternUtils.PASSWORD_TYPE_KEY, - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId); - switch (quality) { - case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: - case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: - return R.string.lock_to_app_unlock_pin; - case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: - case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: - case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: - return R.string.lock_to_app_unlock_password; - case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: - if (getLockSettings().getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, false, - userId)) { - return R.string.lock_to_app_unlock_pattern; - } - } - } catch (RemoteException e) { - } - return 0; - } - - public void clearPrompt() { - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - } - } - - public void showLockTaskPrompt(TaskRecord task) { - clearPrompt(); - mRequestedTask = task; - final int unlockStringId = getLockString(task.userId); - - final Resources r = Resources.getSystem(); - final String description= r.getString(mAccessibilityService.isEnabled() - ? R.string.lock_to_app_description_accessible - : R.string.lock_to_app_description); - AlertDialog.Builder builder = new AlertDialog.Builder(mContext) - .setTitle(r.getString(R.string.lock_to_app_title)) - .setMessage(description) - .setPositiveButton(r.getString(R.string.lock_to_app_positive), this) - .setNegativeButton(r.getString(R.string.lock_to_app_negative), this); - if (unlockStringId != 0) { - builder.setView(R.layout.lock_to_app_checkbox); - } - mDialog = builder.create(); - - mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - mDialog.getWindow().getAttributes().privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; - mDialog.show(); - - if (unlockStringId != 0) { - String unlockString = mContext.getString(unlockStringId); - mCheckbox = (CheckBox) mDialog.findViewById(R.id.lock_to_app_checkbox); - mCheckbox.setText(unlockString); - - // Remember state. - try { - boolean useLock = Settings.System.getInt(mContext.getContentResolver(), - Settings.System.LOCK_TO_APP_EXIT_LOCKED) != 0; - mCheckbox.setChecked(useLock); - } catch (SettingNotFoundException e) { - } - } else { - mCheckbox = null; - } - } - - @Override - public void onClick(DialogInterface dialog, int which) { - if (DialogInterface.BUTTON_POSITIVE == which) { - Slog.d(TAG, "accept lock-to-app request"); - // Set whether to use the lock screen when exiting. - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.LOCK_TO_APP_EXIT_LOCKED, - mCheckbox != null && mCheckbox.isChecked() ? 1 : 0); - - // Start lock-to-app. - mService.startLockTaskMode(mRequestedTask); - } else { - Slog.d(TAG, "ignore lock-to-app request"); - } - } - -} diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 433ab60..ffaa03d 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -269,11 +269,13 @@ final class PendingIntentRecord extends IIntentSender.Stub { try { // If a completion callback has been requested, require // that the broadcast be delivered synchronously - owner.broadcastIntentInPackage(key.packageName, uid, + int sent = owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType, finishedReceiver, code, null, null, requiredPermission, (finishedReceiver != null), false, userId); - sendFinish = false; + if (sent == ActivityManager.BROADCAST_SUCCESS) { + sendFinish = false; + } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 5b22255..a4aff77 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -177,7 +177,7 @@ final class ProcessList { // 1280x800 or larger screen with around 1GB RAM. Values are in KB. private final int[] mOomMinFreeHigh = new int[] { 73728, 92160, 110592, - 129024, 147456, 184320 + 129024, 225000, 325000 }; // The actual OOM killer memory levels we are using. private final int[] mOomMinFree = new int[mOomAdj.length]; @@ -235,22 +235,16 @@ final class ProcessList { Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs); } - // We've now baked in the increase to the basic oom values above, since - // they seem to be useful more generally for devices that are tight on - // memory than just for 64 bit. This should probably have some more - // tuning done, so not deleting it quite yet... - final boolean is64bit = false; //Build.SUPPORTED_64_BIT_ABIS.length > 0; + if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { + // Increase the high min-free levels for cached processes for 64-bit + mOomMinFreeHigh[4] = 225000; + mOomMinFreeHigh[5] = 325000; + } for (int i=0; i<mOomAdj.length; i++) { int low = mOomMinFreeLow[i]; int high = mOomMinFreeHigh[i]; mOomMinFree[i] = (int)(low + ((high-low)*scale)); - if (is64bit) { - // On 64 bit devices, we consume more baseline RAM, because 64 bit is cool! - // To avoid being all pagey and stuff, scale up the memory levels to - // give us some breathing room. - mOomMinFree[i] = (3*mOomMinFree[i])/2; - } } if (minfree_abs >= 0) { diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index bffb541..d05910b 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -582,9 +582,10 @@ public final class ProcessStatsService extends IProcessStats.Stub { pw.println("Process stats (procstats) dump options:"); pw.println(" [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]"); pw.println(" [--details] [--full-details] [--current] [--hours N] [--last N]"); - pw.println(" [--active] [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]"); + pw.println(" [--max N] --active] [--commit] [--reset] [--clear] [--write] [-h]"); + pw.println(" [<package.name>]"); pw.println(" --checkin: perform a checkin: print and delete old committed states."); - pw.println(" --c: print only state in checkin format."); + pw.println(" -c: print only state in checkin format."); pw.println(" --csv: output data suitable for putting in a spreadsheet."); pw.println(" --csv-screen: on, off."); pw.println(" --csv-mem: norm, mod, low, crit."); @@ -595,6 +596,7 @@ public final class ProcessStatsService extends IProcessStats.Stub { pw.println(" --current: only dump current state."); pw.println(" --hours: aggregate over about N last hours."); pw.println(" --last: only show the last committed stats at index N (starting at 1)."); + pw.println(" --max: for -a, max num of historical batches to print."); pw.println(" --active: only show currently active processes/services."); pw.println(" --commit: commit current stats to disk and reset to start new stats."); pw.println(" --reset: reset current stats, without committing."); @@ -636,6 +638,7 @@ public final class ProcessStatsService extends IProcessStats.Stub { boolean dumpAll = false; int aggregateHours = 0; int lastIndex = 0; + int maxNum = 2; boolean activeOnly = false; String reqPackage = null; boolean csvSepScreenStats = false; @@ -734,6 +737,20 @@ public final class ProcessStatsService extends IProcessStats.Stub { dumpHelp(pw); return; } + } else if ("--max".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("Error: argument required for --max"); + dumpHelp(pw); + return; + } + try { + maxNum = Integer.parseInt(args[i]); + } catch (NumberFormatException e) { + pw.println("Error: --max argument not an int -- " + args[i]); + dumpHelp(pw); + return; + } } else if ("--active".equals(arg)) { activeOnly = true; currentOnly = true; @@ -892,7 +909,11 @@ public final class ProcessStatsService extends IProcessStats.Stub { try { ArrayList<String> files = getCommittedFiles(0, false, !isCheckin); if (files != null) { - for (int i=0; i<files.size(); i++) { + int start = isCheckin ? 0 : (files.size() - maxNum); + if (start < 0) { + start = 0; + } + for (int i=start; i<files.size(); i++) { if (DEBUG) Slog.d(TAG, "Retrieving state: " + files.get(i)); try { AtomicFile file = new AtomicFile(new File(files.get(i))); @@ -947,19 +968,6 @@ public final class ProcessStatsService extends IProcessStats.Stub { } } if (!isCheckin) { - if (!currentOnly) { - if (sepNeeded) { - pw.println(); - } - pw.println("AGGREGATED OVER LAST 24 HOURS:"); - dumpAggregatedStats(pw, 24, now, reqPackage, isCompact, - dumpDetails, dumpFullDetails, dumpAll, activeOnly); - pw.println(); - pw.println("AGGREGATED OVER LAST 3 HOURS:"); - dumpAggregatedStats(pw, 3, now, reqPackage, isCompact, - dumpDetails, dumpFullDetails, dumpAll, activeOnly); - sepNeeded = true; - } synchronized (mAm) { if (isCompact) { mProcessStats.dumpCheckinLocked(pw, reqPackage); @@ -977,8 +985,21 @@ public final class ProcessStatsService extends IProcessStats.Stub { } else { mProcessStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly); } + sepNeeded = true; } } + if (!currentOnly) { + if (sepNeeded) { + pw.println(); + } + pw.println("AGGREGATED OVER LAST 24 HOURS:"); + dumpAggregatedStats(pw, 24, now, reqPackage, isCompact, + dumpDetails, dumpFullDetails, dumpAll, activeOnly); + pw.println(); + pw.println("AGGREGATED OVER LAST 3 HOURS:"); + dumpAggregatedStats(pw, 3, now, reqPackage, isCompact, + dumpDetails, dumpFullDetails, dumpAll, activeOnly); + } } } } diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index afc781f..9311f25 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -63,6 +63,12 @@ public class TaskPersister { private static final String IMAGES_DIRNAME = "recent_images"; static final String IMAGE_EXTENSION = ".png"; + // Directory where restored historical task XML/PNG files are placed. This directory + // contains subdirs named after TASKS_DIRNAME and IMAGES_DIRNAME mirroring the + // ancestral device's dataset. This needs to match the RECENTS_TASK_RESTORE_DIR + // value in RecentsBackupHelper. + private static final String RESTORED_TASKS = "restored_" + TASKS_DIRNAME; + private static final String TAG_TASK = "task"; static File sImagesDir; @@ -166,7 +172,7 @@ public class TaskPersister { break; } } - if (queueNdx < 0) { + if (queueNdx < 0 && task.isPersistable) { mWriteQueue.add(new TaskWriteQueueItem(task)); } } else { @@ -473,13 +479,15 @@ public class TaskPersister { if (DEBUG) Slog.d(TAG, "mRecents=" + tasks); for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = tasks.get(taskNdx); - if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" + - task.isPersistable); - if (task.isPersistable && !task.stack.isHomeStack()) { + if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + + " persistable=" + task.isPersistable); + if ((task.isPersistable || task.inRecents) + && !task.stack.isHomeStack()) { if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task); persistentTaskIds.add(task.taskId); } else { - if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task); + if (DEBUG) Slog.d(TAG, + "omitting from persistentTaskIds task=" + task); } } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 4dfd23b..ee93233 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -144,7 +144,7 @@ final class TaskRecord { boolean mReuseTask = false; private Bitmap mLastThumbnail; // Last thumbnail captured for this item. - private final File mLastThumbnailFile; // File containing last thubmnail. + private final File mLastThumbnailFile; // File containing last thumbnail. private final String mFilename; CharSequence lastDescription; // Last description captured for this item. diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index dfc8df5..36263ec 100644 --- a/services/core/java/com/android/server/am/UserSwitchingDialog.java +++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java @@ -17,17 +17,11 @@ package com.android.server.am; import android.app.AlertDialog; -import android.app.Service; -import android.content.ActivityNotFoundException; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.content.res.Resources; -import android.os.Handler; -import android.os.Message; -import android.util.Slog; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.TextView; @@ -39,11 +33,10 @@ import com.android.internal.R; * in the background rather than just freeze the screen and not know if the user-switch affordance * was being handled. */ -final class UserSwitchingDialog extends AlertDialog { +final class UserSwitchingDialog extends AlertDialog + implements ViewTreeObserver.OnWindowShownListener { private static final String TAG = "ActivityManagerUserSwitchingDialog"; - private static final int MSG_START_USER = 1; - private final ActivityManagerService mService; private final int mUserId; @@ -74,19 +67,21 @@ final class UserSwitchingDialog extends AlertDialog { @Override public void show() { + // Slog.v(TAG, "show called"); super.show(); - // TODO: Instead of just an arbitrary delay, wait for a signal that the window was fully - // displayed by the window manager - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_USER), 250); + final View decorView = getWindow().getDecorView(); + if (decorView != null) { + decorView.getViewTreeObserver().addOnWindowShownListener(this); + } } - private final Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_START_USER: - mService.startUserInForeground(mUserId, UserSwitchingDialog.this); - break; - } + @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); } - }; + } } diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index c382be0..3fa21d0 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIFI; import java.net.Inet4Address; @@ -43,45 +44,41 @@ import com.android.server.net.BaseNetworkObserver; * Class to manage a 464xlat CLAT daemon. */ public class Nat464Xlat extends BaseNetworkObserver { - private Context mContext; - private INetworkManagementService mNMService; - private IConnectivityManager mConnService; - // Whether we started clatd and expect it to be running. - private boolean mIsStarted; - // Whether the clatd interface exists (i.e., clatd is running). - private boolean mIsRunning; - // The LinkProperties of the clat interface. - private LinkProperties mLP; - // Current LinkProperties of the network. Includes mLP as a stacked link when clat is active. - private LinkProperties mBaseLP; + private static final String TAG = "Nat464Xlat"; + + // This must match the interface prefix in clatd.c. + private static final String CLAT_PREFIX = "v4-"; + + private final INetworkManagementService mNMService; + // ConnectivityService Handler for LinkProperties updates. - private Handler mHandler; - // Marker to connote which network we're augmenting. - private Messenger mNetworkMessenger; + private final Handler mHandler; - // This must match the interface name in clatd.conf. - private static final String CLAT_INTERFACE_NAME = "clat4"; + // The network we're running on, and its type. + private final NetworkAgentInfo mNetwork; - private static final String TAG = "Nat464Xlat"; + // Internal state variables. + // + // The possible states are: + // - Idle: start() not called. Everything is null. + // - Starting: start() called. Interfaces are non-null. isStarted() returns true. + // mIsRunning is false. + // - Running: start() called, and interfaceLinkStateChanged() told us that mIface is up. + // mIsRunning is true. + // + // Once mIface is non-null and isStarted() is true, methods called by ConnectivityService on + // its handler thread must not modify any internal state variables; they are only updated by the + // interface observers, called on the notification threads. + private String mBaseIface; + private String mIface; + private boolean mIsRunning; - public Nat464Xlat(Context context, INetworkManagementService nmService, - IConnectivityManager connService, Handler handler) { - mContext = context; + public Nat464Xlat( + Context context, INetworkManagementService nmService, + Handler handler, NetworkAgentInfo nai) { mNMService = nmService; - mConnService = connService; mHandler = handler; - - mIsStarted = false; - mIsRunning = false; - mLP = new LinkProperties(); - - // If this is a runtime restart, it's possible that clatd is already - // running, but we don't know about it. If so, stop it. - try { - if (mNMService.isClatdStarted()) { - mNMService.stopClatd(); - } - } catch(RemoteException e) {} // Well, we tried. + mNetwork = nai; } /** @@ -94,137 +91,196 @@ public class Nat464Xlat extends BaseNetworkObserver { final boolean connected = nai.networkInfo.isConnected(); final boolean hasIPv4Address = (nai.linkProperties != null) ? nai.linkProperties.hasIPv4Address() : false; - Slog.d(TAG, "requiresClat: netType=" + netType + - ", connected=" + connected + - ", hasIPv4Address=" + hasIPv4Address); - // Only support clat on mobile for now. - return netType == TYPE_MOBILE && connected && !hasIPv4Address; + // Only support clat on mobile and wifi for now, because these are the only IPv6-only + // networks we can connect to. + return connected && !hasIPv4Address && (netType == TYPE_MOBILE || netType == TYPE_WIFI); } - public boolean isRunningClat(NetworkAgentInfo network) { - return mNetworkMessenger == network.messenger; + /** + * Determines whether clatd is started. Always true, except a) if start has not yet been called, + * or b) if our interface was removed. + */ + public boolean isStarted() { + return mIface != null; + } + + /** + * Clears internal state. Must not be called by ConnectivityService. + */ + private void clear() { + mIface = null; + mBaseIface = null; + mIsRunning = false; } /** - * Starts the clat daemon. - * @param lp The link properties of the interface to start clatd on. + * Starts the clat daemon. Called by ConnectivityService on the handler thread. */ - public void startClat(NetworkAgentInfo network) { - if (mNetworkMessenger != null && mNetworkMessenger != network.messenger) { - Slog.e(TAG, "startClat: too many networks requesting clat"); + public void start() { + if (isStarted()) { + Slog.e(TAG, "startClat: already started"); return; } - mNetworkMessenger = network.messenger; - LinkProperties lp = network.linkProperties; - mBaseLP = new LinkProperties(lp); - if (mIsStarted) { - Slog.e(TAG, "startClat: already started"); + + if (mNetwork.linkProperties == null) { + Slog.e(TAG, "startClat: Can't start clat with null LinkProperties"); return; } - String iface = lp.getInterfaceName(); - Slog.i(TAG, "Starting clatd on " + iface + ", lp=" + lp); + try { - mNMService.startClatd(iface); + mNMService.registerObserver(this); } catch(RemoteException e) { - Slog.e(TAG, "Error starting clat daemon: " + e); + Slog.e(TAG, "startClat: Can't register interface observer for clat on " + mNetwork); + return; + } + + mBaseIface = mNetwork.linkProperties.getInterfaceName(); + if (mBaseIface == null) { + Slog.e(TAG, "startClat: Can't start clat on null interface"); + return; + } + mIface = CLAT_PREFIX + mBaseIface; + // From now on, isStarted() will return true. + + Slog.i(TAG, "Starting clatd on " + mBaseIface); + try { + mNMService.startClatd(mBaseIface); + } catch(RemoteException|IllegalStateException e) { + Slog.e(TAG, "Error starting clatd: " + e); } - mIsStarted = true; } /** - * Stops the clat daemon. + * Stops the clat daemon. Called by ConnectivityService on the handler thread. */ - public void stopClat() { - if (mIsStarted) { + public void stop() { + if (isStarted()) { Slog.i(TAG, "Stopping clatd"); try { - mNMService.stopClatd(); - } catch(RemoteException e) { - Slog.e(TAG, "Error stopping clat daemon: " + e); + mNMService.stopClatd(mBaseIface); + } catch(RemoteException|IllegalStateException e) { + Slog.e(TAG, "Error stopping clatd: " + e); } - mIsStarted = false; - mIsRunning = false; - mNetworkMessenger = null; - mBaseLP = null; - mLP.clear(); + // When clatd stops and its interface is deleted, interfaceRemoved() will notify + // ConnectivityService and call clear(). } else { - Slog.e(TAG, "stopClat: already stopped"); + Slog.e(TAG, "clatd: already stopped"); } } - private void updateConnectivityService() { - Message msg = mHandler.obtainMessage( - NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED, mBaseLP); - msg.replyTo = mNetworkMessenger; + private void updateConnectivityService(LinkProperties lp) { + Message msg = mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED, lp); + msg.replyTo = mNetwork.messenger; Slog.i(TAG, "sending message to ConnectivityService: " + msg); msg.sendToTarget(); } - // Copies the stacked clat link in oldLp, if any, to the LinkProperties in nai. - public void fixupLinkProperties(NetworkAgentInfo nai, LinkProperties oldLp) { - if (isRunningClat(nai) && - nai.linkProperties != null && - !nai.linkProperties.getAllInterfaceNames().contains(CLAT_INTERFACE_NAME)) { - Slog.d(TAG, "clatd running, updating NAI for " + nai.linkProperties.getInterfaceName()); + /** + * Copies the stacked clat link in oldLp, if any, to the LinkProperties in mNetwork. + * This is necessary because the LinkProperties in mNetwork come from the transport layer, which + * has no idea that 464xlat is running on top of it. + */ + public void fixupLinkProperties(LinkProperties oldLp) { + if (mNetwork.clatd != null && + mIsRunning && + mNetwork.linkProperties != null && + !mNetwork.linkProperties.getAllInterfaceNames().contains(mIface)) { + Slog.d(TAG, "clatd running, updating NAI for " + mIface); for (LinkProperties stacked: oldLp.getStackedLinks()) { - if (CLAT_INTERFACE_NAME.equals(stacked.getInterfaceName())) { - nai.linkProperties.addStackedLink(stacked); + if (mIface.equals(stacked.getInterfaceName())) { + mNetwork.linkProperties.addStackedLink(stacked); break; } } } } + private LinkProperties makeLinkProperties(LinkAddress clatAddress) { + LinkProperties stacked = new LinkProperties(); + stacked.setInterfaceName(mIface); + + // Although the clat interface is a point-to-point tunnel, we don't + // point the route directly at the interface because some apps don't + // understand routes without gateways (see, e.g., http://b/9597256 + // http://b/9597516). Instead, set the next hop of the route to the + // clat IPv4 address itself (for those apps, it doesn't matter what + // the IP of the gateway is, only that there is one). + RouteInfo ipv4Default = new RouteInfo( + new LinkAddress(Inet4Address.ANY, 0), + clatAddress.getAddress(), mIface); + stacked.addRoute(ipv4Default); + stacked.addLinkAddress(clatAddress); + return stacked; + } + + private LinkAddress getLinkAddress(String iface) { + try { + InterfaceConfiguration config = mNMService.getInterfaceConfig(iface); + return config.getLinkAddress(); + } catch(RemoteException|IllegalStateException e) { + Slog.e(TAG, "Error getting link properties: " + e); + return null; + } + } + + private void maybeSetIpv6NdOffload(String iface, boolean on) { + if (mNetwork.networkInfo.getType() != TYPE_WIFI) { + return; + } + try { + Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface); + mNMService.setInterfaceIpv6NdOffload(iface, on); + } catch(RemoteException|IllegalStateException e) { + Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e); + } + } + @Override - public void interfaceAdded(String iface) { - if (iface.equals(CLAT_INTERFACE_NAME)) { - Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME + - " added, mIsRunning = " + mIsRunning + " -> true"); - mIsRunning = true; - - // Create the LinkProperties for the clat interface by fetching the - // IPv4 address for the interface and adding an IPv4 default route, - // then stack the LinkProperties on top of the link it's running on. - try { - InterfaceConfiguration config = mNMService.getInterfaceConfig(iface); - LinkAddress clatAddress = config.getLinkAddress(); - mLP.clear(); - mLP.setInterfaceName(iface); - - // Although the clat interface is a point-to-point tunnel, we don't - // point the route directly at the interface because some apps don't - // understand routes without gateways (see, e.g., http://b/9597256 - // http://b/9597516). Instead, set the next hop of the route to the - // clat IPv4 address itself (for those apps, it doesn't matter what - // the IP of the gateway is, only that there is one). - RouteInfo ipv4Default = new RouteInfo(new LinkAddress(Inet4Address.ANY, 0), - clatAddress.getAddress(), iface); - mLP.addRoute(ipv4Default); - mLP.addLinkAddress(clatAddress); - mBaseLP.addStackedLink(mLP); - Slog.i(TAG, "Adding stacked link. tracker LP: " + mBaseLP); - updateConnectivityService(); - } catch(RemoteException e) { - Slog.e(TAG, "Error getting link properties: " + e); + public void interfaceLinkStateChanged(String iface, boolean up) { + // Called by the InterfaceObserver on its own thread, so can race with stop(). + if (isStarted() && up && mIface.equals(iface)) { + Slog.i(TAG, "interface " + iface + " is up, mIsRunning " + mIsRunning + "->true"); + + if (!mIsRunning) { + LinkAddress clatAddress = getLinkAddress(iface); + if (clatAddress == null) { + return; + } + mIsRunning = true; + maybeSetIpv6NdOffload(mBaseIface, false); + LinkProperties lp = new LinkProperties(mNetwork.linkProperties); + lp.addStackedLink(makeLinkProperties(clatAddress)); + Slog.i(TAG, "Adding stacked link " + mIface + " on top of " + mBaseIface); + updateConnectivityService(lp); } } } @Override public void interfaceRemoved(String iface) { - if (iface == CLAT_INTERFACE_NAME) { + if (isStarted() && mIface.equals(iface)) { + Slog.i(TAG, "interface " + iface + " removed, mIsRunning " + mIsRunning + "->false"); + if (mIsRunning) { - NetworkUtils.resetConnections( - CLAT_INTERFACE_NAME, - NetworkUtils.RESET_IPV4_ADDRESSES); - mBaseLP.removeStackedLink(mLP); - updateConnectivityService(); + // The interface going away likely means clatd has crashed. Ask netd to stop it, + // because otherwise when we try to start it again on the same base interface netd + // will complain that it's already started. + // + // Note that this method can be called by the interface observer at the same time + // that ConnectivityService calls stop(). In this case, the second call to + // stopClatd() will just throw IllegalStateException, which we'll ignore. + try { + mNMService.unregisterObserver(this); + mNMService.stopClatd(mBaseIface); + } catch (RemoteException|IllegalStateException e) { + // Well, we tried. + } + maybeSetIpv6NdOffload(mBaseIface, true); + LinkProperties lp = new LinkProperties(mNetwork.linkProperties); + lp.removeStackedLink(mIface); + clear(); + updateConnectivityService(lp); } - Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME + - " removed, mIsRunning = " + mIsRunning + " -> false"); - mIsRunning = false; - mLP.clear(); - Slog.i(TAG, "mLP = " + mLP); } } -}; +} diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 15ffc0d..12da5c3 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -63,9 +63,12 @@ public class NetworkAgentInfo { public final Messenger messenger; public final AsyncChannel asyncChannel; + // Used by ConnectivityService to keep track of 464xlat. + public Nat464Xlat clatd; + public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, - NetworkMisc misc) { + NetworkMisc misc, NetworkRequest defaultRequest) { this.messenger = messenger; asyncChannel = ac; network = null; @@ -73,7 +76,7 @@ public class NetworkAgentInfo { linkProperties = lp; networkCapabilities = nc; currentScore = score; - networkMonitor = new NetworkMonitor(context, handler, this); + networkMonitor = new NetworkMonitor(context, handler, this, defaultRequest); networkMisc = misc; created = false; validated = false; diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index fb98236..78f3705 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -27,6 +27,7 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; +import android.net.NetworkRequest; import android.net.TrafficStats; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; @@ -46,6 +47,7 @@ import android.telephony.CellInfoGsm; import android.telephony.CellInfoLte; import android.telephony.CellInfoWcdma; import android.telephony.TelephonyManager; +import android.util.Log; import com.android.internal.util.Protocol; import com.android.internal.util.State; @@ -221,6 +223,7 @@ public class NetworkMonitor extends StateMachine { private final TelephonyManager mTelephonyManager; private final WifiManager mWifiManager; private final AlarmManager mAlarmManager; + private final NetworkRequest mDefaultRequest; private String mServer; private boolean mIsCaptivePortalCheckEnabled = false; @@ -238,7 +241,8 @@ public class NetworkMonitor extends StateMachine { private State mCaptivePortalState = new CaptivePortalState(); private State mLingeringState = new LingeringState(); - public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo) { + public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, + NetworkRequest defaultRequest) { // Add suffix indicating which NetworkMonitor we're talking about. super(TAG + networkAgentInfo.name()); @@ -248,6 +252,7 @@ public class NetworkMonitor extends StateMachine { mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + mDefaultRequest = defaultRequest; addState(mDefaultState); addState(mOfflineState, mDefaultState); @@ -272,6 +277,11 @@ public class NetworkMonitor extends StateMachine { start(); } + @Override + protected void log(String s) { + Log.d(TAG + mNetworkAgentInfo.name(), s); + } + private class DefaultState extends State { @Override public boolean processMessage(Message message) { @@ -363,14 +373,25 @@ public class NetworkMonitor extends StateMachine { case CMD_REEVALUATE: if (message.arg1 != mReevaluateToken) return HANDLED; - if (mNetworkAgentInfo.isVPN()) { - transitionTo(mValidatedState); - return HANDLED; - } - // If network provides no internet connectivity adjust evaluation. - if (!mNetworkAgentInfo.networkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_INTERNET)) { - // TODO: Try to verify something works. Do all gateways respond to pings? + // Don't bother validating networks that don't satisify the default request. + // This includes: + // - VPNs which can be considered explicitly desired by the user and the + // user's desire trumps whether the network validates. + // - Networks that don't provide internet access. It's unclear how to + // validate such networks. + // - Untrusted networks. It's unsafe to prompt the user to sign-in to + // such networks and the user didn't express interest in connecting to + // such networks (an app did) so the user may be unhappily surprised when + // asked to sign-in to a network they didn't want to connect to in the + // first place. Validation could be done to adjust the network scores + // however these networks are app-requested and may not be intended for + // general usage, in which case general validation may not be an accurate + // measure of the network's quality. Only the app knows how to evaluate + // the network so don't bother validating here. Furthermore sending HTTP + // packets over the network may be undesirable, for example an extremely + // expensive metered network, or unwanted leaking of the User Agent string. + if (!mDefaultRequest.networkCapabilities.satisfiedByNetworkCapabilities( + mNetworkAgentInfo.networkCapabilities)) { transitionTo(mValidatedState); return HANDLED; } diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java index 07fe7ba..7d1da01 100644 --- a/services/core/java/com/android/server/connectivity/PacManager.java +++ b/services/core/java/com/android/server/connectivity/PacManager.java @@ -265,14 +265,9 @@ public class PacManager { } Intent intent = new Intent(); intent.setClassName(PAC_PACKAGE, PAC_SERVICE); - // Already bound no need to bind again. if ((mProxyConnection != null) && (mConnection != null)) { - if (mLastPort != -1) { - sendPacBroadcast(new ProxyInfo(mPacUrl, mLastPort)); - } else { - Log.e(TAG, "Received invalid port from Local Proxy," - + " PAC will not be operational"); - } + // Already bound no need to bind again, just download the new file. + IoThread.getHandler().post(mPacDownloader); return; } mConnection = new ServiceConnection() { diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 238402f..debda14 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -191,8 +191,8 @@ public class PermissionMonitor { } try { if (add) { - mNetd.setPermission(CHANGE_NETWORK_STATE, toIntArray(network)); - mNetd.setPermission(CONNECTIVITY_INTERNAL, toIntArray(system)); + mNetd.setPermission("NETWORK", toIntArray(network)); + mNetd.setPermission("SYSTEM", toIntArray(system)); } else { mNetd.clearPermission(toIntArray(network)); mNetd.clearPermission(toIntArray(system)); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 3f6b71a..f08a652 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -23,6 +23,7 @@ import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; +import android.Manifest; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.PendingIntent; @@ -45,6 +46,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.NetworkInfo; @@ -214,82 +216,77 @@ public class Vpn { * @return true if the operation is succeeded. */ public synchronized boolean prepare(String oldPackage, String newPackage) { - // Return false if the package does not match. - if (oldPackage != null && !oldPackage.equals(mPackage)) { - // The package doesn't match. If this VPN was not previously authorized, return false - // to force user authorization. Otherwise, revoke the VPN anyway. + if (oldPackage != null && getAppUid(oldPackage, mUserHandle) != mOwnerUID) { + // The package doesn't match. We return false (to obtain user consent) unless the user + // has already consented to that VPN package. if (!oldPackage.equals(VpnConfig.LEGACY_VPN) && isVpnUserPreConsented(oldPackage)) { - long token = Binder.clearCallingIdentity(); - try { - // This looks bizarre, but it is what ConfirmDialog in VpnDialogs is doing when - // the user clicks through to allow the VPN to consent. So we are emulating the - // action of the dialog without actually showing it. - prepare(null, oldPackage); - } finally { - Binder.restoreCallingIdentity(token); - } + prepareInternal(oldPackage); return true; } return false; } // Return true if we do not need to revoke. - if (newPackage == null || - (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) { + if (newPackage == null || (!newPackage.equals(VpnConfig.LEGACY_VPN) && + getAppUid(newPackage, mUserHandle) == mOwnerUID)) { return true; } // Check if the caller is authorized. enforceControlPermission(); - // Reset the interface. - if (mInterface != null) { - mStatusIntent = null; - agentDisconnect(); - jniReset(mInterface); - mInterface = null; - mVpnUsers = null; - } + prepareInternal(newPackage); + return true; + } + + /** Prepare the VPN for the given package. Does not perform permission checks. */ + private void prepareInternal(String newPackage) { + long token = Binder.clearCallingIdentity(); + try { + // Reset the interface. + if (mInterface != null) { + mStatusIntent = null; + agentDisconnect(); + jniReset(mInterface); + mInterface = null; + mVpnUsers = null; + } + + // Revoke the connection or stop LegacyVpnRunner. + if (mConnection != null) { + try { + mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION, + Parcel.obtain(), null, IBinder.FLAG_ONEWAY); + } catch (Exception e) { + // ignore + } + mContext.unbindService(mConnection); + mConnection = null; + } else if (mLegacyVpnRunner != null) { + mLegacyVpnRunner.exit(); + mLegacyVpnRunner = null; + } - // Revoke the connection or stop LegacyVpnRunner. - if (mConnection != null) { try { - mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION, - Parcel.obtain(), null, IBinder.FLAG_ONEWAY); + mNetd.denyProtect(mOwnerUID); } catch (Exception e) { - // ignore + Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e); } - mContext.unbindService(mConnection); - mConnection = null; - } else if (mLegacyVpnRunner != null) { - mLegacyVpnRunner.exit(); - mLegacyVpnRunner = null; - } - long token = Binder.clearCallingIdentity(); - try { - mNetd.denyProtect(mOwnerUID); - } catch (Exception e) { - Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e); - } finally { - Binder.restoreCallingIdentity(token); - } + Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); + mPackage = newPackage; + mOwnerUID = getAppUid(newPackage, mUserHandle); + try { + mNetd.allowProtect(mOwnerUID); + } catch (Exception e) { + Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e); + } + mConfig = null; - Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); - mPackage = newPackage; - mOwnerUID = getAppUid(newPackage, mUserHandle); - token = Binder.clearCallingIdentity(); - try { - mNetd.allowProtect(mOwnerUID); - } catch (Exception e) { - Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e); + updateState(DetailedState.IDLE, "prepare"); } finally { Binder.restoreCallingIdentity(token); } - mConfig = null; - - updateState(DetailedState.IDLE, "prepare"); - return true; } /** @@ -579,7 +576,13 @@ public class Vpn { } private boolean isRunningLocked() { - return mVpnUsers != null; + return mNetworkAgent != null && mInterface != null; + } + + // Returns true if the VPN has been established and the calling UID is its owner. Used to check + // that a call to mutate VPN state is admissible. + private boolean isCallerEstablishedOwnerLocked() { + return isRunningLocked() && Binder.getCallingUid() == mOwnerUID; } // Note: Return type guarantees results are deduped and sorted, which callers require. @@ -594,7 +597,7 @@ public class Vpn { // Note: This function adds to mVpnUsers but does not publish list to NetworkAgent. private void addVpnUserLocked(int userHandle) { - if (!isRunningLocked()) { + if (mVpnUsers == null) { throw new IllegalStateException("VPN is not active"); } @@ -646,7 +649,7 @@ public class Vpn { } private void removeVpnUserLocked(int userHandle) { - if (!isRunningLocked()) { + if (mVpnUsers == null) { throw new IllegalStateException("VPN is not active"); } final List<UidRange> ranges = uidRangesForUser(userHandle); @@ -739,31 +742,7 @@ public class Vpn { }; private void enforceControlPermission() { - // System user is allowed to control VPN. - if (Binder.getCallingUid() == Process.SYSTEM_UID) { - return; - } - int appId = UserHandle.getAppId(Binder.getCallingUid()); - final long token = Binder.clearCallingIdentity(); - try { - // System VPN dialogs are also allowed to control VPN. - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0); - if (((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) && (appId == app.uid)) { - return; - } - // SystemUI dialogs are also allowed to control VPN. - ApplicationInfo sysUiApp = pm.getApplicationInfo("com.android.systemui", 0); - if (((sysUiApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0) && (appId == sysUiApp.uid)) { - return; - } - } catch (Exception e) { - // ignore - } finally { - Binder.restoreCallingIdentity(token); - } - - throw new SecurityException("Unauthorized Caller"); + mContext.enforceCallingPermission(Manifest.permission.CONTROL_VPN, "Unauthorized Caller"); } private class Connection implements ServiceConnection { @@ -790,27 +769,61 @@ public class Vpn { } public synchronized boolean addAddress(String address, int prefixLength) { - if (Binder.getCallingUid() != mOwnerUID || mInterface == null || mNetworkAgent == null) { + if (!isCallerEstablishedOwnerLocked()) { return false; } boolean success = jniAddAddress(mInterface, address, prefixLength); - if (mNetworkAgent != null) { - mNetworkAgent.sendLinkProperties(makeLinkProperties()); - } + mNetworkAgent.sendLinkProperties(makeLinkProperties()); return success; } public synchronized boolean removeAddress(String address, int prefixLength) { - if (Binder.getCallingUid() != mOwnerUID || mInterface == null || mNetworkAgent == null) { + if (!isCallerEstablishedOwnerLocked()) { return false; } boolean success = jniDelAddress(mInterface, address, prefixLength); - if (mNetworkAgent != null) { - mNetworkAgent.sendLinkProperties(makeLinkProperties()); - } + mNetworkAgent.sendLinkProperties(makeLinkProperties()); return success; } + public synchronized boolean setUnderlyingNetworks(Network[] networks) { + if (!isCallerEstablishedOwnerLocked()) { + return false; + } + if (networks == null) { + mConfig.underlyingNetworks = null; + } else { + mConfig.underlyingNetworks = new Network[networks.length]; + for (int i = 0; i < networks.length; ++i) { + if (networks[i] == null) { + mConfig.underlyingNetworks[i] = null; + } else { + mConfig.underlyingNetworks[i] = new Network(networks[i].netId); + } + } + } + return true; + } + + public synchronized Network[] getUnderlyingNetworks() { + if (!isRunningLocked()) { + return null; + } + return mConfig.underlyingNetworks; + } + + public synchronized boolean appliesToUid(int uid) { + if (!isRunningLocked()) { + return false; + } + for (UidRange uidRange : mVpnUsers) { + if (uidRange.start <= uid && uid <= uidRange.stop) { + return true; + } + } + return false; + } + private native int jniCreate(int mtu); private native String jniGetName(int tun); private native int jniSetAddresses(String interfaze, String addresses); diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 45d3771..d919bf6 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -21,7 +21,6 @@ import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; -import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; @@ -120,6 +119,7 @@ class AutomaticBrightnessController { // The minimum and maximum screen brightnesses. private final int mScreenBrightnessRangeMinimum; private final int mScreenBrightnessRangeMaximum; + private final float mDozeScaleFactor; // Amount of time to delay auto-brightness after screen on while waiting for // the light sensor to warm-up in milliseconds. @@ -171,9 +171,12 @@ class AutomaticBrightnessController { // The last screen auto-brightness gamma. (For printing in dump() only.) private float mLastScreenAutoBrightnessGamma = 1.0f; + // Are we going to adjust brightness while dozing. + private boolean mDozing; + public AutomaticBrightnessController(Callbacks callbacks, Looper looper, - SensorManager sensorManager, Spline autoBrightnessSpline, - int lightSensorWarmUpTime, int brightnessMin, int brightnessMax) { + SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime, + int brightnessMin, int brightnessMax, float dozeScaleFactor) { mCallbacks = callbacks; mTwilight = LocalServices.getService(TwilightManager.class); mSensorManager = sensorManager; @@ -181,6 +184,7 @@ class AutomaticBrightnessController { mScreenBrightnessRangeMinimum = brightnessMin; mScreenBrightnessRangeMaximum = brightnessMax; mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime; + mDozeScaleFactor = dozeScaleFactor; mHandler = new AutomaticBrightnessHandler(looper); mAmbientLightRingBuffer = new AmbientLightRingBuffer(); @@ -195,11 +199,20 @@ class AutomaticBrightnessController { } public int getAutomaticScreenBrightness() { + if (mDozing) { + return (int) (mScreenAutoBrightness * mDozeScaleFactor); + } return mScreenAutoBrightness; } - public void configure(boolean enable, float adjustment) { - boolean changed = setLightSensorEnabled(enable); + public void configure(boolean enable, float adjustment, boolean dozing) { + // While dozing, the application processor may be suspended which will prevent us from + // receiving new information from the light sensor. On some devices, we may be able to + // switch to a wake-up light sensor instead but for now we will simply disable the sensor + // and hold onto the last computed screen auto brightness. We save the dozing flag for + // debugging purposes. + mDozing = dozing; + boolean changed = setLightSensorEnabled(enable && !dozing); changed |= setScreenAutoBrightnessAdjustment(adjustment); if (changed) { updateAutoBrightness(false /*sendUpdate*/); @@ -230,6 +243,7 @@ class AutomaticBrightnessController { pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); pw.println(" mScreenAutoBrightnessAdjustment=" + mScreenAutoBrightnessAdjustment); pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma); + pw.println(" mDozing=" + mDozing); } private boolean setLightSensorEnabled(boolean enable) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 2d5b99e..78610ff 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -155,6 +155,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // True if auto-brightness should be used. private boolean mUseSoftwareAutoBrightnessConfig; + // True if should use light sensor to automatically determine doze screen brightness. + private final boolean mAllowAutoBrightnessWhileDozingConfig; + // True if we should fade the screen while turning it off, false if we should play // a stylish color fade animation instead. private boolean mColorFadeFadesConfig; @@ -295,6 +298,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mUseSoftwareAutoBrightnessConfig = resources.getBoolean( com.android.internal.R.bool.config_automatic_brightness_available); + + mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean( + com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing); + if (mUseSoftwareAutoBrightnessConfig) { int[] lux = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels); @@ -302,6 +309,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); int lightSensorWarmUpTimeConfig = resources.getInteger( com.android.internal.R.integer.config_lightSensorWarmupTime); + final float dozeScaleFactor = resources.getFraction( + com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor, + 1, 1); Spline screenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); if (screenAutoBrightnessSpline == null) { @@ -326,7 +336,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController = new AutomaticBrightnessController(this, handler.getLooper(), sensorManager, screenAutoBrightnessSpline, lightSensorWarmUpTimeConfig, screenBrightnessRangeMinimum, - mScreenBrightnessRangeMaximum); + mScreenBrightnessRangeMaximum, dozeScaleFactor); } } @@ -523,7 +533,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } else { state = Display.STATE_DOZE; } - brightness = mPowerRequest.dozeScreenBrightness; + if (!mAllowAutoBrightnessWhileDozingConfig) { + brightness = mPowerRequest.dozeScreenBrightness; + } break; case DisplayPowerRequest.POLICY_DIM: case DisplayPowerRequest.POLICY_BRIGHT: @@ -574,19 +586,26 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call brightness = PowerManager.BRIGHTNESS_OFF; } - // Use default brightness when dozing unless overridden. - if (brightness < 0 && (state == Display.STATE_DOZE - || state == Display.STATE_DOZE_SUSPEND)) { - brightness = mScreenBrightnessDozeConfig; - } - // Configure auto-brightness. boolean autoBrightnessEnabled = false; if (mAutomaticBrightnessController != null) { + final boolean autoBrightnessEnabledInDoze = mAllowAutoBrightnessWhileDozingConfig + && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND); autoBrightnessEnabled = mPowerRequest.useAutoBrightness - && state == Display.STATE_ON && brightness < 0; + && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) + && brightness < 0; mAutomaticBrightnessController.configure(autoBrightnessEnabled, - mPowerRequest.screenAutoBrightnessAdjustment); + mPowerRequest.screenAutoBrightnessAdjustment, state != Display.STATE_ON); + } + + // Apply brightness boost. + // We do this here after configuring auto-brightness so that we don't + // disable the light sensor during this temporary state. That way when + // boost ends we will be able to resume normal auto-brightness behavior + // without any delay. + if (mPowerRequest.boostScreenBrightness + && brightness != PowerManager.BRIGHTNESS_OFF) { + brightness = PowerManager.BRIGHTNESS_ON; } // Apply auto-brightness. @@ -609,6 +628,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAppliedAutoBrightness = false; } + // Use default brightness when dozing unless overridden. + if (brightness < 0 && (state == Display.STATE_DOZE + || state == Display.STATE_DOZE_SUSPEND)) { + brightness = mScreenBrightnessDozeConfig; + } + // Apply manual brightness. // Use the current brightness setting from the request, which is expected // provide a nominal default value for the case where auto-brightness @@ -644,11 +669,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Animate the screen brightness when the screen is on or dozing. // Skip the animation when the screen is off or suspended. - if (state == Display.STATE_ON || state == Display.STATE_DOZE) { - animateScreenBrightness(brightness, - slowChange ? BRIGHTNESS_RAMP_RATE_SLOW : BRIGHTNESS_RAMP_RATE_FAST); - } else { - animateScreenBrightness(brightness, 0); + if (!mPendingScreenOff) { + if (state == Display.STATE_ON || state == Display.STATE_DOZE) { + animateScreenBrightness(brightness, + slowChange ? BRIGHTNESS_RAMP_RATE_SLOW : BRIGHTNESS_RAMP_RATE_FAST); + } else { + animateScreenBrightness(brightness, 0); + } } // Determine whether the display is ready for use in the newly requested state. @@ -1021,6 +1048,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig); + pw.println(" mAllowAutoBrightnessWhileDozingConfig=" + + mAllowAutoBrightnessWhileDozingConfig); pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig); mHandler.runWithScissors(new Runnable() { diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 3b23b6a..f514531 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -248,6 +248,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mInfo.width = mWidth; mInfo.height = mHeight; mInfo.refreshRate = mRefreshRate; + mInfo.supportedRefreshRates = new float[] { mRefreshRate }; mInfo.densityDpi = mDensityDpi; mInfo.xDpi = mDensityDpi; mInfo.yDpi = mDensityDpi; diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index c2b4478..28d5fc0 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -23,6 +23,7 @@ import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; import android.os.Handler; import android.os.IBinder; +import android.os.SystemProperties; import android.os.IBinder.DeathRecipient; import android.os.Message; import android.os.RemoteException; @@ -257,6 +258,7 @@ final class VirtualDisplayAdapter extends DisplayAdapter { mInfo.width = mWidth; mInfo.height = mHeight; mInfo.refreshRate = 60; + mInfo.supportedRefreshRates = new float[] { 60.0f }; mInfo.densityDpi = mDensityDpi; mInfo.xDpi = mDensityDpi; mInfo.yDpi = mDensityDpi; @@ -277,6 +279,15 @@ final class VirtualDisplayAdapter extends DisplayAdapter { } if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) { mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION; + + if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) { + // For demonstration purposes, allow rotation of the external display. + // In the future we might allow the user to configure this directly. + if ("portrait".equals(SystemProperties.get( + "persist.demo.remoterotation"))) { + mInfo.rotation = Surface.ROTATION_270; + } + } } mInfo.type = Display.TYPE_VIRTUAL; mInfo.touch = DisplayDeviceInfo.TOUCH_NONE; diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index a17d731..6b010d9 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -625,6 +625,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mInfo.width = mWidth; mInfo.height = mHeight; mInfo.refreshRate = mRefreshRate; + mInfo.supportedRefreshRates = new float[] { mRefreshRate }; mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame mInfo.flags = mFlags; mInfo.type = Display.TYPE_WIFI; diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java index cb92112..59d5605 100644 --- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java +++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java @@ -57,8 +57,9 @@ final class ActiveSourceHandler { * Handles the incoming active source command. * * @param newActive new active source information + * @param deviceType device type of the new active source */ - void process(ActiveSource newActive) { + void process(ActiveSource newActive, int deviceType) { // Seq #17 HdmiCecLocalDeviceTv tv = mSource; ActiveSource activeSource = tv.getActiveSource(); @@ -68,7 +69,7 @@ final class ActiveSourceHandler { } HdmiDeviceInfo device = mService.getDeviceInfo(newActive.logicalAddress); if (device == null) { - tv.startNewDeviceAction(newActive); + tv.startNewDeviceAction(newActive, deviceType); } if (!tv.isProhibitMode()) { @@ -88,11 +89,8 @@ final class ActiveSourceHandler { tv.updateActiveSource(current); invokeCallback(HdmiControlManager.RESULT_SUCCESS); } else { - HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange( - getSourceAddress(), newActive.physicalAddress, current.physicalAddress); - mService.sendCecCommand(routingChange); - tv.addAndStartAction( - new RoutingControlAction(tv, current.physicalAddress, true, mCallback)); + tv.startRoutingControl(newActive.physicalAddress, current.physicalAddress, true, + mCallback); } } } diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index b0a3a66..bb12eae 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -205,13 +205,6 @@ final class Constants { static final int UNKNOWN_VOLUME = -1; - // IRT(Initiator Repetition Time) in millisecond as recommended in the standard. - // Outgoing UCP commands, when in 'Press and Hold' mode, should be this much apart - // from the adjacent one so as not to place unnecessarily heavy load on the CEC line. - // TODO: This value might need tweaking per product basis. Consider putting it - // in config.xml to allow customization. - static final int IRT_MS = 300; - static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback"; static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv"; diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java new file mode 100644 index 0000000..68311de --- /dev/null +++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java @@ -0,0 +1,133 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiDeviceInfo; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Buffer storage to keep incoming messages for later processing. Used to + * handle messages that arrive when the device is not ready. Useful when + * keeping the messages from a connected device which are not discovered yet. + */ +final class DelayedMessageBuffer { + private final ArrayList<HdmiCecMessage> mBuffer = new ArrayList<>(); + private final HdmiCecLocalDevice mDevice; + + DelayedMessageBuffer(HdmiCecLocalDevice device) { + mDevice = device; + } + + /** + * Add a new message to the buffer. The buffer keeps selected messages in + * the order they are received. + * + * @param message {@link HdmiCecMessage} to add + */ + void add(HdmiCecMessage message) { + boolean buffered = true; + + // Note that all the messages are not handled in the same manner. + // For <Active Source> we keep the latest one only. + // TODO: This might not be the best way to choose the active source. + // Devise a better way to pick up the best one. + switch (message.getOpcode()) { + case Constants.MESSAGE_ACTIVE_SOURCE: + removeActiveSource(); + mBuffer.add(message); + break; + case Constants.MESSAGE_INITIATE_ARC: + mBuffer.add(message); + break; + default: + buffered = false; + break; + } + if (buffered) { + HdmiLogger.debug("Buffering message:" + message); + } + } + + private void removeActiveSource() { + // Uses iterator to remove elements while looping through the list. + for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) { + HdmiCecMessage message = iter.next(); + if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { + iter.remove(); + } + } + } + + void processAllMessages() { + for (HdmiCecMessage message : mBuffer) { + mDevice.onMessage(message); + HdmiLogger.debug("Processing message:" + message); + } + mBuffer.clear(); + } + + /** + * Process messages from a given logical device. Called by + * {@link NewDeviceAction} actions when they finish adding the device + * information. + * <p><Active Source> is not processed in this method but processed + * separately via {@link #processActiveSource()}. + * + * @param address logical address of CEC device which the messages to process + * are associated with + */ + void processMessagesForDevice(int address) { + HdmiLogger.debug("Processing message for address:" + address); + for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) { + HdmiCecMessage message = iter.next(); + if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { + continue; + } + if (message.getSource() == address) { + mDevice.onMessage(message); + HdmiLogger.debug("Processing message:" + message); + iter.remove(); + } + } + } + + /** + * Process <Active Source>. + * + * <p>The message has a dependency on TV input framework. Should be invoked + * after we get the callback + * {@link android.media.tv.TvInputManager.TvInputCallback#onInputAdded(String)} + * to ensure the processing of the message takes effect when transformed + * to input change callback. + * + * @param address logical address of the device to be the active source + */ + void processActiveSource(int address) { + for (Iterator<HdmiCecMessage> iter = mBuffer.iterator(); iter.hasNext(); ) { + HdmiCecMessage message = iter.next(); + if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE + && message.getSource() == address) { + mDevice.onMessage(message); + HdmiLogger.debug("Processing message:" + message); + iter.remove(); + } + } + } +} diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index 2ec9778..da404c4 100644 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -326,6 +326,8 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { Slog.v(TAG, "--------------------------------------------"); mCallback.onDeviceDiscoveryDone(result); finish(); + // Process any commands buffered while device discovery action was in progress. + tv().processAllDelayedMessages(); } private void checkAndProceedStage() { diff --git a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java index d965caa..3dd1522 100644 --- a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java +++ b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java @@ -70,7 +70,8 @@ final class DevicePowerStatusAction extends HdmiCecFeatureAction { @Override boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS) { + if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS + || mTargetAddress != cmd.getSource()) { return false; } if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) { diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java index d155e84..5a1d896 100644 --- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java @@ -95,7 +95,7 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { sendCommand(mGivePowerStatus, new SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error == Constants.SEND_RESULT_NAK) { + if (error != Constants.SEND_RESULT_SUCCESS) { invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); finish(); return; @@ -168,6 +168,10 @@ final class DeviceSelectAction extends HdmiCecFeatureAction { } private void sendSetStreamPath() { + // Turn the active source invalidated, which remains so till <Active Source> comes from + // the selected device. + tv().getActiveSource().invalidate(); + tv().setActivePath(mTarget.getPhysicalAddress()); sendCommand(HdmiCecMessageBuilder.buildSetStreamPath( getSourceAddress(), mTarget.getPhysicalAddress())); invokeCallback(HdmiControlManager.RESULT_SUCCESS); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java index fc53c50..d26be57 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java @@ -85,8 +85,7 @@ abstract class HdmiCecFeatureAction { * Process the command. Called whenever a new command arrives. * * @param cmd command to process - * @return true if the command was consumed in the process; Otherwise false, which - * indicates that the command shall be handled by other actions. + * @return true if the command was consumed in the process; Otherwise false. */ abstract boolean processCommand(HdmiCecMessage cmd); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java index 61c9424..2eca42b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java @@ -18,6 +18,10 @@ package com.android.server.hdmi; import android.view.KeyEvent; +import libcore.util.EmptyArray; + +import java.util.Arrays; + /** * Helper class to translate android keycode to hdmi cec keycode and vice versa. */ @@ -156,46 +160,60 @@ final class HdmiCecKeycode { /** * A mapping between Android and CEC keycode. + * <p> + * Normal implementation of this looks like * - * <p>Normal implementation of this looks like * <pre> - * new KeycodeEntry(KeyEvent.KEYCODE_DPAD_CENTER, CEC_KEYCODE_SELECT); + * new KeycodeEntry(KeyEvent.KEYCODE_DPAD_CENTER, CEC_KEYCODE_SELECT); * </pre> - * <p>However, some keys in CEC requires additional parameter. - * In order to use parameterized cec key, add unique android keycode (existing or custom) - * corresponding to a pair of cec keycode and and its param. + * <p> + * However, some keys in CEC requires additional parameter. In order to use parameterized cec + * key, add unique android keycode (existing or custom) corresponding to a pair of cec keycode + * and and its param. + * * <pre> - * new KeycodeEntry(CUSTOME_ANDORID_KEY_1, CEC_KEYCODE_SELECT_BROADCAST_TYPE, - * UI_BROADCAST_TOGGLE_ALL); - * new KeycodeEntry(CUSTOME_ANDORID_KEY_2, CEC_KEYCODE_SELECT_BROADCAST_TYPE, - * UI_BROADCAST_ANALOGUE); + * new KeycodeEntry(CUSTOME_ANDORID_KEY_1, CEC_KEYCODE_SELECT_BROADCAST_TYPE, + * UI_BROADCAST_TOGGLE_ALL); + * new KeycodeEntry(CUSTOME_ANDORID_KEY_2, CEC_KEYCODE_SELECT_BROADCAST_TYPE, + * UI_BROADCAST_ANALOGUE); * </pre> */ private static class KeycodeEntry { private final int mAndroidKeycode; - private final int mCecKeycode; private final boolean mIsRepeatable; + private final byte[] mCecKeycodeAndParams; - private KeycodeEntry(int androidKeycode, int cecKeycode, boolean isRepeatable) { + private KeycodeEntry(int androidKeycode, int cecKeycode, boolean isRepeatable, + byte[] cecParams) { mAndroidKeycode = androidKeycode; - mCecKeycode = cecKeycode; mIsRepeatable = isRepeatable; + mCecKeycodeAndParams = new byte[cecParams.length + 1]; + System.arraycopy(cecParams, 0, mCecKeycodeAndParams, 1, cecParams.length); + mCecKeycodeAndParams[0] = (byte) (cecKeycode & 0xFF); + } + + private KeycodeEntry(int androidKeycode, int cecKeycode, boolean isRepeatable) { + this(androidKeycode, cecKeycode, isRepeatable, EmptyArray.BYTE); + } + + private KeycodeEntry(int androidKeycode, int cecKeycode, byte[] cecParams) { + this(androidKeycode, cecKeycode, true, cecParams); } private KeycodeEntry(int androidKeycode, int cecKeycode) { - this(androidKeycode, cecKeycode, true); + this(androidKeycode, cecKeycode, true, EmptyArray.BYTE); } - private int toCecKeycodeIfMatched(int androidKeycode) { + private byte[] toCecKeycodeAndParamIfMatched(int androidKeycode) { if (mAndroidKeycode == androidKeycode) { - return mCecKeycode; + return mCecKeycodeAndParams; } else { - return UNSUPPORTED_KEYCODE; + return null; } } - private int toAndroidKeycodeIfMatched(int cecKeycode) { - if (cecKeycode == mCecKeycode) { + private int toAndroidKeycodeIfMatched(byte[] cecKeycodeAndParams) { + if (Arrays.equals(mCecKeycodeAndParams, cecKeycodeAndParams)) { return mAndroidKeycode; } else { return UNSUPPORTED_KEYCODE; @@ -211,6 +229,11 @@ final class HdmiCecKeycode { } } + private static byte[] intToSingleByteArray(int value) { + return new byte[] { + (byte) (value & 0xFF) }; + } + // Keycode entry container for all mappings. // Note that order of entry is the same as above cec keycode definition. private static final KeycodeEntry[] KEYCODE_ENTRIES = new KeycodeEntry[] { @@ -227,19 +250,25 @@ final class HdmiCecKeycode { new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_UP), // No Android keycode defined for CEC_KEYCODE_LEFT_DOWN new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_DOWN), - new KeycodeEntry(KeyEvent.KEYCODE_HOME, CEC_KEYCODE_ROOT_MENU, false), - new KeycodeEntry(KeyEvent.KEYCODE_SETTINGS, CEC_KEYCODE_SETUP_MENU, false), - new KeycodeEntry(KeyEvent.KEYCODE_MENU, CEC_KEYCODE_CONTENTS_MENU, false), + new KeycodeEntry(KeyEvent.KEYCODE_HOME, CEC_KEYCODE_ROOT_MENU), + new KeycodeEntry(KeyEvent.KEYCODE_SETTINGS, CEC_KEYCODE_SETUP_MENU), + new KeycodeEntry(KeyEvent.KEYCODE_TV_CONTENTS_MENU, CEC_KEYCODE_CONTENTS_MENU, false), // No Android keycode defined for CEC_KEYCODE_FAVORITE_MENU new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_FAVORITE_MENU), + // Note that both BACK and ESCAPE are mapped to EXIT of CEC keycode. + // This would be problematic when translates CEC keycode to Android keycode. + // In current implementation, we pick BACK as mapping of EXIT key. + // If you'd like to map CEC EXIT to Android EXIT key, change order of + // the following two definition. new KeycodeEntry(KeyEvent.KEYCODE_BACK, CEC_KEYCODE_EXIT), + new KeycodeEntry(KeyEvent.KEYCODE_ESCAPE, CEC_KEYCODE_EXIT), // RESERVED new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_TOP_MENU, CEC_KEYCODE_MEDIA_TOP_MENU), - // No Android keycode defined for CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU), + new KeycodeEntry(KeyEvent.KEYCODE_TV_MEDIA_CONTEXT_MENU, + CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU), // RESERVED // No Android keycode defined for CEC_KEYCODE_NUMBER_ENTRY_MODE - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_NUMBER_ENTRY_MODE), + new KeycodeEntry(KeyEvent.KEYCODE_TV_NUMBER_ENTRY, CEC_KEYCODE_NUMBER_ENTRY_MODE), new KeycodeEntry(KeyEvent.KEYCODE_11, CEC_KEYCODE_NUMBER_11), new KeycodeEntry(KeyEvent.KEYCODE_12, CEC_KEYCODE_NUMBER_12), new KeycodeEntry(KeyEvent.KEYCODE_0, CEC_KEYCODE_NUMBER_0_OR_NUMBER_10), @@ -276,7 +305,12 @@ final class HdmiCecKeycode { new KeycodeEntry(KeyEvent.KEYCODE_VOLUME_MUTE, CEC_KEYCODE_MUTE, false), new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PLAY, CEC_KEYCODE_PLAY), new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_STOP, CEC_KEYCODE_STOP), + // Note that we map both MEDIA_PAUSE and MEDIA_PLAY_PAUSE to CEC PAUSE key. + // When it translates CEC PAUSE key, it picks Android MEDIA_PAUSE key as a mapping of + // it. If you'd like to choose MEDIA_PLAY_PAUSE, please change order of the following + // two lines. new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PAUSE, CEC_KEYCODE_PAUSE), + new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, CEC_KEYCODE_PAUSE), new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_RECORD, CEC_KEYCODE_RECORD), new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_REWIND, CEC_KEYCODE_REWIND), new KeycodeEntry(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, CEC_KEYCODE_FAST_FORWARD), @@ -291,48 +325,61 @@ final class HdmiCecKeycode { new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RESERVED), // No Android keycode defined for CEC_KEYCODE_ANGLE new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_ANGLE), - // No Android keycode defined for CEC_KEYCODE_SUB_PICTURE - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SUB_PICTURE), + new KeycodeEntry(KeyEvent.KEYCODE_CAPTIONS, CEC_KEYCODE_SUB_PICTURE), // No Android keycode defined for CEC_KEYCODE_VIDEO_ON_DEMAND new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_VIDEO_ON_DEMAND), new KeycodeEntry(KeyEvent.KEYCODE_GUIDE, CEC_KEYCODE_ELECTRONIC_PROGRAM_GUIDE), - // No Android keycode defined for CEC_KEYCODE_TIMER_PROGRAMMING - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_TIMER_PROGRAMMING), + new KeycodeEntry(KeyEvent.KEYCODE_TV_TIMER_PROGRAMMING, CEC_KEYCODE_TIMER_PROGRAMMING), // No Android keycode defined for CEC_KEYCODE_INITIAL_CONFIGURATION new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_INITIAL_CONFIGURATION), // No Android keycode defined for CEC_KEYCODE_SELECT_BROADCAST_TYPE new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_BROADCAST_TYPE), + new KeycodeEntry(KeyEvent.KEYCODE_TV_TERRESTRIAL_ANALOG, + CEC_KEYCODE_SELECT_BROADCAST_TYPE, true, + intToSingleByteArray(UI_BROADCAST_ANALOGUE)), + new KeycodeEntry(KeyEvent.KEYCODE_TV_TERRESTRIAL_DIGITAL, + CEC_KEYCODE_SELECT_BROADCAST_TYPE, true, + intToSingleByteArray(UI_BROADCAST_DIGITAL_TERRESTRIAL)), + new KeycodeEntry(KeyEvent.KEYCODE_TV_SATELLITE_BS, + CEC_KEYCODE_SELECT_BROADCAST_TYPE, true, + intToSingleByteArray(UI_BROADCAST_DIGITAL_SATELLITE)), + new KeycodeEntry(KeyEvent.KEYCODE_TV_SATELLITE_CS, + CEC_KEYCODE_SELECT_BROADCAST_TYPE, true, + intToSingleByteArray(UI_BROADCAST_DIGITAL_COMMNICATIONS_SATELLITE)), + new KeycodeEntry(KeyEvent.KEYCODE_TV_NETWORK, + CEC_KEYCODE_SELECT_BROADCAST_TYPE, true, + intToSingleByteArray(UI_BROADCAST_TOGGLE_ANALOGUE_DIGITAL)), // No Android keycode defined for CEC_KEYCODE_SELECT_SOUND_PRESENTATION new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_SOUND_PRESENTATION), // RESERVED // The following deterministic key definitions do not need key mapping // since they are supposed to be generated programmatically only. // No Android keycode defined for CEC_KEYCODE_PLAY_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PLAY_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PLAY_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_PAUSE_PLAY_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PAUSE_PLAY_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PAUSE_PLAY_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_RECORD_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RECORD_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RECORD_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_PAUSE_RECORD_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PAUSE_RECORD_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_PAUSE_RECORD_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_STOP_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_STOP_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_STOP_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_MUTE_FUNCTION new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_MUTE_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_RESTORE_VOLUME_FUNCTION new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_RESTORE_VOLUME_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_TUNE_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_TUNE_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_TUNE_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_SELECT_MEDIA_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_MEDIA_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_MEDIA_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_SELECT_AV_INPUT_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_AV_INPUT_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_AV_INPUT_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_SELECT_AUDIO_INPUT_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_AUDIO_INPUT_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_SELECT_AUDIO_INPUT_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_POWER_TOGGLE_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_POWER_TOGGLE_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_POWER_TOGGLE_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_POWER_OFF_FUNCTION - new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_POWER_OFF_FUNCTION), + new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_POWER_OFF_FUNCTION, false), // No Android keycode defined for CEC_KEYCODE_POWER_ON_FUNCTION new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_POWER_ON_FUNCTION, false), // RESERVED @@ -347,31 +394,31 @@ final class HdmiCecKeycode { }; /** - * Translate Android keycode to Hdmi Cec keycode. + * Translate Android keycode to Hdmi Cec keycode and params. * * @param keycode Android keycode. For details, refer {@link KeyEvent} - * @return single byte CEC keycode if matched. + * @return byte array of CEC keycode and params if matched. Otherwise, return null. */ - static int androidKeyToCecKey(int keycode) { + static byte[] androidKeyToCecKey(int keycode) { for (int i = 0; i < KEYCODE_ENTRIES.length; ++i) { - int cecKeycode = KEYCODE_ENTRIES[i].toCecKeycodeIfMatched(keycode); - if (cecKeycode != UNSUPPORTED_KEYCODE) { - return cecKeycode; + byte[] cecKeycodeAndParams = KEYCODE_ENTRIES[i].toCecKeycodeAndParamIfMatched(keycode); + if (cecKeycodeAndParams != null) { + return cecKeycodeAndParams; } } - return UNSUPPORTED_KEYCODE; + return null; } /** - * Translate Hdmi CEC keycode to Android keycode. + * Translate Hdmi CEC keycode with params to Android keycode. * - * @param keycode CEC keycode - * @return cec keycode corresponding to the given android keycode. - * If finds no matched keycode, return {@link #UNSUPPORTED_KEYCODE} + * @param cecKeycodeAndParams CEC keycode and params + * @return cec keycode corresponding to the given android keycode. If finds no matched keycode, + * return {@link #UNSUPPORTED_KEYCODE} */ - static int cecKeyToAndroidKey(int keycode) { + static int cecKeycodeAndParamsToAndroidKey(byte[] cecKeycodeAndParams) { for (int i = 0; i < KEYCODE_ENTRIES.length; ++i) { - int androidKey = KEYCODE_ENTRIES[i].toAndroidKeycodeIfMatched(keycode); + int androidKey = KEYCODE_ENTRIES[i].toAndroidKeycodeIfMatched(cecKeycodeAndParams); if (androidKey != UNSUPPORTED_KEYCODE) { return androidKey; } @@ -399,7 +446,6 @@ final class HdmiCecKeycode { * Returns {@code true} if given Android keycode is supported, otherwise {@code false}. */ static boolean isSupportedKeycode(int androidKeycode) { - return HdmiCecKeycode.androidKeyToCecKey(androidKeycode) - != HdmiCecKeycode.UNSUPPORTED_KEYCODE; - } + return HdmiCecKeycode.androidKeyToCecKey(androidKeycode) != null; + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 8f9af61..4f8b9fb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -34,7 +34,6 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; /** @@ -125,7 +124,7 @@ abstract class HdmiCecLocalDevice { // A collection of FeatureAction. // Note that access to this collection should happen in service thread. - private final LinkedList<HdmiCecFeatureAction> mActions = new LinkedList<>(); + private final ArrayList<HdmiCecFeatureAction> mActions = new ArrayList<>(); private final Handler mHandler = new Handler () { @Override @@ -290,12 +289,14 @@ abstract class HdmiCecLocalDevice { @ServiceThreadOnly private boolean dispatchMessageToAction(HdmiCecMessage message) { assertRunOnServiceThread(); - for (HdmiCecFeatureAction action : mActions) { - if (action.processCommand(message)) { - return true; - } + boolean processed = false; + // Use copied action list in that processCommand may remove itself. + for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) { + // Iterates all actions to check whether incoming message is consumed. + boolean result = action.processCommand(message); + processed = processed || result; } - return false; + return processed; } @ServiceThreadOnly @@ -425,9 +426,7 @@ abstract class HdmiCecLocalDevice { final long downTime = SystemClock.uptimeMillis(); final byte[] params = message.getParams(); - // Note that we don't support parameterized keycode now. - // TODO: translate parameterized keycode as well. - final int keycode = HdmiCecKeycode.cecKeyToAndroidKey(params[0]); + final int keycode = HdmiCecKeycode.cecKeycodeAndParamsToAndroidKey(params); int keyRepeatCount = 0; if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { if (keycode == mLastKeycode) { @@ -517,8 +516,8 @@ abstract class HdmiCecLocalDevice { } protected boolean handleVendorCommand(HdmiCecMessage message) { - if (!mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), - message.getParams(), false)) { + if (!mService.invokeVendorCommandListenersOnReceived(mDeviceType, message.getSource(), + message.getDestination(), message.getParams(), false)) { // Vendor command listener may not have been registered yet. Respond with // <Feature Abort> [NOT_IN_CORRECT_MODE] so that the sender can try again later. mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); @@ -530,8 +529,8 @@ abstract class HdmiCecLocalDevice { byte[] params = message.getParams(); int vendorId = HdmiUtils.threeBytesToInt(params); if (vendorId == mService.getVendorId()) { - if (!mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), params, - true)) { + if (!mService.invokeVendorCommandListenersOnReceived(mDeviceType, message.getSource(), + message.getDestination(), params, true)) { mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); } } else if (message.getDestination() != Constants.ADDR_BROADCAST && diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 780d54b..85a1a15 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -50,6 +50,8 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { assertRunOnServiceThread(); mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, mService.getPhysicalAddress(), mDeviceType)); + mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( + mAddress, mService.getVendorId())); startQueuedActions(); } @@ -156,7 +158,7 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { assertRunOnServiceThread(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); maySetActiveSource(physicalAddress); - maySendActiveSource(); + maySendActiveSource(message.getSource()); wakeUpIfActiveSource(); return true; // Broadcast message. } @@ -196,10 +198,13 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { } } - private void maySendActiveSource() { + private void maySendActiveSource(int dest) { if (mIsActiveSource) { mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( mAddress, mService.getPhysicalAddress())); + // Always reports menu-status active to receive RCP. + mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus( + mAddress, dest, Constants.MENU_STATE_ACTIVATED)); } } @@ -207,7 +212,7 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleRequestActiveSource(HdmiCecMessage message) { assertRunOnServiceThread(); - maySendActiveSource(); + maySendActiveSource(message.getSource()); return true; // Broadcast message. } @@ -230,4 +235,4 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice { super.dump(pw); pw.println("mIsActiveSource: " + mIsActiveSource); } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 0fb4b48..3f78949 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -30,6 +30,8 @@ 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.HdmiRecordSources; @@ -37,6 +39,9 @@ import android.hardware.hdmi.HdmiTimerRecordSources; 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; @@ -49,6 +54,7 @@ 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; @@ -110,6 +116,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // If true, TV wakes itself up when receiving <Text/Image View On>. private boolean mAutoWakeup; + // List of the logical address of local CEC devices. Unmodifiable, thread-safe. + private List<Integer> mLocalDeviceAddresses; + private final HdmiCecStandbyModeHandler mStandbyHandler; // If true, do not do routing control/send active source for internal source. @@ -120,6 +129,26 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // other CEC devices since they might not have logical address. private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>(); + // Message buffer used to buffer selected messages to process later. <Active Source> + // from a source device, for instance, needs to be buffered if the device is not + // discovered yet. The buffered commands are taken out and when they are ready to + // handle. + private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this); + + // Defines the callback invoked when TV input framework is updated with input status. + // We are interested in the notification for HDMI input addition event, in order to + // process any CEC commands that arrived before the input is added. + private final TvInputCallback mTvInputCallback = new TvInputCallback() { + @Override + public void onInputAdded(String inputId) { + TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId); + HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo(); + if (info != null && info.isCecDevice()) { + mDelayedMessageBuffer.processActiveSource(info.getLogicalAddress()); + } + } + }; + HdmiCecLocalDeviceTv(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_TV); mPrevPortId = Constants.INVALID_PORT_ID; @@ -133,6 +162,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected void onAddressAllocated(int logicalAddress, int reason) { assertRunOnServiceThread(); + mService.registerTvInputCallback(mTvInputCallback); mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( mAddress, mService.getPhysicalAddress(), mDeviceType)); mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( @@ -141,10 +171,22 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE); launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && reason != HdmiControlService.INITIATED_BY_BOOT_UP); + mLocalDeviceAddresses = initLocalDeviceAddresses(); launchDeviceDiscovery(); startQueuedActions(); } + + @ServiceThreadOnly + private List<Integer> initLocalDeviceAddresses() { + assertRunOnServiceThread(); + List<Integer> addresses = new ArrayList<>(); + for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { + addresses.add(device.getDeviceInfo().getLogicalAddress()); + } + return Collections.unmodifiableList(addresses); + } + @Override @ServiceThreadOnly protected int getPreferredAddress() { @@ -320,11 +362,22 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return; } int newPath = mService.portIdToPath(portId); + startRoutingControl(oldPath, newPath, true, callback); + } + + @ServiceThreadOnly + void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus, + IHdmiControlCallback callback) { + assertRunOnServiceThread(); + if (oldPath == newPath) { + return; + } HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); mService.sendCecCommand(routingChange); removeAction(RoutingControlAction.class); - addAndStartAction(new RoutingControlAction(this, newPath, true, callback)); + addAndStartAction( + new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback)); } @ServiceThreadOnly @@ -351,15 +404,28 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (!action.isEmpty()) { action.get(0).processKeyEvent(keyCode, isPressed); } else { - if (isPressed && getActiveSource().isValid()) { - int logicalAddress = getActiveSource().logicalAddress; - addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); - } else { - Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed); + if (isPressed) { + int logicalAddress = findKeyReceiverAddress(); + if (logicalAddress != Constants.ADDR_INVALID) { + addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); + return; + } } + Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed); } } + private int findKeyReceiverAddress() { + if (getActiveSource().isValid()) { + return getActiveSource().logicalAddress; + } + HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath()); + if (info != null) { + return info.getLogicalAddress(); + } + return Constants.ADDR_INVALID; + } + private static void invokeCallback(IHdmiControlCallback callback, int result) { if (callback == null) { return; @@ -377,11 +443,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); - if (getCecDeviceInfo(logicalAddress) == null) { - handleNewDeviceAtTheTailOfActivePath(physicalAddress); + HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); + if (info == null) { + if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) { + mDelayedMessageBuffer.add(message); + } } else { ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); - ActiveSourceHandler.create(this, null).process(activeSource); + ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType()); } return true; } @@ -471,7 +540,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (!isInDeviceList(address, path)) { handleNewDeviceAtTheTailOfActivePath(path); } - startNewDeviceAction(ActiveSource.of(address, path)); + startNewDeviceAction(ActiveSource.of(address, path), type); return true; } @@ -507,7 +576,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return false; } - void startNewDeviceAction(ActiveSource activeSource) { + void startNewDeviceAction(ActiveSource activeSource, int deviceType) { for (NewDeviceAction action : getActions(NewDeviceAction.class)) { // If there is new device action which has the same logical address and path // ignore new request. @@ -523,19 +592,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress, - activeSource.physicalAddress)); + activeSource.physicalAddress, deviceType)); } - private void handleNewDeviceAtTheTailOfActivePath(int path) { + private boolean handleNewDeviceAtTheTailOfActivePath(int path) { // Seq #22 if (isTailOfActivePath(path, getActivePath())) { - removeAction(RoutingControlAction.class); int newPath = mService.portIdToPath(getActivePortId()); setActivePath(newPath); - mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( - mAddress, getActivePath(), newPath)); - addAndStartAction(new RoutingControlAction(this, newPath, false, null)); + startRoutingControl(getActivePath(), newPath, false, null); + return true; } + return false; } /** @@ -680,7 +748,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (getSystemAudioModeSetting()) { addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress())); } - if (isArcFeatureEnabled()) { + if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) { startArcAction(true); } } @@ -738,7 +806,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } boolean isSystemAudioActivated() { - if (getAvrDeviceInfo() == null) { + if (!hasSystemAudioDevice()) { return false; } synchronized (mLock) { @@ -914,6 +982,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!canStartArcUpdateAction(message.getSource(), true)) { + if (getAvrDeviceInfo() == null) { + // AVR may not have been discovered yet. Delay the message processing. + mDelayedMessageBuffer.add(message); + return true; + } mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); if (!isConnectedToArcPort(message.getSource())) { displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); @@ -1000,6 +1073,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { OneTouchRecordAction action = actions.get(0); if (action.getRecorderAddress() != message.getSource()) { announceOneTouchRecordResult( + message.getSource(), HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); } return super.handleRecordTvScreen(message); @@ -1018,20 +1092,20 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { protected boolean handleTimerClearedStatus(HdmiCecMessage message) { byte[] params = message.getParams(); int timerClearedStatusData = params[0] & 0xFF; - announceTimerRecordingResult(timerClearedStatusData); + announceTimerRecordingResult(message.getSource(), timerClearedStatusData); return true; } - void announceOneTouchRecordResult(int result) { - mService.invokeOneTouchRecordResult(result); + void announceOneTouchRecordResult(int recorderAddress, int result) { + mService.invokeOneTouchRecordResult(recorderAddress, result); } - void announceTimerRecordingResult(int result) { - mService.invokeTimerRecordingResult(result); + void announceTimerRecordingResult(int recorderAddress, int result) { + mService.invokeTimerRecordingResult(recorderAddress, result); } - void announceClearTimerRecordingResult(int result) { - mService.invokeClearTimerRecordingResult(result); + void announceClearTimerRecordingResult(int recorderAddress, int result) { + mService.invokeClearTimerRecordingResult(recorderAddress, result); } private boolean isMessageForSystemAudio(HdmiCecMessage message) { @@ -1182,15 +1256,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - @ServiceThreadOnly private boolean isLocalDeviceAddress(int address) { - assertRunOnServiceThread(); - for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { - if (device.isAddressOf(address)) { - return true; - } - } - return false; + return mLocalDeviceAddresses.contains(address); } @ServiceThreadOnly @@ -1240,6 +1307,17 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } + List<HdmiDeviceInfo> getSafeCecDevicesLocked() { + ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); + for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { + if (isLocalDeviceAddress(info.getLogicalAddress())) { + continue; + } + infoList.add(info); + } + return infoList; + } + /** * Called when a device is newly added or a new device is detected or * existing device is updated. @@ -1276,12 +1354,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); // Seq #23 if (isTailOfActivePath(path, getActivePath())) { - removeAction(RoutingControlAction.class); int newPath = mService.portIdToPath(getActivePortId()); - mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( - mAddress, getActivePath(), newPath)); - mActiveSource.invalidate(); - addAndStartAction(new RoutingControlAction(this, getActivePath(), true, null)); + startRoutingControl(getActivePath(), newPath, true, null); } } @@ -1297,13 +1371,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Seq #24 if (getActivePortId() != Constants.INVALID_PORT_ID) { if (!routingForBootup && !isProhibitMode()) { - removeAction(RoutingControlAction.class); int newPath = mService.portIdToPath(getActivePortId()); setActivePath(newPath); - mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(mAddress, - getActivePath(), newPath)); - addAndStartAction(new RoutingControlAction(this, getActivePortId(), - routingForBootup, null)); + startRoutingControl(getActivePath(), newPath, routingForBootup, null); } } else { int activePath = mService.getPhysicalAddress(); @@ -1404,6 +1474,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { super.disableDevice(initiatedByCec, callback); assertRunOnServiceThread(); + mService.unregisterTvInputCallback(mTvInputCallback); // Remove any repeated working actions. // HotplugDetectionAction will be reinstated during the wake up process. // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> @@ -1495,19 +1566,21 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); - announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); + announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); return Constants.ABORT_NOT_IN_CORRECT_MODE; } if (!checkRecorder(recorderAddress)) { Slog.w(TAG, "Invalid recorder address:" + recorderAddress); - announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); + announceOneTouchRecordResult(recorderAddress, + ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); return Constants.ABORT_NOT_IN_CORRECT_MODE; } if (!checkRecordSource(recordSource)) { Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); - announceOneTouchRecordResult(ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); + announceOneTouchRecordResult(recorderAddress, + ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); return Constants.ABORT_UNABLE_TO_DETERMINE; } @@ -1522,13 +1595,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); - announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); + announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); return; } if (!checkRecorder(recorderAddress)) { Slog.w(TAG, "Invalid recorder address:" + recorderAddress); - announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); + announceOneTouchRecordResult(recorderAddress, + ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); return; } @@ -1554,13 +1628,14 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); - announceTimerRecordingResult(TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED); + announceTimerRecordingResult(recorderAddress, + TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED); return; } if (!checkRecorder(recorderAddress)) { Slog.w(TAG, "Invalid recorder address:" + recorderAddress); - announceTimerRecordingResult( + announceTimerRecordingResult(recorderAddress, TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); return; } @@ -1568,6 +1643,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (!checkTimerRecordingSource(sourceType, recordSource)) { Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); announceTimerRecordingResult( + recorderAddress, TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); return; } @@ -1588,26 +1664,29 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { assertRunOnServiceThread(); if (!mService.isControlEnabled()) { Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); - announceClearTimerRecordingResult(CLEAR_TIMER_STATUS_CEC_DISABLE); + announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE); return; } if (!checkRecorder(recorderAddress)) { Slog.w(TAG, "Invalid recorder address:" + recorderAddress); - announceClearTimerRecordingResult(CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION); + announceClearTimerRecordingResult(recorderAddress, + CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION); return; } if (!checkTimerRecordingSource(sourceType, recordSource)) { Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); - announceClearTimerRecordingResult(CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); + announceClearTimerRecordingResult(recorderAddress, + CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); return; } sendClearTimerMessage(recorderAddress, sourceType, recordSource); } - private void sendClearTimerMessage(int recorderAddress, int sourceType, byte[] recordSource) { + private void sendClearTimerMessage(final int recorderAddress, int sourceType, + byte[] recordSource) { HdmiCecMessage message = null; switch (sourceType) { case TIMER_RECORDING_TYPE_DIGITAL: @@ -1624,7 +1703,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { break; default: Slog.w(TAG, "Invalid source type:" + recorderAddress); - announceClearTimerRecordingResult(CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); + announceClearTimerRecordingResult(recorderAddress, + CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); return; } @@ -1632,7 +1712,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override public void onSendCompleted(int error) { if (error != Constants.SEND_RESULT_SUCCESS) { - announceClearTimerRecordingResult( + announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); } } @@ -1673,6 +1753,18 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress)); } + @ServiceThreadOnly + void processAllDelayedMessages() { + assertRunOnServiceThread(); + mDelayedMessageBuffer.processAllMessages(); + } + + @ServiceThreadOnly + void processDelayedMessages(int address) { + assertRunOnServiceThread(); + mDelayedMessageBuffer.processMessagesForDevice(address); + } + @Override protected void dump(final IndentingPrintWriter pw) { super.dump(pw); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index d703989..53740fe 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -29,11 +29,16 @@ public final class HdmiCecMessageValidator { static final int ERROR_SOURCE = 1; static final int ERROR_DESTINATION = 2; static final int ERROR_PARAMETER = 3; + static final int ERROR_PARAMETER_SHORT = 4; private final HdmiControlService mService; interface ParameterValidator { - boolean isValid(byte[] params); + /** + * @return errorCode errorCode can be {@link #OK}, {@link #ERROR_PARAMETER} or + * {@link #ERROR_PARAMETER_SHORT}. + */ + int isValid(byte[] params); } // Only the direct addressing is allowed. @@ -74,7 +79,7 @@ public final class HdmiCecMessageValidator { addValidationInfo(Constants.MESSAGE_SET_STREAM_PATH, physicalAddressValidator, DEST_BROADCAST); addValidationInfo(Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, - physicalAddressValidator, DEST_DIRECT); + new SystemAudioModeRequestValidator(), DEST_DIRECT); // Messages have no parameter. FixedLengthValidator noneValidator = new FixedLengthValidator(0); @@ -213,9 +218,10 @@ public final class HdmiCecMessageValidator { } // Check the parameter type. - if (!info.parameterValidator.isValid(message.getParams())) { + int errorCode = info.parameterValidator.isValid(message.getParams()); + if (errorCode != OK) { HdmiLogger.warning("Unexpected parameters: " + message); - return ERROR_PARAMETER; + return errorCode; } return OK; } @@ -228,8 +234,10 @@ public final class HdmiCecMessageValidator { } @Override - public boolean isValid(byte[] params) { - return params.length == mLength; + public int isValid(byte[] params) { + // If the length is longer than expected, we assume it's OK since the parameter can be + // extended in the future version. + return params.length < mLength ? ERROR_PARAMETER_SHORT : OK; } } @@ -243,8 +251,8 @@ public final class HdmiCecMessageValidator { } @Override - public boolean isValid(byte[] params) { - return params.length >= mMinLength && params.length <= mMaxLength; + public int isValid(byte[] params) { + return params.length < mMinLength ? ERROR_PARAMETER_SHORT : OK; } } @@ -270,8 +278,7 @@ public final class HdmiCecMessageValidator { * Check if the given type is valid. A valid type is one of the actual logical device types * defined in the standard ({@link HdmiDeviceInfo#DEVICE_TV}, * {@link HdmiDeviceInfo#DEVICE_PLAYBACK}, {@link HdmiDeviceInfo#DEVICE_TUNER}, - * {@link HdmiDeviceInfo#DEVICE_RECORDER}, and - * {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM}). + * {@link HdmiDeviceInfo#DEVICE_RECORDER}, and {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM}). * * @param type device type * @return true if the given type is valid @@ -282,33 +289,49 @@ public final class HdmiCecMessageValidator { && type != HdmiDeviceInfo.DEVICE_RESERVED; } + private static int toErrorCode(boolean success) { + return success ? OK : ERROR_PARAMETER; + } + private class PhysicalAddressValidator implements ParameterValidator { @Override - public boolean isValid(byte[] params) { - if (params.length != 2) { - return false; + public int isValid(byte[] params) { + if (params.length < 2) { + return ERROR_PARAMETER_SHORT; + } + return toErrorCode(isValidPhysicalAddress(params, 0)); + } + } + + private class SystemAudioModeRequestValidator extends PhysicalAddressValidator { + @Override + public int isValid(byte[] params) { + // TV can send <System Audio Mode Request> with no parameters to terminate system audio. + if (params.length == 0) { + return OK; } - return isValidPhysicalAddress(params, 0); + return super.isValid(params); } } private class ReportPhysicalAddressValidator implements ParameterValidator { @Override - public boolean isValid(byte[] params) { - if (params.length != 3) { - return false; + public int isValid(byte[] params) { + if (params.length < 3) { + return ERROR_PARAMETER_SHORT; } - return isValidPhysicalAddress(params, 0) && isValidType(params[2]); + return toErrorCode(isValidPhysicalAddress(params, 0) && isValidType(params[2])); } } private class RoutingChangeValidator implements ParameterValidator { @Override - public boolean isValid(byte[] params) { - if (params.length != 4) { - return false; + public int isValid(byte[] params) { + if (params.length < 4) { + return ERROR_PARAMETER_SHORT; } - return isValidPhysicalAddress(params, 0) && isValidPhysicalAddress(params, 2); + return toErrorCode( + isValidPhysicalAddress(params, 0) && isValidPhysicalAddress(params, 2)); } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 60d1520..8a25f62 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -48,6 +48,8 @@ import android.hardware.hdmi.IHdmiRecordListener; import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; import android.hardware.hdmi.IHdmiVendorCommandListener; import android.media.AudioManager; +import android.media.tv.TvInputManager; +import android.media.tv.TvInputManager.TvInputCallback; import android.net.Uri; import android.os.Build; import android.os.Handler; @@ -90,6 +92,8 @@ import java.util.Locale; */ public final class HdmiControlService extends SystemService { private static final String TAG = "HdmiControlService"; + private final Locale HONG_KONG = new Locale("zh", "HK"); + private final Locale MACAU = new Locale("zh", "MO"); static final String PERMISSION = "android.permission.HDMI_CEC"; @@ -146,13 +150,25 @@ public final class HdmiControlService extends SystemService { } break; case Intent.ACTION_CONFIGURATION_CHANGED: - String language = Locale.getDefault().getISO3Language(); + String language = getMenuLanguage(); if (!mLanguage.equals(language)) { onLanguageChanged(language); } break; } } + + private String getMenuLanguage() { + Locale locale = Locale.getDefault(); + if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) { + // Android always returns "zho" for all Chinese variants. + // Use "bibliographic" code defined in CEC639-2 for traditional + // Chinese used in Taiwan/Hong Kong/Macau. + return "chi"; + } else { + return locale.getISO3Language(); + } + } } // A thread to handle synchronous IO of CEC and MHL control service. @@ -258,6 +274,9 @@ public final class HdmiControlService extends SystemService { @Nullable private HdmiMhlControllerStub mMhlController; + @Nullable + private TvInputManager mTvInputManager; + // Last input port before switching to the MHL port. Should switch back to this port // when the mobile device sends the request one touch play with off. // Gets invalidated if we go to other port/input. @@ -303,6 +322,7 @@ public final class HdmiControlService extends SystemService { } } else { Slog.i(TAG, "Device does not support HDMI-CEC."); + return; } mMhlController = HdmiMhlControllerStub.create(this); @@ -315,20 +335,45 @@ public final class HdmiControlService extends SystemService { mMessageValidator = new HdmiCecMessageValidator(this); publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); - // Register broadcast receiver for power state change. if (mCecController != null) { + // Register broadcast receiver for power state change. IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); + + // Register ContentObserver to monitor the settings change. + registerContentObserver(); + } + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + mTvInputManager = (TvInputManager) getContext().getSystemService( + Context.TV_INPUT_SERVICE); } } + TvInputManager getTvInputManager() { + return mTvInputManager; + } + + void registerTvInputCallback(TvInputCallback callback) { + if (mTvInputManager == null) return; + mTvInputManager.registerCallback(callback, mHandler); + } + + void unregisterTvInputCallback(TvInputCallback callback) { + if (mTvInputManager == null) return; + mTvInputManager.unregisterCallback(callback); + } + /** * Called when the initialization of local devices is complete. */ - private void onInitializeCecComplete() { + private void onInitializeCecComplete(int initiatedBy) { if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) { mPowerStatus = HdmiControlManager.POWER_STATUS_ON; } @@ -336,7 +381,22 @@ public final class HdmiControlService extends SystemService { if (isTvDevice()) { mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup())); - registerContentObserver(); + } + int reason = -1; + switch (initiatedBy) { + case INITIATED_BY_BOOT_UP: + reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START; + break; + case INITIATED_BY_ENABLE_CEC: + reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING; + break; + case INITIATED_BY_SCREEN_ON: + case INITIATED_BY_WAKE_UP_MESSAGE: + reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP; + break; + } + if (reason != -1) { + invokeVendorCommandListenersOnControlStateChanged(true, reason); } } @@ -401,10 +461,6 @@ public final class HdmiControlService extends SystemService { Global.putInt(cr, key, toInt(value)); } - private void unregisterSettingsObserver() { - getContext().getContentResolver().unregisterContentObserver(mSettingsObserver); - } - private void initializeCec(int initiatedBy) { mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED); initializeLocalDevices(initiatedBy); @@ -459,7 +515,7 @@ public final class HdmiControlService extends SystemService { if (initiatedBy != INITIATED_BY_HOTPLUG) { // In case of the hotplug we don't call onInitializeCecComplete() // since we reallocate the logical address only. - onInitializeCecComplete(); + onInitializeCecComplete(initiatedBy); } notifyAddressAllocated(allocatedDevices, initiatedBy); } @@ -690,7 +746,8 @@ public final class HdmiControlService extends SystemService { assertRunOnServiceThread(); int errorCode = mMessageValidator.isValid(message); if (errorCode != HdmiCecMessageValidator.OK) { - // We'll not response on the messages with the invalid source or destination. + // We'll not response on the messages with the invalid source or destination + // or with parameter length shorter than specified in the standard. if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) { maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); } @@ -729,20 +786,18 @@ public final class HdmiControlService extends SystemService { void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); - ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); - for (int type : mLocalDevices) { - if (type == HdmiDeviceInfo.DEVICE_TV) { - // Skip the reallocation of the logical address on TV. - continue; - } - HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); - if (localDevice == null) { - localDevice = HdmiCecLocalDevice.create(this, type); - localDevice.init(); + if (connected && !isTvDevice()) { + ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); + for (int type : mLocalDevices) { + HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); + if (localDevice == null) { + localDevice = HdmiCecLocalDevice.create(this, type); + localDevice.init(); + } + localDevices.add(localDevice); } - localDevices.add(localDevice); + allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG); } - allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG); for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { device.onHotplug(portId, connected); @@ -827,6 +882,7 @@ public final class HdmiControlService extends SystemService { @ServiceThreadOnly void handleMhlHotplugEvent(int portId, boolean connected) { assertRunOnServiceThread(); + // Hotplug event is used to add/remove MHL devices as TV input. if (connected) { HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId); HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice); @@ -834,17 +890,14 @@ public final class HdmiControlService extends SystemService { oldDevice.onDeviceRemoved(); Slog.i(TAG, "Old device of port " + portId + " is removed"); } + invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE); + updateSafeMhlInput(); } else { HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId); if (device != null) { device.onDeviceRemoved(); - // There is no explicit event for device removal. - // Hence we remove the device on hotplug event. - HdmiDeviceInfo deviceInfo = device.getInfo(); - if (deviceInfo != null) { - invokeDeviceEventListeners(deviceInfo, DEVICE_EVENT_REMOVE_DEVICE); - updateSafeMhlInput(); - } + invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE); + updateSafeMhlInput(); } else { Slog.w(TAG, "No device to remove:[portId=" + portId); } @@ -880,11 +933,8 @@ public final class HdmiControlService extends SystemService { assertRunOnServiceThread(); HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); - // Hotplug event should already have been called before device status change event. if (device != null) { device.setDeviceStatusChange(adopterId, deviceId); - invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_ADD_DEVICE); - updateSafeMhlInput(); } else { Slog.w(TAG, "No mhl device exists for device status event[portId:" + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]"); @@ -1025,6 +1075,7 @@ public final class HdmiControlService extends SystemService { @Override public HdmiDeviceInfo getActiveSource() { + enforceAccessPermission(); HdmiCecLocalDeviceTv tv = tv(); if (tv == null) { Slog.w(TAG, "Local tv device not available"); @@ -1238,6 +1289,19 @@ public final class HdmiControlService extends SystemService { } } + // Returns all the CEC devices on the bus including system audio, switch, + // even those of reserved type. + @Override + public List<HdmiDeviceInfo> getDeviceList() { + enforceAccessPermission(); + HdmiCecLocalDeviceTv tv = tv(); + synchronized (mLock) { + return (tv == null) + ? Collections.<HdmiDeviceInfo>emptyList() + : tv.getSafeCecDevicesLocked(); + } + } + @Override public void setSystemAudioVolume(final int oldIndex, final int newIndex, final int maxIndex) { @@ -1344,11 +1408,13 @@ public final class HdmiControlService extends SystemService { @Override public void setHdmiRecordListener(IHdmiRecordListener listener) { + enforceAccessPermission(); HdmiControlService.this.setHdmiRecordListener(listener); } @Override public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { + enforceAccessPermission(); runOnServiceThread(new Runnable() { @Override public void run() { @@ -1363,6 +1429,7 @@ public final class HdmiControlService extends SystemService { @Override public void stopOneTouchRecord(final int recorderAddress) { + enforceAccessPermission(); runOnServiceThread(new Runnable() { @Override public void run() { @@ -1378,6 +1445,7 @@ public final class HdmiControlService extends SystemService { @Override public void startTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource) { + enforceAccessPermission(); runOnServiceThread(new Runnable() { @Override public void run() { @@ -1393,6 +1461,7 @@ public final class HdmiControlService extends SystemService { @Override public void clearTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource) { + enforceAccessPermission(); runOnServiceThread(new Runnable() { @Override public void run() { @@ -1620,11 +1689,11 @@ public final class HdmiControlService extends SystemService { } } - void invokeOneTouchRecordResult(int result) { + void invokeOneTouchRecordResult(int recorderAddress, int result) { synchronized (mLock) { if (mRecordListenerRecord != null) { try { - mRecordListenerRecord.mListener.onOneTouchRecordResult(result); + mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); } @@ -1632,11 +1701,11 @@ public final class HdmiControlService extends SystemService { } } - void invokeTimerRecordingResult(int result) { + void invokeTimerRecordingResult(int recorderAddress, int result) { synchronized (mLock) { if (mRecordListenerRecord != null) { try { - mRecordListenerRecord.mListener.onTimerRecordingResult(result); + mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); } @@ -1644,11 +1713,12 @@ public final class HdmiControlService extends SystemService { } } - void invokeClearTimerRecordingResult(int result) { + void invokeClearTimerRecordingResult(int recorderAddress, int result) { synchronized (mLock) { if (mRecordListenerRecord != null) { try { - mRecordListenerRecord.mListener.onClearTimerRecordingResult(result); + mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress, + result); } catch (RemoteException e) { Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); } @@ -1696,7 +1766,7 @@ public final class HdmiControlService extends SystemService { } boolean isTvDevice() { - return tv() != null; + return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV); } private HdmiCecLocalDevicePlayback playback() { @@ -1782,6 +1852,8 @@ public final class HdmiControlService extends SystemService { private void onStandby() { assertRunOnServiceThread(); mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; + invokeVendorCommandListenersOnControlStateChanged(false, + HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY); final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); disableDevices(new PendingActionClearedCallback() { @@ -1820,9 +1892,6 @@ public final class HdmiControlService extends SystemService { for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { device.disableDevice(mStandbyMessageReceived, callback); } - if (isTvDevice()) { - unregisterSettingsObserver(); - } } mMhlController.clearAllLocalDevices(); @@ -1867,8 +1936,8 @@ public final class HdmiControlService extends SystemService { } } - boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params, - boolean hasVendorId) { + boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, + byte[] params, boolean hasVendorId) { synchronized (mLock) { if (mVendorCommandListenerRecords.isEmpty()) { return false; @@ -1878,7 +1947,7 @@ public final class HdmiControlService extends SystemService { continue; } try { - record.mListener.onReceived(srcAddress, params, hasVendorId); + record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId); } catch (RemoteException e) { Slog.e(TAG, "Failed to notify vendor command reception", e); } @@ -1887,6 +1956,22 @@ public final class HdmiControlService extends SystemService { } } + boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) { + synchronized (mLock) { + if (mVendorCommandListenerRecords.isEmpty()) { + return false; + } + for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { + try { + record.mListener.onControlStateChanged(enabled, reason); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e); + } + } + return true; + } + } + private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) { HdmiMhlVendorCommandListenerRecord record = new HdmiMhlVendorCommandListenerRecord(listener); @@ -1936,6 +2021,11 @@ public final class HdmiControlService extends SystemService { void setControlEnabled(boolean enabled) { assertRunOnServiceThread(); + if (!enabled) { + // Call the vendor handler before the service is disabled. + invokeVendorCommandListenersOnControlStateChanged(false, + HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING); + } int value = toInt(enabled); mCecController.setOption(OPTION_CEC_ENABLE, value); mMhlController.setOption(OPTION_MHL_ENABLE, value); @@ -2008,9 +2098,7 @@ public final class HdmiControlService extends SystemService { // may not be the MHL-enabled one. In this case the device info to be passed to // input change listener should be the one describing the corresponding HDMI port. HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); - HdmiDeviceInfo info = (device != null && device.getInfo() != null) - ? device.getInfo() - : mPortDeviceMap.get(portId); + HdmiDeviceInfo info = (device != null) ? device.getInfo() : mPortDeviceMap.get(portId); invokeInputChangeListener(info); } @@ -2042,7 +2130,7 @@ public final class HdmiControlService extends SystemService { assertRunOnServiceThread(); Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); - intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRAM_PARAM1, extra); + intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra); getContext().sendBroadcastAsUser(intent, UserHandle.ALL, HdmiControlService.PERMISSION); } diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java index 2562ffc..7f6971f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiLogger.java +++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; import android.annotation.Nullable; +import android.os.Build; import android.os.SystemClock; import android.util.Pair; import android.util.Slog; @@ -41,7 +42,7 @@ final class HdmiLogger { // Logging duration for same error message. private static final long ERROR_LOG_DURATTION_MILLIS = 20 * 1000; // 20s - private static final boolean DEBUG = false; + private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE); private static final ThreadLocal<HdmiLogger> sLogger = new ThreadLocal<>(); @@ -82,7 +83,7 @@ final class HdmiLogger { } private void debugInternal(String logMessage) { - if (!DEBUG) { + if (IS_USER_BUILD) { return; } Slog.d(TAG, logMessage); diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index 2074085..64f0703 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -47,6 +47,7 @@ final class NewDeviceAction extends HdmiCecFeatureAction { private final int mDeviceLogicalAddress; private final int mDevicePhysicalAddress; + private final int mDeviceType; private int mVendorId; private String mDisplayName; @@ -57,12 +58,14 @@ final class NewDeviceAction extends HdmiCecFeatureAction { * @param source {@link HdmiCecLocalDevice} instance * @param deviceLogicalAddress logical address of the device in interest * @param devicePhysicalAddress physical address of the device in interest + * @param deviceType type of the device */ NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress, - int devicePhysicalAddress) { + int devicePhysicalAddress, int deviceType) { super(source); mDeviceLogicalAddress = deviceLogicalAddress; mDevicePhysicalAddress = devicePhysicalAddress; + mDeviceType = deviceType; mVendorId = Constants.UNKNOWN_VENDOR_ID; } @@ -155,10 +158,12 @@ final class NewDeviceAction extends HdmiCecFeatureAction { HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo( mDeviceLogicalAddress, mDevicePhysicalAddress, tv().getPortId(mDevicePhysicalAddress), - HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress), - mVendorId, mDisplayName); + mDeviceType, mVendorId, mDisplayName); tv().addCecDevice(deviceInfo); + // Consume CEC messages we already got for this newly found device. + tv().processDelayedMessages(mDeviceLogicalAddress); + if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress) == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { tv().onNewAvrAdded(deviceInfo); diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java index 7db991a..e764a1c 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java @@ -92,7 +92,8 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction { @Override boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS) { + if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS + || mTargetAddress != cmd.getSource()) { return false; } if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) { diff --git a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java index 39c0d7f..d80b81f 100644 --- a/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java +++ b/services/core/java/com/android/server/hdmi/OneTouchRecordAction.java @@ -64,27 +64,26 @@ public class OneTouchRecordAction extends HdmiCecFeatureAction { // if failed to send <Record On>, display error message and finish action. if (error != Constants.SEND_RESULT_SUCCESS) { tv().announceOneTouchRecordResult( + mRecorderAddress, ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); finish(); return; } - - mState = STATE_WAITING_FOR_RECORD_STATUS; - addTimer(mState, RECORD_STATUS_TIMEOUT_MS); } }); + mState = STATE_WAITING_FOR_RECORD_STATUS; + addTimer(mState, RECORD_STATUS_TIMEOUT_MS); } @Override boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WAITING_FOR_RECORD_STATUS) { + if (mState != STATE_WAITING_FOR_RECORD_STATUS || mRecorderAddress != cmd.getSource()) { return false; } switch (cmd.getOpcode()) { case Constants.MESSAGE_RECORD_STATUS: return handleRecordStatus(cmd); - } return false; } @@ -96,7 +95,7 @@ public class OneTouchRecordAction extends HdmiCecFeatureAction { } int recordStatus = cmd.getParams()[0]; - tv().announceOneTouchRecordResult(recordStatus); + tv().announceOneTouchRecordResult(mRecorderAddress, recordStatus); Slog.i(TAG, "Got record status:" + recordStatus + " from " + cmd.getSource()); // If recording started successfully, change state and keep this action until <Record Off> @@ -123,7 +122,8 @@ public class OneTouchRecordAction extends HdmiCecFeatureAction { return; } - tv().announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); + tv().announceOneTouchRecordResult(mRecorderAddress, + ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); finish(); } diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java index ed978e0..eef5010 100644 --- a/services/core/java/com/android/server/hdmi/SendKeyAction.java +++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java @@ -15,7 +15,7 @@ */ package com.android.server.hdmi; -import static com.android.server.hdmi.Constants.IRT_MS; +import static com.android.server.hdmi.HdmiConfig.IRT_MS; import android.util.Slog; import android.view.KeyEvent; @@ -35,6 +35,11 @@ import android.view.KeyEvent; final class SendKeyAction extends HdmiCecFeatureAction { private static final String TAG = "SendKeyAction"; + // Amount of time this action waits for a new release key input event. When timed out, + // the action sends out UCR and finishes its lifecycle. Used to deal with missing key release + // event, which can lead the device on the receiving end to generating unintended key repeats. + private static final int AWAIT_RELEASE_KEY_MS = 1000; + // State in which the action is at work. The state is set in {@link #start()} and // persists throughout the process till it is set back to {@code STATE_NONE} at the end. private static final int STATE_PROCESSING_KEYCODE = 1; @@ -45,6 +50,10 @@ final class SendKeyAction extends HdmiCecFeatureAction { // The key code of the last key press event the action is passed via processKeyEvent. private int mLastKeycode; + // The time stamp when the last CEC key command was sent. Used to determine the press-and-hold + // operation. + private long mLastSendKeyTime; + /** * Constructor. * @@ -61,6 +70,7 @@ final class SendKeyAction extends HdmiCecFeatureAction { @Override public boolean start() { sendKeyDown(mLastKeycode); + mLastSendKeyTime = getCurrentTime(); // finish action for non-repeatable key. if (!HdmiCecKeycode.isRepeatableKey(mLastKeycode)) { sendKeyUp(); @@ -68,10 +78,14 @@ final class SendKeyAction extends HdmiCecFeatureAction { return true; } mState = STATE_PROCESSING_KEYCODE; - addTimer(mState, IRT_MS); + addTimer(mState, AWAIT_RELEASE_KEY_MS); return true; } + private long getCurrentTime() { + return System.currentTimeMillis(); + } + /** * Called when a key event should be handled for the action. * @@ -83,24 +97,32 @@ final class SendKeyAction extends HdmiCecFeatureAction { Slog.w(TAG, "Not in a valid state"); return; } - // A new key press event that comes in with a key code different from the last - // one sets becomes a new key code to be used for press-and-hold operation. - // Removes any pending timer and starts a new timer for itself. - // Key release event indicates that the action shall be finished. Send UCR - // command and terminate the action. Other release events are ignored. if (isPressed) { + // A new key press event that comes in with a key code different from the last + // one becomes a new key code to be used for press-and-hold operation. if (keycode != mLastKeycode) { sendKeyDown(keycode); + mLastSendKeyTime = getCurrentTime(); if (!HdmiCecKeycode.isRepeatableKey(keycode)) { sendKeyUp(); finish(); return; } - mActionTimer.clearTimerMessage(); - addTimer(mState, IRT_MS); - mLastKeycode = keycode; + } else { + // Press-and-hold key transmission takes place if Android key inputs are + // repeatedly coming in and more than IRT_MS has passed since the last + // press-and-hold key transmission. + if (getCurrentTime() - mLastSendKeyTime >= IRT_MS) { + sendKeyDown(keycode); + mLastSendKeyTime = getCurrentTime(); + } } + mActionTimer.clearTimerMessage(); + addTimer(mState, AWAIT_RELEASE_KEY_MS); + mLastKeycode = keycode; } else { + // Key release event indicates that the action shall be finished. Send UCR + // command and terminate the action. Other release events are ignored. if (keycode == mLastKeycode) { sendKeyUp(); finish(); @@ -109,12 +131,12 @@ final class SendKeyAction extends HdmiCecFeatureAction { } private void sendKeyDown(int keycode) { - int cecKeycode = HdmiCecKeycode.androidKeyToCecKey(keycode); - if (cecKeycode == HdmiCecKeycode.UNSUPPORTED_KEYCODE) { + byte[] cecKeycodeAndParams = HdmiCecKeycode.androidKeyToCecKey(keycode); + if (cecKeycodeAndParams == null) { return; } sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), - mTargetAddress, new byte[] { (byte) (cecKeycode & 0xFF) })); + mTargetAddress, cecKeycodeAndParams)); } private void sendKeyUp() { @@ -130,15 +152,12 @@ final class SendKeyAction extends HdmiCecFeatureAction { @Override public void handleTimerEvent(int state) { - // Timer event occurs every IRT_MS milliseconds to perform key-repeat (or press-and-hold) - // operation. If the last received key code is as same as the one with which the action - // is started, plus there was no key release event in last IRT_MS timeframe, send a UCP - // command and start another timer to schedule the next press-and-hold command. + // Timeout on waiting for the release key event. Send UCR and quit the action. if (mState != STATE_PROCESSING_KEYCODE) { Slog.w(TAG, "Not in a valid state"); return; } - sendKeyDown(mLastKeycode); - addTimer(mState, IRT_MS); + sendKeyUp(); + finish(); } } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index 0871194..6023354 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -125,6 +125,9 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { @Override final boolean processCommand(HdmiCecMessage cmd) { + if (cmd.getSource() != mAvrLogicalAddress) { + return false; + } switch (mState) { case STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE: if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java index 3653aac..512d537 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java @@ -58,33 +58,29 @@ final class SystemAudioAutoInitiationAction extends HdmiCecFeatureAction { @Override boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS) { + if (mState != STATE_WAITING_FOR_SYSTEM_AUDIO_MODE_STATUS + || mAvrAddress != cmd.getSource()) { return false; } - switch (cmd.getOpcode()) { - case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: - handleSystemAudioModeStatusMessage(); - return true; - default: - return false; + if (cmd.getOpcode() == Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS) { + handleSystemAudioModeStatusMessage(); + return true; } + return false; } private void handleSystemAudioModeStatusMessage() { - // If the last setting is system audio, turn on system audio whatever AVR status is. - if (tv().getSystemAudioModeSetting()) { - if (canChangeSystemAudio()) { - addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true, null)); - } - } else { - // If the last setting is non-system audio, turn off system audio mode - // and update system audio status (volume or mute). - tv().setSystemAudioMode(false, true); - if (canChangeSystemAudio()) { - addAndStartAction(new SystemAudioStatusAction(tv(), mAvrAddress, null)); - } + if (!canChangeSystemAudio()) { + HdmiLogger.debug("Cannot change system audio mode in auto initiation action."); + finish(); + return; } + + boolean systemAudioModeSetting = tv().getSystemAudioModeSetting(); + // Update AVR's system audio mode regardless of AVR's status. + addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, systemAudioModeSetting, + null)); finish(); } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java index bfcda50..dba3591 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java @@ -79,7 +79,7 @@ final class SystemAudioStatusAction extends HdmiCecFeatureAction { @Override boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS) { + if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS || mAvrAddress != cmd.getSource()) { return false; } diff --git a/services/core/java/com/android/server/hdmi/TimerRecordingAction.java b/services/core/java/com/android/server/hdmi/TimerRecordingAction.java index 8fc0182..16fc25f 100644 --- a/services/core/java/com/android/server/hdmi/TimerRecordingAction.java +++ b/services/core/java/com/android/server/hdmi/TimerRecordingAction.java @@ -74,7 +74,7 @@ public class TimerRecordingAction extends HdmiCecFeatureAction { mRecorderAddress, mRecordSource); break; default: - tv().announceTimerRecordingResult( + tv().announceTimerRecordingResult(mRecorderAddress, TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); finish(); return; @@ -83,7 +83,7 @@ public class TimerRecordingAction extends HdmiCecFeatureAction { @Override public void onSendCompleted(int error) { if (error != Constants.SEND_RESULT_SUCCESS) { - tv().announceTimerRecordingResult( + tv().announceTimerRecordingResult(mRecorderAddress, TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); finish(); return; @@ -96,11 +96,8 @@ public class TimerRecordingAction extends HdmiCecFeatureAction { @Override boolean processCommand(HdmiCecMessage cmd) { - if (mState != STATE_WAITING_FOR_TIMER_STATUS) { - return false; - } - - if (cmd.getSource() != mRecorderAddress) { + if (mState != STATE_WAITING_FOR_TIMER_STATUS + || cmd.getSource() != mRecorderAddress) { return false; } @@ -117,7 +114,7 @@ public class TimerRecordingAction extends HdmiCecFeatureAction { byte[] timerStatusData = cmd.getParams(); // [Timer Status Data] should be one or three bytes. if (timerStatusData.length == 1 || timerStatusData.length == 3) { - tv().announceTimerRecordingResult(bytesToInt(timerStatusData)); + tv().announceTimerRecordingResult(mRecorderAddress, bytesToInt(timerStatusData)); Slog.i(TAG, "Received [Timer Status Data]:" + Arrays.toString(timerStatusData)); } else { Slog.w(TAG, "Invalid [Timer Status Data]:" + Arrays.toString(timerStatusData)); @@ -141,7 +138,8 @@ public class TimerRecordingAction extends HdmiCecFeatureAction { } int reason = params[1] & 0xFF; Slog.i(TAG, "[Feature Abort] for " + messageType + " reason:" + reason); - tv().announceTimerRecordingResult(TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); + tv().announceTimerRecordingResult(mRecorderAddress, + TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); finish(); return true; } @@ -166,7 +164,8 @@ public class TimerRecordingAction extends HdmiCecFeatureAction { return; } - tv().announceTimerRecordingResult(TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); + tv().announceTimerRecordingResult(mRecorderAddress, + TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); finish(); } } diff --git a/services/core/java/com/android/server/hdmi/VolumeControlAction.java b/services/core/java/com/android/server/hdmi/VolumeControlAction.java index 4338fc7..cd38b1f 100644 --- a/services/core/java/com/android/server/hdmi/VolumeControlAction.java +++ b/services/core/java/com/android/server/hdmi/VolumeControlAction.java @@ -16,10 +16,10 @@ package com.android.server.hdmi; -import static com.android.server.hdmi.Constants.IRT_MS; import static com.android.server.hdmi.Constants.MESSAGE_FEATURE_ABORT; import static com.android.server.hdmi.Constants.MESSAGE_REPORT_AUDIO_STATUS; import static com.android.server.hdmi.Constants.MESSAGE_USER_CONTROL_PRESSED; +import static com.android.server.hdmi.HdmiConfig.IRT_MS; import android.media.AudioManager; @@ -45,6 +45,7 @@ final class VolumeControlAction extends HdmiCecFeatureAction { private boolean mIsVolumeUp; private long mLastKeyUpdateTime; private int mLastAvrVolume; + private boolean mLastAvrMute; private boolean mSentKeyPressed; /** @@ -74,6 +75,7 @@ final class VolumeControlAction extends HdmiCecFeatureAction { mAvrAddress = avrAddress; mIsVolumeUp = isVolumeUp; mLastAvrVolume = UNKNOWN_AVR_VOLUME; + mLastAvrMute = false; mSentKeyPressed = false; updateLastKeyUpdateTime(); @@ -108,6 +110,8 @@ final class VolumeControlAction extends HdmiCecFeatureAction { HdmiLogger.debug("Volume Key Status Changed[old:%b new:%b]", mIsVolumeUp, isVolumeUp); sendVolumeKeyReleased(); mIsVolumeUp = isVolumeUp; + sendVolumeKeyPressed(); + resetTimer(); } updateLastKeyUpdateTime(); } @@ -129,9 +133,8 @@ final class VolumeControlAction extends HdmiCecFeatureAction { return handleReportAudioStatus(cmd); case MESSAGE_FEATURE_ABORT: return handleFeatureAbort(cmd); - default: - return false; } + return false; } private boolean handleReportAudioStatus(HdmiCecMessage cmd) { @@ -139,9 +142,12 @@ final class VolumeControlAction extends HdmiCecFeatureAction { boolean mute = (params[0] & 0x80) == 0x80; int volume = params[0] & 0x7F; mLastAvrVolume = volume; + mLastAvrMute = mute; if (shouldUpdateAudioVolume(mute)) { HdmiLogger.debug("Force volume change[mute:%b, volume=%d]", mute, volume); tv().setAudioStatus(mute, volume); + mLastAvrVolume = UNKNOWN_AVR_VOLUME; + mLastAvrMute = false; } return true; } @@ -182,8 +188,9 @@ final class VolumeControlAction extends HdmiCecFeatureAction { sendVolumeKeyReleased(); } if (mLastAvrVolume != UNKNOWN_AVR_VOLUME) { - tv().setAudioStatus(false, mLastAvrVolume); + tv().setAudioStatus(mLastAvrMute, mLastAvrVolume); mLastAvrVolume = UNKNOWN_AVR_VOLUME; + mLastAvrMute = false; } } diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/services/core/java/com/android/server/input/InputWindowHandle.java index 9a70f38..9149fcc 100644 --- a/services/core/java/com/android/server/input/InputWindowHandle.java +++ b/services/core/java/com/android/server/input/InputWindowHandle.java @@ -44,7 +44,6 @@ public final class InputWindowHandle { // Window layout params attributes. (WindowManager.LayoutParams) public int layoutParamsFlags; - public int layoutParamsPrivateFlags; public int layoutParamsType; // Dispatching timeout. diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index c6d2db2..83d6986 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -137,11 +137,15 @@ public class JobSchedulerService extends com.android.server.SystemService public void onReceive(Context context, Intent intent) { Slog.d(TAG, "Receieved: " + intent.getAction()); if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { - int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1); - if (DEBUG) { - Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); + // If this is an outright uninstall rather than the first half of an + // app update sequence, cancel the jobs associated with the app. + if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1); + if (DEBUG) { + Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); + } + cancelJobsForUid(uidRemoved); } - cancelJobsForUid(uidRemoved); } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); if (DEBUG) { diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index 5a81b4c..7e2b970 100644 --- a/services/core/java/com/android/server/location/GpsLocationProvider.java +++ b/services/core/java/com/android/server/location/GpsLocationProvider.java @@ -72,6 +72,9 @@ 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; import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; @@ -88,6 +91,7 @@ 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; @@ -162,6 +166,9 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final int GPS_CAPABILITY_MSA = 0x0000004; private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008; private static final int GPS_CAPABILITY_ON_DEMAND_TIME = 0x0000010; + private static final int GPS_CAPABILITY_GEOFENCING = 0x0000020; + private static final int GPS_CAPABILITY_MEASUREMENTS = 0x0000040; + private static final int GPS_CAPABILITY_NAV_MESSAGES = 0x0000080; // The AGPS SUPL mode private static final int AGPS_SUPL_MODE_MSA = 0x02; @@ -348,20 +355,9 @@ public class GpsLocationProvider implements LocationProviderInterface { private final ILocationManager mILocationManager; private Location mLocation = new Location(LocationManager.GPS_PROVIDER); private Bundle mLocationExtras = new Bundle(); - private GpsStatusListenerHelper mListenerHelper = new GpsStatusListenerHelper() { - @Override - protected boolean isSupported() { - return GpsLocationProvider.isSupported(); - } - - @Override - protected boolean registerWithService() { - return true; - } - - @Override - protected void unregisterFromService() {} - }; + private final GpsStatusListenerHelper mListenerHelper; + private final GpsMeasurementsProvider mGpsMeasurementsProvider; + private final GpsNavigationMessageProvider mGpsNavigationMessageProvider; // Handler for processing events private Handler mHandler; @@ -409,41 +405,6 @@ public class GpsLocationProvider implements LocationProviderInterface { } }; - private final GpsMeasurementsProvider mGpsMeasurementsProvider = new GpsMeasurementsProvider() { - @Override - public boolean isSupported() { - return native_is_measurement_supported(); - } - - @Override - protected boolean registerWithService() { - return native_start_measurement_collection(); - } - - @Override - protected void unregisterFromService() { - native_stop_measurement_collection(); - } - }; - - private final GpsNavigationMessageProvider mGpsNavigationMessageProvider = - new GpsNavigationMessageProvider() { - @Override - protected boolean isSupported() { - return native_is_navigation_message_supported(); - } - - @Override - protected boolean registerWithService() { - return native_start_navigation_message_collection(); - } - - @Override - protected void unregisterFromService() { - native_stop_navigation_message_collection(); - } - }; - public IGpsStatusProvider getGpsStatusProvider() { return mGpsStatusProvider; } @@ -473,14 +434,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)) { - int networkState; - if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) { - networkState = LocationProvider.TEMPORARILY_UNAVAILABLE; - } else { - networkState = LocationProvider.AVAILABLE; - } - + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE)) { // retrieve NetworkInfo result for this UID NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); @@ -488,31 +442,50 @@ public class GpsLocationProvider implements LocationProviderInterface { mContext.getSystemService(Context.CONNECTIVITY_SERVICE); info = connManager.getNetworkInfo(info.getType()); + int networkState; + if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false) || + !info.isConnected()) { + networkState = LocationProvider.TEMPORARILY_UNAVAILABLE; + } else { + networkState = LocationProvider.AVAILABLE; + } + + updateNetworkState(networkState, info); } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action) || Intent.ACTION_SCREEN_OFF.equals(action) || Intent.ACTION_SCREEN_ON.equals(action)) { updateLowPowerMode(); - } else if (action.equals(SIM_STATE_CHANGED) - || action.equals(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE) - || action.equals(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED)) { - Log.d(TAG, "received SIM realted action: " + action); - TelephonyManager phone = (TelephonyManager) - mContext.getSystemService(Context.TELEPHONY_SERVICE); - String mccMnc = phone.getSimOperator(); - if (!TextUtils.isEmpty(mccMnc)) { - Log.d(TAG, "SIM MCC/MNC is available: " + mccMnc); - synchronized (mLock) { - reloadGpsProperties(context, mProperties); - mNIHandler.setSuplEsEnabled(mSuplEsEnabled); - } - } else { - Log.d(TAG, "SIM MCC/MNC is still not available"); - } + } else if (action.equals(SIM_STATE_CHANGED)) { + subscriptionOrSimChanged(context); } } }; + private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener = + new OnSubscriptionsChangedListener() { + @Override + public void onSubscriptionsChanged() { + subscriptionOrSimChanged(mContext); + } + }; + + private void subscriptionOrSimChanged(Context context) { + Log.d(TAG, "received SIM realted action: "); + TelephonyManager phone = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + String mccMnc = phone.getSimOperator(); + if (!TextUtils.isEmpty(mccMnc)) { + Log.d(TAG, "SIM MCC/MNC is available: " + mccMnc); + synchronized (mLock) { + reloadGpsProperties(context, mProperties); + mNIHandler.setSuplEsEnabled(mSuplEsEnabled); + } + } else { + Log.d(TAG, "SIM MCC/MNC is still not available"); + } + } + private void checkSmsSuplInit(Intent intent) { SmsMessage[] messages = Intents.getMessagesFromIntent(intent); for (int i=0; i <messages.length; i++) { @@ -667,6 +640,16 @@ public class GpsLocationProvider implements LocationProviderInterface { 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) + .registerOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); + // construct handler, listen for events mHandler = new ProviderHandler(looper); listenForBroadcasts(); @@ -694,6 +677,62 @@ public class GpsLocationProvider implements LocationProviderInterface { mHandler.getLooper()); } }); + + mListenerHelper = new GpsStatusListenerHelper(mHandler) { + @Override + protected boolean isAvailableInPlatform() { + return GpsLocationProvider.isSupported(); + } + + @Override + protected boolean isGpsEnabled() { + return isEnabled(); + } + }; + + mGpsMeasurementsProvider = new GpsMeasurementsProvider(mHandler) { + @Override + public boolean isAvailableInPlatform() { + return native_is_measurement_supported(); + } + + @Override + protected boolean registerWithService() { + return native_start_measurement_collection(); + } + + @Override + protected void unregisterFromService() { + native_stop_measurement_collection(); + } + + @Override + protected boolean isGpsEnabled() { + return isEnabled(); + } + }; + + mGpsNavigationMessageProvider = new GpsNavigationMessageProvider(mHandler) { + @Override + protected boolean isAvailableInPlatform() { + return native_is_navigation_message_supported(); + } + + @Override + protected boolean registerWithService() { + return native_start_navigation_message_collection(); + } + + @Override + protected void unregisterFromService() { + native_stop_navigation_message_collection(); + } + + @Override + protected boolean isGpsEnabled() { + return isEnabled(); + } + }; } private void listenForBroadcasts() { @@ -715,15 +754,11 @@ public class GpsLocationProvider implements LocationProviderInterface { intentFilter = new IntentFilter(); intentFilter.addAction(ALARM_WAKEUP); intentFilter.addAction(ALARM_TIMEOUT); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + 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); - // TODO: remove the use TelephonyIntents. We are using it because SIM_STATE_CHANGED - // is not reliable at the moment. - intentFilter.addAction(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE); - intentFilter.addAction(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED); mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, mHandler); } @@ -1443,7 +1478,9 @@ public class GpsLocationProvider implements LocationProviderInterface { } if (wasNavigating != mNavigating) { - mListenerHelper.onStatusChanged(mNavigating); + mListenerHelper.onGpsEnabledChanged(mNavigating); + mGpsMeasurementsProvider.onGpsEnabledChanged(mNavigating); + mGpsNavigationMessageProvider.onGpsEnabledChanged(mNavigating); // send an intent to notify that the GPS has been enabled or disabled Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); @@ -1596,6 +1633,11 @@ public class GpsLocationProvider implements LocationProviderInterface { mPeriodicTimeInjection = true; requestUtcTime(); } + + mGpsMeasurementsProvider.onCapabilitiesUpdated( + (capabilities & GPS_CAPABILITY_MEASUREMENTS) == GPS_CAPABILITY_MEASUREMENTS); + mGpsNavigationMessageProvider.onCapabilitiesUpdated( + (capabilities & GPS_CAPABILITY_NAV_MESSAGES) == GPS_CAPABILITY_NAV_MESSAGES); } /** diff --git a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java index 1c48257..0514e0c 100644 --- a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java @@ -18,7 +18,9 @@ package com.android.server.location; import android.location.GpsMeasurementsEvent; import android.location.IGpsMeasurementsListener; +import android.os.Handler; import android.os.RemoteException; +import android.util.Log; /** * An base implementation for GPS measurements provider. @@ -29,8 +31,10 @@ import android.os.RemoteException; */ public abstract class GpsMeasurementsProvider extends RemoteListenerHelper<IGpsMeasurementsListener> { - public GpsMeasurementsProvider() { - super("GpsMeasurementsProvider"); + private static final String TAG = "GpsMeasurementsProvider"; + + public GpsMeasurementsProvider(Handler handler) { + super(handler, TAG); } public void onMeasurementsAvailable(final GpsMeasurementsEvent event) { @@ -41,7 +45,56 @@ public abstract class GpsMeasurementsProvider listener.onGpsMeasurementsReceived(event); } }; - foreach(operation); } + + public void onCapabilitiesUpdated(boolean isGpsMeasurementsSupported) { + int status = isGpsMeasurementsSupported ? + GpsMeasurementsEvent.STATUS_READY : + GpsMeasurementsEvent.STATUS_NOT_SUPPORTED; + setSupported(isGpsMeasurementsSupported, new StatusChangedOperation(status)); + } + + @Override + protected ListenerOperation<IGpsMeasurementsListener> getHandlerOperation(int result) { + final int status; + switch (result) { + case RESULT_SUCCESS: + status = GpsMeasurementsEvent.STATUS_READY; + break; + case RESULT_NOT_AVAILABLE: + case RESULT_NOT_SUPPORTED: + case RESULT_INTERNAL_ERROR: + status = GpsMeasurementsEvent.STATUS_NOT_SUPPORTED; + break; + case RESULT_GPS_LOCATION_DISABLED: + status = GpsMeasurementsEvent.STATUS_GPS_LOCATION_DISABLED; + break; + default: + Log.v(TAG, "Unhandled addListener result: " + result); + return null; + } + 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 final int mStatus; + + public StatusChangedOperation(int status) { + mStatus = status; + } + + @Override + public void execute(IGpsMeasurementsListener listener) throws RemoteException { + listener.onStatusChanged(mStatus); + } + } } diff --git a/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java b/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java index fca7378..13d22fc 100644 --- a/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java @@ -18,7 +18,9 @@ package com.android.server.location; import android.location.GpsNavigationMessageEvent; import android.location.IGpsNavigationMessageListener; +import android.os.Handler; import android.os.RemoteException; +import android.util.Log; /** * An base implementation for GPS navigation messages provider. @@ -29,8 +31,10 @@ import android.os.RemoteException; */ public abstract class GpsNavigationMessageProvider extends RemoteListenerHelper<IGpsNavigationMessageListener> { - public GpsNavigationMessageProvider() { - super("GpsNavigationMessageProvider"); + private static final String TAG = "GpsNavigationMessageProvider"; + + public GpsNavigationMessageProvider(Handler handler) { + super(handler, TAG); } public void onNavigationMessageAvailable(final GpsNavigationMessageEvent event) { @@ -42,7 +46,57 @@ public abstract class GpsNavigationMessageProvider listener.onGpsNavigationMessageReceived(event); } }; - foreach(operation); } + + public void onCapabilitiesUpdated(boolean isGpsNavigationMessageSupported) { + int status = isGpsNavigationMessageSupported ? + GpsNavigationMessageEvent.STATUS_READY : + GpsNavigationMessageEvent.STATUS_NOT_SUPPORTED; + setSupported(isGpsNavigationMessageSupported, new StatusChangedOperation(status)); + } + + @Override + protected ListenerOperation<IGpsNavigationMessageListener> getHandlerOperation(int result) { + final int status; + switch (result) { + case RESULT_SUCCESS: + status = GpsNavigationMessageEvent.STATUS_READY; + break; + case RESULT_NOT_AVAILABLE: + case RESULT_NOT_SUPPORTED: + case RESULT_INTERNAL_ERROR: + status = GpsNavigationMessageEvent.STATUS_NOT_SUPPORTED; + break; + case RESULT_GPS_LOCATION_DISABLED: + status = GpsNavigationMessageEvent.STATUS_GPS_LOCATION_DISABLED; + break; + default: + Log.v(TAG, "Unhandled addListener result: " + result); + return null; + } + 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 + implements ListenerOperation<IGpsNavigationMessageListener> { + private final int mStatus; + + public StatusChangedOperation(int status) { + mStatus = status; + } + + @Override + public void execute(IGpsNavigationMessageListener listener) throws RemoteException { + listener.onStatusChanged(mStatus); + } + } } diff --git a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java index 27cf3d8..376b4a5 100644 --- a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java +++ b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java @@ -17,39 +17,64 @@ package com.android.server.location; import android.location.IGpsStatusListener; +import android.os.Handler; import android.os.RemoteException; /** * Implementation of a handler for {@link IGpsStatusListener}. */ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusListener> { - public GpsStatusListenerHelper() { - super("GpsStatusListenerHelper"); - } + public GpsStatusListenerHelper(Handler handler) { + super(handler, "GpsStatusListenerHelper"); - public void onFirstFix(final int timeToFirstFix) { - Operation operation = new Operation() { + Operation nullOperation = new Operation() { @Override - public void execute(IGpsStatusListener listener) throws RemoteException { - listener.onFirstFix(timeToFirstFix); - } + public void execute(IGpsStatusListener iGpsStatusListener) throws RemoteException {} }; + setSupported(GpsLocationProvider.isSupported(), nullOperation); + } + + @Override + protected boolean registerWithService() { + return true; + } + + @Override + protected void unregisterFromService() {} + + @Override + protected ListenerOperation<IGpsStatusListener> getHandlerOperation(int result) { + return null; + } + @Override + protected void handleGpsEnabledChanged(boolean enabled) { + Operation operation; + if (enabled) { + operation = new Operation() { + @Override + public void execute(IGpsStatusListener listener) throws RemoteException { + listener.onGpsStarted(); + } + }; + } else { + operation = new Operation() { + @Override + public void execute(IGpsStatusListener listener) throws RemoteException { + listener.onGpsStopped(); + } + }; + } foreach(operation); } - public void onStatusChanged(final boolean isNavigating) { + public void onFirstFix(final int timeToFirstFix) { Operation operation = new Operation() { @Override public void execute(IGpsStatusListener listener) throws RemoteException { - if (isNavigating) { - listener.onGpsStarted(); - } else { - listener.onGpsStopped(); - } + listener.onFirstFix(timeToFirstFix); } }; - foreach(operation); } @@ -76,7 +101,6 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi usedInFixMask); } }; - foreach(operation); } @@ -87,7 +111,6 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi listener.onNmeaReceived(timestamp, nmea); } }; - foreach(operation); } diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java index 451af18..402b601 100644 --- a/services/core/java/com/android/server/location/RemoteListenerHelper.java +++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java @@ -19,35 +19,41 @@ package com.android.server.location; import com.android.internal.util.Preconditions; import android.annotation.NonNull; +import android.os.Handler; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; import android.util.Log; -import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; /** * 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; + + private final Handler mHandler; private final String mTag; - private final HashMap<IBinder, LinkedListener> mListenerMap = - new HashMap<IBinder, LinkedListener>(); - protected RemoteListenerHelper(String name) { + private final HashMap<IBinder, LinkedListener> mListenerMap = new HashMap<>(); + + private boolean mIsRegistered; + private boolean mHasIsSupported; + private boolean mIsSupported; + + protected RemoteListenerHelper(Handler handler, String name) { Preconditions.checkNotNull(name); + mHandler = handler; mTag = name; } public boolean addListener(@NonNull TListener listener) { Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener."); - if (!isSupported()) { - Log.e(mTag, "Refused to add listener, the feature is not supported."); - return false; - } - IBinder binder = listener.asBinder(); LinkedListener deathListener = new LinkedListener(listener); synchronized (mListenerMap) { @@ -55,75 +61,126 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { // listener already added return true; } - try { binder.linkToDeath(deathListener, 0 /* flags */); } catch (RemoteException e) { // if the remote process registering the listener is already death, just swallow the - // exception and continue - Log.e(mTag, "Remote listener already died.", e); + // exception and return + Log.v(mTag, "Remote listener already died.", e); return false; } - mListenerMap.put(binder, deathListener); - if (mListenerMap.size() == 1) { - if (!registerWithService()) { - Log.e(mTag, "RegisterWithService failed, listener will be removed."); - removeListener(listener); - return false; - } + + // update statuses we already know about, starting from the ones that will never change + int result; + if (!isAvailableInPlatform()) { + result = RESULT_NOT_AVAILABLE; + } else if (mHasIsSupported && !mIsSupported) { + result = RESULT_NOT_SUPPORTED; + } else if (!isGpsEnabled()) { + result = RESULT_GPS_LOCATION_DISABLED; + } else if (!tryRegister()) { + // only attempt to register if GPS is enabled, otherwise we will register once GPS + // becomes available + result = RESULT_INTERNAL_ERROR; + } else if (mHasIsSupported && mIsSupported) { + result = RESULT_SUCCESS; + } else { + // at this point if the supported flag is not set, the notification will be sent + // asynchronously in the future + return true; } + post(listener, getHandlerOperation(result)); } - return true; } - public boolean removeListener(@NonNull TListener listener) { + public void removeListener(@NonNull TListener listener) { Preconditions.checkNotNull(listener, "Attempted to remove a 'null' listener."); - if (!isSupported()) { - Log.e(mTag, "Refused to remove listener, the feature is not supported."); - return false; - } - IBinder binder = listener.asBinder(); LinkedListener linkedListener; synchronized (mListenerMap) { linkedListener = mListenerMap.remove(binder); - if (mListenerMap.isEmpty() && linkedListener != null) { - unregisterFromService(); + if (mListenerMap.isEmpty()) { + tryUnregister(); } } - if (linkedListener != null) { binder.unlinkToDeath(linkedListener, 0 /* flags */); } - return true; } - protected abstract boolean isSupported(); + 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; } - protected void foreach(ListenerOperation operation) { - Collection<LinkedListener> linkedListeners; + protected void foreach(ListenerOperation<TListener> operation) { synchronized (mListenerMap) { - Collection<LinkedListener> values = mListenerMap.values(); - linkedListeners = new ArrayList<LinkedListener>(values); + foreachUnsafe(operation); } + } - for (LinkedListener linkedListener : linkedListeners) { - TListener listener = linkedListener.getUnderlyingListener(); - try { - operation.execute(listener); - } catch (RemoteException e) { - Log.e(mTag, "Error in monitored listener.", e); - removeListener(listener); - } + protected void setSupported(boolean value, ListenerOperation<TListener> notifier) { + synchronized (mListenerMap) { + mHasIsSupported = true; + mIsSupported = value; + foreachUnsafe(notifier); + } + } + + private void foreachUnsafe(ListenerOperation<TListener> operation) { + for (LinkedListener linkedListener : mListenerMap.values()) { + post(linkedListener.getUnderlyingListener(), operation); + } + } + + private void post(TListener listener, ListenerOperation<TListener> operation) { + if (operation != null) { + mHandler.post(new HandlerRunnable(listener, operation)); + } + } + + private boolean tryRegister() { + if (!mIsRegistered) { + mIsRegistered = registerWithService(); + } + return mIsRegistered; + } + + private void tryUnregister() { + if (!mIsRegistered) { + return; } + unregisterFromService(); + mIsRegistered = false; } private class LinkedListener implements IBinder.DeathRecipient { @@ -144,4 +201,23 @@ abstract class RemoteListenerHelper<TListener extends IInterface> { removeListener(mListener); } } + + private class HandlerRunnable implements Runnable { + private final TListener mListener; + private final ListenerOperation<TListener> mOperation; + + public HandlerRunnable(TListener listener, ListenerOperation<TListener> operation) { + mListener = listener; + mOperation = operation; + } + + @Override + public void run() { + try { + mOperation.execute(mListener); + } catch (RemoteException e) { + Log.v(mTag, "Error in monitored listener.", e); + } + } + } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 921b68b..9440697 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -90,6 +90,7 @@ 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 = @@ -139,6 +140,8 @@ 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); } /** @@ -248,6 +251,12 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { 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. + mAudioManagerInternal.adjustMasterVolumeForUid(direction, flags, packageName, uid); + return; + } int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); if (useSuggested) { if (AudioSystem.isStreamActive(stream, 0)) { diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 02c9fcb5..053c988 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -86,6 +86,7 @@ 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; @@ -104,6 +105,8 @@ 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 @@ -424,7 +427,7 @@ public class MediaSessionService extends SystemService implements Monitor { private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) { for (int i = mSessionsListeners.size() - 1; i >= 0; i--) { - if (mSessionsListeners.get(i).mListener == listener) { + if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) { return i; } } @@ -761,6 +764,7 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(); synchronized (mLock) { + pw.println(mSessionsListeners.size() + " sessions listeners."); int count = mAllSessions.size(); pw.println(count + " Sessions:"); for (int i = 0; i < count; i++) { @@ -805,7 +809,12 @@ public class MediaSessionService extends SystemService implements Monitor { + flags + ", suggestedStream=" + suggestedStream); } - if (session == null) { + boolean preferSuggestedStream = false; + if (isValidLocalStreamType(suggestedStream) + && AudioSystem.isStreamActive(suggestedStream, 0)) { + preferSuggestedStream = true; + } + if (session == null || preferSuggestedStream) { if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) { if (DEBUG) { @@ -814,8 +823,13 @@ public class MediaSessionService extends SystemService implements Monitor { return; } try { - mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags, - getContext().getOpPackageName()); + if (mUseMasterVolume) { + mAudioService.adjustMasterVolume(direction, flags, + getContext().getOpPackageName()); + } else { + mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags, + getContext().getOpPackageName()); + } } catch (RemoteException e) { Log.e(TAG, "Error adjusting default volume.", e); } @@ -959,6 +973,12 @@ public class MediaSessionService extends SystemService implements Monitor { return keyCode == KeyEvent.KEYCODE_HEADSETHOOK; } + // we only handle public stream types, which are 0-5 + private boolean isValidLocalStreamType(int streamType) { + return streamType >= AudioManager.STREAM_VOICE_CALL + && streamType <= AudioManager.STREAM_NOTIFICATION; + } + private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler); class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable, 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 cdfb656..e9b3f8b 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -91,7 +91,8 @@ public final class MediaProjectionManagerService extends SystemService public void onStart() { publishBinderService(Context.MEDIA_PROJECTION_SERVICE, new BinderService(), false /*allowIsolated*/); - mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback); + mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback, + MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } @Override @@ -325,7 +326,7 @@ public final class MediaProjectionManagerService extends SystemService final long token = Binder.clearCallingIdentity(); try { - dump(pw); + MediaProjectionManagerService.this.dump(pw); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java index 857b9e9..b5a450d 100644 --- a/services/core/java/com/android/server/net/IpConfigStore.java +++ b/services/core/java/com/android/server/net/IpConfigStore.java @@ -122,8 +122,10 @@ public class IpConfigStore { out.writeUTF(proxyProperties.getHost()); out.writeUTF(PROXY_PORT_KEY); out.writeInt(proxyProperties.getPort()); - out.writeUTF(EXCLUSION_LIST_KEY); - out.writeUTF(exclusionList); + if (exclusionList != null) { + out.writeUTF(EXCLUSION_LIST_KEY); + out.writeUTF(exclusionList); + } written = true; break; case PAC: diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/services/core/java/com/android/server/net/NetworkIdentitySet.java index 397f9f4..f230bb3 100644 --- a/services/core/java/com/android/server/net/NetworkIdentitySet.java +++ b/services/core/java/com/android/server/net/NetworkIdentitySet.java @@ -29,7 +29,8 @@ import java.util.HashSet; * * @hide */ -public class NetworkIdentitySet extends HashSet<NetworkIdentity> { +public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements + Comparable<NetworkIdentitySet> { private static final int VERSION_INIT = 1; private static final int VERSION_ADD_ROAMING = 2; private static final int VERSION_ADD_NETWORK_ID = 3; @@ -92,4 +93,14 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> { return null; } } + + @Override + public int compareTo(NetworkIdentitySet another) { + if (isEmpty()) return -1; + if (another.isEmpty()) return 1; + + final NetworkIdentity ident = iterator().next(); + final NetworkIdentity anotherIdent = another.iterator().next(); + return ident.compareTo(anotherIdent); + } } diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index 475482f..3ac20f7 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -22,15 +22,20 @@ 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.text.format.DateUtils.SECOND_IN_MILLIS; +import static android.text.format.DateUtils.WEEK_IN_MILLIS; +import android.net.ConnectivityManager; import android.net.NetworkIdentity; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; -import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.AtomicFile; +import libcore.io.IoUtils; + import com.android.internal.util.ArrayUtils; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; @@ -44,15 +49,13 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.net.ProtocolException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.Map; import java.util.Objects; -import libcore.io.IoUtils; - /** * Collection of {@link NetworkStatsHistory}, stored based on combined key of * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. @@ -70,7 +73,7 @@ public class NetworkStatsCollection implements FileRotator.Reader { private static final int VERSION_UNIFIED_INIT = 16; - private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap(); + private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>(); private final long mBucketDuration; @@ -145,12 +148,13 @@ public class NetworkStatsCollection implements FileRotator.Reader { NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) { final NetworkStatsHistory combined = new NetworkStatsHistory( mBucketDuration, estimateBuckets(), fields); - for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) { - final Key key = entry.getKey(); + 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 && templateMatches(template, key.ident)) { - combined.recordHistory(entry.getValue(), start, end); + final NetworkStatsHistory value = mStats.valueAt(i); + combined.recordHistory(value, start, end); } } return combined; @@ -170,11 +174,11 @@ public class NetworkStatsCollection implements FileRotator.Reader { // shortcut when we know stats will be empty if (start == end) return stats; - for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) { - final Key key = mapEntry.getKey(); + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); if (templateMatches(template, key.ident)) { - final NetworkStatsHistory history = mapEntry.getValue(); - historyEntry = history.getValues(start, end, now, historyEntry); + final NetworkStatsHistory value = mStats.valueAt(i); + historyEntry = value.getValues(start, end, now, historyEntry); entry.iface = IFACE_ALL; entry.uid = key.uid; @@ -225,8 +229,10 @@ public class NetworkStatsCollection implements FileRotator.Reader { * into this collection. */ public void recordCollection(NetworkStatsCollection another) { - for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) { - recordHistory(entry.getKey(), entry.getValue()); + for (int i = 0; i < another.mStats.size(); i++) { + final Key key = another.mStats.keyAt(i); + final NetworkStatsHistory value = another.mStats.valueAt(i); + recordHistory(key, value); } } @@ -460,7 +466,7 @@ public class NetworkStatsCollection implements FileRotator.Reader { } private int estimateBuckets() { - return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5) + return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5) / mBucketDuration); } @@ -482,6 +488,54 @@ public class NetworkStatsCollection implements FileRotator.Reader { } } + public void dumpCheckin(PrintWriter pw, long start, long end) { + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth"); + dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt"); + } + + /** + * Dump all contained stats that match requested parameters, but group + * together all matching {@link NetworkTemplate} under a single prefix. + */ + private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, + String groupPrefix) { + final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>(); + + // Walk through all history, grouping by matching network templates + for (int i = 0; i < mStats.size(); i++) { + final Key key = mStats.keyAt(i); + final NetworkStatsHistory value = mStats.valueAt(i); + + if (!templateMatches(groupTemplate, key.ident)) continue; + + final Key groupKey = new Key(null, key.uid, key.set, key.tag); + NetworkStatsHistory groupHistory = grouped.get(groupKey); + if (groupHistory == null) { + groupHistory = new NetworkStatsHistory(value.getBucketDuration()); + grouped.put(groupKey, groupHistory); + } + groupHistory.recordHistory(value, start, end); + } + + for (int i = 0; i < grouped.size(); i++) { + final Key key = grouped.keyAt(i); + final NetworkStatsHistory value = grouped.valueAt(i); + + if (value.size() == 0) continue; + + pw.print("c,"); + pw.print(groupPrefix); pw.print(','); + pw.print(key.uid); pw.print(','); + pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(','); + pw.print(key.tag); + pw.println(); + + value.dumpCheckin(pw); + } + } + /** * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} * in the given {@link NetworkIdentitySet}. @@ -528,7 +582,20 @@ public class NetworkStatsCollection implements FileRotator.Reader { @Override public int compareTo(Key another) { - return Integer.compare(uid, another.uid); + int res = 0; + if (ident != null && another.ident != null) { + res = ident.compareTo(another.ident); + } + if (res == 0) { + res = Integer.compare(uid, another.uid); + } + if (res == 0) { + res = Integer.compare(set, another.set); + } + if (res == 0) { + res = Integer.compare(tag, another.tag); + } + return res; } } } diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index cea084b..d5d7667 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -41,6 +41,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.HashSet; @@ -124,23 +125,36 @@ public class NetworkStatsRecorder { * as reference is valid. */ public NetworkStatsCollection getOrLoadCompleteLocked() { - NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; - if (complete == null) { - if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie); - try { - complete = new NetworkStatsCollection(mBucketDuration); - mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE); - complete.recordCollection(mPending); - mComplete = new WeakReference<NetworkStatsCollection>(complete); - } catch (IOException e) { - Log.wtf(TAG, "problem completely reading network stats", e); - recoverFromWtf(); - } catch (OutOfMemoryError e) { - Log.wtf(TAG, "problem completely reading network stats", e); - recoverFromWtf(); - } + NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; + if (res == null) { + res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE); + mComplete = new WeakReference<NetworkStatsCollection>(res); } - return complete; + return res; + } + + public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) { + NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; + if (res == null) { + res = loadLocked(start, end); + } + return res; + } + + private NetworkStatsCollection loadLocked(long start, long end) { + if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie); + final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration); + try { + mRotator.readMatching(res, start, end); + res.recordCollection(mPending); + } catch (IOException e) { + Log.wtf(TAG, "problem completely reading network stats", e); + recoverFromWtf(); + } catch (OutOfMemoryError e) { + Log.wtf(TAG, "problem completely reading network stats", e); + recoverFromWtf(); + } + return res; } /** @@ -384,6 +398,11 @@ public class NetworkStatsRecorder { } } + public void dumpCheckin(PrintWriter pw, long start, long end) { + // Only load and dump stats from the requested window + getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end); + } + /** * Recover from {@link FileRotator} failure by dumping state to * {@link DropBoxManager} and deleting contents. diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index b5aa4d8..61f9a26 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -45,7 +45,6 @@ import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES; import static android.provider.Settings.Global.NETSTATS_DEV_ROTATE_AGE; import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES; import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL; -import static android.provider.Settings.Global.NETSTATS_REPORT_XT_OVER_DEV; import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED; import static android.provider.Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE; import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION; @@ -105,6 +104,7 @@ import android.provider.Settings; import android.provider.Settings.Global; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; +import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; @@ -184,7 +184,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public long getPollInterval(); public long getTimeCacheMaxAge(); public boolean getSampleEnabled(); - public boolean getReportXtOverDev(); public static class Config { public final long bucketDuration; @@ -229,8 +228,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private NetworkStatsRecorder mUidRecorder; private NetworkStatsRecorder mUidTagRecorder; - /** Cached {@link #mDevRecorder} stats. */ - private NetworkStatsCollection mDevStatsCached; /** Cached {@link #mXtRecorder} stats. */ private NetworkStatsCollection mXtStatsCached; @@ -305,7 +302,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // read historical network stats from disk, since policy service // might need them right away. - mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked(); mXtStatsCached = mXtRecorder.getOrLoadCompleteLocked(); // bootstrap initial stats to prevent double-counting later @@ -386,7 +382,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mUidRecorder = null; mUidTagRecorder = null; - mDevStatsCached = null; mXtStatsCached = null; mSystemReady = false; @@ -523,48 +518,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Return network summary, splicing between {@link #mDevStatsCached} - * and {@link #mXtStatsCached} when appropriate. + * Return network summary, splicing between DEV and XT stats when + * appropriate. */ private NetworkStats internalGetSummaryForNetwork( NetworkTemplate template, long start, long end) { - if (!mSettings.getReportXtOverDev()) { - // shortcut when XT reporting disabled - return mDevStatsCached.getSummary(template, start, end); - } - - // splice stats between DEV and XT, switching over from DEV to XT at - // first atomic bucket. - final long firstAtomicBucket = mXtStatsCached.getFirstAtomicBucketMillis(); - final NetworkStats dev = mDevStatsCached.getSummary( - template, Math.min(start, firstAtomicBucket), Math.min(end, firstAtomicBucket)); - final NetworkStats xt = mXtStatsCached.getSummary( - template, Math.max(start, firstAtomicBucket), Math.max(end, firstAtomicBucket)); - - xt.combineAllValues(dev); - return xt; + // We've been using pure XT stats long enough that we no longer need to + // splice DEV and XT together. + return mXtStatsCached.getSummary(template, start, end); } /** - * Return network history, splicing between {@link #mDevStatsCached} - * and {@link #mXtStatsCached} when appropriate. + * Return network history, splicing between DEV and XT stats when + * appropriate. */ private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, int fields) { - if (!mSettings.getReportXtOverDev()) { - // shortcut when XT reporting disabled - return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields); - } - - // splice stats between DEV and XT, switching over from DEV to XT at - // first atomic bucket. - final long firstAtomicBucket = mXtStatsCached.getFirstAtomicBucketMillis(); - final NetworkStatsHistory dev = mDevStatsCached.getHistory( - template, UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, firstAtomicBucket); - final NetworkStatsHistory xt = mXtStatsCached.getHistory( - template, UID_ALL, SET_ALL, TAG_NONE, fields, firstAtomicBucket, Long.MAX_VALUE); - - xt.recordEntireHistory(dev); - return xt; + // We've been using pure XT stats long enough that we no longer need to + // splice DEV and XT together. + return mXtStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields); } @Override @@ -1124,12 +1095,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + protected void dump(FileDescriptor fd, PrintWriter rawWriter, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); + long duration = DateUtils.DAY_IN_MILLIS; final HashSet<String> argSet = new HashSet<String>(); for (String arg : args) { argSet.add(arg); + + if (arg.startsWith("--duration=")) { + try { + duration = Long.parseLong(arg.substring(11)); + } catch (NumberFormatException ignored) { + } + } } // usage: dumpsys netstats --full --uid --tag --poll --checkin @@ -1139,7 +1118,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail"); final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail"); - final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + final IndentingPrintWriter pw = new IndentingPrintWriter(rawWriter, " "); synchronized (mStatsLock) { if (poll) { @@ -1149,13 +1128,24 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } if (checkin) { - // list current stats files to verify rotation - pw.println("Current files:"); - pw.increaseIndent(); - for (String file : mBaseDir.list()) { - pw.println(file); + final long end = System.currentTimeMillis(); + final long start = end - duration; + + pw.print("v1,"); + pw.print(start / SECOND_IN_MILLIS); pw.print(','); + pw.print(end / SECOND_IN_MILLIS); pw.println(); + + pw.println("xt"); + mXtRecorder.dumpCheckin(rawWriter, start, end); + + if (includeUid) { + pw.println("uid"); + mUidRecorder.dumpCheckin(rawWriter, start, end); + } + if (includeTag) { + pw.println("tag"); + mUidTagRecorder.dumpCheckin(rawWriter, start, end); } - pw.decreaseIndent(); return; } @@ -1329,10 +1319,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true); } @Override - public boolean getReportXtOverDev() { - return getGlobalBoolean(NETSTATS_REPORT_XT_OVER_DEV, true); - } - @Override public Config getDevConfig() { return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 05ad1fe..5de1a64 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -51,7 +51,9 @@ public class ConditionProviders extends ManagedServices { = new ArrayMap<IBinder, IConditionListener>(); private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>(); private final CountdownConditionProvider mCountdown = new CountdownConditionProvider(); + private final NextAlarmTracker mNextAlarmTracker; private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider(); + private final NextAlarmConditionProvider mNextAlarm = new NextAlarmConditionProvider(); private Condition mExitCondition; private ComponentName mExitConditionComponent; @@ -62,6 +64,7 @@ public class ConditionProviders extends ManagedServices { mZenModeHelper = zenModeHelper; mZenModeHelper.addCallback(new ZenModeHelperCallback()); loadZenConfig(); + mNextAlarmTracker = new NextAlarmTracker(context); } @Override @@ -99,6 +102,8 @@ public class ConditionProviders extends ManagedServices { } mCountdown.dump(pw, filter); mDowntime.dump(pw, filter); + mNextAlarm.dump(pw, filter); + mNextAlarmTracker.dump(pw, filter); } @Override @@ -109,6 +114,7 @@ public class ConditionProviders extends ManagedServices { @Override public void onBootPhaseAppsCanStart() { super.onBootPhaseAppsCanStart(); + mNextAlarmTracker.init(); mCountdown.attachBase(mContext); registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT, UserHandle.USER_OWNER); @@ -116,6 +122,16 @@ public class ConditionProviders extends ManagedServices { registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT, UserHandle.USER_OWNER); mDowntime.setCallback(new DowntimeCallback()); + mNextAlarm.attachBase(mContext); + registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT, + UserHandle.USER_OWNER); + mNextAlarm.setCallback(new NextAlarmCallback()); + } + + @Override + public void onUserSwitched() { + super.onUserSwitched(); + mNextAlarmTracker.onUserSwitched(); } @Override @@ -244,9 +260,11 @@ 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); r.info = info; r.condition = c; - // if manual, exit zen if false (or failed) + // 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; @@ -259,6 +277,10 @@ public class ConditionProviders extends ManagedServices { "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 @@ -534,23 +556,42 @@ public class ConditionProviders extends ManagedServices { private class DowntimeCallback implements DowntimeConditionProvider.Callback { @Override - public void onDowntimeChanged(boolean inDowntime) { + public void onDowntimeChanged(int downtimeMode) { final int mode = mZenModeHelper.getZenMode(); final ZenModeConfig config = mZenModeHelper.getConfig(); - // enter downtime - if (inDowntime && mode == Global.ZEN_MODE_OFF && config != null) { + final boolean inDowntime = downtimeMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + || downtimeMode == Global.ZEN_MODE_NO_INTERRUPTIONS; + final boolean downtimeCurrent = mDowntime.isDowntimeCondition(mExitCondition); + // enter downtime, or update mode if reconfigured during an active downtime + if (inDowntime && (mode == Global.ZEN_MODE_OFF || downtimeCurrent) && config != null) { final Condition condition = mDowntime.createCondition(config.toDowntimeInfo(), - Condition.STATE_TRUE); - mZenModeHelper.setZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtimeEnter"); + config.sleepNone, Condition.STATE_TRUE); + mZenModeHelper.setZenMode(downtimeMode, "downtimeEnter"); setZenModeCondition(condition, "downtime"); } // exit downtime - if (!inDowntime && mDowntime.isDowntimeCondition(mExitCondition) - && (mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS - || mode == Global.ZEN_MODE_NO_INTERRUPTIONS)) { + if (!inDowntime && downtimeCurrent && (mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + || mode == Global.ZEN_MODE_NO_INTERRUPTIONS)) { mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit"); } } + + @Override + public NextAlarmTracker getNextAlarmTracker() { + return mNextAlarmTracker; + } + } + + private class NextAlarmCallback implements NextAlarmConditionProvider.Callback { + @Override + public boolean isInDowntime() { + return mDowntime.isInDowntime(); + } + + @Override + public NextAlarmTracker getNextAlarmTracker() { + return mNextAlarmTracker; + } } private static class ConditionRecord { diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java index efe47c3..097589a 100644 --- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java +++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java @@ -18,12 +18,14 @@ 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; @@ -62,9 +64,11 @@ public class DowntimeConditionProvider extends ConditionProviderService { private final Calendar mCalendar = Calendar.getInstance(); private final Context mContext = this; private final ArraySet<Integer> mDays = new ArraySet<Integer>(); + private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>(); private boolean mConnected; - private boolean mInDowntime; + private NextAlarmTracker mTracker; + private int mDowntimeMode; private ZenModeConfig mConfig; private Callback mCallback; @@ -75,7 +79,8 @@ public class DowntimeConditionProvider extends ConditionProviderService { public void dump(PrintWriter pw, DumpFilter filter) { pw.println(" DowntimeConditionProvider:"); pw.print(" mConnected="); pw.println(mConnected); - pw.print(" mInDowntime="); pw.println(mInDowntime); + pw.print(" mDowntimeMode="); pw.println(Global.zenModeToString(mDowntimeMode)); + pw.print(" mFiredAlarms="); pw.println(mFiredAlarms); } public void attachBase(Context base) { @@ -100,12 +105,15 @@ public class DowntimeConditionProvider extends ConditionProviderService { filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); mContext.registerReceiver(mReceiver, filter); + mTracker = mCallback.getNextAlarmTracker(); + mTracker.addCallback(mTrackerCallback); init(); } @Override public void onDestroy() { if (DEBUG) Slog.d(TAG, "onDestroy"); + mTracker.removeCallback(mTrackerCallback); mConnected = false; } @@ -113,8 +121,9 @@ public class DowntimeConditionProvider extends ConditionProviderService { public void onRequestConditions(int relevance) { if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance); if ((relevance & Condition.FLAG_RELEVANT_NOW) != 0) { - if (mInDowntime && mConfig != null) { - notifyCondition(createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE)); + if (isInDowntime() && mConfig != null) { + notifyCondition(createCondition(mConfig.toDowntimeInfo(), mConfig.sleepNone, + Condition.STATE_TRUE)); } } } @@ -124,10 +133,10 @@ public class DowntimeConditionProvider extends ConditionProviderService { if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId); final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId); if (downtime != null && mConfig != null) { - final int state = mConfig.toDowntimeInfo().equals(downtime) && mInDowntime + final int state = mConfig.toDowntimeInfo().equals(downtime) && isInDowntime() ? Condition.STATE_TRUE : Condition.STATE_FALSE; if (DEBUG) Slog.d(TAG, "notify condition state: " + Condition.stateToString(state)); - notifyCondition(createCondition(downtime, state)); + notifyCondition(createCondition(downtime, mConfig.sleepNone, state)); } } @@ -146,19 +155,28 @@ public class DowntimeConditionProvider extends ConditionProviderService { } public boolean isInDowntime() { - return mInDowntime; + return mDowntimeMode != Global.ZEN_MODE_OFF; } - public Condition createCondition(DowntimeInfo downtime, int state) { + public Condition createCondition(DowntimeInfo downtime, boolean orAlarm, 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 time = getTime(System.currentTimeMillis(), downtime.endHour, downtime.endMinute); - final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(time)); + final long now = System.currentTimeMillis(); + long endTime = getTime(now, downtime.endHour, downtime.endMinute); + if (orAlarm) { + 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); - return new Condition(id, summary, "", "", 0, state, Condition.FLAG_RELEVANT_NOW); + final String line1 = mContext.getString(R.string.downtime_condition_line_one); + return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW); } public boolean isDowntimeCondition(Condition condition) { @@ -189,30 +207,50 @@ public class DowntimeConditionProvider extends ConditionProviderService { if (end < start) { end = addDays(end, 1); } - return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end); + final boolean orAlarm = mConfig.sleepNone; + return isInDowntime(-1, time, start, end, orAlarm) + || isInDowntime(0, time, start, end, orAlarm); } - private boolean isInDowntime(int daysOffset, long time, long start, long end) { + private boolean isInDowntime(int daysOffset, long time, long start, long end, boolean orAlarm) { final int n = Calendar.SATURDAY; final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1; start = addDays(start, daysOffset); end = addDays(end, daysOffset); + if (orAlarm) { + end = findFiredAlarm(start, end); + } return mDays.contains(day) && time >= start && time < end; } + private long findFiredAlarm(long start, long end) { + final int N = mFiredAlarms.size(); + for (int i = 0; i < N; i++) { + final long firedAlarm = mFiredAlarms.valueAt(i); + if (firedAlarm > start && firedAlarm < end) { + return firedAlarm; + } + } + return end; + } + private void reevaluateDowntime() { - final boolean inDowntime = isInDowntime(System.currentTimeMillis()); - if (DEBUG) Slog.d(TAG, "inDowntime=" + inDowntime); - if (inDowntime == mInDowntime) return; - Slog.i(TAG, (inDowntime ? "Entering" : "Exiting" ) + " downtime"); - mInDowntime = inDowntime; - ZenLog.traceDowntime(mInDowntime, getDayOfWeek(System.currentTimeMillis()), mDays); + final long now = System.currentTimeMillis(); + final boolean inDowntimeNow = isInDowntime(now); + final int downtimeMode = inDowntimeNow ? (mConfig.sleepNone + ? Global.ZEN_MODE_NO_INTERRUPTIONS : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + : Global.ZEN_MODE_OFF; + if (DEBUG) Slog.d(TAG, "downtimeMode=" + downtimeMode); + if (downtimeMode == mDowntimeMode) return; + mDowntimeMode = downtimeMode; + Slog.i(TAG, (isInDowntime() ? "Entering" : "Exiting" ) + " downtime"); + ZenLog.traceDowntime(mDowntimeMode, getDayOfWeek(now), mDays); fireDowntimeChanged(); } private void fireDowntimeChanged() { if (mCallback != null) { - mCallback.onDowntimeChanged(mInDowntime); + mCallback.onDowntimeChanged(mDowntimeMode); } } @@ -255,11 +293,14 @@ public class DowntimeConditionProvider extends ConditionProviderService { time = addDays(time, 1); } final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, - new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); + 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, %s in the future, now=%s", - action, ts(time), time - now, ts(now))); + 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); } } @@ -268,6 +309,35 @@ public class DowntimeConditionProvider extends ConditionProviderService { return new Date(time) + " (" + time + ")"; } + private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { + if (!booted) return; // we don't know yet + // update condition description if we're in downtime (mode = none) + if (isInDowntime() && mConfig != null && mConfig.sleepNone) { + notifyCondition(createCondition(mConfig.toDowntimeInfo(), true /*orAlarm*/, + Condition.STATE_TRUE)); + } + if (nextAlarm == null) return; // not fireable + if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm)); + if (System.currentTimeMillis() > wakeupTime) { + if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime)); + trimFiredAlarms(); + mFiredAlarms.add(wakeupTime); + } + reevaluateDowntime(); + } + + private void trimFiredAlarms() { + // remove fired alarms over 2 days old + final long keepAfter = System.currentTimeMillis() - 2 * 24 * 60 * 60 * 1000; + final int N = mFiredAlarms.size(); + for (int i = N - 1; i >= 0; i--) { + final long firedAlarm = mFiredAlarms.valueAt(i); + if (firedAlarm < keepAfter) { + mFiredAlarms.removeAt(i); + } + } + } + private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -280,6 +350,10 @@ public class DowntimeConditionProvider extends ConditionProviderService { } 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); } @@ -288,7 +362,15 @@ public class DowntimeConditionProvider extends ConditionProviderService { } }; + private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() { + @Override + public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { + DowntimeConditionProvider.this.onEvaluateNextAlarm(nextAlarm, wakeupTime, booted); + } + }; + public interface Callback { - void onDowntimeChanged(boolean inDowntime); + void onDowntimeChanged(int downtimeMode); + NextAlarmTracker getNextAlarmTracker(); } } diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java new file mode 100644 index 0000000..35bbaa0 --- /dev/null +++ b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java @@ -0,0 +1,210 @@ +/* + * 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.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 (unless the built-in downtime condition is also available). + * + * <p>When this next alarm is selected as the active exit condition, follow subsequent changes + * to the user's next alarm, assuming it remains within the 12-hr window. + * + * <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 String NEXT_ALARM_PATH = "next_alarm"; + public static final ComponentName COMPONENT = + new ComponentName("android", NextAlarmConditionProvider.class.getName()); + + private final Context mContext = this; + + private NextAlarmTracker mTracker; + private boolean mConnected; + private long mLookaheadThreshold; + private Callback mCallback; + private Uri mCurrentSubscription; + + public NextAlarmConditionProvider() { + if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()"); + } + + 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(" mCurrentSubscription="); pw.println(mCurrentSubscription); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + @Override + public void onConnected() { + if (DEBUG) Slog.d(TAG, "onConnected"); + mLookaheadThreshold = mContext.getResources() + .getInteger(R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS; + mConnected = true; + mTracker = mCallback.getNextAlarmTracker(); + 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 (!mConnected || (relevance & Condition.FLAG_RELEVANT_NOW) == 0) return; + + final AlarmClockInfo nextAlarm = mTracker.getNextAlarm(); + if (nextAlarm == null) return; // no next alarm + if (mCallback != null && mCallback.isInDowntime()) return; // prefer downtime condition + if (!isWithinLookaheadThreshold(nextAlarm)) return; // alarm not within window + + // next alarm exists, and is within the configured lookahead threshold + notifyCondition(newConditionId(), nextAlarm, Condition.STATE_TRUE, "request"); + } + + @Override + public void onSubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); + if (!isNextAlarmCondition(conditionId)) { + notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition"); + return; + } + mCurrentSubscription = conditionId; + mTracker.evaluate(); + } + + @Override + public void onUnsubscribe(Uri conditionId) { + if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); + if (conditionId != null && conditionId.equals(mCurrentSubscription)) { + mCurrentSubscription = null; + } + } + + 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() { + return new Uri.Builder().scheme(Condition.SCHEME) + .authority(ZenModeConfig.SYSTEM_AUTHORITY) + .appendPath(NEXT_ALARM_PATH) + .appendPath(Integer.toString(mTracker.getCurrentUserId())) + .build(); + } + + private boolean isNextAlarmCondition(Uri conditionId) { + return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME) + && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) + && conditionId.getPathSegments().size() == 2 + && conditionId.getPathSegments().get(0).equals(NEXT_ALARM_PATH) + && conditionId.getPathSegments().get(1) + .equals(Integer.toString(mTracker.getCurrentUserId())); + } + + private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { + final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm); + if (DEBUG) Slog.d(TAG, "onEvaluate mCurrentSubscription=" + mCurrentSubscription + + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime) + + " withinThreshold=" + withinThreshold + + " booted=" + booted); + if (mCurrentSubscription == null) return; // no one cares + if (!booted) { + // we don't know yet + notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_UNKNOWN, "!booted"); + return; + } + if (!withinThreshold) { + // next alarm outside threshold or in the past, condition = false + notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within"); + mCurrentSubscription = null; + return; + } + // next alarm in the future and within threshold, condition = true + notifyCondition(mCurrentSubscription, 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); + } + }; + + public interface Callback { + boolean isInDowntime(); + NextAlarmTracker getNextAlarmTracker(); + } +} diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java new file mode 100644 index 0000000..234f545 --- /dev/null +++ b/services/core/java/com/android/server/notification/NextAlarmTracker.java @@ -0,0 +1,263 @@ +/* + * 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/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 97f0a1e..24fc455 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -20,6 +20,7 @@ public interface NotificationDelegate { void onSetDisabled(int status); void onClearAll(int callingUid, int callingPid, int userId); void onNotificationClick(int callingUid, int callingPid, String key); + void onNotificationActionClick(int callingUid, int callingPid, String key, int actionIndex); void onNotificationClear(int callingUid, int callingPid, String pkg, String tag, int id, int userId); void onNotificationError(int callingUid, int callingPid, diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 22f060f..d60c57f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -41,6 +41,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.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -541,6 +542,20 @@ public class NotificationManagerService extends SystemService { } @Override + public void onNotificationActionClick(int callingUid, int callingPid, String key, + int actionIndex) { + synchronized (mNotificationList) { + EventLogTags.writeNotificationActionClicked(key, actionIndex); + NotificationRecord r = mNotificationsByKey.get(key); + if (r == null) { + Log.w(TAG, "No notification with key: " + key); + return; + } + // TODO: Log action click via UsageStats. + } + } + + @Override public void onNotificationClear(int callingUid, int callingPid, String pkg, String tag, int id, int userId) { cancelNotification(callingUid, callingPid, pkg, tag, id, 0, @@ -643,7 +658,7 @@ public class NotificationManagerService extends SystemService { } }; - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -659,6 +674,8 @@ public class NotificationManagerService extends SystemService { || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED)) || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART)) || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { + int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, + UserHandle.USER_ALL); String pkgList[] = null; boolean queryReplace = queryRemove && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); @@ -679,8 +696,10 @@ public class NotificationManagerService extends SystemService { if (packageChanged) { // We cancel notifications for packages which have just been disabled try { - final int enabled = getContext().getPackageManager() - .getApplicationEnabledSetting(pkgName); + final IPackageManager pm = AppGlobals.getPackageManager(); + final int enabled = pm.getApplicationEnabledSetting(pkgName, + changeUserId != UserHandle.USER_ALL ? changeUserId : + UserHandle.USER_OWNER); if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { cancelNotifications = false; @@ -691,6 +710,8 @@ public class NotificationManagerService extends SystemService { if (DBG) { Slog.i(TAG, "Exception trying to look up app enabled setting", e); } + } catch (RemoteException e) { + // Failed to talk to PackageManagerService Should never happen! } } pkgList = new String[]{pkgName}; @@ -700,13 +721,22 @@ public class NotificationManagerService extends SystemService { for (String pkgName : pkgList) { if (cancelNotifications) { cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, 0, 0, !queryRestart, - UserHandle.USER_ALL, REASON_PACKAGE_CHANGED, null); + changeUserId, REASON_PACKAGE_CHANGED, null); } } } mListeners.onPackagesChanged(queryReplace, pkgList); mConditionProviders.onPackagesChanged(queryReplace, pkgList); - } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + } + } + }; + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(Intent.ACTION_SCREEN_ON)) { // Keep track of screen on/off state, but do not turn off the notification light // until user passes through the lock screen or views the notification. mScreenOn = true; @@ -889,6 +919,7 @@ public class NotificationManagerService extends SystemService { filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_ADDED); getContext().registerReceiver(mIntentReceiver, filter); + IntentFilter pkgFilter = new IntentFilter(); pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -896,9 +927,12 @@ public class NotificationManagerService extends SystemService { pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); pkgFilter.addDataScheme("package"); - getContext().registerReceiver(mIntentReceiver, pkgFilter); + getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null, + null); + IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - getContext().registerReceiver(mIntentReceiver, sdFilter); + getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null, + null); mSettingsObserver = new SettingsObserver(mHandler); @@ -1698,14 +1732,6 @@ public class NotificationManagerService extends SystemService { } } - // This conditional is a dirty hack to limit the logging done on - // behalf of the download manager without affecting other apps. - if (!pkg.equals("com.android.providers.downloads") - || Log.isLoggable("DownloadManager", Log.VERBOSE)) { - EventLogTags.writeNotificationEnqueue(callingUid, callingPid, - pkg, id, tag, userId, notification.toString()); - } - if (pkg == null || notification == null) { throw new IllegalArgumentException("null not allowed: pkg=" + pkg + " id=" + id + " notification=" + notification); @@ -1755,6 +1781,14 @@ public class NotificationManagerService extends SystemService { } mRankingHelper.extractSignals(r); + // This conditional is a dirty hack to limit the logging done on + // behalf of the download manager without affecting other apps. + if (!pkg.equals("com.android.providers.downloads") + || Log.isLoggable("DownloadManager", Log.VERBOSE)) { + EventLogTags.writeNotificationEnqueue(callingUid, callingPid, + pkg, id, tag, userId, notification.toString(), + (old != null) ? 1 : 0); + } // 3. Apply local rules // blocked apps @@ -1835,8 +1869,8 @@ public class NotificationManagerService extends SystemService { final Notification notification = record.sbn.getNotification(); // Should this notification make noise, vibe, or use the LED? - final boolean canInterrupt = (record.score >= SCORE_INTERRUPTION_THRESHOLD) && - !record.isIntercepted(); + final boolean aboveThreshold = record.score >= SCORE_INTERRUPTION_THRESHOLD; + final boolean canInterrupt = aboveThreshold && !record.isIntercepted(); if (DBG || record.isIntercepted()) Slog.v(TAG, "pkg=" + record.sbn.getPackageName() + " canInterrupt=" + canInterrupt + @@ -1897,12 +1931,7 @@ public class NotificationManagerService extends SystemService { if (hasValidSound) { boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; - AudioAttributes audioAttributes; - if (notification.audioAttributes != null) { - audioAttributes = notification.audioAttributes; - } else { - audioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT; - } + AudioAttributes audioAttributes = audioAttributesForNotification(notification); mSoundNotification = record; // do not play notifications if stream volume is 0 (typically because // ringer mode is silent) or if there is a user of exclusive audio focus @@ -1980,7 +2009,7 @@ public class NotificationManagerService extends SystemService { if (mLedNotification != null && record.getKey().equals(mLedNotification.getKey())) { mLedNotification = null; } - if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && canInterrupt) { + if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold) { mLights.add(record.getKey()); updateLightsLocked(); if (mUseAttentionLight) { @@ -1996,7 +2025,9 @@ public class NotificationManagerService extends SystemService { } private static AudioAttributes audioAttributesForNotification(Notification n) { - if (n.audioAttributes != null) { + if (n.audioAttributes != null + && !Notification.AUDIO_ATTRIBUTES_DEFAULT.equals(n.audioAttributes)) { + // the audio attributes are set and different from the default, use them return n.audioAttributes; } else if (n.audioStreamType >= 0 && n.audioStreamType < AudioSystem.getNumStreamTypes()) { // the stream type is valid, use it @@ -2358,6 +2389,8 @@ public class NotificationManagerService extends SystemService { // Save it for users of getHistoricalNotifications() mArchive.record(r.sbn); + + EventLogTags.writeNotificationCanceled(r.getKey(), reason); } /** diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index 6cc5e0e..1a3da79 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -34,6 +34,7 @@ import java.util.Date; public class ZenLog { private static final String TAG = "ZenLog"; + private static final boolean DEBUG = Build.IS_DEBUGGABLE; private static final int SIZE = Build.IS_DEBUGGABLE ? 100 : 20; @@ -74,8 +75,8 @@ public class ZenLog { append(TYPE_SET_RINGER_MODE, ringerModeToString(ringerMode)); } - public static void traceDowntime(boolean inDowntime, int day, ArraySet<Integer> days) { - append(TYPE_DOWNTIME, inDowntime + ",day=" + day + ",days=" + days); + public static void traceDowntime(int downtimeMode, int day, ArraySet<Integer> days) { + append(TYPE_DOWNTIME, zenModeToString(downtimeMode) + ",day=" + day + ",days=" + days); } public static void traceSetZenMode(int mode, String reason) { @@ -166,7 +167,7 @@ public class ZenLog { sSize++; } } - Slog.d(TAG, typeToString(type) + ": " + msg); + if (DEBUG) Slog.d(TAG, typeToString(type) + ": " + msg); } public static void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index a7eebf8..cb9a45e 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -23,9 +23,9 @@ import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.ServiceManager; +import android.util.ArraySet; import android.util.Log; -import java.util.HashSet; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -59,7 +59,7 @@ public class BackgroundDexOptService extends JobService { if (pm.isStorageLow()) { return false; } - final HashSet<String> pkgs = pm.getPackagesThatNeedDexOpt(); + final ArraySet<String> pkgs = pm.getPackagesThatNeedDexOpt(); if (pkgs == null) { return false; } diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java index 203d990..6d18531 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java @@ -141,4 +141,11 @@ class CrossProfileIntentFilter extends IntentFilter { return "CrossProfileIntentFilter{0x" + Integer.toHexString(System.identityHashCode(this)) + " " + Integer.toString(mTargetUserId) + "}"; } + + 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/GrantedPermissions.java b/services/core/java/com/android/server/pm/GrantedPermissions.java index 14258a4..8f0f935 100644 --- a/services/core/java/com/android/server/pm/GrantedPermissions.java +++ b/services/core/java/com/android/server/pm/GrantedPermissions.java @@ -17,13 +17,12 @@ package com.android.server.pm; import android.content.pm.ApplicationInfo; - -import java.util.HashSet; +import android.util.ArraySet; class GrantedPermissions { int pkgFlags; - HashSet<String> grantedPermissions = new HashSet<String>(); + ArraySet<String> grantedPermissions = new ArraySet<String>(); int[] gids; @@ -34,7 +33,7 @@ class GrantedPermissions { @SuppressWarnings("unchecked") GrantedPermissions(GrantedPermissions base) { pkgFlags = base.pkgFlags; - grantedPermissions = (HashSet<String>) base.grantedPermissions.clone(); + grantedPermissions = new ArraySet<>(base.grantedPermissions); if (base.gids != null) { gids = base.gids.clone(); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index fb0e357..cc1b3ad 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -497,12 +497,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // haven't been overridden. if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { try { - if (stageCid != null) { + final List<File> fromFiles = mResolvedInheritedFiles; + final File toDir = resolveStageDir(); + + if (isLinkPossible(fromFiles, toDir)) { + linkFiles(fromFiles, toDir); + } else { // TODO: this should delegate to DCS so the system process // avoids holding open FDs into containers. - copyFiles(mResolvedInheritedFiles, resolveStageDir()); - } else { - linkFiles(mResolvedInheritedFiles, resolveStageDir()); + copyFiles(fromFiles, toDir); } } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, @@ -721,7 +724,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // This is kind of hacky; we're creating a half-parsed package that is // straddled between the inherited and staged APKs. final PackageLite pkg = new PackageLite(null, baseApk, null, - splitPaths.toArray(new String[splitPaths.size()])); + splitPaths.toArray(new String[splitPaths.size()]), null); final boolean isForwardLocked = (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0; @@ -733,6 +736,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * Determine if creating hard links between source and destination is + * possible. That is, do they all live on the same underlying device. + */ + private boolean isLinkPossible(List<File> fromFiles, File toDir) { + try { + final StructStat toStat = Os.stat(toDir.getAbsolutePath()); + for (File fromFile : fromFiles) { + final StructStat fromStat = Os.stat(fromFile.getAbsolutePath()); + if (fromStat.st_dev != toStat.st_dev) { + return false; + } + } + } catch (ErrnoException e) { + Slog.w(TAG, "Failed to detect if linking possible: " + e); + return false; + } + return true; + } + private static void linkFiles(List<File> fromFiles, File toDir) throws IOException { for (File fromFile : fromFiles) { final File toFile = new File(toDir, fromFile.getName()); @@ -760,7 +783,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!FileUtils.copyFile(fromFile, tmpFile)) { throw new IOException("Failed to copy " + fromFile + " to " + tmpFile); } - + try { + Os.chmod(tmpFile.getAbsolutePath(), 0644); + } catch (ErrnoException e) { + throw new IOException("Failed to chmod " + tmpFile); + } final File toFile = new File(toDir, fromFile.getName()); if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile); if (!tmpFile.renameTo(toFile)) { diff --git a/services/core/java/com/android/server/pm/PackageKeySetData.java b/services/core/java/com/android/server/pm/PackageKeySetData.java index 9f9bafd..8f12c03 100644 --- a/services/core/java/com/android/server/pm/PackageKeySetData.java +++ b/services/core/java/com/android/server/pm/PackageKeySetData.java @@ -16,10 +16,9 @@ package com.android.server.pm; -import com.android.internal.util.ArrayUtils; +import android.util.ArrayMap; -import java.util.HashMap; -import java.util.Map; +import com.android.internal.util.ArrayUtils; public class PackageKeySetData { @@ -34,7 +33,7 @@ public class PackageKeySetData { private long[] mDefinedKeySets; - private final Map<String, Long> mKeySetAliases = new HashMap<String, Long>(); + private final ArrayMap<String, Long> mKeySetAliases = new ArrayMap<String, Long>(); PackageKeySetData() { mProperSigningKeySet = KEYSET_UNASSIGNED; @@ -132,7 +131,7 @@ public class PackageKeySetData { return mDefinedKeySets; } - protected Map<String, Long> getAliases() { + protected ArrayMap<String, Long> getAliases() { return mKeySetAliases; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 242baa9..902a00f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -41,6 +41,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY; import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; 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_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.PackageParser.isApkFile; @@ -84,6 +85,8 @@ import android.app.AppGlobals; import android.app.IActivityManager; import android.app.admin.IDevicePolicyManager; import android.app.backup.IBackupManager; +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -199,8 +202,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -329,9 +330,11 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean mFactoryTest; final boolean mOnlyCore; final boolean mLazyDexOpt; + final long mDexOptLRUThresholdInMills; final DisplayMetrics mMetrics; final int mDefParseFlags; final String[] mSeparateProcesses; + final boolean mIsUpgrade; // This is where all application persistent data goes. final File mAppDataDir; @@ -371,20 +374,20 @@ public class PackageManagerService extends IPackageManager.Stub { // Keys are String (package name), values are Package. This also serves // as the lock for the global state. Methods that must be called with // this lock held have the prefix "LP". - final HashMap<String, PackageParser.Package> mPackages = - new HashMap<String, PackageParser.Package>(); + final ArrayMap<String, PackageParser.Package> mPackages = + new ArrayMap<String, PackageParser.Package>(); // Tracks available target package names -> overlay package paths. - final HashMap<String, HashMap<String, PackageParser.Package>> mOverlays = - new HashMap<String, HashMap<String, PackageParser.Package>>(); + final ArrayMap<String, ArrayMap<String, PackageParser.Package>> mOverlays = + new ArrayMap<String, ArrayMap<String, PackageParser.Package>>(); final Settings mSettings; boolean mRestoredSettings; // System configuration read by SystemConfig. final int[] mGlobalGids; - final SparseArray<HashSet<String>> mSystemPermissions; - final HashMap<String, FeatureInfo> mAvailableFeatures; + final SparseArray<ArraySet<String>> mSystemPermissions; + final ArrayMap<String, FeatureInfo> mAvailableFeatures; // If mac_permissions.xml was found for seinfo labeling. boolean mFoundPolicyFile; @@ -403,8 +406,8 @@ public class PackageManagerService extends IPackageManager.Stub { } // Currently known shared libraries. - final HashMap<String, SharedLibraryEntry> mSharedLibraries = - new HashMap<String, SharedLibraryEntry>(); + final ArrayMap<String, SharedLibraryEntry> mSharedLibraries = + new ArrayMap<String, SharedLibraryEntry>(); // All available activities, for your resolving pleasure. final ActivityIntentResolver mActivities = @@ -422,23 +425,23 @@ public class PackageManagerService extends IPackageManager.Stub { // Mapping from provider base names (first directory in content URI codePath) // to the provider information. - final HashMap<String, PackageParser.Provider> mProvidersByAuthority = - new HashMap<String, PackageParser.Provider>(); + final ArrayMap<String, PackageParser.Provider> mProvidersByAuthority = + new ArrayMap<String, PackageParser.Provider>(); // Mapping from instrumentation class names to info about them. - final HashMap<ComponentName, PackageParser.Instrumentation> mInstrumentation = - new HashMap<ComponentName, PackageParser.Instrumentation>(); + final ArrayMap<ComponentName, PackageParser.Instrumentation> mInstrumentation = + new ArrayMap<ComponentName, PackageParser.Instrumentation>(); // Mapping from permission names to info about them. - final HashMap<String, PackageParser.PermissionGroup> mPermissionGroups = - new HashMap<String, PackageParser.PermissionGroup>(); + final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups = + new ArrayMap<String, PackageParser.PermissionGroup>(); // Packages whose data we have transfered into another package, thus // should no longer exist. - final HashSet<String> mTransferedPackages = new HashSet<String>(); + final ArraySet<String> mTransferedPackages = new ArraySet<String>(); // Broadcast actions that are only available to the system. - final HashSet<String> mProtectedBroadcasts = new HashSet<String>(); + final ArraySet<String> mProtectedBroadcasts = new ArraySet<String>(); /** List of packages waiting for verification. */ final SparseArray<PackageVerificationState> mPendingVerification @@ -449,7 +452,7 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageInstallerService mInstallerService; - HashSet<PackageParser.Package> mDeferredDexOpt = null; + ArraySet<PackageParser.Package> mDeferredDexOpt = null; // Cache of users who need badging. SparseBooleanArray mUserNeedsBadging = new SparseBooleanArray(); @@ -473,24 +476,24 @@ public class PackageManagerService extends IPackageManager.Stub { // 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> - final SparseArray<HashMap<String, ArrayList<String>>> mUidMap; + final SparseArray<ArrayMap<String, ArrayList<String>>> mUidMap; public PendingPackageBroadcasts() { - mUidMap = new SparseArray<HashMap<String, ArrayList<String>>>(2); + mUidMap = new SparseArray<ArrayMap<String, ArrayList<String>>>(2); } public ArrayList<String> get(int userId, String packageName) { - HashMap<String, ArrayList<String>> packages = getOrAllocate(userId); + ArrayMap<String, ArrayList<String>> packages = getOrAllocate(userId); return packages.get(packageName); } public void put(int userId, String packageName, ArrayList<String> components) { - HashMap<String, ArrayList<String>> packages = getOrAllocate(userId); + ArrayMap<String, ArrayList<String>> packages = getOrAllocate(userId); packages.put(packageName, components); } public void remove(int userId, String packageName) { - HashMap<String, ArrayList<String>> packages = mUidMap.get(userId); + ArrayMap<String, ArrayList<String>> packages = mUidMap.get(userId); if (packages != null) { packages.remove(packageName); } @@ -508,7 +511,7 @@ public class PackageManagerService extends IPackageManager.Stub { return mUidMap.keyAt(n); } - public HashMap<String, ArrayList<String>> packagesForUserId(int userId) { + public ArrayMap<String, ArrayList<String>> packagesForUserId(int userId) { return mUidMap.get(userId); } @@ -525,10 +528,10 @@ public class PackageManagerService extends IPackageManager.Stub { mUidMap.clear(); } - private HashMap<String, ArrayList<String>> getOrAllocate(int userId) { - HashMap<String, ArrayList<String>> map = mUidMap.get(userId); + private ArrayMap<String, ArrayList<String>> getOrAllocate(int userId) { + ArrayMap<String, ArrayList<String>> map = mUidMap.get(userId); if (map == null) { - map = new HashMap<String, ArrayList<String>>(); + map = new ArrayMap<String, ArrayList<String>>(); mUidMap.put(userId, map); } return map; @@ -565,7 +568,7 @@ public class PackageManagerService extends IPackageManager.Stub { static UserManagerService sUserManager; // Stores a list of users whose package restrictions file needs to be updated - private HashSet<Integer> mDirtyUsers = new HashSet<Integer>(); + private ArraySet<Integer> mDirtyUsers = new ArraySet<Integer>(); final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); @@ -1294,6 +1297,15 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + // TODO: add a property to control this? + long dexOptLRUThresholdInMinutes; + if (mLazyDexOpt) { + dexOptLRUThresholdInMinutes = 30; // only last 30 minutes of apps for eng builds. + } else { + dexOptLRUThresholdInMinutes = 7 * 24 * 60; // apps used in the 7 days for users. + } + mDexOptLRUThresholdInMills = dexOptLRUThresholdInMinutes * 60 * 1000; + String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { if ("*".equals(separateProcesses)) { @@ -1384,7 +1396,7 @@ public class PackageManagerService extends IPackageManager.Stub { // scanning install directories. final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING; - final HashSet<String> alreadyDexOpted = new HashSet<String>(); + final ArraySet<String> alreadyDexOpted = new ArraySet<String>(); /** * Add everything in the in the boot class path to the @@ -1412,8 +1424,6 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!"); } - boolean didDexOptLibraryOrTool = false; - final List<String> allInstructionSets = getAllInstructionSets(); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()])); @@ -1446,7 +1456,6 @@ public class PackageManagerService extends IPackageManager.Stub { } else { mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet); } - didDexOptLibraryOrTool = true; } } catch (FileNotFoundException e) { Slog.w(TAG, "Library not found: " + lib); @@ -1497,10 +1506,8 @@ public class PackageManagerService extends IPackageManager.Stub { false); if (dexoptRequired == DexFile.DEXOPT_NEEDED) { mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet); - didDexOptLibraryOrTool = true; } else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) { mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet); - didDexOptLibraryOrTool = true; } } catch (FileNotFoundException e) { Slog.w(TAG, "Jar not found: " + path); @@ -1750,7 +1757,8 @@ public class PackageManagerService extends IPackageManager.Stub { // If this is first boot after an OTA, and a normal boot, then // we need to clear code cache directories. - if (!Build.FINGERPRINT.equals(mSettings.mFingerprint) && !onlyCore) { + 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); @@ -1790,6 +1798,11 @@ public class PackageManagerService extends IPackageManager.Stub { return mOnlyCore; } + @Override + public boolean isUpgrade() { + return mIsUpgrade; + } + private String getRequiredVerifierLPr() { final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE, @@ -2369,7 +2382,7 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.PERMISSION_GRANTED; } } else { - HashSet<String> perms = mSystemPermissions.get(uid); + ArraySet<String> perms = mSystemPermissions.get(uid); if (perms != null && perms.contains(permName)) { return PackageManager.PERMISSION_GRANTED; } @@ -2790,11 +2803,11 @@ public class PackageManagerService extends IPackageManager.Stub { PackageManager.SIGNATURE_NO_MATCH; } - HashSet<Signature> set1 = new HashSet<Signature>(); + ArraySet<Signature> set1 = new ArraySet<Signature>(); for (Signature sig : s1) { set1.add(sig); } - HashSet<Signature> set2 = new HashSet<Signature>(); + ArraySet<Signature> set2 = new ArraySet<Signature>(); for (Signature sig : s2) { set2.add(sig); } @@ -2829,11 +2842,11 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.SIGNATURE_NO_MATCH; } - HashSet<Signature> existingSet = new HashSet<Signature>(); + ArraySet<Signature> existingSet = new ArraySet<Signature>(); for (Signature sig : existingSigs.mSignatures) { existingSet.add(sig); } - HashSet<Signature> scannedCompatSet = new HashSet<Signature>(); + ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>(); for (Signature sig : scannedPkg.mSignatures) { try { Signature[] chainSignatures = sig.getChainSignatures(); @@ -2860,6 +2873,38 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.SIGNATURE_NO_MATCH; } + private boolean isRecoverSignatureUpdateNeeded(PackageParser.Package scannedPkg) { + if (isExternal(scannedPkg)) { + return mSettings.isExternalDatabaseVersionOlderThan( + DatabaseVersion.SIGNATURE_MALFORMED_RECOVER); + } else { + return mSettings.isInternalDatabaseVersionOlderThan( + DatabaseVersion.SIGNATURE_MALFORMED_RECOVER); + } + } + + private int compareSignaturesRecover(PackageSignatures existingSigs, + PackageParser.Package scannedPkg) { + if (!isRecoverSignatureUpdateNeeded(scannedPkg)) { + return PackageManager.SIGNATURE_NO_MATCH; + } + + String msg = null; + try { + if (Signature.areEffectiveMatch(existingSigs.mSignatures, scannedPkg.mSignatures)) { + logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for " + + scannedPkg.packageName); + return PackageManager.SIGNATURE_MATCH; + } + } catch (CertificateException e) { + msg = e.getMessage(); + } + + logCriticalInfo(Log.INFO, + "Failed to recover certificates for " + scannedPkg.packageName + ": " + msg); + return PackageManager.SIGNATURE_NO_MATCH; + } + @Override public String[] getPackagesForUid(int uid) { uid = UserHandle.getAppId(uid); @@ -3249,7 +3294,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_PREFERRED) { Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions"); } - mSettings.writePackageRestrictionsLPr(userId); + scheduleWritePackageRestrictionsLocked(userId); } } } @@ -4023,7 +4068,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private void createIdmapsForPackageLI(PackageParser.Package pkg) { - HashMap<String, PackageParser.Package> overlays = mOverlays.get(pkg.packageName); + ArrayMap<String, PackageParser.Package> overlays = mOverlays.get(pkg.packageName); if (overlays == null) { Slog.w(TAG, "Unable to create idmap for " + pkg.packageName + ": no overlay packages"); return; @@ -4044,7 +4089,7 @@ public class PackageManagerService extends IPackageManager.Stub { opkg.baseCodePath + ": overlay not trusted"); return false; } - HashMap<String, PackageParser.Package> overlaySet = mOverlays.get(pkg.packageName); + ArrayMap<String, PackageParser.Package> overlaySet = mOverlays.get(pkg.packageName); if (overlaySet == null) { Slog.e(TAG, "was about to create idmap for " + pkg.baseCodePath + " and " + opkg.baseCodePath + " but target package has no known overlays"); @@ -4148,7 +4193,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (ps != null && ps.codePath.equals(srcFile) && ps.timeStamp == srcFile.lastModified() - && !isCompatSignatureUpdateNeeded(pkg)) { + && !isCompatSignatureUpdateNeeded(pkg) + && !isRecoverSignatureUpdateNeeded(pkg)) { long mSigningKeySetId = ps.keySetData.getProperSigningKeySet(); if (ps.signatures.mSignatures != null && ps.signatures.mSignatures.length != 0 @@ -4230,10 +4276,10 @@ public class PackageManagerService extends IPackageManager.Stub { // version of the new path against what we have stored to determine // what to do. if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath); - if (pkg.mVersionCode < ps.versionCode) { + if (pkg.mVersionCode <= ps.versionCode) { // The system package has been updated and the code path does not match // Ignore entry. Skip it. - logCriticalInfo(Log.INFO, "Package " + ps.name + " at " + scanFile + Slog.i(TAG, "Package " + ps.name + " at " + scanFile + " ignored: updated version " + ps.versionCode + " better than this " + pkg.mVersionCode); if (!updatedPkg.codePath.equals(scanFile)) { @@ -4323,7 +4369,7 @@ public class PackageManagerService extends IPackageManager.Stub { * already installed version, hide it. It will be scanned later * and re-added like an update. */ - if (pkg.mVersionCode < ps.versionCode) { + if (pkg.mVersionCode <= ps.versionCode) { shouldHideSystemApp = true; logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile + " but new version " + pkg.mVersionCode + " better than installed " @@ -4425,6 +4471,10 @@ public class PackageManagerService extends IPackageManager.Stub { == PackageManager.SIGNATURE_MATCH; } if (!match) { + match = compareSignaturesRecover(pkgSetting.signatures, pkg) + == PackageManager.SIGNATURE_MATCH; + } + if (!match) { throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + pkg.packageName + " signatures do not match the " + "previously installed version; ignoring!"); @@ -4441,6 +4491,10 @@ public class PackageManagerService extends IPackageManager.Stub { == PackageManager.SIGNATURE_MATCH; } if (!match) { + match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg) + == PackageManager.SIGNATURE_MATCH; + } + if (!match) { throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, "Package " + pkg.packageName + " has no signatures that match those in shared user " @@ -4467,7 +4521,7 @@ public class PackageManagerService extends IPackageManager.Stub { public void performBootDexOpt() { enforceSystemOrRoot("Only the system can request dexopt be performed"); - final HashSet<PackageParser.Package> pkgs; + final ArraySet<PackageParser.Package> pkgs; synchronized (mPackages) { pkgs = mDeferredDexOpt; mDeferredDexOpt = null; @@ -4490,7 +4544,7 @@ public class PackageManagerService extends IPackageManager.Stub { } // Give priority to system apps that listen for pre boot complete. Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); - HashSet<String> pkgNames = getPackageNamesForIntent(intent); + ArraySet<String> pkgNames = getPackageNamesForIntent(intent); for (Iterator<PackageParser.Package> it = pkgs.iterator(); it.hasNext();) { PackageParser.Package pkg = it.next(); if (pkgNames.contains(pkg.packageName)) { @@ -4546,6 +4600,11 @@ public class PackageManagerService extends IPackageManager.Stub { sortedPkgs.add(pkg); } + // If we want to be lazy, filter everything that wasn't recently used. + if (mLazyDexOpt) { + filterRecentlyUsedApps(sortedPkgs); + } + int i = 0; int total = sortedPkgs.size(); File dataDir = Environment.getDataDirectory(); @@ -4564,28 +4623,19 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void filterRecentlyUsedApps(HashSet<PackageParser.Package> pkgs) { + private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs) { // Filter out packages that aren't recently used. // // The exception is first boot of a non-eng device (aka !mLazyDexOpt), which // should do a full dexopt. if (mLazyDexOpt || (!isFirstBoot() && mPackageUsage.isHistoricalPackageUsageAvailable())) { - // TODO: add a property to control this? - long dexOptLRUThresholdInMinutes; - if (mLazyDexOpt) { - dexOptLRUThresholdInMinutes = 30; // only last 30 minutes of apps for eng builds. - } else { - dexOptLRUThresholdInMinutes = 7 * 24 * 60; // apps used in the 7 days for users. - } - long dexOptLRUThresholdInMills = dexOptLRUThresholdInMinutes * 60 * 1000; - int total = pkgs.size(); int skipped = 0; long now = System.currentTimeMillis(); for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) { PackageParser.Package pkg = i.next(); long then = pkg.mLastPackageUsageTimeInMills; - if (then + dexOptLRUThresholdInMills < now) { + if (then + mDexOptLRUThresholdInMills < now) { if (DEBUG_DEXOPT) { Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " + ((then == 0) ? "never" : new Date(then))); @@ -4600,14 +4650,14 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private HashSet<String> getPackageNamesForIntent(Intent intent) { + private ArraySet<String> getPackageNamesForIntent(Intent intent) { List<ResolveInfo> ris = null; try { ris = AppGlobals.getPackageManager().queryIntentReceivers( intent, null, 0, UserHandle.USER_OWNER); } catch (RemoteException e) { } - HashSet<String> pkgNames = new HashSet<String>(); + ArraySet<String> pkgNames = new ArraySet<String>(); if (ris != null) { for (ResolveInfo ri : ris) { pkgNames.add(ri.activityInfo.packageName); @@ -4685,8 +4735,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } - public HashSet<String> getPackagesThatNeedDexOpt() { - HashSet<String> pkgs = null; + public ArraySet<String> getPackagesThatNeedDexOpt() { + ArraySet<String> pkgs = null; synchronized (mPackages) { for (PackageParser.Package p : mPackages.values()) { if (DEBUG_DEXOPT) { @@ -4696,7 +4746,7 @@ public class PackageManagerService extends IPackageManager.Stub { continue; } if (pkgs == null) { - pkgs = new HashSet<String>(); + pkgs = new ArraySet<String>(); } pkgs.add(p.packageName); } @@ -4709,7 +4759,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets, - boolean forceDex, boolean defer, HashSet<String> done) { + boolean forceDex, boolean defer, ArraySet<String> done) { for (int i=0; i<libs.size(); i++) { PackageParser.Package libPkg; String libName; @@ -4734,7 +4784,7 @@ public class PackageManagerService extends IPackageManager.Stub { static final int DEX_OPT_FAILED = -1; private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets, - boolean forceDex, boolean defer, HashSet<String> done) { + boolean forceDex, boolean defer, ArraySet<String> done) { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); @@ -4812,7 +4862,7 @@ public class PackageManagerService extends IPackageManager.Stub { // our list of deferred dexopts. if (defer && isDexOptNeeded != DexFile.UP_TO_DATE) { if (mDeferredDexOpt == null) { - mDeferredDexOpt = new HashSet<PackageParser.Package>(); + mDeferredDexOpt = new ArraySet<PackageParser.Package>(); } mDeferredDexOpt.add(pkg); return DEX_OPT_DEFERRED; @@ -4909,7 +4959,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private static String[] getDexCodeInstructionSets(String[] instructionSets) { - HashSet<String> dexCodeInstructionSets = new HashSet<String>(instructionSets.length); + ArraySet<String> dexCodeInstructionSets = new ArraySet<String>(instructionSets.length); for (String instructionSet : instructionSets) { dexCodeInstructionSets.add(getDexCodeInstructionSet(instructionSet)); } @@ -4952,9 +5002,9 @@ public class PackageManagerService extends IPackageManager.Stub { private int performDexOptLI(PackageParser.Package pkg, String[] instructionSets, boolean forceDex, boolean defer, boolean inclDependencies) { - HashSet<String> done; + ArraySet<String> done; if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) { - done = new HashSet<String>(); + done = new ArraySet<String>(); done.add(pkg.packageName); } else { done = null; @@ -5388,6 +5438,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (!pkgSetting.keySetData.isUsingUpgradeKeySets() || pkgSetting.sharedUser != null) { try { verifySignaturesLP(pkgSetting, pkg); + // We just determined the app is signed correctly, so bring + // over the latest parsed certs. + pkgSetting.signatures.mSignatures = pkg.mSignatures; } catch (PackageManagerException e) { if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { throw e; @@ -5420,7 +5473,8 @@ public class PackageManagerService extends IPackageManager.Stub { + pkg.packageName + " upgrade keys do not match the " + "previously installed version"); } else { - // signatures may have changed as result of upgrade + // We just determined the app is signed correctly, so bring + // over the latest parsed certs. pkgSetting.signatures.mSignatures = pkg.mSignatures; } } @@ -6139,7 +6193,7 @@ public class PackageManagerService extends IPackageManager.Stub { r = null; for (i=0; i<N; i++) { PackageParser.Permission p = pkg.permissions.get(i); - HashMap<String, BasePermission> permissionMap = + ArrayMap<String, BasePermission> permissionMap = p.tree ? mSettings.mPermissionTrees : mSettings.mPermissions; p.group = mPermissionGroups.get(p.info.group); @@ -6267,9 +6321,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkg.mOverlayTarget != null && !pkg.mOverlayTarget.equals("android")) { if (!mOverlays.containsKey(pkg.mOverlayTarget)) { mOverlays.put(pkg.mOverlayTarget, - new HashMap<String, PackageParser.Package>()); + new ArrayMap<String, PackageParser.Package>()); } - HashMap<String, PackageParser.Package> map = mOverlays.get(pkg.mOverlayTarget); + ArrayMap<String, PackageParser.Package> map = mOverlays.get(pkg.mOverlayTarget); map.put(pkg.packageName, pkg); PackageParser.Package orig = mPackages.get(pkg.mOverlayTarget); if (orig != null && !createIdmapForPackagePairLI(orig, pkg)) { @@ -6386,7 +6440,7 @@ public class PackageManagerService extends IPackageManager.Stub { mResolveActivity.applicationInfo = pkg.applicationInfo; mResolveActivity.name = mCustomResolverComponentName.getClassName(); mResolveActivity.packageName = pkg.applicationInfo.packageName; - mResolveActivity.processName = null; + mResolveActivity.processName = pkg.applicationInfo.packageName; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; @@ -6930,13 +6984,13 @@ public class PackageManagerService extends IPackageManager.Stub { return; } final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; - HashSet<String> origPermissions = gp.grantedPermissions; + ArraySet<String> origPermissions = gp.grantedPermissions; boolean changedPermission = false; if (replace) { ps.permissionsFixed = false; if (gp == ps) { - origPermissions = new HashSet<String>(gp.grantedPermissions); + origPermissions = new ArraySet<String>(gp.grantedPermissions); gp.grantedPermissions.clear(); gp.gids = mGlobalGids; } @@ -7083,7 +7137,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private boolean grantSignaturePermission(String perm, PackageParser.Package pkg, - BasePermission bp, HashSet<String> origPermissions) { + BasePermission bp, ArraySet<String> origPermissions) { boolean allowed; allowed = (compareSignatures( bp.packageSetting.signatures.mSignatures, pkg.mSignatures) @@ -7328,6 +7382,23 @@ public class PackageManagerService extends IPackageManager.Stub { out.println(Integer.toHexString(System.identityHashCode(filter))); } + @Override + protected Object filterToLabel(PackageParser.ActivityIntentInfo filter) { + return filter.activity; + } + + protected void dumpFilterLabel(PrintWriter out, String prefix, Object label, int count) { + PackageParser.Activity activity = (PackageParser.Activity)label; + out.print(prefix); out.print( + Integer.toHexString(System.identityHashCode(activity))); + out.print(' '); + activity.printComponentShortName(out); + if (count > 1) { + out.print(" ("); out.print(count); out.print(" filters)"); + } + out.println(); + } + // List<ResolveInfo> filterEnabled(List<ResolveInfo> resolveInfoList) { // final Iterator<ResolveInfo> i = resolveInfoList.iterator(); // final List<ResolveInfo> retList = Lists.newArrayList(); @@ -7341,8 +7412,8 @@ public class PackageManagerService extends IPackageManager.Stub { // } // Keys are String (activity class name), values are Activity. - private final HashMap<ComponentName, PackageParser.Activity> mActivities - = new HashMap<ComponentName, PackageParser.Activity>(); + private final ArrayMap<ComponentName, PackageParser.Activity> mActivities + = new ArrayMap<ComponentName, PackageParser.Activity>(); private int mFlags; } @@ -7527,6 +7598,23 @@ public class PackageManagerService extends IPackageManager.Stub { out.println(Integer.toHexString(System.identityHashCode(filter))); } + @Override + protected Object filterToLabel(PackageParser.ServiceIntentInfo filter) { + return filter.service; + } + + protected void dumpFilterLabel(PrintWriter out, String prefix, Object label, int count) { + PackageParser.Service service = (PackageParser.Service)label; + out.print(prefix); out.print( + Integer.toHexString(System.identityHashCode(service))); + out.print(' '); + service.printComponentShortName(out); + if (count > 1) { + out.print(" ("); out.print(count); out.print(" filters)"); + } + out.println(); + } + // List<ResolveInfo> filterEnabled(List<ResolveInfo> resolveInfoList) { // final Iterator<ResolveInfo> i = resolveInfoList.iterator(); // final List<ResolveInfo> retList = Lists.newArrayList(); @@ -7540,8 +7628,8 @@ public class PackageManagerService extends IPackageManager.Stub { // } // Keys are String (activity class name), values are Activity. - private final HashMap<ComponentName, PackageParser.Service> mServices - = new HashMap<ComponentName, PackageParser.Service>(); + private final ArrayMap<ComponentName, PackageParser.Service> mServices + = new ArrayMap<ComponentName, PackageParser.Service>(); private int mFlags; }; @@ -7734,8 +7822,25 @@ public class PackageManagerService extends IPackageManager.Stub { out.println(Integer.toHexString(System.identityHashCode(filter))); } - private final HashMap<ComponentName, PackageParser.Provider> mProviders - = new HashMap<ComponentName, PackageParser.Provider>(); + @Override + protected Object filterToLabel(PackageParser.ProviderIntentInfo filter) { + return filter.provider; + } + + protected void dumpFilterLabel(PrintWriter out, String prefix, Object label, int count) { + PackageParser.Provider provider = (PackageParser.Provider)label; + out.print(prefix); out.print( + Integer.toHexString(System.identityHashCode(provider))); + out.print(' '); + provider.printComponentShortName(out); + if (count > 1) { + out.print(" ("); out.print(count); out.print(" filters)"); + } + out.println(); + } + + private final ArrayMap<ComponentName, PackageParser.Provider> mProviders + = new ArrayMap<ComponentName, PackageParser.Provider>(); private int mFlags; }; @@ -8755,11 +8860,10 @@ public class PackageManagerService extends IPackageManager.Stub { if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { // Check for downgrading. if ((installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) == 0) { - if (pkgLite.versionCode < pkg.mVersionCode) { - Slog.w(TAG, "Can't install update of " + packageName - + " update version " + pkgLite.versionCode - + " is older than installed version " - + pkg.mVersionCode); + try { + checkDowngrade(pkg, pkgLite); + } catch (PackageManagerException e) { + Slog.w(TAG, "Downgrade detected: " + e.getMessage()); return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE; } } @@ -8931,6 +9035,7 @@ public class PackageManagerService extends IPackageManager.Stub { && isVerificationEnabled(userIdentifier, installFlags)) { final Intent verification = new Intent( Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); + verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); verification.setDataAndType(Uri.fromFile(new File(origin.resolvedPath)), PACKAGE_MIME_TYPE); verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -10404,6 +10509,60 @@ public class PackageManagerService extends IPackageManager.Stub { String oldCodePath = null; boolean systemApp = false; synchronized (mPackages) { + // Check if installing already existing package + if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { + String oldName = mSettings.mRenamedPackages.get(pkgName); + if (pkg.mOriginalPackages != null + && pkg.mOriginalPackages.contains(oldName) + && mPackages.containsKey(oldName)) { + // This package is derived from an original package, + // and this device has been updating from that original + // name. We must continue using the original name, so + // rename the new package here. + pkg.setPackageName(oldName); + pkgName = pkg.packageName; + replace = true; + if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName=" + + oldName + " pkgName=" + pkgName); + } else if (mPackages.containsKey(pkgName)) { + // This package, under its official name, already exists + // on the device; we should replace it. + replace = true; + if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName); + } + } + + PackageSetting ps = mSettings.mPackages.get(pkgName); + if (ps != null) { + if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); + + // Quick sanity check that we're signed correctly if updating; + // we'll check this again later when scanning, but we want to + // bail early here before tripping over redefined permissions. + if (!ps.keySetData.isUsingUpgradeKeySets() || ps.sharedUser != null) { + try { + verifySignaturesLP(ps, pkg); + } catch (PackageManagerException e) { + res.setError(e.error, e.getMessage()); + return; + } + } else { + if (!checkUpgradeKeySetLP(ps, pkg)) { + res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + + pkg.packageName + " upgrade keys do not match the " + + "previously installed version"); + return; + } + } + + oldCodePath = mSettings.mPackages.get(pkgName).codePathString; + if (ps.pkg != null && ps.pkg.applicationInfo != null) { + systemApp = (ps.pkg.applicationInfo.flags & + ApplicationInfo.FLAG_SYSTEM) != 0; + } + res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); + } + // Check whether the newly-scanned package wants to define an already-defined perm int N = pkg.permissions.size(); for (int i = N-1; i >= 0; i--) { @@ -10444,38 +10603,6 @@ public class PackageManagerService extends IPackageManager.Stub { } } - // Check if installing already existing package - if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { - String oldName = mSettings.mRenamedPackages.get(pkgName); - if (pkg.mOriginalPackages != null - && pkg.mOriginalPackages.contains(oldName) - && mPackages.containsKey(oldName)) { - // This package is derived from an original package, - // and this device has been updating from that original - // name. We must continue using the original name, so - // rename the new package here. - pkg.setPackageName(oldName); - pkgName = pkg.packageName; - replace = true; - if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName=" - + oldName + " pkgName=" + pkgName); - } else if (mPackages.containsKey(pkgName)) { - // This package, under its official name, already exists - // on the device; we should replace it. - replace = true; - if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName); - } - } - PackageSetting ps = mSettings.mPackages.get(pkgName); - if (ps != null) { - if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); - oldCodePath = mSettings.mPackages.get(pkgName).codePathString; - if (ps.pkg != null && ps.pkg.applicationInfo != null) { - systemApp = (ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SYSTEM) != 0; - } - res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); - } } if (systemApp && onSd) { @@ -11538,7 +11665,7 @@ public class PackageManagerService extends IPackageManager.Stub { + userId + ":"); filter.dump(new LogPrinter(Log.INFO, TAG), " "); pir.addFilter(new PreferredActivity(filter, match, set, activity, always)); - mSettings.writePackageRestrictionsLPr(userId); + scheduleWritePackageRestrictionsLocked(userId); } } @@ -11654,8 +11781,7 @@ public class PackageManagerService extends IPackageManager.Stub { int user = UserHandle.getCallingUserId(); if (clearPackagePreferredActivitiesLPw(packageName, user)) { - mSettings.writePackageRestrictionsLPr(user); - scheduleWriteSettingsLocked(); + scheduleWritePackageRestrictionsLocked(user); } } } @@ -11705,8 +11831,7 @@ public class PackageManagerService extends IPackageManager.Stub { int user = UserHandle.getCallingUserId(); clearPackagePreferredActivitiesLPw(null, user); mSettings.readDefaultPreferredAppsLPw(this, user); - mSettings.writePackageRestrictionsLPr(user); - scheduleWriteSettingsLocked(); + scheduleWritePackageRestrictionsLocked(user); } } @@ -11758,7 +11883,7 @@ public class PackageManagerService extends IPackageManager.Stub { filter.dump(new LogPrinter(Log.INFO, TAG), " "); mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter( new PersistentPreferredActivity(filter, activity)); - mSettings.writePackageRestrictionsLPr(userId); + scheduleWritePackageRestrictionsLocked(userId); } } @@ -11800,7 +11925,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (changed) { - mSettings.writePackageRestrictionsLPr(userId); + scheduleWritePackageRestrictionsLocked(userId); } } } @@ -11818,10 +11943,22 @@ public class PackageManagerService extends IPackageManager.Stub { return; } synchronized (mPackages) { - CrossProfileIntentFilter filter = new CrossProfileIntentFilter(intentFilter, + CrossProfileIntentFilter newFilter = new CrossProfileIntentFilter(intentFilter, ownerPackage, UserHandle.getUserId(callingUid), targetUserId, flags); - mSettings.editCrossProfileIntentResolverLPw(sourceUserId).addFilter(filter); - mSettings.writePackageRestrictionsLPr(sourceUserId); + CrossProfileIntentResolver resolver = + mSettings.editCrossProfileIntentResolverLPw(sourceUserId); + ArrayList<CrossProfileIntentFilter> existing = resolver.findFilters(intentFilter); + // We have all those whose filter is equal. Now checking if the rest is equal as well. + if (existing != null) { + int size = existing.size(); + for (int i = 0; i < size; i++) { + if (newFilter.equalsIgnoreFilter(existing.get(i))) { + return; + } + } + } + resolver.addFilter(newFilter); + scheduleWritePackageRestrictionsLocked(sourceUserId); } } @@ -11837,15 +11974,15 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { CrossProfileIntentResolver resolver = mSettings.editCrossProfileIntentResolverLPw(sourceUserId); - HashSet<CrossProfileIntentFilter> set = - new HashSet<CrossProfileIntentFilter>(resolver.filterSet()); + ArraySet<CrossProfileIntentFilter> set = + new ArraySet<CrossProfileIntentFilter>(resolver.filterSet()); for (CrossProfileIntentFilter filter : set) { if (filter.getOwnerPackage().equals(ownerPackage) && filter.getOwnerUserId() == callingUserId) { resolver.removeFilter(filter); } } - mSettings.writePackageRestrictionsLPr(sourceUserId); + scheduleWritePackageRestrictionsLocked(sourceUserId); } } @@ -12471,22 +12608,22 @@ public class PackageManagerService extends IPackageManager.Stub { if (!checkin && dumpState.isDumping(DumpState.DUMP_RESOLVERS)) { if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:" : "Activity Resolver Table:", " ", packageName, - dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) { dumpState.setTitlePrinted(true); } if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:", " ", packageName, - dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) { dumpState.setTitlePrinted(true); } if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:" : "Service Resolver Table:", " ", packageName, - dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) { dumpState.setTitlePrinted(true); } if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:" : "Provider Resolver Table:", " ", packageName, - dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS), true)) { dumpState.setTitlePrinted(true); } } @@ -12499,7 +12636,7 @@ public class PackageManagerService extends IPackageManager.Stub { dumpState.getTitlePrinted() ? "\nPreferred Activities User " + user + ":" : "Preferred Activities User " + user + ":", " ", - packageName, true)) { + packageName, true, false)) { dumpState.setTitlePrinted(true); } } @@ -12592,8 +12729,8 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.dumpPackagesLPr(pw, packageName, dumpState, checkin); } - if (!checkin && dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) { - mSettings.dumpSharedUsersLPr(pw, packageName, dumpState); + if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) { + mSettings.dumpSharedUsersLPr(pw, packageName, dumpState, checkin); } if (!checkin && dumpState.isDumping(DumpState.DUMP_INSTALLS) && packageName == null) { @@ -12609,23 +12746,17 @@ public class PackageManagerService extends IPackageManager.Stub { pw.println(); pw.println("Package warning messages:"); - final File fname = getSettingsProblemFile(); - FileInputStream in = null; + BufferedReader in = null; + String line = null; try { - in = new FileInputStream(fname); - final int avail = in.available(); - final byte[] data = new byte[avail]; - in.read(data); - pw.print(new String(data)); - } catch (FileNotFoundException e) { - } catch (IOException e) { - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - } + in = new BufferedReader(new FileReader(getSettingsProblemFile())); + while ((line = in.readLine()) != null) { + if (line.contains("ignored: updated version")) continue; + pw.println(line); } + } catch (IOException ignored) { + } finally { + IoUtils.closeQuietly(in); } } @@ -12635,6 +12766,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { in = new BufferedReader(new FileReader(getSettingsProblemFile())); while ((line = in.readLine()) != null) { + if (line.contains("ignored: updated version")) continue; pw.print("msg,"); pw.println(line); } @@ -13383,4 +13515,59 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } } + + public void getUsageStatsIfNoPackageUsageInfo() { + if (!mPackageUsage.isHistoricalPackageUsageAvailable()) { + UsageStatsManager usm = (UsageStatsManager) mContext.getSystemService(Context.USAGE_STATS_SERVICE); + if (usm == null) { + throw new IllegalStateException("UsageStatsManager must be initialized"); + } + long now = System.currentTimeMillis(); + Map<String, UsageStats> stats = usm.queryAndAggregateUsageStats(now - mDexOptLRUThresholdInMills, now); + for (Map.Entry<String, UsageStats> entry : stats.entrySet()) { + String packageName = entry.getKey(); + PackageParser.Package pkg = mPackages.get(packageName); + if (pkg == null) { + continue; + } + UsageStats usage = entry.getValue(); + pkg.mLastPackageUsageTimeInMills = usage.getLastTimeUsed(); + mPackageUsage.mIsHistoricalPackageUsageAvailable = true; + } + } + } + + /** + * Check and throw if the given before/after packages would be considered a + * downgrade. + */ + private static void checkDowngrade(PackageParser.Package before, PackageInfoLite after) + throws PackageManagerException { + if (after.versionCode < before.mVersionCode) { + throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE, + "Update version code " + after.versionCode + " is older than current " + + before.mVersionCode); + } else if (after.versionCode == before.mVersionCode) { + if (after.baseRevisionCode < before.baseRevisionCode) { + throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE, + "Update base revision code " + after.baseRevisionCode + + " is older than current " + before.baseRevisionCode); + } + + if (!ArrayUtils.isEmpty(after.splitNames)) { + for (int i = 0; i < after.splitNames.length; i++) { + final String splitName = after.splitNames[i]; + final int j = ArrayUtils.indexOf(before.splitNames, splitName); + if (j != -1) { + if (after.splitRevisionCodes[i] < before.splitRevisionCodes[j]) { + throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE, + "Update split " + splitName + " revision code " + + after.splitRevisionCodes[i] + " is older than current " + + before.splitRevisionCodes[j]); + } + } + } + } + } + } } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index bf13fd9..1dcadb4 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -21,10 +21,10 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import android.content.pm.PackageUserState; +import android.util.ArraySet; import android.util.SparseArray; import java.io.File; -import java.util.HashSet; /** * Settings base class for pending and resolved classes. @@ -321,8 +321,8 @@ class PackageSettingBase extends GrantedPermissions { void setUserState(int userId, int enabled, boolean installed, boolean stopped, boolean notLaunched, boolean hidden, - String lastDisableAppCaller, HashSet<String> enabledComponents, - HashSet<String> disabledComponents, boolean blockUninstall) { + String lastDisableAppCaller, ArraySet<String> enabledComponents, + ArraySet<String> disabledComponents, boolean blockUninstall) { PackageUserState state = modifyUserState(userId); state.enabled = enabled; state.installed = installed; @@ -335,39 +335,39 @@ class PackageSettingBase extends GrantedPermissions { state.blockUninstall = blockUninstall; } - HashSet<String> getEnabledComponents(int userId) { + ArraySet<String> getEnabledComponents(int userId) { return readUserState(userId).enabledComponents; } - HashSet<String> getDisabledComponents(int userId) { + ArraySet<String> getDisabledComponents(int userId) { return readUserState(userId).disabledComponents; } - void setEnabledComponents(HashSet<String> components, int userId) { + void setEnabledComponents(ArraySet<String> components, int userId) { modifyUserState(userId).enabledComponents = components; } - void setDisabledComponents(HashSet<String> components, int userId) { + void setDisabledComponents(ArraySet<String> components, int userId) { modifyUserState(userId).disabledComponents = components; } - void setEnabledComponentsCopy(HashSet<String> components, int userId) { + void setEnabledComponentsCopy(ArraySet<String> components, int userId) { modifyUserState(userId).enabledComponents = components != null - ? new HashSet<String>(components) : null; + ? new ArraySet<String>(components) : null; } - void setDisabledComponentsCopy(HashSet<String> components, int userId) { + void setDisabledComponentsCopy(ArraySet<String> components, int userId) { modifyUserState(userId).disabledComponents = components != null - ? new HashSet<String>(components) : null; + ? new ArraySet<String>(components) : null; } PackageUserState modifyUserStateComponents(int userId, boolean disabled, boolean enabled) { PackageUserState state = modifyUserState(userId); if (disabled && state.disabledComponents == null) { - state.disabledComponents = new HashSet<String>(1); + state.disabledComponents = new ArraySet<String>(1); } if (enabled && state.enabledComponents == null) { - state.enabledComponents = new HashSet<String>(1); + state.enabledComponents = new ArraySet<String>(1); } return state; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7de56c8..393ebd6 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -63,6 +63,8 @@ import android.content.pm.Signature; import android.content.pm.UserInfo; import android.content.pm.PackageUserState; import android.content.pm.VerifierDeviceIdentity; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -78,8 +80,6 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -103,7 +103,7 @@ final class Settings { * Note that care should be taken to make sure all database upgrades are * idempotent. */ - private static final int CURRENT_DATABASE_VERSION = DatabaseVersion.SIGNATURE_END_ENTITY; + private static final int CURRENT_DATABASE_VERSION = DatabaseVersion.SIGNATURE_MALFORMED_RECOVER; /** * This class contains constants that can be referred to from upgrade code. @@ -121,6 +121,14 @@ final class Settings { * just the signing certificate. */ public static final int SIGNATURE_END_ENTITY = 2; + + /** + * There was a window of time in + * {@link android.os.Build.VERSION_CODES#LOLLIPOP} where we persisted + * certificates after potentially mutating them. To switch back to the + * original untouched certificates, we need to force a collection pass. + */ + public static final int SIGNATURE_MALFORMED_RECOVER = 3; } private static final boolean DEBUG_STOPPED = false; @@ -159,11 +167,11 @@ final class Settings { private final File mStoppedPackagesFilename; private final File mBackupStoppedPackagesFilename; - final HashMap<String, PackageSetting> mPackages = - new HashMap<String, PackageSetting>(); + final ArrayMap<String, PackageSetting> mPackages = + new ArrayMap<String, PackageSetting>(); // List of replaced system applications - private final HashMap<String, PackageSetting> mDisabledSysPackages = - new HashMap<String, PackageSetting>(); + private final ArrayMap<String, PackageSetting> mDisabledSysPackages = + new ArrayMap<String, PackageSetting>(); private static int mFirstAvailableUid = 0; @@ -206,8 +214,8 @@ final class Settings { final SparseArray<CrossProfileIntentResolver> mCrossProfileIntentResolvers = new SparseArray<CrossProfileIntentResolver>(); - final HashMap<String, SharedUserSetting> mSharedUsers = - new HashMap<String, SharedUserSetting>(); + final ArrayMap<String, SharedUserSetting> mSharedUsers = + new ArrayMap<String, SharedUserSetting>(); private final ArrayList<Object> mUserIds = new ArrayList<Object>(); private final SparseArray<Object> mOtherUserIds = new SparseArray<Object>(); @@ -217,12 +225,12 @@ final class Settings { new ArrayList<Signature>(); // Mapping from permission names to info about them. - final HashMap<String, BasePermission> mPermissions = - new HashMap<String, BasePermission>(); + final ArrayMap<String, BasePermission> mPermissions = + new ArrayMap<String, BasePermission>(); // Mapping from permission tree names to info about them. - final HashMap<String, BasePermission> mPermissionTrees = - new HashMap<String, BasePermission>(); + final ArrayMap<String, BasePermission> mPermissionTrees = + new ArrayMap<String, BasePermission>(); // Packages that have been uninstalled and still need their external // storage data deleted. @@ -232,7 +240,7 @@ final class Settings { // Keys are the new names of the packages, values are the original // names. The packages appear everwhere else under their original // names. - final HashMap<String, String> mRenamedPackages = new HashMap<String, String>(); + final ArrayMap<String, String> mRenamedPackages = new ArrayMap<String, String>(); final StringBuilder mReadMessages = new StringBuilder(); @@ -437,7 +445,7 @@ final class Settings { void transferPermissionsLPw(String origPkg, String newPkg) { // Transfer ownership of permissions to the new package. for (int i=0; i<2; i++) { - HashMap<String, BasePermission> permissions = + ArrayMap<String, BasePermission> permissions = i == 0 ? mPermissionTrees : mPermissions; for (BasePermission bp : permissions.values()) { if (origPkg.equals(bp.sourcePackage)) { @@ -582,7 +590,7 @@ final class Settings { } p.appId = dis.appId; // Clone permissions - p.grantedPermissions = new HashSet<String>(dis.grantedPermissions); + p.grantedPermissions = new ArraySet<String>(dis.grantedPermissions); // Clone component info List<UserInfo> users = getAllUsers(); if (users != null) { @@ -1138,8 +1146,8 @@ final class Settings { final boolean blockUninstall = blockUninstallStr == null ? false : Boolean.parseBoolean(blockUninstallStr); - HashSet<String> enabledComponents = null; - HashSet<String> disabledComponents = null; + ArraySet<String> enabledComponents = null; + ArraySet<String> disabledComponents = null; int packageDepth = parser.getDepth(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -1189,9 +1197,9 @@ final class Settings { } } - private HashSet<String> readComponentsLPr(XmlPullParser parser) + private ArraySet<String> readComponentsLPr(XmlPullParser parser) throws IOException, XmlPullParserException { - HashSet<String> components = null; + ArraySet<String> components = null; int type; int outerDepth = parser.getDepth(); String tagName; @@ -1207,7 +1215,7 @@ final class Settings { String componentName = parser.getAttributeValue(null, ATTR_NAME); if (componentName != null) { if (components == null) { - components = new HashSet<String>(); + components = new ArraySet<String>(); } components.add(componentName); } @@ -1921,7 +1929,7 @@ final class Settings { } ArrayList<PackageSetting> getListOfIncompleteInstallPackagesLPr() { - final HashSet<String> kList = new HashSet<String>(mPackages.keySet()); + final ArraySet<String> kList = new ArraySet<String>(mPackages.keySet()); final Iterator<String> its = kList.iterator(); final ArrayList<PackageSetting> ret = new ArrayList<PackageSetting>(); while (its.hasNext()) { @@ -2511,7 +2519,7 @@ final class Settings { return defValue; } - private void readPermissionsLPw(HashMap<String, BasePermission> out, XmlPullParser parser) + private void readPermissionsLPw(ArrayMap<String, BasePermission> out, XmlPullParser parser) throws IOException, XmlPullParserException { int outerDepth = parser.getDepth(); int type; @@ -3016,7 +3024,7 @@ final class Settings { } } - private void readGrantedPermissionsLPw(XmlPullParser parser, HashSet<String> outPerms) + private void readGrantedPermissionsLPw(XmlPullParser parser, ArraySet<String> outPerms) throws IOException, XmlPullParserException { int outerDepth = parser.getDepth(); int type; @@ -3090,8 +3098,8 @@ final class Settings { int sourceUserId = mCrossProfileIntentResolvers.keyAt(i); CrossProfileIntentResolver cpir = mCrossProfileIntentResolvers.get(sourceUserId); boolean needsWriting = false; - HashSet<CrossProfileIntentFilter> cpifs = - new HashSet<CrossProfileIntentFilter>(cpir.filterSet()); + ArraySet<CrossProfileIntentFilter> cpifs = + new ArraySet<CrossProfileIntentFilter>(cpir.filterSet()); for (CrossProfileIntentFilter cpif : cpifs) { if (cpif.getTargetUserId() == userId) { needsWriting = true; @@ -3147,7 +3155,7 @@ final class Settings { return ps; } - private String compToString(HashSet<String> cmp) { + private String compToString(ArraySet<String> cmp) { return cmp != null ? Arrays.toString(cmp.toArray()) : "[]"; } @@ -3477,7 +3485,7 @@ final class Settings { pw.print(prefix); pw.print(" lastDisabledCaller: "); pw.println(lastDisabledAppCaller); } - HashSet<String> cmp = ps.getDisabledComponents(user.id); + ArraySet<String> cmp = ps.getDisabledComponents(user.id); if (cmp != null && cmp.size() > 0) { pw.print(prefix); pw.println(" disabledComponents:"); for (String s : cmp) { @@ -3600,31 +3608,36 @@ final class Settings { } } - void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState) { + void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState, + boolean checkin) { boolean printedSomething = false; for (SharedUserSetting su : mSharedUsers.values()) { if (packageName != null && su != dumpState.getSharedUser()) { continue; } - if (!printedSomething) { - if (dumpState.onTitlePrinted()) - pw.println(); - pw.println("Shared users:"); - printedSomething = true; - } - pw.print(" SharedUser ["); - pw.print(su.name); - 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); + if (!checkin) { + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(); + pw.println("Shared users:"); + printedSomething = true; + } + pw.print(" SharedUser ["); + pw.print(su.name); + 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); + } + } else { + pw.print("suid,"); pw.print(su.userId); pw.print(","); pw.println(su.name); } } } diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index ca1eeea..2b406f7 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -16,7 +16,7 @@ package com.android.server.pm; -import java.util.HashSet; +import android.util.ArraySet; /** * Settings data for a particular shared user ID we know about. @@ -29,7 +29,7 @@ final class SharedUserSetting extends GrantedPermissions { // flags that are associated with this uid, regardless of any package flags int uidFlags; - final HashSet<PackageSetting> packages = new HashSet<PackageSetting>(); + final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>(); final PackageSignatures signatures = new PackageSignatures(); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 0cf2249..db0f53b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1298,7 +1298,12 @@ public class UserManagerService extends IUserManager.Stub { if (userHandle == 0 || user == null || mRemovingUserIds.get(userHandle)) { return false; } + + // We remember deleted user IDs to prevent them from being + // reused during the current boot; they can still be reused + // after a reboot. mRemovingUserIds.put(userHandle, true); + try { mAppOpsService.removeUser(userHandle); } catch (RemoteException e) { @@ -1387,18 +1392,6 @@ public class UserManagerService extends IUserManager.Stub { // Remove this user from the list mUsers.remove(userHandle); - // Have user ID linger for several seconds to let external storage VFS - // cache entries expire. This must be greater than the 'entry_valid' - // timeout used by the FUSE daemon. - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - synchronized (mPackagesLock) { - mRemovingUserIds.delete(userHandle); - } - } - }, MINUTE_IN_MILLIS); - mRestrictionsPinStates.remove(userHandle); // Remove user file AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX)); diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 94a628d..1349926 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -38,6 +38,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -70,9 +71,9 @@ final class Notifier { private static final boolean DEBUG = false; - private static final int POWER_STATE_UNKNOWN = 0; - private static final int POWER_STATE_AWAKE = 1; - private static final int POWER_STATE_ASLEEP = 2; + private static final int INTERACTIVE_STATE_UNKNOWN = 0; + private static final int INTERACTIVE_STATE_AWAKE = 1; + private static final int INTERACTIVE_STATE_ASLEEP = 2; private static final int MSG_USER_ACTIVITY = 1; private static final int MSG_BROADCAST = 2; @@ -92,17 +93,17 @@ final class Notifier { private final Intent mScreenOnIntent; private final Intent mScreenOffIntent; - // The current power state. - private int mActualPowerState; - private int mLastGoToSleepReason; + // The current interactive state. + private int mActualInteractiveState; + private int mLastReason; // True if there is a pending transition that needs to be reported. private boolean mPendingWakeUpBroadcast; private boolean mPendingGoToSleepBroadcast; - // The currently broadcasted power state. This reflects what other parts of the + // The currently broadcasted interactive state. This reflects what other parts of the // system have observed. - private int mBroadcastedPowerState; + private int mBroadcastedInteractiveState; private boolean mBroadcastInProgress; private long mBroadcastStartTime; @@ -236,62 +237,83 @@ final class Notifier { } /** - * Notifies that the device is changing interactive state. + * Notifies that the device is changing wakefulness. */ - public void onInteractiveStateChangeStarted(boolean interactive, final int reason) { + public void onWakefulnessChangeStarted(int wakefulness, int reason) { if (DEBUG) { - Slog.d(TAG, "onInteractiveChangeStarted: interactive=" + interactive + Slog.d(TAG, "onWakefulnessChangeStarted: wakefulness=" + wakefulness + ", reason=" + reason); } + // We handle interactive state changes once they start so that the system can + // set everything up or the user to begin interacting with applications. + final boolean interactive = PowerManagerInternal.isInteractive(wakefulness); + if (interactive) { + handleWakefulnessChange(wakefulness, interactive, reason); + } else { + mLastReason = reason; + } + + // Start input as soon as we start waking up or going to sleep. + mInputManagerInternal.setInteractive(interactive); + } + + /** + * Notifies that the device has finished changing wakefulness. + */ + public void onWakefulnessChangeFinished(int wakefulness) { + if (DEBUG) { + Slog.d(TAG, "onWakefulnessChangeFinished: wakefulness=" + wakefulness); + } + + // Handle interactive state changes once they are finished so that the system can + // finish pending transitions (such as turning the screen off) before causing + // applications to change state visibly. + final boolean interactive = PowerManagerInternal.isInteractive(wakefulness); + if (!interactive) { + handleWakefulnessChange(wakefulness, interactive, mLastReason); + } + } + + private void handleWakefulnessChange(final int wakefulness, boolean interactive, + final int reason) { + // Tell the activity manager about changes in wakefulness, not just interactivity. + // It needs more granularity than other components. + mHandler.post(new Runnable() { + @Override + public void run() { + mActivityManagerInternal.onWakefulnessChanged(wakefulness); + } + }); + + // Handle changes in the overall interactive state. + boolean interactiveChanged = false; synchronized (mLock) { + // Broadcast interactive state changes. if (interactive) { // Waking up... - if (mActualPowerState != POWER_STATE_AWAKE) { - mActualPowerState = POWER_STATE_AWAKE; + interactiveChanged = (mActualInteractiveState != INTERACTIVE_STATE_AWAKE); + if (interactiveChanged) { + mActualInteractiveState = INTERACTIVE_STATE_AWAKE; mPendingWakeUpBroadcast = true; mHandler.post(new Runnable() { @Override public void run() { EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0); mPolicy.wakingUp(); - mActivityManagerInternal.wakingUp(); } }); updatePendingBroadcastLocked(); } } else { // Going to sleep... - mLastGoToSleepReason = reason; - } - } - - mInputManagerInternal.setInteractive(interactive); - - if (interactive) { - try { - mBatteryStats.noteInteractive(true); - } catch (RemoteException ex) { } - } - } - - /** - * Notifies that the device has finished changing interactive state. - */ - public void onInteractiveStateChangeFinished(boolean interactive) { - if (DEBUG) { - Slog.d(TAG, "onInteractiveChangeFinished"); - } - - synchronized (mLock) { - if (!interactive) { - // Finished going to sleep... // This is a good time to make transitions that we don't want the user to see, // such as bringing the key guard to focus. There's no guarantee for this, // however because the user could turn the device on again at any time. // Some things may need to be protected by other mechanisms that defer screen on. - if (mActualPowerState != POWER_STATE_ASLEEP) { - mActualPowerState = POWER_STATE_ASLEEP; + interactiveChanged = (mActualInteractiveState != INTERACTIVE_STATE_ASLEEP); + if (interactiveChanged) { + mActualInteractiveState = INTERACTIVE_STATE_ASLEEP; mPendingGoToSleepBroadcast = true; if (mUserActivityPending) { mUserActivityPending = false; @@ -301,7 +323,7 @@ final class Notifier { @Override public void run() { int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER; - switch (mLastGoToSleepReason) { + switch (reason) { case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN; break; @@ -311,7 +333,6 @@ final class Notifier { } EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0); mPolicy.goingToSleep(why); - mActivityManagerInternal.goingToSleep(); } }); updatePendingBroadcastLocked(); @@ -319,9 +340,10 @@ final class Notifier { } } - if (!interactive) { + // Notify battery stats. + if (interactiveChanged) { try { - mBatteryStats.noteInteractive(false); + mBatteryStats.noteInteractive(interactive); } catch (RemoteException ex) { } } } @@ -366,9 +388,9 @@ final class Notifier { private void updatePendingBroadcastLocked() { if (!mBroadcastInProgress - && mActualPowerState != POWER_STATE_UNKNOWN + && mActualInteractiveState != INTERACTIVE_STATE_UNKNOWN && (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast - || mActualPowerState != mBroadcastedPowerState)) { + || mActualInteractiveState != mBroadcastedInteractiveState)) { mBroadcastInProgress = true; mSuspendBlocker.acquire(); Message msg = mHandler.obtainMessage(MSG_BROADCAST); @@ -396,16 +418,16 @@ final class Notifier { private void sendNextBroadcast() { final int powerState; synchronized (mLock) { - if (mBroadcastedPowerState == POWER_STATE_UNKNOWN) { + if (mBroadcastedInteractiveState == INTERACTIVE_STATE_UNKNOWN) { // Broadcasted power state is unknown. Send wake up. mPendingWakeUpBroadcast = false; - mBroadcastedPowerState = POWER_STATE_AWAKE; - } else if (mBroadcastedPowerState == POWER_STATE_AWAKE) { + mBroadcastedInteractiveState = INTERACTIVE_STATE_AWAKE; + } else if (mBroadcastedInteractiveState == INTERACTIVE_STATE_AWAKE) { // Broadcasted power state is awake. Send asleep if needed. if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast - || mActualPowerState == POWER_STATE_ASLEEP) { + || mActualInteractiveState == INTERACTIVE_STATE_ASLEEP) { mPendingGoToSleepBroadcast = false; - mBroadcastedPowerState = POWER_STATE_ASLEEP; + mBroadcastedInteractiveState = INTERACTIVE_STATE_ASLEEP; } else { finishPendingBroadcastLocked(); return; @@ -413,9 +435,9 @@ final class Notifier { } else { // Broadcasted power state is asleep. Send awake if needed. if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast - || mActualPowerState == POWER_STATE_AWAKE) { + || mActualInteractiveState == INTERACTIVE_STATE_AWAKE) { mPendingWakeUpBroadcast = false; - mBroadcastedPowerState = POWER_STATE_AWAKE; + mBroadcastedInteractiveState = INTERACTIVE_STATE_AWAKE; } else { finishPendingBroadcastLocked(); return; @@ -423,12 +445,12 @@ final class Notifier { } mBroadcastStartTime = SystemClock.uptimeMillis(); - powerState = mBroadcastedPowerState; + powerState = mBroadcastedInteractiveState; } EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, 1); - if (powerState == POWER_STATE_AWAKE) { + if (powerState == INTERACTIVE_STATE_AWAKE) { sendWakeUpBroadcast(); } else { sendGoToSleepBroadcast(); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 52807c0..4d1ab4c 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -61,7 +61,6 @@ import android.os.WorkSource; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import android.util.TimeUtils; import android.view.Display; @@ -73,6 +72,11 @@ import java.util.ArrayList; import libcore.util.Objects; +import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; +import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; +import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; +import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; + /** * The power manager service is responsible for coordinating power management * functions on the device. @@ -88,6 +92,8 @@ public final class PowerManagerService extends SystemService private static final int MSG_USER_ACTIVITY_TIMEOUT = 1; // Message: Sent when the device enters or exits a dreaming or dozing state. private static final int MSG_SANDMAN = 2; + // Message: Sent when the screen brightness boost expires. + private static final int MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 3; // Dirty bit: mWakeLocks changed private static final int DIRTY_WAKE_LOCKS = 1 << 0; @@ -111,24 +117,8 @@ public final class PowerManagerService extends SystemService private static final int DIRTY_PROXIMITY_POSITIVE = 1 << 9; // Dirty bit: dock state changed private static final int DIRTY_DOCK_STATE = 1 << 10; - - // Wakefulness: The device is asleep and can only be awoken by a call to wakeUp(). - // The screen should be off or in the process of being turned off by the display controller. - // The device typically passes through the dozing state first. - private static final int WAKEFULNESS_ASLEEP = 0; - // Wakefulness: The device is fully awake. It can be put to sleep by a call to goToSleep(). - // When the user activity timeout expires, the device may start dreaming or go to sleep. - private static final int WAKEFULNESS_AWAKE = 1; - // Wakefulness: The device is dreaming. It can be awoken by a call to wakeUp(), - // which ends the dream. The device goes to sleep when goToSleep() is called, when - // the dream ends or when unplugged. - // User activity may brighten the screen but does not end the dream. - private static final int WAKEFULNESS_DREAMING = 2; - // Wakefulness: The device is dozing. It is almost asleep but is allowing a special - // low-power "doze" dream to run which keeps the display on but lets the application - // processor be suspended. It can be awoken by a call to wakeUp() which ends the dream. - // The device fully goes to sleep if the dream cannot be started or ends on its own. - private static final int WAKEFULNESS_DOZING = 3; + // Dirty bit: brightness boost changed + private static final int DIRTY_SCREEN_BRIGHTNESS_BOOST = 1 << 11; // Summarizes the state of all active wakelocks. private static final int WAKE_LOCK_CPU = 1 << 0; @@ -149,6 +139,11 @@ public final class PowerManagerService extends SystemService private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000; private static final int DEFAULT_SLEEP_TIMEOUT = -1; + // Screen brightness boost timeout. + // Hardcoded for now until we decide what the right policy should be. + // This should perhaps be a setting. + private static final int SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 5 * 1000; + // Power hints defined in hardware/libhardware/include/hardware/power.h. private static final int POWER_HINT_INTERACTION = 2; private static final int POWER_HINT_LOW_POWER = 5; @@ -178,6 +173,7 @@ public final class PowerManagerService extends SystemService // Indicates whether the device is awake or asleep or somewhere in between. // This is distinct from the screen power state, which is managed separately. private int mWakefulness; + private boolean mWakefulnessChanging; // True if the sandman has just been summoned for the first time since entering the // dreaming or dozing state. Indicates whether a new dream should begin. @@ -196,10 +192,6 @@ public final class PowerManagerService extends SystemService // A bitfield that summarizes the state of all active wakelocks. private int mWakeLockSummary; - // True if the device is in an interactive state. - private boolean mInteractive; - private boolean mInteractiveChanging; - // If true, instructs the display controller to wait for the proximity sensor to // go negative before turning the screen on. private boolean mRequestWaitForNegativeProximity; @@ -215,6 +207,10 @@ public final class PowerManagerService extends SystemService // Timestamp of last interactive power hint. private long mLastInteractivePowerHintTime; + // Timestamp of the last screen brightness boost. + private long mLastScreenBrightnessBoostTime; + private boolean mScreenBrightnessBoostInProgress; + // A bitfield that summarizes the effect of the user activity timer. private int mUserActivitySummary; @@ -281,6 +277,9 @@ public final class PowerManagerService extends SystemService // True if the device should wake up when plugged or unplugged. private boolean mWakeUpWhenPluggedOrUnpluggedConfig; + // True if the device should wake up when plugged or unplugged in theater mode. + private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig; + // True if the device should suspend when the screen is off due to proximity. private boolean mSuspendWhenScreenOffDueToProximityConfig; @@ -420,6 +419,9 @@ public final class PowerManagerService extends SystemService // True if the battery level is currently considered low. private boolean mBatteryLevelLow; + // True if theater mode is enabled + private boolean mTheaterModeEnabled; + private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners = new ArrayList<PowerManagerInternal.LowPowerModeListener>(); @@ -448,7 +450,6 @@ public final class PowerManagerService extends SystemService mHalInteractiveModeEnabled = true; mWakefulness = WAKEFULNESS_AWAKE; - mInteractive = true; nativeInit(); nativeSetAutoSuspend(false); @@ -568,6 +569,9 @@ public final class PowerManagerService extends SystemService resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.THEATER_MODE_ON), + false, mSettingsObserver, UserHandle.USER_ALL); // Go. readConfigurationLocked(); updateSettingsLocked(); @@ -585,6 +589,8 @@ public final class PowerManagerService extends SystemService com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay); mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean( com.android.internal.R.bool.config_unplugTurnsOnScreen); + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug); mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean( com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity); mDreamsSupportedConfig = resources.getBoolean( @@ -636,6 +642,8 @@ public final class PowerManagerService extends SystemService UserHandle.USER_CURRENT); mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver, Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC); + mTheaterModeEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.THEATER_MODE_ON, 0) == 1; final int oldScreenBrightnessSetting = mScreenBrightnessSetting; mScreenBrightnessSetting = Settings.System.getIntForUser(resolver, @@ -1021,9 +1029,7 @@ public final class PowerManagerService extends SystemService } mLastWakeTime = eventTime; - mDirty |= DIRTY_WAKEFULNESS; - mWakefulness = WAKEFULNESS_AWAKE; - setInteractiveStateLocked(true, 0); + setWakefulnessLocked(WAKEFULNESS_AWAKE, 0); userActivityNoUpdateLocked( eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); @@ -1083,10 +1089,8 @@ public final class PowerManagerService extends SystemService } mLastSleepTime = eventTime; - mDirty |= DIRTY_WAKEFULNESS; - mWakefulness = WAKEFULNESS_DOZING; mSandmanSummoned = true; - setInteractiveStateLocked(false, reason); + setWakefulnessLocked(WAKEFULNESS_DOZING, reason); // Report the number of wake locks that will be cleared by going to sleep. int numWakeLocksCleared = 0; @@ -1135,10 +1139,8 @@ public final class PowerManagerService extends SystemService try { Slog.i(TAG, "Nap time (uid " + uid +")..."); - mDirty |= DIRTY_WAKEFULNESS; - mWakefulness = WAKEFULNESS_DREAMING; mSandmanSummoned = true; - setInteractiveStateLocked(true, 0); + setWakefulnessLocked(WAKEFULNESS_DREAMING, 0); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } @@ -1161,29 +1163,28 @@ public final class PowerManagerService extends SystemService try { Slog.i(TAG, "Sleeping (uid " + uid +")..."); - mDirty |= DIRTY_WAKEFULNESS; - mWakefulness = WAKEFULNESS_ASLEEP; - setInteractiveStateLocked(false, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + setWakefulnessLocked(WAKEFULNESS_ASLEEP, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return true; } - private void setInteractiveStateLocked(boolean interactive, int reason) { - if (mInteractive != interactive) { - finishInteractiveStateChangeLocked(); + private void setWakefulnessLocked(int wakefulness, int reason) { + if (mWakefulness != wakefulness) { + finishWakefulnessChangeLocked(); - mInteractive = interactive; - mInteractiveChanging = true; - mNotifier.onInteractiveStateChangeStarted(interactive, reason); + mWakefulness = wakefulness; + mWakefulnessChanging = true; + mDirty |= DIRTY_WAKEFULNESS; + mNotifier.onWakefulnessChangeStarted(wakefulness, reason); } } - private void finishInteractiveStateChangeLocked() { - if (mInteractiveChanging) { - mNotifier.onInteractiveStateChangeFinished(mInteractive); - mInteractiveChanging = false; + private void finishWakefulnessChangeLocked() { + if (mWakefulnessChanging) { + mNotifier.onWakefulnessChangeFinished(mWakefulness); + mWakefulnessChanging = false; } } @@ -1208,6 +1209,7 @@ public final class PowerManagerService extends SystemService // Phase 0: Basic state updates. updateIsPoweredLocked(mDirty); updateStayOnLocked(mDirty); + updateScreenBrightnessBoostLocked(mDirty); // Phase 1: Update wakefulness. // Loop because the wake lock and user activity computations are influenced @@ -1234,7 +1236,7 @@ public final class PowerManagerService extends SystemService // Phase 4: Send notifications, if needed. if (mDisplayReady) { - finishInteractiveStateChangeLocked(); + finishWakefulnessChangeLocked(); } // Phase 5: Update suspend blocker. @@ -1334,6 +1336,11 @@ public final class PowerManagerService extends SystemService return false; } + // Don't wake while theater mode is enabled. + if (mTheaterModeEnabled && !mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig) { + return false; + } + // Otherwise wake up! return true; } @@ -1418,7 +1425,7 @@ public final class PowerManagerService extends SystemService if (DEBUG_SPEW) { Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness=" - + wakefulnessToString(mWakefulness) + + PowerManagerInternal.wakefulnessToString(mWakefulness) + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)); } } @@ -1495,7 +1502,7 @@ public final class PowerManagerService extends SystemService if (DEBUG_SPEW) { Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness=" - + wakefulnessToString(mWakefulness) + + PowerManagerInternal.wakefulnessToString(mWakefulness) + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout)); } @@ -1610,7 +1617,8 @@ public final class PowerManagerService extends SystemService || mProximityPositive || (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0 || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT - | USER_ACTIVITY_SCREEN_DIM)) != 0; + | USER_ACTIVITY_SCREEN_DIM)) != 0 + || mScreenBrightnessBoostInProgress; } /** @@ -1794,9 +1802,10 @@ public final class PowerManagerService extends SystemService final boolean oldDisplayReady = mDisplayReady; if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED - | DIRTY_SETTINGS)) != 0) { + | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) { mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(); + // Determine appropriate screen brightness and auto-brightness adjustments. int screenBrightness = mScreenBrightnessSettingDefault; float screenAutoBrightnessAdjustment = 0.0f; boolean autoBrightness = (mScreenBrightnessModeSetting == @@ -1824,14 +1833,15 @@ public final class PowerManagerService extends SystemService mScreenBrightnessSettingMaximum), mScreenBrightnessSettingMinimum); screenAutoBrightnessAdjustment = Math.max(Math.min( screenAutoBrightnessAdjustment, 1.0f), -1.0f); + + // Update display power request. mDisplayPowerRequest.screenBrightness = screenBrightness; mDisplayPowerRequest.screenAutoBrightnessAdjustment = screenAutoBrightnessAdjustment; mDisplayPowerRequest.useAutoBrightness = autoBrightness; - mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked(); - mDisplayPowerRequest.lowPowerMode = mLowPowerModeEnabled; + mDisplayPowerRequest.boostScreenBrightness = mScreenBrightnessBoostInProgress; if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) { mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager; @@ -1847,17 +1857,41 @@ public final class PowerManagerService extends SystemService mRequestWaitForNegativeProximity = false; if (DEBUG_SPEW) { - Slog.d(TAG, "updateScreenStateLocked: mDisplayReady=" + mDisplayReady + Slog.d(TAG, "updateDisplayPowerStateLocked: mDisplayReady=" + mDisplayReady + ", policy=" + mDisplayPowerRequest.policy + ", mWakefulness=" + mWakefulness + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) - + ", mBootCompleted=" + mBootCompleted); + + ", mBootCompleted=" + mBootCompleted + + ", mScreenBrightnessBoostInProgress=" + + mScreenBrightnessBoostInProgress); } } return mDisplayReady && !oldDisplayReady; } + private void updateScreenBrightnessBoostLocked(int dirty) { + if ((dirty & DIRTY_SCREEN_BRIGHTNESS_BOOST) != 0) { + if (mScreenBrightnessBoostInProgress) { + final long now = SystemClock.uptimeMillis(); + mHandler.removeMessages(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT); + if (mLastScreenBrightnessBoostTime > mLastSleepTime) { + final long boostTimeout = mLastScreenBrightnessBoostTime + + SCREEN_BRIGHTNESS_BOOST_TIMEOUT; + if (boostTimeout > now) { + Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, boostTimeout); + return; + } + } + mScreenBrightnessBoostInProgress = false; + userActivityNoUpdateLocked(now, + PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + } + } + } + private static boolean isValidBrightness(int value) { return value >= 0 && value <= 255; } @@ -1885,7 +1919,8 @@ public final class PowerManagerService extends SystemService if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0 || (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 - || !mBootCompleted) { + || !mBootCompleted + || mScreenBrightnessBoostInProgress) { return DisplayPowerRequest.POLICY_BRIGHT; } @@ -1982,15 +2017,13 @@ public final class PowerManagerService extends SystemService final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0); final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked(); final boolean autoSuspend = !needDisplaySuspendBlocker; + final boolean interactive = mDisplayPowerRequest.isBrightOrDim(); // Disable auto-suspend if needed. - if (!autoSuspend) { - if (mDecoupleHalAutoSuspendModeFromDisplayConfig) { - setHalAutoSuspendModeLocked(false); - } - if (mDecoupleHalInteractiveModeFromDisplayConfig) { - setHalInteractiveModeLocked(true); - } + // FIXME We should consider just leaving auto-suspend enabled forever since + // we already hold the necessary wakelocks. + if (!autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) { + setHalAutoSuspendModeLocked(false); } // First acquire suspend blockers if needed. @@ -2003,6 +2036,22 @@ public final class PowerManagerService extends SystemService mHoldingDisplaySuspendBlocker = true; } + // Inform the power HAL about interactive mode. + // Although we could set interactive strictly based on the wakefulness + // as reported by isInteractive(), it is actually more desirable to track + // the display policy state instead so that the interactive state observed + // by the HAL more accurately tracks transitions between AWAKE and DOZING. + // Refer to getDesiredScreenPolicyLocked() for details. + if (mDecoupleHalInteractiveModeFromDisplayConfig) { + // When becoming non-interactive, we want to defer sending this signal + // until the display is actually ready so that all transitions have + // completed. This is probably a good sign that things have gotten + // too tangled over here... + if (interactive || mDisplayReady) { + setHalInteractiveModeLocked(interactive); + } + } + // Then release suspend blockers if needed. if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) { mWakeLockSuspendBlocker.release(); @@ -2014,13 +2063,8 @@ public final class PowerManagerService extends SystemService } // Enable auto-suspend if needed. - if (autoSuspend) { - if (mDecoupleHalInteractiveModeFromDisplayConfig) { - setHalInteractiveModeLocked(false); - } - if (mDecoupleHalAutoSuspendModeFromDisplayConfig) { - setHalAutoSuspendModeLocked(true); - } + if (autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) { + setHalAutoSuspendModeLocked(true); } } @@ -2042,6 +2086,9 @@ public final class PowerManagerService extends SystemService return true; } } + if (mScreenBrightnessBoostInProgress) { + return true; + } // Let the system suspend if the screen is off or dozing. return false; } @@ -2078,7 +2125,7 @@ public final class PowerManagerService extends SystemService private boolean isInteractiveInternal() { synchronized (mLock) { - return mInteractive; + return PowerManagerInternal.isInteractive(mWakefulness); } } @@ -2205,6 +2252,41 @@ public final class PowerManagerService extends SystemService light.setFlashing(color, Light.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0); } + private void boostScreenBrightnessInternal(long eventTime, int uid) { + synchronized (mLock) { + if (!mSystemReady || mWakefulness == WAKEFULNESS_ASLEEP + || eventTime < mLastScreenBrightnessBoostTime) { + return; + } + + Slog.i(TAG, "Brightness boost activated (uid " + uid +")..."); + mLastScreenBrightnessBoostTime = eventTime; + mScreenBrightnessBoostInProgress = true; + mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST; + + userActivityNoUpdateLocked(eventTime, + PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid); + updatePowerStateLocked(); + } + } + + /** + * Called when a screen brightness boost timeout has occurred. + * + * This function must have no other side-effects besides setting the dirty + * bit and calling update power state. + */ + private void handleScreenBrightnessBoostTimeout() { // runs on handler thread + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "handleScreenBrightnessBoostTimeout"); + } + + mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST; + updatePowerStateLocked(); + } + } + private void setScreenBrightnessOverrideFromWindowManagerInternal(int brightness) { synchronized (mLock) { if (mScreenBrightnessOverrideFromWindowManager != brightness) { @@ -2321,8 +2403,8 @@ public final class PowerManagerService extends SystemService synchronized (mLock) { pw.println("Power Manager State:"); pw.println(" mDirty=0x" + Integer.toHexString(mDirty)); - pw.println(" mWakefulness=" + wakefulnessToString(mWakefulness)); - pw.println(" mInteractive=" + mInteractive); + pw.println(" mWakefulness=" + PowerManagerInternal.wakefulnessToString(mWakefulness)); + pw.println(" mWakefulnessChanging=" + mWakefulnessChanging); pw.println(" mIsPowered=" + mIsPowered); pw.println(" mPlugType=" + mPlugType); pw.println(" mBatteryLevel=" + mBatteryLevel); @@ -2348,6 +2430,10 @@ public final class PowerManagerService extends SystemService + TimeUtils.formatUptime(mLastUserActivityTimeNoChangeLights)); pw.println(" mLastInteractivePowerHintTime=" + TimeUtils.formatUptime(mLastInteractivePowerHintTime)); + pw.println(" mLastScreenBrightnessBoostTime=" + + TimeUtils.formatUptime(mLastScreenBrightnessBoostTime)); + pw.println(" mScreenBrightnessBoostInProgress=" + + mScreenBrightnessBoostInProgress); pw.println(" mDisplayReady=" + mDisplayReady); pw.println(" mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker); pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker); @@ -2360,6 +2446,10 @@ public final class PowerManagerService extends SystemService + mDecoupleHalInteractiveModeFromDisplayConfig); pw.println(" mWakeUpWhenPluggedOrUnpluggedConfig=" + mWakeUpWhenPluggedOrUnpluggedConfig); + pw.println(" mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=" + + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig); + pw.println(" mTheaterModeEnabled=" + + mTheaterModeEnabled); pw.println(" mSuspendWhenScreenOffDueToProximityConfig=" + mSuspendWhenScreenOffDueToProximityConfig); pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); @@ -2449,21 +2539,6 @@ public final class PowerManagerService extends SystemService return suspendBlocker; } - private static String wakefulnessToString(int wakefulness) { - switch (wakefulness) { - case WAKEFULNESS_ASLEEP: - return "Asleep"; - case WAKEFULNESS_AWAKE: - return "Awake"; - case WAKEFULNESS_DREAMING: - return "Dreaming"; - case WAKEFULNESS_DOZING: - return "Dozing"; - default: - return Integer.toString(wakefulness); - } - } - private static WorkSource copyWorkSource(WorkSource workSource) { return workSource != null ? new WorkSource(workSource) : null; } @@ -2540,6 +2615,9 @@ public final class PowerManagerService extends SystemService case MSG_SANDMAN: handleSandman(); break; + case MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT: + handleScreenBrightnessBoostTimeout(); + break; } } } @@ -3040,21 +3118,6 @@ public final class PowerManagerService extends SystemService } /** - * Used by device administration to set the maximum screen off timeout. - * - * This method must only be called by the device administration policy manager. - */ - @Override // Binder call - public void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs) { - final long ident = Binder.clearCallingIdentity(); - try { - setMaximumScreenOffTimeoutFromDeviceAdminInternal(timeMs); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - /** * Used by the settings application and brightness control widgets to * temporarily override the current screen brightness setting so that the * user can observe the effect of an intended settings change without applying @@ -3121,6 +3184,24 @@ public final class PowerManagerService extends SystemService } @Override // Binder call + public void boostScreenBrightness(long eventTime) { + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.DEVICE_POWER, null); + + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + boostScreenBrightnessInternal(eventTime, uid); + } 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) { @@ -3181,6 +3262,11 @@ public final class PowerManagerService extends SystemService } @Override + public void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs) { + setMaximumScreenOffTimeoutFromDeviceAdminInternal(timeMs); + } + + @Override public boolean getLowPowerModeEnabled() { synchronized (mLock) { return mLowPowerModeEnabled; diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index c28e0bc..58c3ea1 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -23,4 +23,5 @@ public interface StatusBarManagerInternal { void buzzBeepBlinked(); void notificationLightPulse(int argb, int onMillis, int offMillis); void notificationLightOff(); + void showScreenPinningRequest(); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index f85e2d9..9828cd4 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.util.Slog; +import android.view.WindowManager; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; @@ -141,6 +142,16 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } } + + @Override + public void showScreenPinningRequest() { + if (mBar != null) { + try { + mBar.showScreenPinningRequest(); + } catch (RemoteException e) { + } + } + } }; // ================================================================================ @@ -295,9 +306,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } - /** + /** * Hide or show the on-screen Menu key. Only call this from the window manager, typically in - * response to a window with FLAG_NEEDS_MENU_KEY set. + * response to a window with {@link android.view.WindowManager.LayoutParams#needsMenuKey} set + * to {@link android.view.WindowManager.LayoutParams#NEEDS_MENU_SET_TRUE}. */ @Override public void topAppWindowChanged(final boolean menuVisible) { @@ -351,7 +363,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public void setSystemUiVisibility(int vis, int mask) { + public void setSystemUiVisibility(int vis, int mask, String cause) { // also allows calls from window manager which is in this process. enforceStatusBarService(); @@ -363,7 +375,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { mCurrentUserId, vis & StatusBarManager.DISABLE_MASK, mSysUiVisToken, - "WindowManager.LayoutParams"); + cause); } } @@ -523,6 +535,20 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override + public void onNotificationActionClick(String key, int actionIndex) { + enforceStatusBarService(); + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.onNotificationActionClick(callingUid, callingPid, key, + actionIndex); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void onNotificationError(String pkg, String tag, int id, int uid, int initialPid, String message, int userId) { enforceStatusBarService(); diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index b1c918d..57b204d 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -27,11 +27,11 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.net.Uri; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PatternMatcher; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -39,9 +39,7 @@ import android.util.Log; import android.util.Slog; import android.service.trust.ITrustAgentService; import android.service.trust.ITrustAgentServiceCallback; -import android.service.trust.TrustAgentService; -import java.util.ArrayList; import java.util.List; /** @@ -160,7 +158,7 @@ public class TrustAgentWrapper { mTrustManagerService.updateTrust(mUserId, false); break; case MSG_RESTART_TIMEOUT: - unbind(); + destroy(); mTrustManagerService.resetAgent(mName, mUserId); break; case MSG_SET_TRUST_AGENT_FEATURES_COMPLETED: @@ -218,7 +216,7 @@ public class TrustAgentWrapper { } @Override - public void onSetTrustAgentFeaturesEnabledCompleted(boolean result, IBinder token) { + public void onConfigureCompleted(boolean result, IBinder token) { if (DEBUG) Slog.v(TAG, "onSetTrustAgentFeaturesEnabledCompleted(result=" + result); mHandler.obtainMessage(MSG_SET_TRUST_AGENT_FEATURES_COMPLETED, result ? 1 : 0, 0, token).sendToTarget(); @@ -234,6 +232,12 @@ public class TrustAgentWrapper { mTrustManagerService.mArchive.logAgentConnected(mUserId, name); setCallback(mCallback); updateDevicePolicyFeatures(); + + if (mTrustManagerService.isDeviceLockedInner(mUserId)) { + onDeviceLocked(); + } else { + onDeviceUnlocked(); + } } @Override @@ -289,6 +293,7 @@ public class TrustAgentWrapper { onError(e); } } + /** * @see android.service.trust.TrustAgentService#onUnlockAttempt(boolean) */ @@ -300,6 +305,28 @@ public class TrustAgentWrapper { } } + /** + * @see android.service.trust.TrustAgentService#onDeviceLocked() + */ + public void onDeviceLocked() { + try { + if (mTrustAgentService != null) mTrustAgentService.onDeviceLocked(); + } catch (RemoteException e) { + onError(e); + } + } + + /** + * @see android.service.trust.TrustAgentService#onDeviceUnlocked() + */ + public void onDeviceUnlocked() { + try { + if (mTrustAgentService != null) mTrustAgentService.onDeviceUnlocked(); + } catch (RemoteException e) { + onError(e); + } + } + private void setCallback(ITrustAgentServiceCallback callback) { try { if (mTrustAgentService != null) { @@ -318,23 +345,19 @@ public class TrustAgentWrapper { DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); - if ((dpm.getKeyguardDisabledFeatures(null) + if ((dpm.getKeyguardDisabledFeatures(null, mUserId) & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0) { - List<String> features = dpm.getTrustAgentFeaturesEnabled(null, mName); + List<PersistableBundle> config = dpm.getTrustAgentConfiguration( + null, mName, mUserId); trustDisabled = true; - if (DEBUG) Slog.v(TAG, "Detected trust agents disabled. Features = " - + features); - if (features != null && features.size() > 0) { - Bundle bundle = new Bundle(); - bundle.putStringArrayList(TrustAgentService.KEY_FEATURES, - (ArrayList<String>)features); + if (DEBUG) Slog.v(TAG, "Detected trust agents disabled. Config = " + config); + if (config != null && config.size() > 0) { if (DEBUG) { Slog.v(TAG, "TrustAgent " + mName.flattenToShortString() - + " disabled until it acknowledges "+ features); + + " disabled until it acknowledges "+ config); } mSetTrustAgentFeaturesToken = new Binder(); - mTrustAgentService.setTrustAgentFeaturesEnabled(bundle, - mSetTrustAgentFeaturesToken); + mTrustAgentService.onConfigure(config, mSetTrustAgentFeaturesToken); } } final long maxTimeToLock = dpm.getMaximumTimeToLock(null); @@ -371,7 +394,9 @@ public class TrustAgentWrapper { return mMessage; } - public void unbind() { + public void destroy() { + mHandler.removeMessages(MSG_RESTART_TIMEOUT); + if (!mBound) { return; } @@ -382,7 +407,6 @@ public class TrustAgentWrapper { mTrustAgentService = null; mSetTrustAgentFeaturesToken = null; mHandler.sendEmptyMessage(MSG_REVOKE_TRUST); - mHandler.removeMessages(MSG_RESTART_TIMEOUT); } public boolean isConnected() { diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index fefbe0a..cd1c9a5 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -16,6 +16,7 @@ package com.android.server.trust; +import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; @@ -24,7 +25,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.Manifest; -import android.app.ActivityManagerNative; +import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustListener; import android.app.trust.ITrustManager; @@ -41,10 +42,12 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; +import android.os.Binder; import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -57,6 +60,8 @@ import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.Xml; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; import java.io.FileDescriptor; import java.io.IOException; @@ -92,6 +97,10 @@ public class TrustManagerService extends SystemService { private static final int MSG_DISPATCH_UNLOCK_ATTEMPT = 3; private static final int MSG_ENABLED_AGENTS_CHANGED = 4; private static final int MSG_REQUIRE_CREDENTIAL_ENTRY = 5; + private static final int MSG_KEYGUARD_SHOWING_CHANGED = 6; + private static final int MSG_START_USER = 7; + private static final int MSG_CLEANUP_USER = 8; + private static final int MSG_SWITCH_USER = 9; private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<AgentInfo>(); private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<ITrustListener>(); @@ -100,13 +109,23 @@ public class TrustManagerService extends SystemService { /* package */ final TrustArchive mArchive = new TrustArchive(); private final Context mContext; private final LockPatternUtils mLockPatternUtils; + private final UserManager mUserManager; + private final ActivityManager mActivityManager; - private UserManager mUserManager; + @GuardedBy("mUserIsTrusted") + private final SparseBooleanArray mUserIsTrusted = new SparseBooleanArray(); + + @GuardedBy("mDeviceLockedForUser") + private final SparseBooleanArray mDeviceLockedForUser = new SparseBooleanArray(); + + private boolean mTrustAgentsCanRun = false; + private int mCurrentUser = UserHandle.USER_OWNER; public TrustManagerService(Context context) { super(context); mContext = context; mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); mLockPatternUtils = new LockPatternUtils(context); } @@ -117,11 +136,18 @@ public class TrustManagerService extends SystemService { @Override public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY && !isSafeMode()) { + if (isSafeMode()) { + // No trust agents in safe mode. + return; + } + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true); mReceiver.register(mContext); - maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_OWNER); + } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { + mTrustAgentsCanRun = true; refreshAgentList(UserHandle.USER_ALL); + } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { + maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_OWNER); } } @@ -159,11 +185,23 @@ public class TrustManagerService extends SystemService { public void updateTrust(int userId, boolean initiatedByUser) { dispatchOnTrustManagedChanged(aggregateIsTrustManaged(userId), userId); - dispatchOnTrustChanged(aggregateIsTrusted(userId), userId, initiatedByUser); + boolean trusted = aggregateIsTrusted(userId); + boolean changed; + synchronized (mUserIsTrusted) { + changed = mUserIsTrusted.get(userId) != trusted; + mUserIsTrusted.put(userId, trusted); + } + dispatchOnTrustChanged(trusted, userId, initiatedByUser); + if (changed) { + refreshDeviceLockedForUser(userId); + } } void refreshAgentList(int userId) { if (DEBUG) Slog.d(TAG, "refreshAgentList()"); + if (!mTrustAgentsCanRun) { + return; + } if (userId != UserHandle.USER_ALL && userId < UserHandle.USER_OWNER) { Log.e(TAG, "refreshAgentList(userId=" + userId + "): Invalid user handle," + " must be USER_ALL or a specific user.", new Throwable("here")); @@ -184,9 +222,11 @@ public class TrustManagerService extends SystemService { obsoleteAgents.addAll(mActiveAgents); for (UserInfo userInfo : userInfos) { + if (userInfo == null || userInfo.partial || !userInfo.isEnabled() + || userInfo.guestToRemove) continue; if (!userInfo.supportsSwitchTo()) continue; - if (lockPatternUtils.getKeyguardStoredPasswordQuality(userInfo.id) - == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) continue; + if (!mActivityManager.isUserRunning(userInfo.id)) continue; + if (!lockPatternUtils.isSecure(userInfo.id)) continue; if (!mUserHasAuthenticatedSinceBoot.get(userInfo.id)) continue; DevicePolicyManager dpm = lockPatternUtils.getDevicePolicyManager(); int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userInfo.id); @@ -203,10 +243,10 @@ public class TrustManagerService extends SystemService { if (!enabledAgents.contains(name)) continue; if (disableTrustAgents) { - List<String> features = - dpm.getTrustAgentFeaturesEnabled(null /* admin */, name); + List<PersistableBundle> config = + dpm.getTrustAgentConfiguration(null /* admin */, name, userInfo.id); // Disable agent if no features are enabled. - if (features == null || features.isEmpty()) continue; + if (config == null || config.isEmpty()) continue; } AgentInfo agentInfo = new AgentInfo(); @@ -232,13 +272,84 @@ public class TrustManagerService extends SystemService { if (info.agent.isManagingTrust()) { trustMayHaveChanged = true; } - info.agent.unbind(); + info.agent.destroy(); mActiveAgents.remove(info); } } if (trustMayHaveChanged) { - updateTrustAll(); + if (userId == UserHandle.USER_ALL) { + updateTrustAll(); + } else { + updateTrust(userId, false /* initiatedByUser */); + } + } + } + + boolean isDeviceLockedInner(int userId) { + synchronized (mDeviceLockedForUser) { + return mDeviceLockedForUser.get(userId, true); + } + } + + private void refreshDeviceLockedForUser(int userId) { + if (userId != UserHandle.USER_ALL && userId < UserHandle.USER_OWNER) { + Log.e(TAG, "refreshDeviceLockedForUser(userId=" + userId + "): Invalid user handle," + + " must be USER_ALL or a specific user.", new Throwable("here")); + userId = UserHandle.USER_ALL; + } + + List<UserInfo> userInfos; + if (userId == UserHandle.USER_ALL) { + userInfos = mUserManager.getUsers(true /* excludeDying */); + } else { + userInfos = new ArrayList<>(); + userInfos.add(mUserManager.getUserInfo(userId)); + } + + IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + + for (int i = 0; i < userInfos.size(); i++) { + UserInfo info = userInfos.get(i); + + if (info == null || info.partial || !info.isEnabled() || info.guestToRemove + || !info.supportsSwitchTo()) { + continue; + } + + int id = info.id; + boolean secure = mLockPatternUtils.isSecure(id); + boolean trusted = aggregateIsTrusted(id); + boolean showingKeyguard = true; + if (mCurrentUser == id) { + try { + showingKeyguard = wm.isKeyguardLocked(); + } catch (RemoteException e) { + } + } + boolean deviceLocked = secure && showingKeyguard && !trusted; + + boolean changed; + synchronized (mDeviceLockedForUser) { + changed = isDeviceLockedInner(id) != deviceLocked; + mDeviceLockedForUser.put(id, deviceLocked); + } + if (changed) { + dispatchDeviceLocked(id, deviceLocked); + } + } + } + + private void dispatchDeviceLocked(int userId, boolean isLocked) { + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo agent = mActiveAgents.valueAt(i); + if (agent.userId == userId) { + if (isLocked) { + agent.agent.onDeviceLocked(); + } else{ + agent.agent.onDeviceUnlocked(); + } + } } } @@ -260,7 +371,7 @@ public class TrustManagerService extends SystemService { if (info.agent.isManagingTrust()) { trustMayHaveChanged = true; } - info.agent.unbind(); + info.agent.destroy(); mActiveAgents.removeAt(i); } } @@ -278,7 +389,7 @@ public class TrustManagerService extends SystemService { if (info.agent.isManagingTrust()) { trustMayHaveChanged = true; } - info.agent.unbind(); + info.agent.destroy(); mActiveAgents.removeAt(i); } } @@ -505,6 +616,23 @@ public class TrustManagerService extends SystemService { } } + // User lifecycle + + @Override + public void onStartUser(int userId) { + mHandler.obtainMessage(MSG_START_USER, userId, 0, null).sendToTarget(); + } + + @Override + public void onCleanupUser(int userId) { + mHandler.obtainMessage(MSG_CLEANUP_USER, userId, 0, null).sendToTarget(); + } + + @Override + public void onSwitchUser(int userId) { + mHandler.obtainMessage(MSG_SWITCH_USER, userId, 0, null).sendToTarget(); + } + // Plumbing private final IBinder mService = new ITrustManager.Stub() { @@ -535,6 +663,14 @@ public class TrustManagerService extends SystemService { } @Override + public void reportKeyguardShowingChanged() throws RemoteException { + enforceReportPermission(); + // coalesce refresh messages. + mHandler.removeMessages(MSG_KEYGUARD_SHOWING_CHANGED); + mHandler.sendEmptyMessage(MSG_KEYGUARD_SHOWING_CHANGED); + } + + @Override public void registerTrustListener(ITrustListener trustListener) throws RemoteException { enforceListenerPermission(); mHandler.obtainMessage(MSG_REGISTER_LISTENER, trustListener).sendToTarget(); @@ -546,6 +682,15 @@ public class TrustManagerService extends SystemService { mHandler.obtainMessage(MSG_UNREGISTER_LISTENER, trustListener).sendToTarget(); } + @Override + public boolean isDeviceLocked(int userId) throws RemoteException { + userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, + false /* allowAll */, true /* requireFull */, "isDeviceLocked", null); + userId = resolveProfileParent(userId); + + return isDeviceLockedInner(userId); + } + private void enforceReportPermission() { mContext.enforceCallingOrSelfPermission( Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events"); @@ -560,19 +705,21 @@ public class TrustManagerService extends SystemService { protected void dump(FileDescriptor fd, final PrintWriter fout, String[] args) { mContext.enforceCallingPermission(Manifest.permission.DUMP, "dumping TrustManagerService"); - final UserInfo currentUser; - final List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */); - try { - currentUser = ActivityManagerNative.getDefault().getCurrentUser(); - } catch (RemoteException e) { - throw new RuntimeException(e); + if (isSafeMode()) { + fout.println("disabled because the system is in safe mode."); + return; } + if (!mTrustAgentsCanRun) { + fout.println("disabled because the third-party apps can't run yet."); + return; + } + final List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */); mHandler.runWithScissors(new Runnable() { @Override public void run() { fout.println("Trust manager state:"); for (UserInfo user : userInfos) { - dumpUser(fout, user, user.id == currentUser.id); + dumpUser(fout, user, user.id == mCurrentUser); } } }, 1500); @@ -581,11 +728,17 @@ public class TrustManagerService extends SystemService { private void dumpUser(PrintWriter fout, UserInfo user, boolean isCurrent) { fout.printf(" User \"%s\" (id=%d, flags=%#x)", user.name, user.id, user.flags); + if (!user.supportsSwitchTo()) { + fout.println("(managed profile)"); + fout.println(" disabled because switching to this user is not possible."); + return; + } if (isCurrent) { fout.print(" (current)"); } fout.print(": trusted=" + dumpBool(aggregateIsTrusted(user.id))); fout.print(", trustManaged=" + dumpBool(aggregateIsTrustManaged(user.id))); + fout.print(", deviceLocked=" + dumpBool(isDeviceLockedInner(user.id))); fout.println(); fout.println(" Enabled agents:"); boolean duplicateSimpleNames = false; @@ -622,6 +775,19 @@ public class TrustManagerService extends SystemService { } }; + private int resolveProfileParent(int userId) { + long identity = Binder.clearCallingIdentity(); + try { + UserInfo parent = mUserManager.getProfileParent(userId); + if (parent != null) { + return parent.getUserHandle().getIdentifier(); + } + return userId; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -637,10 +803,23 @@ public class TrustManagerService extends SystemService { break; case MSG_ENABLED_AGENTS_CHANGED: refreshAgentList(UserHandle.USER_ALL); + // This is also called when the security mode of a user changes. + refreshDeviceLockedForUser(UserHandle.USER_ALL); break; case MSG_REQUIRE_CREDENTIAL_ENTRY: requireCredentialEntry(msg.arg1); break; + case MSG_KEYGUARD_SHOWING_CHANGED: + refreshDeviceLockedForUser(mCurrentUser); + break; + case MSG_START_USER: + case MSG_CLEANUP_USER: + refreshAgentList(msg.arg1); + break; + case MSG_SWITCH_USER: + mCurrentUser = msg.arg1; + refreshDeviceLockedForUser(UserHandle.USER_ALL); + break; } } }; @@ -674,12 +853,33 @@ public class TrustManagerService extends SystemService { } else if (Intent.ACTION_USER_PRESENT.equals(action)) { updateUserHasAuthenticated(getSendingUserId()); } else if (Intent.ACTION_USER_ADDED.equals(action)) { - int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -100); + int userId = getUserId(intent); if (userId > 0) { maybeEnableFactoryTrustAgents(mLockPatternUtils, userId); - } else { - Log.wtf(TAG, "EXTRA_USER_HANDLE missing or invalid, value=" + userId); } + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + int userId = getUserId(intent); + if (userId > 0) { + mUserHasAuthenticatedSinceBoot.delete(userId); + synchronized (mUserIsTrusted) { + mUserIsTrusted.delete(userId); + } + synchronized (mDeviceLockedForUser) { + mDeviceLockedForUser.delete(userId); + } + refreshAgentList(userId); + refreshDeviceLockedForUser(userId); + } + } + } + + private int getUserId(Intent intent) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -100); + if (userId > 0) { + return userId; + } else { + Slog.wtf(TAG, "EXTRA_USER_HANDLE missing or invalid, value=" + userId); + return -100; } } @@ -688,6 +888,7 @@ public class TrustManagerService extends SystemService { filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); filter.addAction(Intent.ACTION_USER_PRESENT); filter.addAction(Intent.ACTION_USER_ADDED); + filter.addAction(Intent.ACTION_USER_REMOVED); context.registerReceiverAsUser(this, UserHandle.ALL, filter, diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java index 558ffb5..c12dd63 100644 --- a/services/core/java/com/android/server/tv/TvInputHal.java +++ b/services/core/java/com/android/server/tv/TvInputHal.java @@ -55,7 +55,7 @@ final class TvInputHal implements Handler.Callback { private native long nativeOpen(); - private static native int nativeAddStream(long ptr, int deviceId, int streamId, + private static native int nativeAddOrUpdateStream(long ptr, int deviceId, int streamId, Surface surface); private static native int nativeRemoveStream(long ptr, int deviceId, int streamId); private static native TvStreamConfig[] nativeGetStreamConfigs(long ptr, int deviceId, @@ -80,7 +80,7 @@ final class TvInputHal implements Handler.Callback { } } - public int addStream(int deviceId, Surface surface, TvStreamConfig streamConfig) { + public int addOrUpdateStream(int deviceId, Surface surface, TvStreamConfig streamConfig) { synchronized (mLock) { if (mPtr == 0) { return ERROR_NO_INIT; @@ -89,7 +89,7 @@ final class TvInputHal implements Handler.Callback { if (generation != streamConfig.getGeneration()) { return ERROR_STALE_CONFIG; } - if (nativeAddStream(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) { + if (nativeAddOrUpdateStream(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) { return SUCCESS; } else { return ERROR_UNKNOWN; diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 77ab33b..716487c 100644 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -122,6 +122,8 @@ class TvInputHardwareManager implements TvInputHal.Callback { } catch (RemoteException e) { Slog.w(TAG, "Error registering listeners to HdmiControlService:", e); } + } else { + Slog.w(TAG, "HdmiControlService is not available"); } } } @@ -186,6 +188,11 @@ class TvInputHardwareManager implements TvInputHal.Callback { return; } connection.updateConfigsLocked(configs); + String inputId = mHardwareInputIdMap.get(deviceId); + if (inputId != null) { + mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, + convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget(); + } try { connection.getCallbackLocked().onStreamConfigChanged(configs); } catch (RemoteException e) { @@ -255,6 +262,9 @@ class TvInputHardwareManager implements TvInputHal.Callback { mHardwareInputIdMap.put(deviceId, info.getId()); mInputMap.put(info.getId(), info); + // Process pending state changes + + // For logical HDMI devices, they have information from HDMI CEC signals. for (int i = 0; i < mHdmiStateMap.size(); ++i) { TvInputHardwareInfo hardwareInfo = findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i)); @@ -266,8 +276,17 @@ class TvInputHardwareManager implements TvInputHal.Callback { mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, inputId).sendToTarget(); + return; } } + // For the rest of the devices, we can tell by the number of available streams. + Connection connection = mConnections.get(deviceId); + if (connection != null) { + mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, + convertConnectedToState(connection.getConfigsLocked().length > 0), 0, + info.getId()).sendToTarget(); + return; + } } } @@ -280,6 +299,13 @@ class TvInputHardwareManager implements TvInputHal.Callback { return -1; } + private static boolean intArrayContains(int[] array, int value) { + for (int element : array) { + if (element == value) return true; + } + return false; + } + public void addHdmiTvInput(int id, TvInputInfo info) { if (info.getType() != TvInputInfo.TYPE_HDMI) { throw new IllegalArgumentException("info (" + info + ") has non-HDMI type."); @@ -654,28 +680,35 @@ class TvInputHardwareManager implements TvInputHal.Callback { if (mReleased) { throw new IllegalStateException("Device already released."); } - if (surface != null && config == null) { - return false; - } - if (surface == null && mActiveConfig == null) { - return false; - } - int result = TvInputHal.ERROR_UNKNOWN; + int result = TvInputHal.SUCCESS; if (surface == null) { - result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); - mActiveConfig = null; + // The value of config is ignored when surface == null. + if (mActiveConfig != null) { + result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); + mActiveConfig = null; + } else { + // We already have no active stream. + return true; + } } else { - if (config != mActiveConfig && mActiveConfig != null) { + // It's impossible to set a non-null surface with a null config. + if (config == null) { + return false; + } + // Remove stream only if we have an existing active configuration. + if (mActiveConfig != null && !config.equals(mActiveConfig)) { result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); if (result != TvInputHal.SUCCESS) { mActiveConfig = null; - return false; } } - result = mHal.addStream(mInfo.getDeviceId(), surface, config); + // Proceed only if all previous operations succeeded. if (result == TvInputHal.SUCCESS) { - mActiveConfig = config; + result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); + if (result == TvInputHal.SUCCESS) { + mActiveConfig = config; + } } } updateAudioConfigLocked(); @@ -736,20 +769,64 @@ class TvInputHardwareManager implements TvInputHal.Callback { AudioPortConfig sinkConfig = mAudioSink.activeConfig(); AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated; + + int sinkSamplingRate = mDesiredSamplingRate; + int sinkChannelMask = mDesiredChannelMask; + int sinkFormat = mDesiredFormat; + // If sinkConfig != null and values are set to default, fill in the sinkConfig values. + if (sinkConfig != null) { + if (sinkSamplingRate == 0) { + sinkSamplingRate = sinkConfig.samplingRate(); + } + if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) { + sinkChannelMask = sinkConfig.channelMask(); + } + if (sinkFormat == AudioFormat.ENCODING_DEFAULT) { + sinkChannelMask = sinkConfig.format(); + } + } + if (sinkConfig == null - || (mDesiredSamplingRate != 0 - && sinkConfig.samplingRate() != mDesiredSamplingRate) - || (mDesiredChannelMask != AudioFormat.CHANNEL_OUT_DEFAULT - && sinkConfig.channelMask() != mDesiredChannelMask) - || (mDesiredFormat != AudioFormat.ENCODING_DEFAULT - && sinkConfig.format() != mDesiredFormat)) { - sinkConfig = mAudioSource.buildConfig(mDesiredSamplingRate, mDesiredChannelMask, - mDesiredFormat, null); + || sinkConfig.samplingRate() != sinkSamplingRate + || sinkConfig.channelMask() != sinkChannelMask + || sinkConfig.format() != sinkFormat) { + // Check for compatibility and reset to default if necessary. + if (!intArrayContains(mAudioSink.samplingRates(), sinkSamplingRate) + && mAudioSink.samplingRates().length > 0) { + sinkSamplingRate = mAudioSink.samplingRates()[0]; + } + if (!intArrayContains(mAudioSink.channelMasks(), sinkChannelMask)) { + sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; + } + if (!intArrayContains(mAudioSink.formats(), sinkFormat)) { + sinkFormat = AudioFormat.ENCODING_DEFAULT; + } + sinkConfig = mAudioSink.buildConfig(sinkSamplingRate, sinkChannelMask, + sinkFormat, null); shouldRecreateAudioPatch = true; } if (sourceConfig == null || sourceGainConfig != null) { - sourceConfig = mAudioSource.buildConfig(sinkConfig.samplingRate(), - sinkConfig.channelMask(), sinkConfig.format(), sourceGainConfig); + int sourceSamplingRate = 0; + if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) { + sourceSamplingRate = sinkConfig.samplingRate(); + } else if (mAudioSource.samplingRates().length > 0) { + // Use any sampling rate and hope audio patch can handle resampling... + sourceSamplingRate = mAudioSource.samplingRates()[0]; + } + int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT; + for (int inChannelMask : mAudioSource.channelMasks()) { + if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask()) + == AudioFormat.channelCountFromInChannelMask(inChannelMask)) { + sourceChannelMask = inChannelMask; + break; + } + } + int sourceFormat = AudioFormat.ENCODING_DEFAULT; + if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) { + sourceFormat = sinkConfig.format(); + } + sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask, + sourceFormat, sourceGainConfig); shouldRecreateAudioPatch = true; } if (shouldRecreateAudioPatch) { @@ -759,6 +836,9 @@ class TvInputHardwareManager implements TvInputHal.Callback { new AudioPortConfig[] { sourceConfig }, new AudioPortConfig[] { sinkConfig }); mAudioPatch = audioPatchArray[0]; + if (sourceGainConfig != null) { + mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig); + } } } @@ -799,7 +879,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { return false; } - int result = mHal.addStream(mInfo.getDeviceId(), surface, config); + int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); return result == TvInputHal.SUCCESS; } } @@ -914,11 +994,18 @@ class TvInputHardwareManager implements TvInputHal.Callback { break; } case HDMI_DEVICE_UPDATED: { - SomeArgs args = (SomeArgs) msg.obj; - String inputId = (String) args.arg1; - HdmiDeviceInfo info = (HdmiDeviceInfo) args.arg2; - args.recycle(); - mListener.onHdmiDeviceUpdated(inputId, info); + HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; + String inputId = null; + synchronized (mLock) { + inputId = mHdmiInputIdMap.get(info.getId()); + } + if (inputId != null) { + mListener.onHdmiDeviceUpdated(inputId, info); + } else { + Slog.w(TAG, "Could not resolve input ID matching the device info; " + + "ignoring."); + } + break; } default: { Slog.w(TAG, "Unhandled message: " + msg); @@ -986,11 +1073,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { } mHdmiDeviceList.add(deviceInfo); messageType = ListenerHandler.HDMI_DEVICE_UPDATED; - String inputId = mHdmiInputIdMap.get(deviceInfo.getId()); - SomeArgs args = SomeArgs.obtain(); - args.arg1 = inputId; - args.arg2 = deviceInfo; - obj = args; + obj = deviceInfo; break; } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 8a36335..257cbd0 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -62,6 +62,7 @@ import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; +import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -87,6 +88,7 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.R; +import com.android.server.EventLogTags; public class WallpaperManagerService extends IWallpaperManager.Stub { static final String TAG = "WallpaperManagerService"; @@ -99,6 +101,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { * restarting it vs. just reverting to the static wallpaper. */ static final long MIN_WALLPAPER_CRASH_TIME = 10000; + static final int MAX_WALLPAPER_COMPONENT_LOG_LENGTH = 128; static final String WALLPAPER = "wallpaper"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; @@ -272,6 +275,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } else { mWallpaper.lastDiedTime = SystemClock.uptimeMillis(); } + final String flattened = name.flattenToString(); + EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED, + flattened.substring(0, Math.min(flattened.length(), + MAX_WALLPAPER_COMPONENT_LOG_LENGTH))); } } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index fa1c0ff..0cbf03a 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -346,8 +346,7 @@ final class AccessibilityController { case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: - case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: - case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: { + case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: { Rect magnifiedRegionBounds = mTempRect2; mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked( magnifiedRegionBounds); @@ -985,20 +984,18 @@ final class AccessibilityController { final int visibleWindowCount = visibleWindows.size(); for (int i = visibleWindowCount - 1; i >= 0; i--) { - WindowState windowState = visibleWindows.valueAt(i); - - // Compute the bounds in the screen. - Rect boundsInScreen = mTempRect; - computeWindowBoundsInScreen(windowState, boundsInScreen); - + final WindowState windowState = visibleWindows.valueAt(i); final int flags = windowState.mAttrs.flags; - // If the window is not touchable, do not report it but take into account - // the space it takes since the content behind it cannot be touched. + // If the window is not touchable - ignore. if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { continue; } + // Compute the bounds in the screen. + final Rect boundsInScreen = mTempRect; + computeWindowBoundsInScreen(windowState, boundsInScreen); + // If the window is completely covered by other windows - ignore. if (unaccountedSpace.quickReject(boundsInScreen)) { continue; @@ -1015,9 +1012,14 @@ final class AccessibilityController { } } - // Account for the space this window takes. - unaccountedSpace.op(boundsInScreen, unaccountedSpace, - Region.Op.REVERSE_DIFFERENCE); + // Account for the space this window takes if the window + // is not an accessibility overlay which does not change + // the reported windows. + if (windowState.mAttrs.type != + WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { + unaccountedSpace.op(boundsInScreen, unaccountedSpace, + Region.Op.REVERSE_DIFFERENCE); + } // We figured out what is touchable for the entire screen - done. if (unaccountedSpace.isEmpty()) { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index bfc7659..f6e8bcf 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -109,6 +109,8 @@ public class AppTransition implements Dump { /** A window in a new task is being opened behind an existing one in another activity's task. * The new window will show briefly and then be gone. */ public static final int TRANSIT_TASK_OPEN_BEHIND = 16; + /** A window in a task is being animated in-place. */ + public static final int TRANSIT_TASK_IN_PLACE = 17; /** Fraction of animation at which the recents thumbnail stays completely transparent */ private static final float RECENTS_THUMBNAIL_FADEIN_FRACTION = 0.7f; @@ -131,6 +133,7 @@ public class AppTransition implements Dump { private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4; 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 int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE; // These are the possible states for the enter/exit activities during a thumbnail transition @@ -146,6 +149,7 @@ public class AppTransition implements Dump { private IRemoteCallback mNextAppTransitionCallback; private int mNextAppTransitionEnter; private int mNextAppTransitionExit; + private int mNextAppTransitionInPlace; private int mNextAppTransitionStartX; private int mNextAppTransitionStartY; private int mNextAppTransitionStartWidth; @@ -691,8 +695,10 @@ public class AppTransition implements Dump { throw new RuntimeException("Invalid thumbnail transition state"); } - return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, - THUMBNAIL_APP_TRANSITION_DURATION, mThumbnailFastOutSlowInInterpolator); + int duration = Math.max(THUMBNAIL_APP_TRANSITION_ALPHA_DURATION, + THUMBNAIL_APP_TRANSITION_DURATION); + return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration, + mThumbnailFastOutSlowInInterpolator); } /** @@ -833,6 +839,12 @@ public class AppTransition implements Dump { + " anim=" + a + " nextAppTransition=ANIM_CUSTOM" + " transit=" + transit + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); + } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) { + a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace); + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, + "applyAnimation:" + + " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE" + + " transit=" + transit + " 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, @@ -1011,6 +1023,16 @@ public class AppTransition implements Dump { } } + void overrideInPlaceAppTransition(String packageName, int anim) { + if (isTransitionSet()) { + mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE; + mNextAppTransitionPackage = packageName; + mNextAppTransitionInPlace = anim; + } else { + postAnimationCallback(); + } + } + @Override public String toString() { return "mNextAppTransition=0x" + Integer.toHexString(mNextAppTransition); @@ -1090,6 +1112,8 @@ public class AppTransition implements Dump { return "NEXT_TRANSIT_TYPE_NONE"; case NEXT_TRANSIT_TYPE_CUSTOM: return "NEXT_TRANSIT_TYPE_CUSTOM"; + case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE: + return "NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE"; case NEXT_TRANSIT_TYPE_SCALE_UP: return "NEXT_TRANSIT_TYPE_SCALE_UP"; case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP: @@ -1121,6 +1145,12 @@ public class AppTransition implements Dump { pw.print(" mNextAppTransitionExit=0x"); pw.println(Integer.toHexString(mNextAppTransitionExit)); break; + case NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE: + pw.print(" mNextAppTransitionPackage="); + pw.println(mNextAppTransitionPackage); + pw.print(" mNextAppTransitionInPlace=0x"); + pw.print(Integer.toHexString(mNextAppTransitionInPlace)); + break; case NEXT_TRANSIT_TYPE_SCALE_UP: pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX); pw.print(" mNextAppTransitionStartY="); diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java index 69c9144..bf96ea5 100644 --- a/services/core/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java @@ -58,7 +58,9 @@ public class AppWindowAnimator { // the state changes. boolean allDrawn; - // Special surface for thumbnail animation. + // Special surface for thumbnail animation. If deferThumbnailDestruction is enabled, then we + // will make sure that the thumbnail is destroyed after the other surface is completed. This + // requires that the duration of the two animations are the same. SurfaceControl thumbnail; int thumbnailTransactionSeq; int thumbnailX; @@ -68,13 +70,12 @@ public class AppWindowAnimator { Animation thumbnailAnimation; final Transformation thumbnailTransformation = new Transformation(); // This flag indicates that the destruction of the thumbnail surface is synchronized with - // another animation, so do not pre-emptively destroy the thumbnail surface when the animation - // completes + // another animation, so defer the destruction of this thumbnail surface for a single frame + // after the secondary animation completes. boolean deferThumbnailDestruction; - // This is the thumbnail surface that has been bestowed upon this animator, and when the - // surface for this animator's animation is complete, we will destroy the thumbnail surface - // as well. Do not animate or do anything with this surface. - SurfaceControl deferredThumbnail; + // This flag is set if the animator has deferThumbnailDestruction set and has reached the final + // frame of animation. It will extend the animation by one frame and then clean up afterwards. + boolean deferFinalFrameCleanup; /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */ ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<WindowStateAnimator>(); @@ -134,9 +135,7 @@ public class AppWindowAnimator { animation = null; animating = true; } - if (!deferThumbnailDestruction) { - clearThumbnail(); - } + clearThumbnail(); if (mAppToken.deferClearAllDrawn) { mAppToken.allDrawn = false; mAppToken.deferClearAllDrawn = false; @@ -148,13 +147,7 @@ public class AppWindowAnimator { thumbnail.destroy(); thumbnail = null; } - } - - public void clearDeferredThumbnail() { - if (deferredThumbnail != null) { - deferredThumbnail.destroy(); - deferredThumbnail = null; - } + deferThumbnailDestruction = false; } void updateLayers() { @@ -223,19 +216,26 @@ public class AppWindowAnimator { return false; } transformation.clear(); - final boolean more = animation.getTransformation(currentTime, transformation); - if (false && WindowManagerService.DEBUG_ANIM) Slog.v( - TAG, "Stepped animation in " + mAppToken + ": more=" + more + ", xform=" + transformation); - if (!more) { - animation = null; - if (!deferThumbnailDestruction) { + boolean hasMoreFrames = animation.getTransformation(currentTime, transformation); + if (!hasMoreFrames) { + if (deferThumbnailDestruction && !deferFinalFrameCleanup) { + // We are deferring the thumbnail destruction, so extend the animation for one more + // (dummy) frame before we clean up + deferFinalFrameCleanup = true; + hasMoreFrames = true; + } else { + if (false && WindowManagerService.DEBUG_ANIM) Slog.v( + TAG, "Stepped animation in " + mAppToken + ": more=" + hasMoreFrames + + ", xform=" + transformation); + deferFinalFrameCleanup = false; + animation = null; clearThumbnail(); + if (WindowManagerService.DEBUG_ANIM) Slog.v( + TAG, "Finished animation in " + mAppToken + " @ " + currentTime); } - if (WindowManagerService.DEBUG_ANIM) Slog.v( - TAG, "Finished animation in " + mAppToken + " @ " + currentTime); } - hasTransformation = more; - return more; + hasTransformation = hasMoreFrames; + return hasMoreFrames; } // This must be called while inside a transaction. diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index b2575e6..f859fd2 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -252,11 +252,20 @@ class AppWindowToken extends WindowToken { return false; } + @Override void removeAllWindows() { - for (int winNdx = allAppWindows.size() - 1; winNdx >= 0; --winNdx) { + for (int winNdx = allAppWindows.size() - 1; winNdx >= 0; + // removeWindowLocked at bottom of loop may remove multiple entries from + // allAppWindows if the window to be removed has child windows. It also may + // not remove any windows from allAppWindows at all if win is exiting and + // currently animating away. This ensures that winNdx is monotonically decreasing + // and never beyond allAppWindows bounds. + winNdx = Math.min(winNdx - 1, allAppWindows.size() - 1)) { WindowState win = allAppWindows.get(winNdx); - if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) Slog.w(WindowManagerService.TAG, - "removeAllWindows: removing win=" + win); + if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) { + Slog.w(WindowManagerService.TAG, "removeAllWindows: removing win=" + win); + } + win.mService.removeWindowLocked(win.mSession, win); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 30589b1..b0feca8 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -376,10 +376,9 @@ class DisplayContent { stack.dump(prefix + " ", pw); } pw.println(); - pw.println(" Application tokens in bottom up Z order:"); + pw.println(" Application tokens in top down Z order:"); int ndx = 0; - final int numStacks = mStacks.size(); - for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { ArrayList<Task> tasks = mStacks.get(stackNdx).getTasks(); for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { AppTokenList tokens = tasks.get(taskNdx).mAppTokens; diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index edc7c93..c6951bd 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -114,7 +114,6 @@ class DragState { mDragWindowHandle.inputChannel = mServerChannel; mDragWindowHandle.layer = getDragLayerLw(); mDragWindowHandle.layoutParamsFlags = 0; - mDragWindowHandle.layoutParamsPrivateFlags = 0; mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; mDragWindowHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; diff --git a/services/core/java/com/android/server/wm/FakeWindowImpl.java b/services/core/java/com/android/server/wm/FakeWindowImpl.java index c18ea01..1136ced 100644 --- a/services/core/java/com/android/server/wm/FakeWindowImpl.java +++ b/services/core/java/com/android/server/wm/FakeWindowImpl.java @@ -38,7 +38,7 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { public FakeWindowImpl(WindowManagerService service, Looper looper, InputEventReceiver.Factory inputEventReceiverFactory, - String name, int windowType, int layoutParamsFlags, int layoutParamsPrivateFlags, + String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { mService = service; @@ -61,7 +61,6 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { mWindowLayer = getLayerLw(windowType); mWindowHandle.layer = mWindowLayer; mWindowHandle.layoutParamsFlags = layoutParamsFlags; - mWindowHandle.layoutParamsPrivateFlags = layoutParamsPrivateFlags; mWindowHandle.layoutParamsType = windowType; mWindowHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 46aefb6..0327cb3 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -168,8 +168,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle, - final WindowState child, int flags, int privateFlags, final int type, - final boolean isVisible, final boolean hasFocus, final boolean hasWallpaper) { + final WindowState child, int flags, final int type, final boolean isVisible, + final boolean hasFocus, final boolean hasWallpaper) { // Add a window to our list of input windows. inputWindowHandle.name = child.toString(); final boolean modal = (flags & (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL @@ -184,7 +184,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { child.getTouchableRegion(inputWindowHandle.touchableRegion); } inputWindowHandle.layoutParamsFlags = flags; - inputWindowHandle.layoutParamsPrivateFlags = privateFlags; inputWindowHandle.layoutParamsType = type; inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); inputWindowHandle.visible = isVisible; @@ -298,15 +297,14 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { final WindowState u = universeBackground.mWin; if (u.mInputChannel != null && u.mInputWindowHandle != null) { addInputWindowHandleLw(u.mInputWindowHandle, u, u.mAttrs.flags, - u.mAttrs.privateFlags, u.mAttrs.type, - true, u == mInputFocus, false); + u.mAttrs.type, true, u == mInputFocus, false); } addedUniverse = true; } if (child.mWinAnimator != universeBackground) { - addInputWindowHandleLw(inputWindowHandle, child, flags, privateFlags, type, - isVisible, hasFocus, hasWallpaper); + addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, + hasFocus, hasWallpaper); } } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index d737e7f..a4dfd8a 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -155,31 +155,32 @@ final class Session extends IWindowSession.Stub @Override public int add(IWindow window, int seq, WindowManager.LayoutParams attrs, - int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) { + int viewVisibility, Rect outContentInsets, Rect outStableInsets, + InputChannel outInputChannel) { return addToDisplay(window, seq, attrs, viewVisibility, Display.DEFAULT_DISPLAY, - outContentInsets, outInputChannel); + outContentInsets, outStableInsets, outInputChannel); } @Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, - int viewVisibility, int displayId, Rect outContentInsets, + int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, - outContentInsets, outInputChannel); + outContentInsets, outStableInsets, outInputChannel); } @Override public int addWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs, - int viewVisibility, Rect outContentInsets) { + int viewVisibility, Rect outContentInsets, Rect outStableInsets) { return addToDisplayWithoutInputChannel(window, seq, attrs, viewVisibility, - Display.DEFAULT_DISPLAY, outContentInsets); + Display.DEFAULT_DISPLAY, outContentInsets, outStableInsets); } @Override public int addToDisplayWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs, - int viewVisibility, int displayId, Rect outContentInsets) { + int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, - outContentInsets, null); + outContentInsets, outStableInsets, null); } public void remove(IWindow window) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9ceac41..a60be3b 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -60,6 +60,12 @@ class Task { return removed; } + void setSendingToBottom(boolean toBottom) { + for (int appTokenNdx = 0; appTokenNdx < mAppTokens.size(); appTokenNdx++) { + mAppTokens.get(appTokenNdx).sendingToBottom = toBottom; + } + } + @Override public String toString() { return "{taskId=" + taskId + " appTokens=" + mAppTokens + "}"; diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 69d3191..fe2e0a6 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -20,6 +20,9 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static com.android.server.wm.WindowManagerService.DEBUG_KEYGUARD; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_UPDATE_ROTATION; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_MAY_CHANGE; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_FORCE_HIDING_CHANGED; @@ -29,7 +32,6 @@ import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPA import android.content.Context; import android.os.Debug; import android.os.SystemClock; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; @@ -91,6 +93,9 @@ public class WindowAnimator { boolean mKeyguardGoingAwayToNotificationShade; boolean mKeyguardGoingAwayDisableWindowAnimations; + /** Use one animation for all entering activities after keyguard is dismissed. */ + Animation mPostKeyguardExitAnimation; + // forceHiding states. static final int KEYGUARD_NOT_SHOWN = 0; static final int KEYGUARD_ANIMATING_IN = 1; @@ -216,13 +221,28 @@ public class WindowAnimator { } } + private boolean shouldForceHide(WindowState win) { + final WindowState imeTarget = mService.mInputMethodTarget; + final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisibleNow() && + (imeTarget.getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0; + + final WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw(); + final AppWindowToken appShowWhenLocked = winShowWhenLocked == null ? + null : winShowWhenLocked.mAppToken; + final boolean hideWhenLocked = + !(((win.mIsImWindow || imeTarget == win) && showImeOverKeyguard) + || (appShowWhenLocked != null && (appShowWhenLocked == win.mAppToken || + // Show error dialogs over apps that dismiss keyguard. + (win.mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_ERROR) != 0))); + return ((mForceHiding == KEYGUARD_ANIMATING_IN) + && (!win.mWinAnimator.isAnimating() || hideWhenLocked)) + || ((mForceHiding == KEYGUARD_SHOWN) && hideWhenLocked); + } + private void updateWindowsLocked(final int displayId) { ++mAnimTransactionSequence; final WindowList windows = mService.getWindowListLocked(displayId); - ArrayList<WindowStateAnimator> unForceHiding = null; - boolean wallpaperInUnForceHiding = false; - WindowState wallpaper = null; if (mKeyguardGoingAway) { for (int i = windows.size() - 1; i >= 0; i--) { @@ -233,12 +253,18 @@ public class WindowAnimator { final WindowStateAnimator winAnimator = win.mWinAnimator; if ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { if (!winAnimator.mAnimating) { + if (DEBUG_KEYGUARD) Slog.d(TAG, + "updateWindowsLocked: creating delay animation"); + // Create a new animation to delay until keyguard is gone on its own. winAnimator.mAnimation = new AlphaAnimation(1.0f, 1.0f); winAnimator.mAnimation.setDuration(KEYGUARD_ANIM_TIMEOUT_MS); winAnimator.mAnimationIsEntrance = false; + winAnimator.mAnimationStartTime = -1; } } else { + if (DEBUG_KEYGUARD) Slog.d(TAG, + "updateWindowsLocked: StatusBar is no longer keyguard"); mKeyguardGoingAway = false; winAnimator.clearAnimation(); } @@ -248,22 +274,20 @@ public class WindowAnimator { mForceHiding = KEYGUARD_NOT_SHOWN; - final WindowState imeTarget = mService.mInputMethodTarget; - final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisibleNow() && - (imeTarget.getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0; - - final WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw(); - final AppWindowToken appShowWhenLocked = winShowWhenLocked == null ? - null : winShowWhenLocked.mAppToken; - + boolean wallpaperInUnForceHiding = false; + boolean startingInUnForceHiding = false; + ArrayList<WindowStateAnimator> unForceHiding = null; + WindowState wallpaper = null; for (int i = windows.size() - 1; i >= 0; i--) { WindowState win = windows.get(i); WindowStateAnimator winAnimator = win.mWinAnimator; final int flags = win.mAttrs.flags; - + boolean canBeForceHidden = mPolicy.canBeForceHidden(win, win.mAttrs); + boolean shouldBeForceHidden = shouldForceHide(win); if (winAnimator.mSurfaceControl != null) { final boolean wasAnimating = winAnimator.mWasAnimating; final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime); + mAnimating |= nowAnimating; if (WindowManagerService.DEBUG_WALLPAPER) { Slog.v(TAG, win + ": wasAnimating=" + wasAnimating + @@ -282,7 +306,7 @@ public class WindowAnimator { if (mPolicy.isForceHiding(win.mAttrs)) { if (!wasAnimating && nowAnimating) { - if (WindowManagerService.DEBUG_ANIM || + if (DEBUG_KEYGUARD || WindowManagerService.DEBUG_ANIM || WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Animation started that could impact force hide: " + win); mBulkUpdateParams |= SET_FORCE_HIDING_CHANGED; @@ -310,7 +334,7 @@ public class WindowAnimator { mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN; } } - if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, + if (DEBUG_KEYGUARD || WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Force hide " + forceHidingToString() + " hasSurface=" + win.mHasSurface + " policyVis=" + win.mPolicyVisibility @@ -319,43 +343,67 @@ public class WindowAnimator { + " vis=" + win.mViewVisibility + " hidden=" + win.mRootToken.hidden + " anim=" + win.mWinAnimator.mAnimation); - } else if (mPolicy.canBeForceHidden(win, win.mAttrs)) { - final boolean hideWhenLocked = !((win.mIsImWindow && showImeOverKeyguard) || - (appShowWhenLocked != null && appShowWhenLocked == win.mAppToken)); - final boolean changed; - if (((mForceHiding == KEYGUARD_ANIMATING_IN) - && (!winAnimator.isAnimating() || hideWhenLocked)) - || ((mForceHiding == KEYGUARD_SHOWN) && hideWhenLocked)) { - changed = win.hideLw(false, false); - if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG, + } else if (canBeForceHidden) { + if (shouldBeForceHidden) { + if (!win.hideLw(false, false)) { + // Was already hidden + continue; + } + if (DEBUG_KEYGUARD || WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Now policy hidden: " + win); } else { - changed = win.showLw(false, false); - if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG, + boolean applyExistingExitAnimation = mPostKeyguardExitAnimation != null + && !winAnimator.mKeyguardGoingAwayAnimation + && win.hasDrawnLw() + && win.mAttachedWindow == null; + + // If the window is already showing and we don't need to apply an existing + // Keyguard exit animation, skip. + if (!win.showLw(false, false) && !applyExistingExitAnimation) { + continue; + } + final boolean visibleNow = win.isVisibleNow(); + if (!visibleNow) { + // Couldn't really show, must showLw() again when win becomes visible. + win.hideLw(false, false); + continue; + } + if (DEBUG_KEYGUARD || WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Now policy shown: " + win); - if (changed) { - if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0 - && win.isVisibleNow() /*w.isReadyForDisplay()*/) { - if (unForceHiding == null) { - unForceHiding = new ArrayList<WindowStateAnimator>(); - } - unForceHiding.add(winAnimator); - if ((flags & FLAG_SHOW_WALLPAPER) != 0) { - wallpaperInUnForceHiding = true; - } + if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0 + && win.mAttachedWindow == null) { + if (unForceHiding == null) { + unForceHiding = new ArrayList<>(); } - final WindowState currentFocus = mService.mCurrentFocus; - if (currentFocus == null || currentFocus.mLayer < win.mLayer) { - // We are showing on to of the current - // focus, so re-evaluate focus to make - // sure it is correct. - if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.v(TAG, - "updateWindowsLocked: setting mFocusMayChange true"); - mService.mFocusMayChange = true; + unForceHiding.add(winAnimator); + if ((flags & FLAG_SHOW_WALLPAPER) != 0) { + wallpaperInUnForceHiding = true; } + if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { + startingInUnForceHiding = true; + } + } else if (applyExistingExitAnimation) { + // We're already in the middle of an animation. Use the existing + // animation to bring in this window. + if (DEBUG_KEYGUARD) Slog.v(TAG, + "Applying existing Keyguard exit animation to new window: win=" + + win); + Animation a = mPolicy.createForceHideEnterAnimation( + false, mKeyguardGoingAwayToNotificationShade); + winAnimator.setAnimation(a, mPostKeyguardExitAnimation.getStartTime()); + winAnimator.mKeyguardGoingAwayAnimation = true; + } + final WindowState currentFocus = mService.mCurrentFocus; + if (currentFocus == null || currentFocus.mLayer < win.mLayer) { + // We are showing on top of the current + // focus, so re-evaluate focus to make + // sure it is correct. + if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.v(TAG, + "updateWindowsLocked: setting mFocusMayChange true"); + mService.mFocusMayChange = true; } } - if (changed && (flags & FLAG_SHOW_WALLPAPER) != 0) { + if ((flags & FLAG_SHOW_WALLPAPER) != 0) { mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; setPendingLayoutChanges(Display.DEFAULT_DISPLAY, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); @@ -367,6 +415,16 @@ public class WindowAnimator { } } + // If the window doesn't have a surface, the only thing we care about is the correct + // policy visibility. + else if (canBeForceHidden) { + if (shouldBeForceHidden) { + win.hideLw(false, false); + } else { + win.showLw(false, false); + } + } + final AppWindowToken atoken = win.mAppToken; if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) { if (atoken == null || atoken.allDrawn) { @@ -396,40 +454,60 @@ public class WindowAnimator { } // end forall windows // If we have windows that are being show due to them no longer - // being force-hidden, apply the appropriate animation to them. + // being force-hidden, apply the appropriate animation to them if animations are not + // disabled. if (unForceHiding != null) { - boolean startKeyguardExit = true; - for (int i=unForceHiding.size()-1; i>=0; i--) { - Animation a = null; - if (!mKeyguardGoingAwayDisableWindowAnimations) { - a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding, - mKeyguardGoingAwayToNotificationShade); - } - if (a != null) { + if (!mKeyguardGoingAwayDisableWindowAnimations) { + boolean first = true; + for (int i=unForceHiding.size()-1; i>=0; i--) { final WindowStateAnimator winAnimator = unForceHiding.get(i); - winAnimator.setAnimation(a); - winAnimator.keyguardGoingAwayAnimation = true; - if (startKeyguardExit && mKeyguardGoingAway) { - // Do one time only. - mPolicy.startKeyguardExitAnimation(mCurrentTime + a.getStartOffset(), - a.getDuration()); - mKeyguardGoingAway = false; - startKeyguardExit = false; + Animation a = mPolicy.createForceHideEnterAnimation( + wallpaperInUnForceHiding && !startingInUnForceHiding, + mKeyguardGoingAwayToNotificationShade); + if (a != null) { + if (DEBUG_KEYGUARD) Slog.v(TAG, + "Starting keyguard exit animation on window " + winAnimator.mWin); + winAnimator.setAnimation(a); + winAnimator.mKeyguardGoingAwayAnimation = true; + if (first) { + mPostKeyguardExitAnimation = a; + mPostKeyguardExitAnimation.setStartTime(mCurrentTime); + first = false; + } } } + } else if (mKeyguardGoingAway) { + mPolicy.startKeyguardExitAnimation(mCurrentTime, 0 /* duration */); + mKeyguardGoingAway = false; } + // Wallpaper is going away in un-force-hide motion, animate it as well. if (!wallpaperInUnForceHiding && wallpaper != null && !mKeyguardGoingAwayDisableWindowAnimations) { + if (DEBUG_KEYGUARD) Slog.d(TAG, "updateWindowsLocked: wallpaper animating away"); Animation a = mPolicy.createForceHideWallpaperExitAnimation( mKeyguardGoingAwayToNotificationShade); if (a != null) { - WindowStateAnimator animator = wallpaper.mWinAnimator; - animator.setAnimation(a); + wallpaper.mWinAnimator.setAnimation(a); } } } + + if (mPostKeyguardExitAnimation != null) { + // We're in the midst of a keyguard exit animation. + if (mKeyguardGoingAway) { + mPolicy.startKeyguardExitAnimation(mCurrentTime + + mPostKeyguardExitAnimation.getStartOffset(), + mPostKeyguardExitAnimation.getDuration()); + mKeyguardGoingAway = false; + } else if (mCurrentTime - mPostKeyguardExitAnimation.getStartTime() + > mPostKeyguardExitAnimation.getDuration()) { + // Done with the animation, reset. + if (DEBUG_KEYGUARD) Slog.v(TAG, "Done with Keyguard exit animations."); + mPostKeyguardExitAnimation = null; + } + } } private void updateWallpaperLocked(int displayId) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 837672a..67683d5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -73,6 +73,7 @@ import android.graphics.RectF; import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -166,6 +167,7 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_FOCUS = false; static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || false; static final boolean DEBUG_ANIM = false; + static final boolean DEBUG_KEYGUARD = false; static final boolean DEBUG_LAYOUT = false; static final boolean DEBUG_RESIZE = false; static final boolean DEBUG_LAYERS = false; @@ -557,16 +559,26 @@ public class WindowManagerService extends IWindowManager.Stub SettingsObserver mSettingsObserver; private final class SettingsObserver extends ContentObserver { + private final Uri mShowImeWithHardKeyboardUri = + Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); + + private final Uri mDisplayInversionEnabledUri = + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); + public SettingsObserver() { super(new Handler()); ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this); + resolver.registerContentObserver(mShowImeWithHardKeyboardUri, false, this); + resolver.registerContentObserver(mDisplayInversionEnabledUri, false, this); } @Override - public void onChange(boolean selfChange) { - updateShowImeWithHardKeyboard(); + public void onChange(boolean selfChange, Uri uri) { + if (mShowImeWithHardKeyboardUri.equals(uri)) { + updateShowImeWithHardKeyboard(); + } else if (mDisplayInversionEnabledUri.equals(uri)) { + updateCircularDisplayMaskIfNeeded(); + } } } @@ -619,6 +631,9 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; + // Whether or not a layout can cause a wake up when theater mode is enabled. + boolean mAllowTheaterModeWakeFromLayout; + DragState mDragState = null; // For frozen screen animations. @@ -881,6 +896,9 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator = new WindowAnimator(this); + mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean( + com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout); + LocalServices.addService(WindowManagerInternal.class, new LocalService()); initPolicy(); @@ -896,7 +914,7 @@ public class WindowManagerService extends IWindowManager.Stub SurfaceControl.closeTransaction(); } - showCircularDisplayMaskIfNeeded(); + updateCircularDisplayMaskIfNeeded(); showEmulatorDisplayOverlayIfNeeded(); } @@ -1741,7 +1759,7 @@ public class WindowManagerService extends IWindowManager.Stub // wallpaper during the animation so it doesn't flicker out. final boolean hasWallpaper = (w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 || (w.mAppToken != null - && w.mWinAnimator.keyguardGoingAwayAnimation); + && w.mWinAnimator.mKeyguardGoingAwayAnimation); if (hasWallpaper && w.isOnScreen() && (mWallpaperTarget == w || w.isDrawFinishedLw())) { if (DEBUG_WALLPAPER) Slog.v(TAG, @@ -2248,7 +2266,7 @@ public class WindowManagerService extends IWindowManager.Stub public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, - Rect outContentInsets, InputChannel outInputChannel) { + Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { int[] appOp = new int[1]; int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { @@ -2331,6 +2349,11 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } + if (type == TYPE_ACCESSIBILITY_OVERLAY) { + Slog.w(TAG, "Attempted to add Accessibility overlay window with unknown token " + + attrs.token + ". Aborting."); + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; + } token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { @@ -2374,6 +2397,12 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } + } else if (type == TYPE_ACCESSIBILITY_OVERLAY) { + if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) { + Slog.w(TAG, "Attempted to add Accessibility overlay window with bad token " + + attrs.token + ". Aborting."); + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; + } } else if (token.appWindowToken != null) { Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type); // It is not valid to use an app token with other system types; we will @@ -2470,12 +2499,15 @@ public class WindowManagerService extends IWindowManager.Stub } } - win.mWinAnimator.mEnterAnimationPending = true; + final WindowStateAnimator winAnimator = win.mWinAnimator; + winAnimator.mEnterAnimationPending = true; + winAnimator.mEnteringAnimation = true; if (displayContent.isDefaultDisplay) { - mPolicy.getContentInsetHintLw(attrs, outContentInsets); + mPolicy.getInsetHintLw(win.mAttrs, outContentInsets, outStableInsets); } else { outContentInsets.setEmpty(); + outStableInsets.setEmpty(); } if (mInTouchMode) { @@ -2509,9 +2541,8 @@ public class WindowManagerService extends IWindowManager.Stub } mInputMonitor.updateInputWindowsLw(false /*force*/); - if (localLOGV) Slog.v( - TAG, "New client " + client.asBinder() - + ": window=" + win); + if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG, "addWindow: New client " + + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5)); if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) { reportNewConfig = true; @@ -2980,8 +3011,8 @@ public class WindowManagerService extends IWindowManager.Stub return 0; } WindowStateAnimator winAnimator = win.mWinAnimator; - if (win.mRequestedWidth != requestedWidth - || win.mRequestedHeight != requestedHeight) { + if (viewVisibility != View.GONE && (win.mRequestedWidth != requestedWidth + || win.mRequestedHeight != requestedHeight)) { win.mLayoutNeeded = true; win.mRequestedWidth = requestedWidth; win.mRequestedHeight = requestedHeight; @@ -3082,6 +3113,7 @@ public class WindowManagerService extends IWindowManager.Stub if (oldVisibility == View.GONE) { winAnimator.mEnterAnimationPending = true; } + winAnimator.mEnteringAnimation = true; if (toBeDisplayed) { if (win.isDrawnLw() && okToDisplay()) { winAnimator.applyEnterAnimationLocked(); @@ -3150,6 +3182,7 @@ public class WindowManagerService extends IWindowManager.Stub } } else { winAnimator.mEnterAnimationPending = false; + winAnimator.mEnteringAnimation = false; if (winAnimator.mSurfaceControl != null) { if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win + ": mExiting=" + win.mExiting); @@ -4091,6 +4124,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void overridePendingAppTransitionInPlace(String packageName, int anim) { + synchronized(mWindowMap) { + mAppTransition.overrideInPlaceAppTransition(packageName, anim); + } + } + + @Override public void executeAppTransition() { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "executeAppTransition()")) { @@ -4502,6 +4542,15 @@ public class WindowManagerService extends IWindowManager.Stub return delayed; } + void updateTokenInPlaceLocked(AppWindowToken wtoken, int transit) { + if (transit != AppTransition.TRANSIT_UNSET) { + if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) { + wtoken.mAppAnimator.animation = null; + } + applyAnimationLocked(wtoken, null, transit, false, false); + } + } + @Override public void setAppVisibility(IBinder token, boolean visible) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, @@ -5052,6 +5101,10 @@ public class WindowManagerService extends IWindowManager.Stub } } stack.moveTaskToTop(task); + if (mAppTransition.isTransitionSet()) { + task.setSendingToBottom(false); + } + moveStackWindowsLocked(displayContent); } } finally { Binder.restoreCallingIdentity(origId); @@ -5070,6 +5123,9 @@ public class WindowManagerService extends IWindowManager.Stub } final TaskStack stack = task.mStack; stack.moveTaskToBottom(task); + if (mAppTransition.isTransitionSet()) { + task.setSendingToBottom(true); + } moveStackWindowsLocked(stack.getDisplayContent()); } } finally { @@ -5335,6 +5391,8 @@ public class WindowManagerService extends IWindowManager.Stub != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } + if (DEBUG_KEYGUARD) Slog.d(TAG, "keyguardGoingAway: disableWinAnim=" + + disableWindowAnimations + " kgToNotifShade=" + keyguardGoingToNotificationShade); synchronized (mWindowMap) { mAnimator.mKeyguardGoingAway = true; mAnimator.mKeyguardGoingAwayToNotificationShade = keyguardGoingToNotificationShade; @@ -5344,12 +5402,15 @@ public class WindowManagerService extends IWindowManager.Stub } public void keyguardWaitingForActivityDrawn() { + if (DEBUG_KEYGUARD) Slog.d(TAG, "keyguardWaitingForActivityDrawn"); synchronized (mWindowMap) { mKeyguardWaitingForActivityDrawn = true; } } public void notifyActivityDrawnForKeyguard() { + if (DEBUG_KEYGUARD) Slog.d(TAG, "notifyActivityDrawnForKeyguard: waiting=" + + mKeyguardWaitingForActivityDrawn + " Callers=" + Debug.getCallers(5)); synchronized (mWindowMap) { if (mKeyguardWaitingForActivityDrawn) { mPolicy.notifyActivityDrawnForKeyguardLw(); @@ -5808,13 +5869,21 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void showCircularDisplayMaskIfNeeded() { + public void updateCircularDisplayMaskIfNeeded() { // we're fullscreen and not hosted in an ActivityView if (mContext.getResources().getBoolean( com.android.internal.R.bool.config_windowIsRound) && mContext.getResources().getBoolean( com.android.internal.R.bool.config_windowShowCircularMask)) { - mH.sendMessage(mH.obtainMessage(H.SHOW_CIRCULAR_DISPLAY_MASK)); + // Device configuration calls for a circular display mask, but we only enable the mask + // if the accessibility color inversion feature is disabled, as the inverted mask + // causes artifacts. + int inversionState = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0, mCurrentUserId); + int showMask = (inversionState == 1) ? 0 : 1; + Message m = mH.obtainMessage(H.SHOW_CIRCULAR_DISPLAY_MASK); + m.arg1 = showMask; + mH.sendMessage(m); } } @@ -5827,30 +5896,35 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void showCircularMask() { + public void showCircularMask(boolean visible) { synchronized(mWindowMap) { if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - ">>> OPEN TRANSACTION showCircularMask"); + ">>> OPEN TRANSACTION showCircularMask(visible=" + visible + ")"); SurfaceControl.openTransaction(); try { - // TODO(multi-display): support multiple displays - if (mCircularDisplayMask == null) { - int screenOffset = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.circular_display_mask_offset); - - mCircularDisplayMask = new CircularDisplayMask( - getDefaultDisplayContentLocked().getDisplay(), - mFxSession, - mPolicy.windowTypeToLayerLw( - WindowManager.LayoutParams.TYPE_POINTER) - * TYPE_LAYER_MULTIPLIER + 10, screenOffset); + if (visible) { + // TODO(multi-display): support multiple displays + if (mCircularDisplayMask == null) { + int screenOffset = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.circular_display_mask_offset); + + mCircularDisplayMask = new CircularDisplayMask( + getDefaultDisplayContentLocked().getDisplay(), + mFxSession, + mPolicy.windowTypeToLayerLw( + WindowManager.LayoutParams.TYPE_POINTER) + * TYPE_LAYER_MULTIPLIER + 10, screenOffset); + } + mCircularDisplayMask.setVisibility(true); + } else if (mCircularDisplayMask != null) { + mCircularDisplayMask.setVisibility(false); + mCircularDisplayMask = null; } - mCircularDisplayMask.setVisibility(true); } finally { SurfaceControl.closeTransaction(); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION showCircularMask"); + "<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")"); } } } @@ -6025,6 +6099,10 @@ public class WindowManagerService extends IWindowManager.Stub while (true) { if (retryCount++ > 0) { + // Reset max/min layers on retries so we don't accidentally take a screenshot of a + // layer based on the previous try. + maxLayer = 0; + minLayer = Integer.MAX_VALUE; try { Thread.sleep(100); } catch (InterruptedException e) { @@ -6047,7 +6125,17 @@ public class WindowManagerService extends IWindowManager.Stub continue; } } else if (ws.mIsWallpaper) { - // Fall through. + if (appWin == null) { + // We have not ran across the target window yet, so it is probably + // behind the wallpaper. This can happen when the keyguard is up and + // all windows are moved behind the wallpaper. We don't want to + // include the wallpaper layer in the screenshot as it will coverup + // the layer of the target window. + continue; + } + // Fall through. The target window is in front of the wallpaper. For this + // case we want to include the wallpaper layer in the screenshot because + // the target window might have some transparent areas. } else if (appToken != null) { if (ws.mAppToken == null || ws.mAppToken.token != appToken) { // This app window is of no interest if it is not associated with the @@ -7907,7 +7995,7 @@ public class WindowManagerService extends IWindowManager.Stub } case SHOW_CIRCULAR_DISPLAY_MASK: { - showCircularMask(); + showCircularMask(msg.arg1 == 1); break; } @@ -9053,6 +9141,29 @@ public class WindowManagerService extends IWindowManager.Stub int topOpeningLayer = 0; int topClosingLayer = 0; + // Process all applications animating in place + if (transit == AppTransition.TRANSIT_TASK_IN_PLACE) { + // Find the focused window + final WindowState win = + findFocusedWindowLocked(getDefaultDisplayContentLocked()); + if (win != null) { + final AppWindowToken wtoken = win.mAppToken; + final AppWindowAnimator appAnimator = wtoken.mAppAnimator; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now animating app in place " + wtoken); + appAnimator.clearThumbnail(); + appAnimator.animation = null; + updateTokenInPlaceLocked(wtoken, transit); + wtoken.updateReportedVisibilityLocked(); + + appAnimator.mAllAppWinAnimators.clear(); + final int N = wtoken.allAppWindows.size(); + for (int j = 0; j < N; j++) { + appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator); + } + mAnimator.mAnimating |= appAnimator.showAllWindowsLocked(); + } + } + NN = mOpeningApps.size(); for (i=0; i<NN; i++) { AppWindowToken wtoken = mOpeningApps.valueAt(i); @@ -9172,12 +9283,6 @@ public class WindowManagerService extends IWindowManager.Stub topClosingLayer); openingAppAnimator.deferThumbnailDestruction = !mAppTransition.isNextThumbnailTransitionScaleUp(); - if (openingAppAnimator.deferThumbnailDestruction) { - if (closingAppAnimator != null && - closingAppAnimator.animation != null) { - closingAppAnimator.deferredThumbnail = surfaceControl; - } - } } else { anim = mAppTransition.createThumbnailScaleAnimationLocked( displayInfo.appWidth, displayInfo.appHeight, transit); @@ -9216,6 +9321,7 @@ public class WindowManagerService extends IWindowManager.Stub } updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, true /*updateInputWindows*/); mFocusMayChange = false; + notifyActivityDrawnForKeyguard(); } return changes; @@ -9365,7 +9471,6 @@ public class WindowManagerService extends IWindowManager.Stub final int type = attrs.type; if (canBeSeen && (type == TYPE_SYSTEM_DIALOG - || type == TYPE_RECENTS_OVERLAY || type == TYPE_SYSTEM_ERROR || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0)) { mInnerFields.mSyswin = true; @@ -9704,7 +9809,8 @@ public class WindowManagerService extends IWindowManager.Stub atoken.numInterestingWindows = atoken.numDrawnWindows = 0; atoken.startingDisplayed = false; } - if ((w.isOnScreen() || winAnimator.mAttrType == TYPE_BASE_APPLICATION) + if ((w.isOnScreenIgnoringKeyguard() + || winAnimator.mAttrType == TYPE_BASE_APPLICATION) && !w.mExiting && !w.mDestroying) { if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw() @@ -9712,7 +9818,7 @@ public class WindowManagerService extends IWindowManager.Stub if (!w.isDrawnLw()) { Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl + " pv=" + w.mPolicyVisibility - + " mDrawState=" + winAnimator.mDrawState + + " mDrawState=" + winAnimator.drawStateToString() + " ah=" + w.mAttachedHidden + " th=" + atoken.hiddenRequested + " a=" + winAnimator.mAnimating); @@ -9954,8 +10060,12 @@ public class WindowManagerService extends IWindowManager.Stub } if (mTurnOnScreen) { - if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!"); - mPowerManager.wakeUp(SystemClock.uptimeMillis()); + 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!"); + mPowerManager.wakeUp(SystemClock.uptimeMillis()); + } mTurnOnScreen = false; } @@ -10682,9 +10792,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { synchronized (mWindowMap) { FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputEventReceiverFactory, - name, windowType, - layoutParamsFlags, layoutParamsPrivateFlags, canReceiveKeys, - hasFocus, touchFullscreen); + name, windowType, layoutParamsFlags, canReceiveKeys, hasFocus, touchFullscreen); int i=0; while (i<mFakeWindows.size()) { if (mFakeWindows.get(i).mWindowLayer <= fw.mWindowLayer) { @@ -11611,5 +11719,23 @@ public class WindowManagerService extends IWindowManager.Stub checkDrawnWindowsLocked(); } } + + @Override + public void addWindowToken(IBinder token, int type) { + WindowManagerService.this.addWindowToken(token, type); + } + + @Override + public void removeWindowToken(IBinder token, boolean removeWindows) { + synchronized(mWindowMap) { + if (removeWindows) { + WindowToken wtoken = mTokenMap.remove(token); + if (wtoken != null) { + wtoken.removeAllWindows(); + } + } + WindowManagerService.this.removeWindowToken(token); + } + } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 04dab3e..978f5c3 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -29,6 +29,7 @@ import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; 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 android.app.AppOpsManager; @@ -227,13 +228,33 @@ final class WindowState implements WindowManagerPolicy.WindowState { final Rect mCompatFrame = new Rect(); final Rect mContainingFrame = new Rect(); + + final Rect mParentFrame = new Rect(); + + // The entire 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 + // is mostly a special case for TV where some displays don’t have the entire display usable. + // {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} flag can be used to allow + // window display contents to extend into the overscan region. final Rect mOverscanFrame = new Rect(); + + // The display frame minus the stable insets. This value is always constant regardless of if + // the status bar or navigation bar is visible. + final Rect mStableFrame = new Rect(); + + // The area not occupied by the status and navigation bars. So, if both status and navigation + // bars are visible, the decor frame is equal to the stable frame. + final Rect mDecorFrame = new Rect(); + + // Equal to the decor frame if the IME (e.g. keyboard) is not present. Equal to the decor frame + // minus the area occupied by the IME if the IME is present. final Rect mContentFrame = new Rect(); - final Rect mParentFrame = new Rect(); + + // Legacy stuff. Generally equal to the content frame expect when the IME for older apps + // displays hint text. final Rect mVisibleFrame = new Rect(); - final Rect mDecorFrame = new Rect(); - final Rect mStableFrame = new Rect(); boolean mContentChanged; @@ -702,9 +723,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { WindowState ws = this; WindowList windows = getWindowList(); while (true) { - if ((ws.mAttrs.privateFlags - & WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY) != 0) { - return (ws.mAttrs.flags & WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0; + if (ws.mAttrs.needsMenuKey != WindowManager.LayoutParams.NEEDS_MENU_UNSET) { + return ws.mAttrs.needsMenuKey == WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE; } // If we reached the bottom of the range of windows we are considering, // assume no menu is needed. @@ -881,7 +901,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ boolean isVisibleNow() { return mHasSurface && mPolicyVisibility && !mAttachedHidden - && !mRootToken.hidden && !mExiting && !mDestroying; + && (!mRootToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING) + && !mExiting && !mDestroying; } /** @@ -912,7 +933,15 @@ final class WindowState implements WindowManagerPolicy.WindowState { * being visible. */ boolean isOnScreen() { - if (!mHasSurface || !mPolicyVisibility || mDestroying) { + return mPolicyVisibility && isOnScreenIgnoringKeyguard(); + } + + /** + * Like isOnScreen(), but ignores any force hiding of the window due + * to the keyguard. + */ + boolean isOnScreenIgnoringKeyguard() { + if (!mHasSurface || mDestroying) { return false; } final AppWindowToken atoken = mAppToken; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 0c727f3..87d420f 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -40,6 +40,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.os.Debug; +import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; import android.view.Display; @@ -52,10 +53,13 @@ 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; @@ -96,6 +100,8 @@ class WindowStateAnimator { boolean mWasAnimating; // Were we animating going into the most recent animation step? int mAnimLayer; int mLastLayer; + long mAnimationStartTime; + long mLastAnimationTime; SurfaceControl mSurfaceControl; SurfaceControl mPendingDestroySurface; @@ -141,7 +147,12 @@ class WindowStateAnimator { // an enter animation. boolean mEnterAnimationPending; - boolean keyguardGoingAwayAnimation; + /** Used to indicate that this window is undergoing an enter animation. Used for system + * windows to make the callback to View.dispatchOnWindowShownCallback(). Set when the + * window is first added or shown, cleared when the callback has been made. */ + boolean mEnteringAnimation; + + boolean mKeyguardGoingAwayAnimation; /** This is set when there is no Surface */ static final int NO_SURFACE = 0; @@ -161,14 +172,14 @@ class WindowStateAnimator { private static final int SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - static String drawStateToString(int state) { - switch (state) { + String drawStateToString() { + switch (mDrawState) { case NO_SURFACE: return "NO_SURFACE"; case DRAW_PENDING: return "DRAW_PENDING"; case COMMIT_DRAW_PENDING: return "COMMIT_DRAW_PENDING"; case READY_TO_SHOW: return "READY_TO_SHOW"; case HAS_DRAWN: return "HAS_DRAWN"; - default: return Integer.toString(state); + default: return Integer.toString(mDrawState); } } int mDrawState; @@ -204,7 +215,7 @@ class WindowStateAnimator { mIsWallpaper = win.mIsWallpaper; } - public void setAnimation(Animation anim) { + public void setAnimation(Animation anim, long startTime) { if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim); mAnimating = false; mLocalAnimating = false; @@ -215,6 +226,11 @@ class WindowStateAnimator { mTransformation.clear(); mTransformation.setAlpha(mLastHidden ? 0 : 1); mHasLocalTransformation = true; + mAnimationStartTime = startTime; + } + + public void setAnimation(Animation anim) { + setAnimation(anim, -1); } public void clearAnimation() { @@ -223,7 +239,7 @@ class WindowStateAnimator { mLocalAnimating = false; mAnimation.cancel(); mAnimation = null; - keyguardGoingAwayAnimation = false; + mKeyguardGoingAwayAnimation = false; } } @@ -293,11 +309,14 @@ class WindowStateAnimator { final DisplayInfo displayInfo = displayContent.getDisplayInfo(); mAnimDw = displayInfo.appWidth; mAnimDh = displayInfo.appHeight; - mAnimation.setStartTime(currentTime); + mAnimation.setStartTime(mAnimationStartTime != -1 + ? mAnimationStartTime + : currentTime); mLocalAnimating = true; mAnimating = true; } if ((mAnimation != null) && mLocalAnimating) { + mLastAnimationTime = currentTime; if (stepAnimation(currentTime)) { return true; } @@ -345,7 +364,7 @@ class WindowStateAnimator { + (mWin.mAppToken != null ? mWin.mAppToken.reportedVisible : false)); mAnimating = false; - keyguardGoingAwayAnimation = false; + mKeyguardGoingAwayAnimation = false; mLocalAnimating = false; if (mAnimation != null) { mAnimation.cancel(); @@ -428,6 +447,14 @@ class WindowStateAnimator { mWin.mChildWindows.get(i).mWinAnimator.finishExit(); } + if (mEnteringAnimation && mWin.mAppToken == null) { + try { + mEnteringAnimation = false; + mWin.mClient.dispatchWindowShown(); + } catch (RemoteException e) { + } + } + if (!mWin.mExiting) { return; } @@ -472,17 +499,17 @@ class WindowStateAnimator { } boolean finishDrawingLocked() { - if (DEBUG_STARTING_WINDOW && - mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { + final boolean startingWindow = + mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; + if (DEBUG_STARTING_WINDOW && startingWindow) { Slog.v(TAG, "Finishing drawing window " + mWin + ": mDrawState=" - + drawStateToString(mDrawState)); + + drawStateToString()); } if (mDrawState == DRAW_PENDING) { if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION) Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + this + " in " + mSurfaceControl); - if (DEBUG_STARTING_WINDOW && - mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { + if (DEBUG_STARTING_WINDOW && startingWindow) { Slog.v(TAG, "Draw state now committed in " + mWin); } mDrawState = COMMIT_DRAW_PENDING; @@ -496,18 +523,17 @@ class WindowStateAnimator { if (DEBUG_STARTING_WINDOW && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) { Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState=" - + drawStateToString(mDrawState)); + + drawStateToString()); } - if (mDrawState != COMMIT_DRAW_PENDING) { + if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) { return false; } if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) { Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceControl); } mDrawState = READY_TO_SHOW; - final boolean starting = mWin.mAttrs.type == TYPE_APPLICATION_STARTING; final AppWindowToken atoken = mWin.mAppToken; - if (atoken == null || atoken.allDrawn || starting) { + if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) { performShowLocked(); } return true; @@ -962,11 +988,6 @@ class WindowStateAnimator { mWin.mHasSurface = false; mDrawState = NO_SURFACE; } - - // Destroy any deferred thumbnail surfaces - if (mAppAnimator != null) { - mAppAnimator.clearDeferredThumbnail(); - } } void destroyDeferredSurfaceLocked() { @@ -1775,9 +1796,17 @@ class WindowStateAnimator { * @return true if an animation has been loaded. */ boolean applyAnimationLocked(int transit, boolean isEntrance) { - if (mLocalAnimating && mAnimationIsEntrance == isEntrance) { + if ((mLocalAnimating && mAnimationIsEntrance == isEntrance) + || mKeyguardGoingAwayAnimation) { // If we are trying to apply an animation, but already running // an animation of the same type, then just leave that one alone. + + // If we are in a keyguard exit animation, and the window should animate away, modify + // keyguard exit animation such that it also fades out. + if (mAnimation != null && mKeyguardGoingAwayAnimation + && transit == WindowManagerPolicy.TRANSIT_PREVIEW_DONE) { + applyFadeoutDuringKeyguardExitAnimation(); + } return true; } @@ -1835,6 +1864,28 @@ class WindowStateAnimator { return mAnimation != null; } + private void applyFadeoutDuringKeyguardExitAnimation() { + long startTime = mAnimation.getStartTime(); + long duration = mAnimation.getDuration(); + long elapsed = mLastAnimationTime - startTime; + long fadeDuration = duration - elapsed; + if (fadeDuration <= 0) { + // Never mind, this would be no visible animation, so abort the animation change. + return; + } + AnimationSet newAnimation = new AnimationSet(false /* shareInterpolator */); + newAnimation.setDuration(duration); + newAnimation.setStartTime(startTime); + newAnimation.addAnimation(mAnimation); + Animation fadeOut = AnimationUtils.loadAnimation( + mContext, com.android.internal.R.anim.app_starting_exit); + fadeOut.setDuration(fadeDuration); + fadeOut.setStartOffset(elapsed); + newAnimation.addAnimation(fadeOut); + newAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDw, mAnimDh); + mAnimation = newAnimation; + } + public void dump(PrintWriter pw, String prefix, boolean dumpAll) { if (mAnimating || mLocalAnimating || mAnimationIsEntrance || mAnimation != null) { @@ -1854,7 +1905,7 @@ class WindowStateAnimator { if (dumpAll) { pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl); pw.print(prefix); pw.print("mDrawState="); - pw.print(drawStateToString(mDrawState)); + pw.print(drawStateToString()); pw.print(" mLastHidden="); pw.println(mLastHidden); } pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown); diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 2267123..1a672e6 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.os.IBinder; +import android.util.Slog; import java.io.PrintWriter; @@ -29,7 +30,7 @@ import java.io.PrintWriter; class WindowToken { // The window manager! final WindowManagerService service; - + // The actual token. final IBinder token; @@ -77,6 +78,15 @@ class WindowToken { explicit = _explicit; } + void removeAllWindows() { + for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { + WindowState win = windows.get(winNdx); + if (WindowManagerService.DEBUG_WINDOW_MOVEMENT) Slog.w(WindowManagerService.TAG, + "removeAllWindows: removing win=" + win); + win.mService.removeWindowLocked(win.mSession, win); + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("windows="); pw.println(windows); pw.print(prefix); pw.print("windowType="); pw.print(windowType); diff --git a/services/core/jni/com_android_server_input_InputWindowHandle.cpp b/services/core/jni/com_android_server_input_InputWindowHandle.cpp index 03bf7eb..46ec1f4 100644 --- a/services/core/jni/com_android_server_input_InputWindowHandle.cpp +++ b/services/core/jni/com_android_server_input_InputWindowHandle.cpp @@ -36,7 +36,6 @@ static struct { jfieldID inputChannel; jfieldID name; jfieldID layoutParamsFlags; - jfieldID layoutParamsPrivateFlags; jfieldID layoutParamsType; jfieldID dispatchingTimeoutNanos; jfieldID frameLeft; @@ -113,8 +112,6 @@ bool NativeInputWindowHandle::updateInfo() { mInfo->layoutParamsFlags = env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags); - mInfo->layoutParamsPrivateFlags = env->GetIntField(obj, - gInputWindowHandleClassInfo.layoutParamsPrivateFlags); mInfo->layoutParamsType = env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType); mInfo->dispatchingTimeout = env->GetLongField(obj, @@ -251,9 +248,6 @@ int register_android_server_InputWindowHandle(JNIEnv* env) { GET_FIELD_ID(gInputWindowHandleClassInfo.layoutParamsFlags, clazz, "layoutParamsFlags", "I"); - GET_FIELD_ID(gInputWindowHandleClassInfo.layoutParamsPrivateFlags, clazz, - "layoutParamsPrivateFlags", "I"); - GET_FIELD_ID(gInputWindowHandleClassInfo.layoutParamsType, clazz, "layoutParamsType", "I"); diff --git a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp index d730b7e..84482bc 100644 --- a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp @@ -1203,6 +1203,7 @@ static jobject translate_gps_measurement(JNIEnv* env, GpsMeasurement* measuremen usedInFixSetterMethod, (flags & GPS_MEASUREMENT_HAS_USED_IN_FIX) && measurement->used_in_fix); + env->DeleteLocalRef(gpsMeasurementClass); return gpsMeasurementObject; } diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index d5abe0c..5cb0543 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -235,7 +235,7 @@ public: static JTvInputHal* createInstance(JNIEnv* env, jobject thiz); - int addStream(int deviceId, int streamId, const sp<Surface>& surface); + int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface); int removeStream(int deviceId, int streamId); const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs); @@ -312,7 +312,7 @@ JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz) { return new JTvInputHal(env, thiz, device); } -int JTvInputHal::addStream(int deviceId, int streamId, const sp<Surface>& surface) { +int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) { KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { connections.add(streamId, Connection()); @@ -555,14 +555,14 @@ static jlong nativeOpen(JNIEnv* env, jobject thiz) { return (jlong)JTvInputHal::createInstance(env, thiz); } -static int nativeAddStream(JNIEnv* env, jclass clazz, +static int nativeAddOrUpdateStream(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId, jint streamId, jobject jsurface) { JTvInputHal* tvInputHal = (JTvInputHal*)ptr; if (!jsurface) { return BAD_VALUE; } sp<Surface> surface(android_view_Surface_getSurface(env, jsurface)); - return tvInputHal->addStream(deviceId, streamId, surface); + return tvInputHal->addOrUpdateStream(deviceId, streamId, surface); } static int nativeRemoveStream(JNIEnv* env, jclass clazz, @@ -612,8 +612,8 @@ static JNINativeMethod gTvInputHalMethods[] = { /* name, signature, funcPtr */ { "nativeOpen", "()J", (void*) nativeOpen }, - { "nativeAddStream", "(JIILandroid/view/Surface;)I", - (void*) nativeAddStream }, + { "nativeAddOrUpdateStream", "(JIILandroid/view/Surface;)I", + (void*) nativeAddOrUpdateStream }, { "nativeRemoveStream", "(JII)I", (void*) nativeRemoveStream }, { "nativeGetStreamConfigs", "(JII)[Landroid/media/tv/TvStreamConfig;", diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index fe4b7b9..6331dfe 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -34,6 +34,7 @@ import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; +import android.app.backup.IBackupManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -59,8 +60,9 @@ import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.IBinder; -import android.os.IPowerManager; +import android.os.PersistableBundle; import android.os.PowerManager; +import android.os.PowerManagerInternal; import android.os.Process; import android.os.RecoverySystem; import android.os.RemoteCallback; @@ -75,6 +77,7 @@ import android.security.Credentials; import android.security.IKeyChainService; import android.security.KeyChain; import android.security.KeyChain.KeyChainConnection; +import android.text.TextUtils; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -96,6 +99,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.org.conscrypt.TrustedCertificateStore; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo; import org.xmlpull.v1.XmlPullParser; @@ -177,6 +181,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { SECURE_SETTINGS_WHITELIST = new HashSet(); SECURE_SETTINGS_WHITELIST.add(Settings.Secure.DEFAULT_INPUT_METHOD); SECURE_SETTINGS_WHITELIST.add(Settings.Secure.SKIP_FIRST_USE_HINTS); + SECURE_SETTINGS_WHITELIST.add(Settings.Secure.INSTALL_NON_MARKET_APPS); SECURE_SETTINGS_DEVICEOWNER_WHITELIST = new HashSet(); SECURE_SETTINGS_DEVICEOWNER_WHITELIST.addAll(SECURE_SETTINGS_WHITELIST); @@ -202,7 +207,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final LocalService mLocalService; - IPowerManager mIPowerManager; + final PowerManager mPowerManager; + final PowerManagerInternal mPowerManagerInternal; + IWindowManager mIWindowManager; NotificationManager mNotificationManager; @@ -322,7 +329,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { = "permitted-accessiblity-services"; private static final String TAG_ENCRYPTION_REQUESTED = "encryption-requested"; private static final String TAG_MANAGE_TRUST_AGENT_FEATURES = "manage-trust-agent-features"; - private static final String TAG_TRUST_AGENT_FEATURE = "feature"; + private static final String TAG_TRUST_AGENT_COMPONENT_OPTIONS = "trust-agent-component-options"; private static final String TAG_TRUST_AGENT_COMPONENT = "component"; private static final String TAG_PASSWORD_EXPIRATION_DATE = "password-expiration-date"; private static final String TAG_PASSWORD_EXPIRATION_TIMEOUT = "password-expiration-timeout"; @@ -389,6 +396,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long passwordExpirationDate = DEF_PASSWORD_EXPIRATION_DATE; static final int DEF_KEYGUARD_FEATURES_DISABLED = 0; // none + int disabledKeyguardFeatures = DEF_KEYGUARD_FEATURES_DISABLED; boolean encryptionRequested = false; @@ -397,6 +405,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean disableScreenCapture = false; // Can only be set by a device/profile owner. boolean requireAutoTime = false; // Can only be set by a device owner. + static class TrustAgentInfo { + public PersistableBundle options; + TrustAgentInfo(PersistableBundle bundle) { + options = bundle; + } + } + Set<String> accountTypesWithManagementDisabled = new HashSet<String>(); // The list of permitted accessibility services package namesas set by a profile @@ -413,7 +428,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean specifiesGlobalProxy = false; String globalProxySpec = null; String globalProxyExclusionList = null; - HashMap<String, List<String>> trustAgentFeatures = new HashMap<String, List<String>>(); + + HashMap<String, TrustAgentInfo> trustAgentInfos = new HashMap<String, TrustAgentInfo>(); List<String> crossProfileWidgetProviders; @@ -551,16 +567,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } out.endTag(null, TAG_DISABLE_ACCOUNT_MANAGEMENT); } - if (!trustAgentFeatures.isEmpty()) { - Set<Entry<String, List<String>>> set = trustAgentFeatures.entrySet(); + if (!trustAgentInfos.isEmpty()) { + Set<Entry<String, TrustAgentInfo>> set = trustAgentInfos.entrySet(); out.startTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES); - for (Entry<String, List<String>> component : set) { + for (Entry<String, TrustAgentInfo> entry : set) { + TrustAgentInfo trustAgentInfo = entry.getValue(); out.startTag(null, TAG_TRUST_AGENT_COMPONENT); - out.attribute(null, ATTR_VALUE, component.getKey()); - for (String feature : component.getValue()) { - out.startTag(null, TAG_TRUST_AGENT_FEATURE); - out.attribute(null, ATTR_VALUE, feature); - out.endTag(null, TAG_TRUST_AGENT_FEATURE); + out.attribute(null, ATTR_VALUE, entry.getKey()); + if (trustAgentInfo.options != null) { + out.startTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS); + try { + trustAgentInfo.options.saveToXml(out); + } catch (XmlPullParserException e) { + Log.e(LOG_TAG, "Failed to save TrustAgent options", e); + } + out.endTag(null, TAG_TRUST_AGENT_COMPONENT_OPTIONS); } out.endTag(null, TAG_TRUST_AGENT_COMPONENT); } @@ -679,7 +700,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (TAG_DISABLE_ACCOUNT_MANAGEMENT.equals(tag)) { accountTypesWithManagementDisabled = readDisableAccountInfo(parser, tag); } else if (TAG_MANAGE_TRUST_AGENT_FEATURES.equals(tag)) { - trustAgentFeatures = getAllTrustAgentFeatures(parser, tag); + trustAgentInfos = getAllTrustAgentInfos(parser, tag); } else if (TAG_CROSS_PROFILE_WIDGET_PROVIDERS.equals(tag)) { crossProfileWidgetProviders = getCrossProfileWidgetProviders(parser, tag); } else if (TAG_PERMITTED_ACCESSIBILITY_SERVICES.equals(tag)) { @@ -738,11 +759,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return result; } - private HashMap<String, List<String>> getAllTrustAgentFeatures(XmlPullParser parser, - String tag) throws XmlPullParserException, IOException { + private HashMap<String, TrustAgentInfo> getAllTrustAgentInfos( + XmlPullParser parser, String tag) throws XmlPullParserException, IOException { int outerDepthDAM = parser.getDepth(); int typeDAM; - HashMap<String, List<String>> result = new HashMap<String, List<String>>(); + HashMap<String, TrustAgentInfo> result = new HashMap<String, TrustAgentInfo>(); while ((typeDAM=parser.next()) != END_DOCUMENT && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) { if (typeDAM == END_TAG || typeDAM == TEXT) { @@ -751,7 +772,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { String tagDAM = parser.getName(); if (TAG_TRUST_AGENT_COMPONENT.equals(tagDAM)) { final String component = parser.getAttributeValue(null, ATTR_VALUE); - result.put(component, getTrustAgentFeatures(parser, tag)); + final TrustAgentInfo trustAgentInfo = getTrustAgentInfo(parser, tag); + result.put(component, trustAgentInfo); } else { Slog.w(LOG_TAG, "Unknown tag under " + tag + ": " + tagDAM); } @@ -759,20 +781,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return result; } - private List<String> getTrustAgentFeatures(XmlPullParser parser, String tag) + private TrustAgentInfo getTrustAgentInfo(XmlPullParser parser, String tag) throws XmlPullParserException, IOException { int outerDepthDAM = parser.getDepth(); int typeDAM; - ArrayList<String> result = new ArrayList<String>(); + TrustAgentInfo result = new TrustAgentInfo(null); while ((typeDAM=parser.next()) != END_DOCUMENT && (typeDAM != END_TAG || parser.getDepth() > outerDepthDAM)) { if (typeDAM == END_TAG || typeDAM == TEXT) { continue; } String tagDAM = parser.getName(); - if (TAG_TRUST_AGENT_FEATURE.equals(tagDAM)) { - final String feature = parser.getAttributeValue(null, ATTR_VALUE); - result.add(feature); + if (TAG_TRUST_AGENT_COMPONENT_OPTIONS.equals(tagDAM)) { + PersistableBundle bundle = new PersistableBundle(); + bundle.restoreFromXml(parser); + result.options = bundle; } else { Slog.w(LOG_TAG, "Unknown tag under " + tag + ": " + tagDAM); } @@ -879,23 +902,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (DBG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle); DevicePolicyData policy = getUserData(userHandle); IPackageManager pm = AppGlobals.getPackageManager(); - for (int i = policy.mAdminList.size() - 1; i >= 0; i--) { - ActiveAdmin aa = policy.mAdminList.get(i); - try { - if (pm.getPackageInfo(aa.info.getPackageName(), 0, userHandle) == null - || pm.getReceiverInfo(aa.info.getComponent(), 0, userHandle) == null) { - removed = true; - policy.mAdminList.remove(i); - policy.mAdminMap.remove(aa.info.getComponent()); + synchronized (this) { + for (int i = policy.mAdminList.size() - 1; i >= 0; i--) { + ActiveAdmin aa = policy.mAdminList.get(i); + try { + if (pm.getPackageInfo(aa.info.getPackageName(), 0, userHandle) == null + || pm.getReceiverInfo(aa.info.getComponent(), 0, userHandle) == null) { + removed = true; + policy.mAdminList.remove(i); + policy.mAdminMap.remove(aa.info.getComponent()); + } + } catch (RemoteException re) { + // Shouldn't happen } - } catch (RemoteException re) { - // Shouldn't happen } - } - if (removed) { - validatePasswordOwnerLocked(policy); - syncDeviceCapabilitiesLocked(policy); - saveSettingsLocked(policy.mUserHandle); + if (removed) { + validatePasswordOwnerLocked(policy); + syncDeviceCapabilitiesLocked(policy); + saveSettingsLocked(policy.mUserHandle); + } } } @@ -907,8 +932,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mUserManager = UserManager.get(mContext); mHasFeature = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_DEVICE_ADMIN); - mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)) - .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM"); + mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); + mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM"); mLocalService = new LocalService(); if (!mHasFeature) { // Skip the rest of the initialization @@ -1020,14 +1046,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - private IPowerManager getIPowerManager() { - if (mIPowerManager == null) { - IBinder b = ServiceManager.getService(Context.POWER_SERVICE); - mIPowerManager = IPowerManager.Stub.asInterface(b); - } - return mIPowerManager; - } - private IWindowManager getWindowManager() { if (mIWindowManager == null) { IBinder b = ServiceManager.getService(Context.WINDOW_SERVICE); @@ -1174,7 +1192,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int userHandle) { List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo ui : profiles) { - int id = ui.getUserHandle().getIdentifier(); + int id = ui.id; sendAdminCommandLocked(action, reqPolicy, id); } } @@ -1591,7 +1609,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo ui : profiles) { - int profileUserHandle = ui.getUserHandle().getIdentifier(); + int profileUserHandle = ui.id; final DevicePolicyData policy = getUserData(profileUserHandle); final int count = policy.mAdminList.size(); if (count > 0) { @@ -1687,7 +1705,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .setContentTitle(mContext.getString(R.string.ssl_ca_cert_warning)) .setContentText(contentText) .setContentIntent(notifyIntent) - .setOngoing(true) .setPriority(Notification.PRIORITY_HIGH) .setShowWhen(false) .setColor(mContext.getResources().getColor( @@ -1879,7 +1896,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -1926,7 +1943,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -1973,7 +1990,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2034,7 +2051,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2132,7 +2149,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2189,7 +2206,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2233,7 +2250,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2280,7 +2297,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2327,7 +2344,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2374,7 +2391,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2421,7 +2438,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2505,6 +2522,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle) { if (!mHasFeature) { return 0; @@ -2517,6 +2535,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public int getProfileWithMinimumFailedPasswordsForWipe(int userHandle) { + if (!mHasFeature) { + return UserHandle.USER_NULL; + } + enforceCrossUserPermission(userHandle); + synchronized (this) { + ActiveAdmin admin = getAdminWithMinimumFailedPasswordsForWipeLocked(userHandle); + return admin != null ? admin.getUserHandle().getIdentifier() : UserHandle.USER_NULL; + } + } + /** * Returns the admin with the strictest policy on maximum failed passwords for this user and all * profiles that are visible from this user. If the policy for the primary and any other profile @@ -2527,7 +2557,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int count = 0; ActiveAdmin strictestAdmin = null; for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); for (ActiveAdmin admin : policy.mAdminList) { if (admin.maximumFailedPasswordsForWipe == ActiveAdmin.DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) { @@ -2546,13 +2576,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return strictestAdmin; } - public boolean resetPassword(String password, int flags, int userHandle) { + public boolean resetPassword(String passwordOrNull, int flags, int userHandle) { if (!mHasFeature) { return false; } enforceCrossUserPermission(userHandle); enforceNotManagedProfile(userHandle, "reset the password"); + String password = passwordOrNull != null ? passwordOrNull : ""; + int quality; synchronized (this) { // This api can only be called by an active device admin, @@ -2656,7 +2688,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long ident = Binder.clearCallingIdentity(); try { LockPatternUtils utils = new LockPatternUtils(mContext); - utils.saveLockPassword(password, quality, false, userHandle); + if (!TextUtils.isEmpty(password)) { + utils.saveLockPassword(password, quality, false, userHandle); + } else { + utils.clearLock(false, userHandle); + } boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0; if (requireEntry) { utils.requireCredentialEntry(UserHandle.USER_ALL); @@ -2712,12 +2748,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } policy.mLastMaximumTimeToLock = timeMs; - - try { - getIPowerManager().setMaximumScreenOffTimeoutFromDeviceAdmin((int)timeMs); - } catch (RemoteException e) { - Slog.w(LOG_TAG, "Failure talking with power manager", e); - } + mPowerManagerInternal.setMaximumScreenOffTimeoutFromDeviceAdmin((int)timeMs); } finally { Binder.restoreCallingIdentity(ident); } @@ -2739,7 +2770,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Return strictest policy for this user and profiles that are visible from this user. List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); for (int i=0; i<N; i++) { ActiveAdmin admin = policy.mAdminList.get(i); @@ -2772,7 +2803,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long ident = Binder.clearCallingIdentity(); try { // Power off the display - getIPowerManager().goToSleep(SystemClock.uptimeMillis(), + mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN, 0); // Ensure the device is locked new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL); @@ -3056,7 +3087,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void updatePasswordExpirationsLocked(int userHandle) { List<UserInfo> profiles = mUserManager.getProfiles(userHandle); for (UserInfo userInfo : profiles) { - int profileId = userInfo.getUserHandle().getIdentifier(); + int profileId = userInfo.id; DevicePolicyData policy = getUserData(profileId); final int N = policy.mAdminList.size(); if (N > 0) { @@ -3642,6 +3673,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "Trying to set device owner but device owner is already set."); } + // Shutting down backup manager service permanently. + long ident = Binder.clearCallingIdentity(); + try { + IBackupManager ibm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + ibm.setBackupServiceActive(UserHandle.USER_OWNER, false); + } catch (RemoteException e) { + throw new IllegalStateException("Failed deactivating backup service.", e); + } finally { + Binder.restoreCallingIdentity(ident); + } + if (mDeviceOwner == null) { // Device owner is not set and does not exist, set it. mDeviceOwner = DeviceOwner.createWithDeviceOwner(packageName, ownerName); @@ -3913,7 +3956,7 @@ 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 (profileOwner.equals(admin.info)) { + if (profileOwner.equals(admin.info.getComponent())) { return admin; } } @@ -4107,13 +4150,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } - public void setTrustAgentFeaturesEnabled(ComponentName admin, ComponentName agent, - List<String>features, int userHandle) { + public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent, + PersistableBundle args, int userHandle) { if (!mHasFeature) { return; } enforceCrossUserPermission(userHandle); - enforceNotManagedProfile(userHandle, "manage trust agent features"); + enforceNotManagedProfile(userHandle, "set trust agent configuration"); synchronized (this) { if (admin == null) { throw new NullPointerException("admin is null"); @@ -4123,57 +4166,68 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } ActiveAdmin ap = getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES); - ap.trustAgentFeatures.put(agent.flattenToString(), features); + ap.trustAgentInfos.put(agent.flattenToString(), new TrustAgentInfo(args)); saveSettingsLocked(userHandle); syncDeviceCapabilitiesLocked(getUserData(userHandle)); } } - public List<String> getTrustAgentFeaturesEnabled(ComponentName admin, ComponentName agent, - int userHandle) { + public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin, + ComponentName agent, int userHandle) { if (!mHasFeature) { return null; } enforceCrossUserPermission(userHandle); + if (agent == null) { + throw new NullPointerException("agent is null"); + } + synchronized (this) { - if (agent == null) { - throw new NullPointerException("agent is null"); - } final String componentName = agent.flattenToString(); if (admin != null) { final ActiveAdmin ap = getActiveAdminUncheckedLocked(admin, userHandle); - return (ap != null) ? ap.trustAgentFeatures.get(componentName) : null; + if (ap == null) return null; + TrustAgentInfo trustAgentInfo = ap.trustAgentInfos.get(componentName); + if (trustAgentInfo == null || trustAgentInfo.options == null) return null; + List<PersistableBundle> result = new ArrayList<PersistableBundle>(); + result.add(trustAgentInfo.options); + return result; } // Return strictest policy for this user and profiles that are visible from this user. - List<UserInfo> profiles = mUserManager.getProfiles(userHandle); - List<String> result = null; + final List<UserInfo> profiles = mUserManager.getProfiles(userHandle); + List<PersistableBundle> result = null; + + // Search through all admins that use KEYGUARD_DISABLE_TRUST_AGENTS and keep track + // of the options. If any admin doesn't have options, discard options for the rest + // and return null. + boolean allAdminsHaveOptions = true; for (UserInfo userInfo : profiles) { - DevicePolicyData policy = getUserData(userInfo.getUserHandle().getIdentifier()); + DevicePolicyData policy = getUserData(userInfo.id); final int N = policy.mAdminList.size(); - for (int i=0; i<N; i++) { - ActiveAdmin ap = policy.mAdminList.get(i); - // Compute the intersection of all features for active admins that disable - // trust agents: - if ((ap.disabledKeyguardFeatures - & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0) { - final List<String> features = ap.trustAgentFeatures.get(componentName); - if (result == null) { - if (features == null || features.size() == 0) { - result = new ArrayList<String>(); - Slog.w(LOG_TAG, "admin " + ap.info.getPackageName() - + " has null trust agent feature set; all will be disabled"); - } else { - result = new ArrayList<String>(features.size()); - result.addAll(features); + for (int i=0; i < N; i++) { + final ActiveAdmin active = policy.mAdminList.get(i); + final boolean disablesTrust = (active.disabledKeyguardFeatures + & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0; + final TrustAgentInfo info = active.trustAgentInfos.get(componentName); + if (info != null && info.options != null && !info.options.isEmpty()) { + if (disablesTrust) { + if (result == null) { + result = new ArrayList<PersistableBundle>(); } + result.add(info.options); } else { - result.retainAll(features); + Log.w(LOG_TAG, "Ignoring admin " + active.info + + " because it has trust options but doesn't declare " + + "KEYGUARD_DISABLE_TRUST_AGENTS"); } + } else if (disablesTrust) { + allAdminsHaveOptions = false; + break; } } } - return result; + return allAdminsHaveOptions ? result : null; } } @@ -4789,6 +4843,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slog.e(LOG_TAG, "Failed to talk to AudioService.", re); } } + sendChangedNotification(userHandle); } } @@ -5018,13 +5073,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isUninstallBlocked(ComponentName who, String packageName) { + // This function should return true if and only if the package is blocked by + // setUninstallBlocked(). It should still return false for other cases of blocks, such as + // when the package is a system app, or when it is an active device admin. final int userId = UserHandle.getCallingUserId(); synchronized (this) { - if (who == null) { - throw new NullPointerException("ComponentName is null"); + if (who != null) { + getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); } - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); long id = Binder.clearCallingIdentity(); try { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 8a64aa6..4e6a8ea 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -374,6 +374,8 @@ public final class SystemServer { mSystemServiceManager.startService(UsageStatsService.class); mActivityManagerService.setUsageStatsManager( LocalServices.getService(UsageStatsManagerInternal.class)); + // Update after UsageStatsService is available, needed before performBootDexOpt. + mPackageManagerService.getUsageStatsIfNoPackageUsageInfo(); // Tracks whether the updatable WebView is in a ready state and watches for update installs. mSystemServiceManager.startService(WebViewUpdateService.class); @@ -416,6 +418,7 @@ public final class SystemServer { boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false); boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false); boolean disableNetwork = SystemProperties.getBoolean("config.disable_network", false); + boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime", false); boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1"); try { @@ -863,7 +866,7 @@ public final class SystemServer { reportWtf("starting SamplingProfiler Service", e); } - if (!disableNetwork) { + if (!disableNetwork && !disableNetworkTime) { try { Slog.i(TAG, "NetworkTimeUpdateService"); networkTimeUpdater = new NetworkTimeUpdateService(context); diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java index 9496cae..7ab3840 100644 --- a/services/print/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -55,7 +56,8 @@ final class RemotePrintSpooler { private static final boolean DEBUG = false; - private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000; + private static final long BIND_SPOOLER_SERVICE_TIMEOUT = + ("eng".equals(Build.TYPE)) ? 120000 : 10000; private final Object mLock = new Object(); diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java new file mode 100644 index 0000000..bf0e75d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java @@ -0,0 +1,330 @@ +/* + * 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; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.UserInfo; +import android.database.sqlite.SQLiteDatabase; +import android.os.FileUtils; +import android.os.UserManager; +import android.test.AndroidTestCase; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +public class LockSettingsStorageTests extends AndroidTestCase { + LockSettingsStorage mStorage; + File mStorageDir; + + private File mDb; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mStorageDir = new File(getContext().getFilesDir(), "locksettings"); + mDb = getContext().getDatabasePath("locksettings.db"); + + assertTrue(mStorageDir.exists() || mStorageDir.mkdirs()); + assertTrue(FileUtils.deleteContents(mStorageDir)); + assertTrue(!mDb.exists() || mDb.delete()); + + final Context ctx = getContext(); + setContext(new ContextWrapper(ctx) { + @Override + public Object getSystemService(String name) { + if (USER_SERVICE.equals(name)) { + return new UserManager(ctx, null) { + @Override + public UserInfo getProfileParent(int userHandle) { + if (userHandle == 2) { + // User 2 is a profile of user 1. + return new UserInfo(1, "name", 0); + } + if (userHandle == 3) { + // User 3 is a profile of user 0. + return new UserInfo(0, "name", 0); + } + return null; + } + }; + } + return super.getSystemService(name); + } + }); + + mStorage = new LockSettingsStorage(getContext(), new LockSettingsStorage.Callback() { + @Override + public void initialize(SQLiteDatabase db) { + mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0); + } + }) { + @Override + String getLockPatternFilename(int userId) { + return new File(mStorageDir, + super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath(); + } + + @Override + String getLockPasswordFilename(int userId) { + return new File(mStorageDir, + super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath(); + } + }; + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + mStorage.closeDatabase(); + } + + public void testKeyValue_InitializeWorked() { + assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0)); + mStorage.clearCache(); + assertEquals("initialValue", mStorage.readKeyValue("initializedKey", "default", 0)); + } + + public void testKeyValue_WriteThenRead() { + mStorage.writeKeyValue("key", "value", 0); + assertEquals("value", mStorage.readKeyValue("key", "default", 0)); + mStorage.clearCache(); + assertEquals("value", mStorage.readKeyValue("key", "default", 0)); + } + + public void testKeyValue_DefaultValue() { + assertEquals("default", mStorage.readKeyValue("unititialized key", "default", 0)); + assertEquals("default2", mStorage.readKeyValue("unititialized key", "default2", 0)); + } + + public void testKeyValue_Concurrency() { + final Object monitor = new Object(); + List<Thread> threads = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + final int threadId = i; + threads.add(new Thread() { + @Override + public void run() { + synchronized (monitor) { + try { + monitor.wait(); + } catch (InterruptedException e) { + return; + } + mStorage.writeKeyValue("key", "1 from thread " + threadId, 0); + mStorage.readKeyValue("key", "default", 0); + mStorage.writeKeyValue("key", "2 from thread " + threadId, 0); + mStorage.readKeyValue("key", "default", 0); + mStorage.writeKeyValue("key", "3 from thread " + threadId, 0); + mStorage.readKeyValue("key", "default", 0); + mStorage.writeKeyValue("key", "4 from thread " + threadId, 0); + mStorage.readKeyValue("key", "default", 0); + mStorage.writeKeyValue("key", "5 from thread " + threadId, 0); + mStorage.readKeyValue("key", "default", 0); + } + } + }); + threads.get(i).start(); + } + mStorage.writeKeyValue("key", "initalValue", 0); + synchronized (monitor) { + monitor.notifyAll(); + } + for (int i = 0; i < threads.size(); i++) { + try { + threads.get(i).join(); + } catch (InterruptedException e) { + } + } + assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0)); + mStorage.clearCache(); + assertEquals('5', mStorage.readKeyValue("key", "default", 0).charAt(0)); + } + + public void testKeyValue_CacheStarvedWriter() { + final CountDownLatch latch = new CountDownLatch(1); + List<Thread> threads = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + final int threadId = i; + threads.add(new Thread() { + @Override + public void run() { + try { + latch.await(); + } catch (InterruptedException e) { + return; + } + if (threadId == 50) { + mStorage.writeKeyValue("starvedWriterKey", "value", 0); + } else { + mStorage.readKeyValue("starvedWriterKey", "default", 0); + } + } + }); + threads.get(i).start(); + } + latch.countDown(); + for (int i = 0; i < threads.size(); i++) { + try { + threads.get(i).join(); + } catch (InterruptedException e) { + } + } + String cached = mStorage.readKeyValue("key", "default", 0); + mStorage.clearCache(); + String storage = mStorage.readKeyValue("key", "default", 0); + assertEquals("Cached value didn't match stored value", storage, cached); + } + + public void testRemoveUser() { + mStorage.writeKeyValue("key", "value", 0); + mStorage.writePasswordHash(new byte[]{1}, 0); + mStorage.writePatternHash(new byte[]{2}, 0); + + mStorage.writeKeyValue("key", "value", 1); + mStorage.writePasswordHash(new byte[]{1}, 1); + mStorage.writePatternHash(new byte[]{2}, 1); + + mStorage.removeUser(0); + + assertEquals("value", mStorage.readKeyValue("key", "default", 1)); + assertEquals("default", mStorage.readKeyValue("key", "default", 0)); + assertNotNull(mStorage.readPasswordHash(1)); + assertNull(mStorage.readPasswordHash(0)); + assertNotNull(mStorage.readPatternHash(1)); + assertNull(mStorage.readPatternHash(0)); + } + + public void testPassword_Default() { + assertNull(mStorage.readPasswordHash(0)); + } + + public void testPassword_Write() { + mStorage.writePasswordHash("thepassword".getBytes(), 0); + + assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0)); + mStorage.clearCache(); + assertArrayEquals("thepassword".getBytes(), mStorage.readPasswordHash(0)); + } + + 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)); + mStorage.clearCache(); + assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(1)); + assertArrayEquals("profilepassword".getBytes(), mStorage.readPasswordHash(2)); + } + + 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)); + mStorage.clearCache(); + assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(1)); + assertArrayEquals("parentpasswordd".getBytes(), mStorage.readPasswordHash(2)); + } + + public void testPattern_Default() { + assertNull(mStorage.readPasswordHash(0)); + } + + public void testPattern_Write() { + mStorage.writePatternHash("thepattern".getBytes(), 0); + + assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0)); + mStorage.clearCache(); + assertArrayEquals("thepattern".getBytes(), mStorage.readPatternHash(0)); + } + + 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)); + mStorage.clearCache(); + assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(1)); + assertArrayEquals("profilepattern".getBytes(), mStorage.readPatternHash(2)); + } + + 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)); + mStorage.clearCache(); + assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(1)); + assertArrayEquals("parentpatternn".getBytes(), mStorage.readPatternHash(2)); + } + + public void testPrefetch() { + mStorage.writeKeyValue("key", "toBeFetched", 0); + mStorage.writePatternHash("pattern".getBytes(), 0); + mStorage.writePasswordHash("password".getBytes(), 0); + + mStorage.clearCache(); + mStorage.prefetchUser(0); + + assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0)); + assertArrayEquals("pattern".getBytes(), mStorage.readPatternHash(0)); + assertArrayEquals("password".getBytes(), mStorage.readPasswordHash(0)); + } + + public void testFileLocation_Owner() { + LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); + + assertEquals("/data/system/gesture.key", storage.getLockPatternFilename(0)); + assertEquals("/data/system/password.key", storage.getLockPasswordFilename(0)); + } + + public void testFileLocation_SecondaryUser() { + LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); + + assertEquals("/data/system/users/1/gesture.key", storage.getLockPatternFilename(1)); + assertEquals("/data/system/users/1/password.key", storage.getLockPasswordFilename(1)); + } + + public void testFileLocation_ProfileToSecondary() { + LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); + + assertEquals("/data/system/users/1/gesture.key", storage.getLockPatternFilename(2)); + assertEquals("/data/system/users/1/password.key", storage.getLockPasswordFilename(2)); + } + + public void testFileLocation_ProfileToOwner() { + LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); + + assertEquals("/data/system/gesture.key", storage.getLockPatternFilename(3)); + assertEquals("/data/system/password.key", storage.getLockPasswordFilename(3)); + } + + private static void assertArrayEquals(byte[] expected, byte[] actual) { + if (!Arrays.equals(expected, actual)) { + fail("expected:<" + Arrays.toString(expected) + + "> but was:<" + Arrays.toString(actual) + ">"); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 8392672..b74716f 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -838,7 +838,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { info.setDetailedState(DetailedState.CONNECTED, null, null); final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); - return new NetworkState(info, prop, null, null, TEST_SSID); + return new NetworkState(info, prop, null, null, null, TEST_SSID); } private void expectCurrentTime() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java index c3d4ed9..f9a03fc 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java @@ -932,7 +932,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes(); expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes(); expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes(); - expect(mSettings.getReportXtOverDev()).andReturn(true).anyTimes(); final Config config = new Config(bucketDuration, deleteAge, deleteAge); expect(mSettings.getDevConfig()).andReturn(config).anyTimes(); @@ -1007,7 +1006,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { info.setDetailedState(DetailedState.CONNECTED, null, null); final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); - return new NetworkState(info, prop, null, null, TEST_SSID); + return new NetworkState(info, prop, null, null, null, TEST_SSID); } private static NetworkState buildMobile3gState(String subscriberId) { @@ -1016,7 +1015,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { info.setDetailedState(DetailedState.CONNECTED, null, null); final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); - return new NetworkState(info, prop, null, subscriberId, null); + return new NetworkState(info, prop, null, null, subscriberId, null); } private static NetworkState buildMobile4gState(String iface) { @@ -1024,7 +1023,7 @@ public class NetworkStatsServiceTest extends AndroidTestCase { info.setDetailedState(DetailedState.CONNECTED, null, null); final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(iface); - return new NetworkState(info, prop, null); + return new NetworkState(info, prop, null, null); } private NetworkStats buildEmptyStats() { 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 a70ebf4..b631331 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -21,22 +21,15 @@ 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 com.android.internal.content.PackageHelper; +import android.test.AndroidTestCase; +import android.util.ArraySet; +import android.util.Log; + import com.android.internal.os.AtomicFile; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.util.HashSet; - -import android.os.Debug; -import android.os.Environment; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.storage.IMountService; -import android.test.AndroidTestCase; -import android.util.Log; public class PackageManagerSettingsTests extends AndroidTestCase { @@ -182,11 +175,11 @@ public class PackageManagerSettingsTests extends AndroidTestCase { assertEquals(COMPONENT_ENABLED_STATE_ENABLED, ps.getEnabled(1)); // Enable/Disable a component - HashSet<String> components = new HashSet<String>(); + ArraySet<String> components = new ArraySet<String>(); String component1 = PACKAGE_NAME_1 + "/.Component1"; components.add(component1); ps.setDisabledComponents(components, 0); - HashSet<String> componentsDisabled = ps.getDisabledComponents(0); + ArraySet<String> componentsDisabled = ps.getDisabledComponents(0); assertEquals(1, componentsDisabled.size()); assertEquals(component1, componentsDisabled.toArray()[0]); boolean hasEnabled = diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index cfa4436..26ced03 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -39,6 +39,8 @@ class UsageStatsDatabase { private static final String TAG = "UsageStatsDatabase"; private static final boolean DEBUG = UsageStatsService.DEBUG; + private static final String BAK_SUFFIX = ".bak"; + private static final String CHECKED_IN_SUFFIX = UsageStatsXml.CHECKED_IN_SUFFIX; private final Object mLock = new Object(); private final File[] mIntervalDirs; @@ -95,11 +97,79 @@ class UsageStatsDatabase { } } + public interface CheckinAction { + boolean checkin(IntervalStats stats); + } + + /** + * Calls {@link CheckinAction#checkin(IntervalStats)} on the given {@link CheckinAction} + * for all {@link IntervalStats} that haven't been checked-in. + * If any of the calls to {@link CheckinAction#checkin(IntervalStats)} returns false or throws + * an exception, the check-in will be aborted. + * + * @param checkinAction The callback to run when checking-in {@link IntervalStats}. + * @return true if the check-in succeeded. + */ + public boolean checkinDailyFiles(CheckinAction checkinAction) { + synchronized (mLock) { + final TimeSparseArray<AtomicFile> files = + mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY]; + final int fileCount = files.size(); + + // We may have holes in the checkin (if there was an error) + // so find the last checked-in file and go from there. + int lastCheckin = -1; + for (int i = 0; i < fileCount - 1; i++) { + if (files.valueAt(i).getBaseFile().getPath().endsWith(CHECKED_IN_SUFFIX)) { + lastCheckin = i; + } + } + + final int start = lastCheckin + 1; + if (start == fileCount - 1) { + return true; + } + + try { + IntervalStats stats = new IntervalStats(); + for (int i = start; i < fileCount - 1; i++) { + UsageStatsXml.read(files.valueAt(i), stats); + if (!checkinAction.checkin(stats)) { + return false; + } + } + } catch (IOException e) { + Slog.e(TAG, "Failed to check-in", e); + return false; + } + + // We have successfully checked-in the stats, so rename the files so that they + // are marked as checked-in. + for (int i = start; i < fileCount - 1; i++) { + final AtomicFile file = files.valueAt(i); + final File checkedInFile = new File( + file.getBaseFile().getPath() + CHECKED_IN_SUFFIX); + if (!file.getBaseFile().renameTo(checkedInFile)) { + // We must return success, as we've already marked some files as checked-in. + // It's better to repeat ourselves than to lose data. + Slog.e(TAG, "Failed to mark file " + file.getBaseFile().getPath() + + " as checked-in"); + return true; + } + + // AtomicFile needs to set a new backup path with the same -c extension, so + // we replace the old AtomicFile with the updated one. + files.setValueAt(i, new AtomicFile(checkedInFile)); + } + } + return true; + } + private void indexFilesLocked() { final FilenameFilter backupFileFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { - return !name.endsWith(".bak"); + return !name.endsWith(BAK_SUFFIX); } }; @@ -178,8 +248,13 @@ class UsageStatsDatabase { } catch (IOException e) { // Ignore, this is just to make sure there are no backups. } - final File newFile = new File(file.getBaseFile().getParentFile(), - Long.toString(newTime)); + + String newName = Long.toString(newTime); + if (file.getBaseFile().getName().endsWith(CHECKED_IN_SUFFIX)) { + newName = newName + CHECKED_IN_SUFFIX; + } + + final File newFile = new File(file.getBaseFile().getParentFile(), newName); Slog.i(TAG, "Moving file " + file.getBaseFile().getAbsolutePath() + " to " + newFile.getAbsolutePath()); file.getBaseFile().renameTo(newFile); @@ -383,10 +458,10 @@ class UsageStatsDatabase { if (files != null) { for (File f : files) { String path = f.getPath(); - if (path.endsWith(".bak")) { - f = new File(path.substring(0, path.length() - 4)); + if (path.endsWith(BAK_SUFFIX)) { + f = new File(path.substring(0, path.length() - BAK_SUFFIX.length())); } - long beginTime = Long.parseLong(f.getName()); + long beginTime = UsageStatsXml.parseBeginTime(f); if (beginTime < expiryTime) { new AtomicFile(f).delete(); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 2ed9745..485b2a2 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -33,11 +33,11 @@ import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Binder; -import android.os.Debug; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -47,9 +47,12 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.os.BackgroundThread; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.SystemService; import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Arrays; import java.util.List; @@ -176,7 +179,7 @@ public class UsageStatsService extends SystemService implements long currentTimeMillis) { UserUsageStatsService service = mUserState.get(userId); if (service == null) { - service = new UserUsageStatsService(userId, + service = new UserUsageStatsService(getContext(), userId, new File(mUsageStatsDir, Integer.toString(userId)), this); service.init(currentTimeMillis); mUserState.put(userId, service); @@ -319,6 +322,30 @@ public class UsageStatsService extends SystemService implements mHandler.removeMessages(MSG_FLUSH_TO_DISK); } + /** + * Called by the Binder stub. + */ + void dump(String[] args, PrintWriter pw) { + synchronized (mLock) { + IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " "); + ArraySet<String> argSet = new ArraySet<>(); + argSet.addAll(Arrays.asList(args)); + + final int userCount = mUserState.size(); + for (int i = 0; i < userCount; i++) { + idpw.printPair("user", mUserState.keyAt(i)); + idpw.println(); + idpw.increaseIndent(); + if (argSet.contains("--checkin")) { + mUserState.valueAt(i).checkin(idpw); + } else { + mUserState.valueAt(i).dump(idpw); + } + idpw.decreaseIndent(); + } + } + } + class H extends Handler { public H(Looper looper) { super(looper); @@ -349,8 +376,12 @@ public class UsageStatsService extends SystemService implements private class BinderService extends IUsageStatsManager.Stub { private boolean hasPermission(String callingPackage) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == Process.SYSTEM_UID) { + return true; + } final int mode = mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, - Binder.getCallingUid(), callingPackage); + callingUid, callingPackage); if (mode == AppOpsManager.MODE_DEFAULT) { // The default behavior here is to check if PackageManager has given the app // permission. @@ -417,6 +448,18 @@ public class UsageStatsService extends SystemService implements Binder.restoreCallingIdentity(token); } } + + @Override + protected 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 UsageStats from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + UsageStatsService.this.dump(args, pw); + } } /** diff --git a/services/usage/java/com/android/server/usage/UsageStatsXml.java b/services/usage/java/com/android/server/usage/UsageStatsXml.java index 9ce6d63..186813e 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsXml.java +++ b/services/usage/java/com/android/server/usage/UsageStatsXml.java @@ -24,21 +24,30 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; public class UsageStatsXml { private static final String TAG = "UsageStatsXml"; private static final int CURRENT_VERSION = 1; private static final String USAGESTATS_TAG = "usagestats"; private static final String VERSION_ATTR = "version"; + static final String CHECKED_IN_SUFFIX = "-c"; public static long parseBeginTime(AtomicFile file) { - return Long.parseLong(file.getBaseFile().getName()); + return parseBeginTime(file.getBaseFile()); + } + + public static long parseBeginTime(File file) { + String name = file.getName(); + + // Eat as many occurrences of -c as possible. This is due to a bug where -c + // would be appended more than once to a checked-in file, causing a crash + // on boot when indexing files since Long.parseLong() will puke on anything but + // a number. + while (name.endsWith(CHECKED_IN_SUFFIX)) { + name = name.substring(0, name.length() - CHECKED_IN_SUFFIX.length()); + } + return Long.parseLong(name); } public static void read(AtomicFile file, IntervalStats statsOut) throws IOException { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 4916ec2..6596781 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -23,9 +23,13 @@ import android.app.usage.UsageStats; import android.app.usage.UsageStatsManager; import android.content.res.Configuration; import android.os.SystemClock; +import android.content.Context; +import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.usage.UsageStatsDatabase.StatCombiner; import java.io.File; @@ -43,7 +47,13 @@ class UserUsageStatsService { private static final String TAG = "UsageStatsService"; private static final boolean DEBUG = UsageStatsService.DEBUG; private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static final int sDateFormatFlags = + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_NUMERIC_DATE; + private final Context mContext; private final UsageStatsDatabase mDatabase; private final IntervalStats[] mCurrentStats; private boolean mStatsChanged = false; @@ -55,7 +65,8 @@ class UserUsageStatsService { void onStatsUpdated(); } - UserUsageStatsService(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]; @@ -433,6 +444,117 @@ class UserUsageStatsService { tempCal.getTimeInMillis() + ")"); } + // + // -- DUMP related methods -- + // + + void checkin(final IndentingPrintWriter pw) { + mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() { + @Override + public boolean checkin(IntervalStats stats) { + printIntervalStats(pw, stats, false); + return true; + } + }); + } + + void dump(IndentingPrintWriter pw) { + // This is not a check-in, only dump in-memory stats. + for (int interval = 0; interval < mCurrentStats.length; interval++) { + pw.print("In-memory "); + pw.print(intervalToString(interval)); + pw.println(" stats"); + printIntervalStats(pw, mCurrentStats[interval], true); + } + } + + private String formatDateTime(long dateTime, boolean pretty) { + if (pretty) { + return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\""; + } + return Long.toString(dateTime); + } + + private String formatElapsedTime(long elapsedTime, boolean pretty) { + if (pretty) { + return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\""; + } + return Long.toString(elapsedTime); + } + + void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates) { + if (prettyDates) { + pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, + stats.beginTime, stats.endTime, sDateFormatFlags) + "\""); + } else { + pw.printPair("beginTime", stats.beginTime); + pw.printPair("endTime", stats.endTime); + } + pw.println(); + pw.increaseIndent(); + pw.println("packages"); + pw.increaseIndent(); + final ArrayMap<String, UsageStats> pkgStats = stats.packageStats; + final int pkgCount = pkgStats.size(); + for (int i = 0; i < pkgCount; i++) { + final UsageStats usageStats = pkgStats.valueAt(i); + pw.printPair("package", usageStats.mPackageName); + pw.printPair("totalTime", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); + pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); + pw.println(); + } + pw.decreaseIndent(); + + pw.println("configurations"); + pw.increaseIndent(); + final ArrayMap<Configuration, ConfigurationStats> configStats = + stats.configurations; + final int configCount = configStats.size(); + for (int i = 0; i < configCount; i++) { + final ConfigurationStats config = configStats.valueAt(i); + pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration)); + pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); + pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); + pw.printPair("count", config.mActivationCount); + pw.println(); + } + pw.decreaseIndent(); + + pw.println("events"); + pw.increaseIndent(); + final TimeSparseArray<UsageEvents.Event> events = stats.events; + final int eventCount = events != null ? events.size() : 0; + for (int i = 0; i < eventCount; i++) { + final UsageEvents.Event event = events.valueAt(i); + pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); + pw.printPair("type", eventToString(event.mEventType)); + pw.printPair("package", event.mPackage); + if (event.mClass != null) { + pw.printPair("class", event.mClass); + } + if (event.mConfiguration != null) { + pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration)); + } + pw.println(); + } + pw.decreaseIndent(); + pw.decreaseIndent(); + } + + private static String intervalToString(int interval) { + switch (interval) { + case UsageStatsManager.INTERVAL_DAILY: + return "daily"; + case UsageStatsManager.INTERVAL_WEEKLY: + return "weekly"; + case UsageStatsManager.INTERVAL_MONTHLY: + return "monthly"; + case UsageStatsManager.INTERVAL_YEARLY: + return "yearly"; + default: + return "?"; + } + } private static String eventToString(int eventType) { switch (eventType) { diff --git a/services/usb/java/com/android/server/usb/UsbAudioManager.java b/services/usb/java/com/android/server/usb/UsbAudioManager.java new file mode 100644 index 0000000..bb45ee8 --- /dev/null +++ b/services/usb/java/com/android/server/usb/UsbAudioManager.java @@ -0,0 +1,197 @@ +/* + * 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 cc5d004..1cf00d2 100644 --- a/services/usb/java/com/android/server/usb/UsbDebuggingManager.java +++ b/services/usb/java/com/android/server/usb/UsbDebuggingManager.java @@ -207,7 +207,12 @@ public class UsbDebuggingManager implements Runnable { case MESSAGE_ADB_CONFIRM: { String key = (String)msg.obj; - mFingerprints = getFingerprints(key); + String fingerprints = getFingerprints(key); + if ("".equals(fingerprints)) { + sendResponse("NO"); + break; + } + mFingerprints = fingerprints; startConfirmation(key, mFingerprints); break; } @@ -224,16 +229,25 @@ public class UsbDebuggingManager implements Runnable { StringBuilder sb = new StringBuilder(); MessageDigest digester; + if (key == null) { + return ""; + } + try { digester = MessageDigest.getInstance("MD5"); } catch (Exception ex) { - Slog.e(TAG, "Error getting digester: " + ex); + Slog.e(TAG, "Error getting digester", ex); return ""; } byte[] base64_data = key.split("\\s+")[0].getBytes(); - byte[] digest = digester.digest(Base64.decode(base64_data, Base64.DEFAULT)); - + byte[] digest; + try { + digest = digester.digest(Base64.decode(base64_data, Base64.DEFAULT)); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "error doing base64 decoding", e); + return ""; + } for (int i = 0; i < digest.length; i++) { sb.append(hex.charAt((digest[i] >> 4) & 0xf)); sb.append(hex.charAt(digest[i] & 0xf)); diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index c63eb18..23ba3b6 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -209,8 +209,13 @@ public class UsbDeviceManager { mUseUsbNotification = !massStorageSupported; // make sure the ADB_ENABLED setting value matches the current state - Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0); - + try { + Settings.Global.putInt(mContentResolver, + Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0); + } catch (SecurityException e) { + // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't be changed. + Slog.d(TAG, "ADB_ENABLED is restricted."); + } mHandler.sendEmptyMessage(MSG_SYSTEM_READY); } diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 06febb3..e769bda 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -16,8 +16,6 @@ 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.UsbConfiguration; @@ -25,16 +23,13 @@ import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; -import android.media.AudioManager; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -import android.os.UserHandle; import android.util.Slog; import com.android.internal.annotations.GuardedBy; -import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.PrintWriter; @@ -46,11 +41,12 @@ import java.util.HashMap; */ public class UsbHostManager { private static final String TAG = UsbHostManager.class.getSimpleName(); - private static final boolean DEBUG_AUDIO = false; + private static final boolean DEBUG = false; // contains all connected USB devices private final HashMap<String, UsbDevice> mDevices = new HashMap<String, UsbDevice>(); + // USB busses to exclude from USB host support private final String[] mHostBlacklist; @@ -64,14 +60,7 @@ public class UsbHostManager { private ArrayList<UsbInterface> mNewInterfaces; private ArrayList<UsbEndpoint> mNewEndpoints; - // Attributes of any connected USB audio device. - //TODO(pmclean) When we extend to multiple, USB Audio devices, we will need to get - // more clever about this. - private int mConnectedUsbCard = -1; - private int mConnectedUsbDeviceNum = -1; - private boolean mConnectedHasPlayback = false; - private boolean mConnectedHasCapture = false; - private boolean mConnectedHasMIDI = false; + private UsbAudioManager mUsbAudioManager; @GuardedBy("mLock") private UsbSettingsManager mCurrentSettings; @@ -80,6 +69,7 @@ public class UsbHostManager { mContext = context; mHostBlacklist = context.getResources().getStringArray( com.android.internal.R.array.config_usbHostBlacklist); + mUsbAudioManager = new UsbAudioManager(context); } public void setCurrentSettings(UsbSettingsManager settings) { @@ -118,48 +108,6 @@ public class UsbHostManager { return false; } - // Broadcasts the arrival/departure of a USB audio interface - // card - the ALSA card number of the physical interface - // device - the ALSA device number of the physical interface - // enabled - if true, we're connecting a device (it's arrived), else disconnecting - private void sendDeviceNotification(int card, int device, boolean enabled, - boolean hasPlayback, boolean hasCapture, boolean hasMIDI) { - // 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", card); - intent.putExtra("device", device); - intent.putExtra("hasPlayback", hasPlayback); - intent.putExtra("hasCapture", hasCapture); - intent.putExtra("hasMIDI", hasMIDI); - 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; - } - /* Called from JNI in monitorUsbHostBus() to report new USB devices Returns true if successful, in which case the JNI code will continue adding configurations, interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors @@ -169,7 +117,7 @@ public class UsbHostManager { int deviceClass, int deviceSubclass, int deviceProtocol, String manufacturerName, String productName, String serialNumber) { - if (DEBUG_AUDIO) { + if (DEBUG) { Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")"); // Audio Class Codes: // Audio: 0x01 @@ -254,7 +202,7 @@ public class UsbHostManager { /* Called from JNI in monitorUsbHostBus() to finish adding a new device */ private void endUsbDeviceAdded() { - if (DEBUG_AUDIO) { + if (DEBUG) { Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()"); } if (mNewInterface != null) { @@ -266,16 +214,6 @@ public class UsbHostManager { mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()])); } - // Is there an audio interface in there? - final int kUsbClassId_Audio = 0x01; - boolean isAudioDevice = false; - for (int ntrfaceIndex = 0; !isAudioDevice && ntrfaceIndex < mNewInterfaces.size(); - ntrfaceIndex++) { - UsbInterface ntrface = mNewInterfaces.get(ntrfaceIndex); - if (ntrface.getInterfaceClass() == kUsbClassId_Audio) { - isAudioDevice = true; - } - } synchronized (mLock) { if (mNewDevice != null) { @@ -284,6 +222,7 @@ public class UsbHostManager { mDevices.put(mNewDevice.getDeviceName(), mNewDevice); Slog.d(TAG, "Added device " + mNewDevice); getCurrentSettings().deviceAttached(mNewDevice); + mUsbAudioManager.deviceAdded(mNewDevice); } else { Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded"); } @@ -292,81 +231,14 @@ public class UsbHostManager { mNewInterfaces = null; mNewEndpoints = null; } - - if (!isAudioDevice) { - return; // bail - } - - //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. - mConnectedUsbCard = cardsParser.getNumCardRecords() - 1; - mConnectedUsbDeviceNum = 0; - - mConnectedHasPlayback = devicesParser.hasPlaybackDevices(mConnectedUsbCard); - mConnectedHasCapture = devicesParser.hasCaptureDevices(mConnectedUsbCard); - mConnectedHasMIDI = devicesParser.hasMIDIDevices(mConnectedUsbCard); - - // Playback device file needed/present? - if (mConnectedHasPlayback && - !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, false)) { - return; - } - - // Capture device file needed/present? - if (mConnectedHasCapture && - !waitForAlsaFile(mConnectedUsbCard, mConnectedUsbDeviceNum, true)) { - return; - } - - if (DEBUG_AUDIO) { - Slog.d(TAG, - "usb: hasPlayback:" + mConnectedHasPlayback + " hasCapture:" + mConnectedHasCapture); - } - - sendDeviceNotification(mConnectedUsbCard, - mConnectedUsbDeviceNum, - true, - mConnectedHasPlayback, - mConnectedHasCapture, - mConnectedHasMIDI); } /* Called from JNI in monitorUsbHostBus to report USB device removal */ private void usbDeviceRemoved(String deviceName) { - if (DEBUG_AUDIO) { - Slog.d(TAG, "usb:UsbHostManager.usbDeviceRemoved() nm:" + deviceName); - } - - if (mConnectedUsbCard != -1 && mConnectedUsbDeviceNum != -1) { - sendDeviceNotification(mConnectedUsbCard, - mConnectedUsbDeviceNum, - false, - mConnectedHasPlayback, - mConnectedHasCapture, - mConnectedHasMIDI); - mConnectedUsbCard = -1; - mConnectedUsbDeviceNum = -1; - mConnectedHasPlayback = false; - mConnectedHasCapture = false; - mConnectedHasMIDI = false; - } - synchronized (mLock) { UsbDevice device = mDevices.remove(deviceName); if (device != null) { + mUsbAudioManager.deviceRemoved(device); getCurrentSettings().deviceDetached(device); } } @@ -418,6 +290,7 @@ public class UsbHostManager { pw.println(" " + name + ": " + mDevices.get(name)); } } + mUsbAudioManager.dump(fd, pw); } private native void monitorUsbHostBus(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java index 8ce7f74..3ca0c84 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java @@ -40,7 +40,6 @@ import android.util.Slog; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.UUID; /** * Helper for {@link SoundTrigger} APIs. @@ -78,7 +77,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { private IRecognitionStatusCallback mActiveListener; private int mKeyphraseId = INVALID_VALUE; private int mCurrentSoundModelHandle = INVALID_VALUE; - private UUID mCurrentSoundModelUuid = null; + private KeyphraseSoundModel mCurrentSoundModel = null; // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer. private RecognitionConfig mRecognitionConfig = null; private boolean mRequested = false; @@ -134,7 +133,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { + (mActiveListener == null ? "null" : mActiveListener.asBinder())); Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle); Slog.d(TAG, "current SoundModel UUID=" - + (mCurrentSoundModelUuid == null ? null : mCurrentSoundModelUuid)); + + (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid)); } if (!mStarted) { @@ -166,20 +165,16 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } // Unload the previous model if the current one isn't invalid - // and, it's not the same as the new one, or we are already started - // if we are already started, we can get multiple calls to start - // if the underlying sound model changes, in which case we should unload and reload. - // The model reuse helps only in cases when we trigger and stop internally - // without a start recognition call. + // and, it's not the same as the new one. + // This helps use cache and reuse the model and just start/stop it when necessary. if (mCurrentSoundModelHandle != INVALID_VALUE - && (!soundModel.uuid.equals(mCurrentSoundModelUuid) || mStarted)) { + && !soundModel.equals(mCurrentSoundModel)) { Slog.w(TAG, "Unloading previous sound model"); int status = mModule.unloadSoundModel(mCurrentSoundModelHandle); if (status != SoundTrigger.STATUS_OK) { Slog.w(TAG, "unloadSoundModel call failed with " + status); } - mCurrentSoundModelHandle = INVALID_VALUE; - mCurrentSoundModelUuid = null; + internalClearSoundModelLocked(); mStarted = false; } @@ -198,7 +193,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // Load the sound model if the current one is null. int soundModelHandle = mCurrentSoundModelHandle; if (mCurrentSoundModelHandle == INVALID_VALUE - || mCurrentSoundModelUuid == null) { + || mCurrentSoundModel == null) { int[] handle = new int[] { INVALID_VALUE }; int status = mModule.loadSoundModel(soundModel, handle); if (status != SoundTrigger.STATUS_OK) { @@ -218,7 +213,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { mRequested = true; mKeyphraseId = keyphraseId; mCurrentSoundModelHandle = soundModelHandle; - mCurrentSoundModelUuid = soundModel.uuid; + mCurrentSoundModel = soundModel; mRecognitionConfig = recognitionConfig; // Register the new listener. This replaces the old one. // There can only be a maximum of one active listener at any given time. @@ -275,14 +270,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return status; } - status = mModule.unloadSoundModel(mCurrentSoundModelHandle); - if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "unloadSoundModel call failed with " + status); - } - - // Clear the internal state once the recognition has been stopped. - // Unload sound model call may fail in scenarios, and we'd still want - // to reload the sound model. + // We leave the sound model loaded but not started, this helps us when we start + // back. + // Also clear the internal state once the recognition has been stopped. internalClearStateLocked(); return status; } @@ -303,11 +293,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { mRequested = false; int status = updateRecognitionLocked(false /* don't notify for synchronous calls */); - status = mModule.unloadSoundModel(mCurrentSoundModelHandle); - if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "unloadSoundModel call failed with " + status); - } - internalClearStateLocked(); } } @@ -456,6 +441,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } catch (RemoteException e) { Slog.w(TAG, "RemoteException in onError", e); } finally { + internalClearSoundModelLocked(); internalClearStateLocked(); if (mModule != null) { mModule.detach(); @@ -535,8 +521,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { mRequested = false; mKeyphraseId = INVALID_VALUE; - mCurrentSoundModelHandle = INVALID_VALUE; - mCurrentSoundModelUuid = null; mRecognitionConfig = null; mActiveListener = null; @@ -550,6 +534,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } + private void internalClearSoundModelLocked() { + mCurrentSoundModelHandle = INVALID_VALUE; + mCurrentSoundModel = null; + } + class MyCallStateListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String arg1) { @@ -581,7 +570,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { pw.print(" keyphrase ID="); pw.println(mKeyphraseId); pw.print(" sound model handle="); pw.println(mCurrentSoundModelHandle); pw.print(" sound model UUID="); - pw.println(mCurrentSoundModelUuid == null ? "null" : mCurrentSoundModelUuid); + pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid); pw.print(" current listener="); pw.println(mActiveListener == null ? "null" : mActiveListener.asBinder()); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 82b7f8b..f5d4867 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -136,11 +136,14 @@ public class VoiceInteractionManagerService extends SystemService { Settings.Secure.VOICE_INTERACTION_SERVICE, userHandle); ComponentName curRecognizer = getCurRecognizer(userHandle); VoiceInteractionServiceInfo curInteractorInfo = null; - if (curInteractorStr == null && curRecognizer != null) { + if (curInteractorStr == null && curRecognizer != null + && !ActivityManager.isLowRamDeviceStatic()) { // If there is no interactor setting, that means we are upgrading // from an older platform version. If the current recognizer is not // set or matches the preferred recognizer, then we want to upgrade // the user to have the default voice interaction service enabled. + // Note that we don't do this for low-RAM devices, since we aren't + // supporting voice interaction services there. curInteractorInfo = findAvailInteractor(userHandle, curRecognizer); if (curInteractorInfo != null) { // Looks good! We'll apply this one. To make it happen, we clear the @@ -150,6 +153,15 @@ public class VoiceInteractionManagerService extends SystemService { } } + // If we are on a svelte device, make sure an interactor is not currently + // enabled; if it is, turn it off. + if (ActivityManager.isLowRamDeviceStatic() && curInteractorStr != null) { + if (!TextUtils.isEmpty(curInteractorStr)) { + setCurInteractor(null, userHandle); + curInteractorStr = ""; + } + } + if (curRecognizer != null) { // If we already have at least a recognizer, then we probably want to // leave things as they are... unless something has disappeared. @@ -171,10 +183,11 @@ public class VoiceInteractionManagerService extends SystemService { } } - // Initializing settings, look for an interactor first. - if (curInteractorInfo == null) { + // Initializing settings, look for an interactor first (but only on non-svelte). + if (curInteractorInfo == null && !ActivityManager.isLowRamDeviceStatic()) { curInteractorInfo = findAvailInteractor(userHandle, null); } + if (curInteractorInfo != null) { // Eventually it will be an error to not specify this. setCurInteractor(new ComponentName(curInteractorInfo.getServiceInfo().packageName, |