summaryrefslogtreecommitdiffstats
path: root/services/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com')
-rw-r--r--services/java/com/android/server/BackupManagerService.java628
-rw-r--r--services/java/com/android/server/ConnectivityService.java275
-rw-r--r--services/java/com/android/server/DevicePolicyManagerService.java8
-rw-r--r--services/java/com/android/server/EventLogTags.logtags12
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java33
-rw-r--r--services/java/com/android/server/LocationManagerService.java5
-rw-r--r--services/java/com/android/server/LockSettingsService.java4
-rw-r--r--services/java/com/android/server/NetworkManagementService.java4
-rw-r--r--services/java/com/android/server/NotificationManagerService.java37
-rw-r--r--services/java/com/android/server/NsdService.java8
-rw-r--r--services/java/com/android/server/SystemServer.java32
-rw-r--r--services/java/com/android/server/WallpaperManagerService.java23
-rw-r--r--services/java/com/android/server/accessibility/TouchExplorer.java143
-rw-r--r--services/java/com/android/server/accounts/AccountManagerService.java60
-rw-r--r--services/java/com/android/server/am/ActiveServices.java256
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java358
-rw-r--r--services/java/com/android/server/am/ActivityStack.java84
-rw-r--r--services/java/com/android/server/am/ActivityStackSupervisor.java31
-rw-r--r--services/java/com/android/server/am/BatteryStatsService.java14
-rw-r--r--services/java/com/android/server/am/BroadcastQueue.java24
-rw-r--r--services/java/com/android/server/am/ConnectionRecord.java2
-rw-r--r--services/java/com/android/server/am/ProcessRecord.java16
-rw-r--r--services/java/com/android/server/am/ProcessStatsService.java32
-rw-r--r--services/java/com/android/server/am/ServiceRecord.java14
-rw-r--r--services/java/com/android/server/am/TaskRecord.java23
-rw-r--r--services/java/com/android/server/connectivity/Vpn.java101
-rw-r--r--services/java/com/android/server/content/ContentService.java2
-rw-r--r--services/java/com/android/server/content/SyncManager.java2
-rw-r--r--services/java/com/android/server/content/SyncOperation.java50
-rw-r--r--services/java/com/android/server/content/SyncQueue.java6
-rw-r--r--services/java/com/android/server/content/SyncStorageEngine.java38
-rw-r--r--services/java/com/android/server/display/DisplayManagerService.java129
-rw-r--r--services/java/com/android/server/display/WifiDisplayAdapter.java120
-rw-r--r--services/java/com/android/server/display/WifiDisplayController.java165
-rw-r--r--services/java/com/android/server/input/InputManagerService.java1
-rw-r--r--services/java/com/android/server/media/MediaRouterService.java1423
-rw-r--r--services/java/com/android/server/media/RemoteDisplayProviderProxy.java443
-rw-r--r--services/java/com/android/server/media/RemoteDisplayProviderWatcher.java219
-rwxr-xr-xservices/java/com/android/server/pm/PackageManagerService.java205
-rw-r--r--services/java/com/android/server/pm/Settings.java98
-rw-r--r--services/java/com/android/server/power/DisplayPowerController.java232
-rw-r--r--services/java/com/android/server/power/DisplayPowerState.java9
-rw-r--r--services/java/com/android/server/power/PowerManagerService.java3
-rw-r--r--services/java/com/android/server/print/PrintManagerService.java2
-rw-r--r--services/java/com/android/server/usb/UsbDeviceManager.java31
-rw-r--r--services/java/com/android/server/wifi/WifiController.java12
-rw-r--r--services/java/com/android/server/wifi/WifiService.java61
-rw-r--r--services/java/com/android/server/wm/AppWindowToken.java1
-rw-r--r--services/java/com/android/server/wm/DisplayContent.java17
-rw-r--r--services/java/com/android/server/wm/StackBox.java4
-rw-r--r--services/java/com/android/server/wm/Task.java5
-rw-r--r--services/java/com/android/server/wm/TaskStack.java8
-rw-r--r--services/java/com/android/server/wm/WindowAnimator.java2
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java136
-rw-r--r--services/java/com/android/server/wm/WindowState.java7
55 files changed, 4582 insertions, 1076 deletions
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index a04ee14..6d65a70 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -46,6 +46,8 @@ import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
@@ -80,7 +82,7 @@ import android.util.StringBuilderPrinter;
import com.android.internal.backup.BackupConstants;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.backup.IObbBackupService;
-import com.android.internal.backup.LocalTransport;
+import com.android.server.EventLogTags;
import com.android.server.PackageManagerBackupAgent.Metadata;
import java.io.BufferedInputStream;
@@ -138,14 +140,20 @@ class BackupManagerService extends IBackupManager.Stub {
private static final boolean DEBUG = true;
private static final boolean MORE_DEBUG = false;
+ // Historical and current algorithm names
+ static final String PBKDF_CURRENT = "PBKDF2WithHmacSHA1";
+ static final String PBKDF_FALLBACK = "PBKDF2WithHmacSHA1And8bit";
+
// Name and current contents version of the full-backup manifest file
static final String BACKUP_MANIFEST_FILENAME = "_manifest";
static final int BACKUP_MANIFEST_VERSION = 1;
static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
- static final int BACKUP_FILE_VERSION = 1;
+ static final int BACKUP_FILE_VERSION = 2;
+ static final int BACKUP_PW_FILE_VERSION = 2;
static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
+ static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
// How often we perform a backup pass. Privileged external callers can
// trigger an immediate pass.
@@ -158,6 +166,9 @@ class BackupManagerService extends IBackupManager.Stub {
// the first backup pass.
private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR;
+ // Retry interval for clear/init when the transport is unavailable
+ private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
+
private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR";
@@ -171,6 +182,8 @@ class BackupManagerService extends IBackupManager.Stub {
private static final int MSG_RESTORE_TIMEOUT = 8;
private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
private static final int MSG_RUN_FULL_RESTORE = 10;
+ private static final int MSG_RETRY_INIT = 11;
+ private static final int MSG_RETRY_CLEAR = 12;
// backup task state machine tick
static final int MSG_BACKUP_RESTORE_STEP = 20;
@@ -251,10 +264,13 @@ class BackupManagerService extends IBackupManager.Stub {
volatile boolean mClearingData;
// Transport bookkeeping
+ final HashMap<String,String> mTransportNames
+ = new HashMap<String,String>(); // component name -> registration name
final HashMap<String,IBackupTransport> mTransports
- = new HashMap<String,IBackupTransport>();
+ = new HashMap<String,IBackupTransport>(); // registration name -> binder
+ final ArrayList<TransportConnection> mTransportConnections
+ = new ArrayList<TransportConnection>();
String mCurrentTransport;
- IBackupTransport mLocalTransport, mGoogleTransport;
ActiveRestoreSession mActiveRestoreSession;
// Watch the device provisioning operation during setup
@@ -300,6 +316,7 @@ class BackupManagerService extends IBackupManager.Stub {
class RestoreParams {
public IBackupTransport transport;
+ public String dirName;
public IRestoreObserver observer;
public long token;
public PackageInfo pkgInfo;
@@ -307,9 +324,10 @@ class BackupManagerService extends IBackupManager.Stub {
public boolean needFullBackup;
public String[] filterSet;
- RestoreParams(IBackupTransport _transport, IRestoreObserver _obs,
+ RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
long _token, PackageInfo _pkg, int _pmToken, boolean _needFullBackup) {
transport = _transport;
+ dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = _pkg;
@@ -318,9 +336,10 @@ class BackupManagerService extends IBackupManager.Stub {
filterSet = null;
}
- RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token,
- boolean _needFullBackup) {
+ RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+ long _token, boolean _needFullBackup) {
transport = _transport;
+ dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = null;
@@ -329,9 +348,10 @@ class BackupManagerService extends IBackupManager.Stub {
filterSet = null;
}
- RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token,
- String[] _filterSet, boolean _needFullBackup) {
+ RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs,
+ long _token, String[] _filterSet, boolean _needFullBackup) {
transport = _transport;
+ dirName = _dirName;
observer = _obs;
token = _token;
pkgInfo = null;
@@ -351,6 +371,16 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
+ class ClearRetryParams {
+ public String transportName;
+ public String packageName;
+
+ ClearRetryParams(String transport, String pkg) {
+ transportName = transport;
+ packageName = pkg;
+ }
+ }
+
class FullParams {
public ParcelFileDescriptor fd;
public final AtomicBoolean latch;
@@ -425,6 +455,8 @@ class BackupManagerService extends IBackupManager.Stub {
private final SecureRandom mRng = new SecureRandom();
private String mPasswordHash;
private File mPasswordHashFile;
+ private int mPasswordVersion;
+ private File mPasswordVersionFile;
private byte[] mPasswordSalt;
// Configuration of PBKDF2 that we use for generating pw hashes and intermediate keys
@@ -510,13 +542,28 @@ class BackupManagerService extends IBackupManager.Stub {
// When it completes successfully, that old journal file will be
// deleted. If we crash prior to that, the old journal is parsed
// at next boot and the journaled requests fulfilled.
+ boolean staged = true;
if (queue.size() > 0) {
// Spin up a backup state sequence and set it running
- PerformBackupTask pbt = new PerformBackupTask(transport, queue, oldJournal);
- Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
- sendMessage(pbtMessage);
+ try {
+ String dirName = transport.transportDirName();
+ PerformBackupTask pbt = new PerformBackupTask(transport, dirName,
+ queue, oldJournal);
+ Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+ sendMessage(pbtMessage);
+ } catch (RemoteException e) {
+ // unable to ask the transport its dir name -- transient failure, since
+ // the above check succeeded. Try again next time.
+ Slog.e(TAG, "Transport became unavailable attempting backup");
+ staged = false;
+ }
} else {
Slog.v(TAG, "Backup requested but nothing pending");
+ staged = false;
+ }
+
+ if (!staged) {
+ // if we didn't actually hand off the wakelock, rewind until next time
synchronized (mQueueLock) {
mBackupRunning = false;
}
@@ -566,7 +613,7 @@ class BackupManagerService extends IBackupManager.Stub {
RestoreParams params = (RestoreParams)msg.obj;
Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
PerformRestoreTask task = new PerformRestoreTask(
- params.transport, params.observer,
+ params.transport, params.dirName, params.observer,
params.token, params.pkgInfo, params.pmToken,
params.needFullBackup, params.filterSet);
Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task);
@@ -593,6 +640,14 @@ class BackupManagerService extends IBackupManager.Stub {
break;
}
+ case MSG_RETRY_CLEAR:
+ {
+ // reenqueues if the transport remains unavailable
+ ClearRetryParams params = (ClearRetryParams)msg.obj;
+ clearBackupData(params.transportName, params.packageName);
+ break;
+ }
+
case MSG_RUN_INITIALIZE:
{
HashSet<String> queue;
@@ -607,6 +662,16 @@ class BackupManagerService extends IBackupManager.Stub {
break;
}
+ case MSG_RETRY_INIT:
+ {
+ synchronized (mQueueLock) {
+ recordInitPendingLocked(msg.arg1 != 0, (String)msg.obj);
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
+ mRunInitIntent);
+ }
+ break;
+ }
+
case MSG_RUN_GET_RESTORE_SETS:
{
// Like other async operations, this is entered with the wakelock held
@@ -752,6 +817,27 @@ class BackupManagerService extends IBackupManager.Stub {
}
mDataDir = Environment.getDownloadCacheDirectory();
+ mPasswordVersion = 1; // unless we hear otherwise
+ mPasswordVersionFile = new File(mBaseStateDir, "pwversion");
+ if (mPasswordVersionFile.exists()) {
+ FileInputStream fin = null;
+ DataInputStream in = null;
+ try {
+ fin = new FileInputStream(mPasswordVersionFile);
+ in = new DataInputStream(fin);
+ mPasswordVersion = in.readInt();
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to read backup pw version");
+ } finally {
+ try {
+ if (in != null) in.close();
+ if (fin != null) fin.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Error closing pw version files");
+ }
+ }
+ }
+
mPasswordHashFile = new File(mBaseStateDir, "pwhash");
if (mPasswordHashFile.exists()) {
FileInputStream fin = null;
@@ -815,13 +901,7 @@ class BackupManagerService extends IBackupManager.Stub {
}
// Set up our transport options and initialize the default transport
- // TODO: Have transports register themselves somehow?
// TODO: Don't create transports that we don't need to?
- mLocalTransport = new LocalTransport(context); // This is actually pretty cheap
- ComponentName localName = new ComponentName(context, LocalTransport.class);
- registerTransport(localName.flattenToShortString(), mLocalTransport);
-
- mGoogleTransport = null;
mCurrentTransport = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.BACKUP_TRANSPORT);
if ("".equals(mCurrentTransport)) {
@@ -829,28 +909,43 @@ class BackupManagerService extends IBackupManager.Stub {
}
if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport);
- // Attach to the Google backup transport. When this comes up, it will set
- // itself as the current transport because we explicitly reset mCurrentTransport
- // to null.
- ComponentName transportComponent = new ComponentName("com.google.android.backup",
- "com.google.android.backup.BackupTransportService");
- try {
- // If there's something out there that is supposed to be the Google
- // backup transport, make sure it's legitimately part of the OS build
- // and not an app lying about its package name.
- ApplicationInfo info = mPackageManager.getApplicationInfo(
- transportComponent.getPackageName(), 0);
- if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- if (DEBUG) Slog.v(TAG, "Binding to Google transport");
- Intent intent = new Intent().setComponent(transportComponent);
- context.bindServiceAsUser(intent, mGoogleConnection, Context.BIND_AUTO_CREATE,
- UserHandle.OWNER);
- } else {
- Slog.w(TAG, "Possible Google transport spoof: ignoring " + info);
+ // Find transport hosts and bind to their services
+ Intent transportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
+ List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
+ transportServiceIntent, 0, UserHandle.USER_OWNER);
+ if (DEBUG) {
+ Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size()));
+ }
+ if (hosts != null) {
+ if (MORE_DEBUG) {
+ for (int i = 0; i < hosts.size(); i++) {
+ ServiceInfo info = hosts.get(i).serviceInfo;
+ Slog.v(TAG, " " + info.packageName + "/" + info.name);
+ }
+ }
+ for (int i = 0; i < hosts.size(); i++) {
+ try {
+ ServiceInfo info = hosts.get(i).serviceInfo;
+ PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0);
+ if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+ ComponentName svcName = new ComponentName(info.packageName, info.name);
+ if (DEBUG) {
+ Slog.i(TAG, "Binding to transport host " + svcName);
+ }
+ Intent intent = new Intent(transportServiceIntent);
+ intent.setComponent(svcName);
+ TransportConnection connection = new TransportConnection();
+ mTransportConnections.add(connection);
+ context.bindServiceAsUser(intent,
+ connection, Context.BIND_AUTO_CREATE,
+ UserHandle.OWNER);
+ } else {
+ Slog.w(TAG, "Transport package not privileged: " + info.packageName);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Problem resolving transport service: " + e.getMessage());
+ }
}
- } catch (PackageManager.NameNotFoundException nnf) {
- // No such package? No binding.
- if (DEBUG) Slog.v(TAG, "Google transport not present");
}
// Now that we know about valid backup participants, parse any
@@ -1043,13 +1138,13 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
- private SecretKey buildPasswordKey(String pw, byte[] salt, int rounds) {
- return buildCharArrayKey(pw.toCharArray(), salt, rounds);
+ private SecretKey buildPasswordKey(String algorithm, String pw, byte[] salt, int rounds) {
+ return buildCharArrayKey(algorithm, pw.toCharArray(), salt, rounds);
}
- private SecretKey buildCharArrayKey(char[] pwArray, byte[] salt, int rounds) {
+ private SecretKey buildCharArrayKey(String algorithm, char[] pwArray, byte[] salt, int rounds) {
try {
- SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
KeySpec ks = new PBEKeySpec(pwArray, salt, rounds, PBKDF2_KEY_SIZE);
return keyFactory.generateSecret(ks);
} catch (InvalidKeySpecException e) {
@@ -1060,8 +1155,8 @@ class BackupManagerService extends IBackupManager.Stub {
return null;
}
- private String buildPasswordHash(String pw, byte[] salt, int rounds) {
- SecretKey key = buildPasswordKey(pw, salt, rounds);
+ private String buildPasswordHash(String algorithm, String pw, byte[] salt, int rounds) {
+ SecretKey key = buildPasswordKey(algorithm, pw, salt, rounds);
if (key != null) {
return byteArrayToHex(key.getEncoded());
}
@@ -1089,13 +1184,13 @@ class BackupManagerService extends IBackupManager.Stub {
return result;
}
- private byte[] makeKeyChecksum(byte[] pwBytes, byte[] salt, int rounds) {
+ private byte[] makeKeyChecksum(String algorithm, byte[] pwBytes, byte[] salt, int rounds) {
char[] mkAsChar = new char[pwBytes.length];
for (int i = 0; i < pwBytes.length; i++) {
mkAsChar[i] = (char) pwBytes[i];
}
- Key checksum = buildCharArrayKey(mkAsChar, salt, rounds);
+ Key checksum = buildCharArrayKey(algorithm, mkAsChar, salt, rounds);
return checksum.getEncoded();
}
@@ -1107,7 +1202,7 @@ class BackupManagerService extends IBackupManager.Stub {
}
// Backup password management
- boolean passwordMatchesSaved(String candidatePw, int rounds) {
+ boolean passwordMatchesSaved(String algorithm, String candidatePw, int rounds) {
// First, on an encrypted device we require matching the device pw
final boolean isEncrypted;
try {
@@ -1150,7 +1245,7 @@ class BackupManagerService extends IBackupManager.Stub {
} else {
// hash the stated current pw and compare to the stored one
if (candidatePw != null && candidatePw.length() > 0) {
- String currentPwHash = buildPasswordHash(candidatePw, mPasswordSalt, rounds);
+ String currentPwHash = buildPasswordHash(algorithm, candidatePw, mPasswordSalt, rounds);
if (mPasswordHash.equalsIgnoreCase(currentPwHash)) {
// candidate hash matches the stored hash -- the password matches
return true;
@@ -1165,11 +1260,37 @@ class BackupManagerService extends IBackupManager.Stub {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupPassword");
- // If the supplied pw doesn't hash to the the saved one, fail
- if (!passwordMatchesSaved(currentPw, PBKDF2_HASH_ROUNDS)) {
+ // When processing v1 passwords we may need to try two different PBKDF2 checksum regimes
+ final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
+
+ // If the supplied pw doesn't hash to the the saved one, fail. The password
+ // might be caught in the legacy crypto mismatch; verify that too.
+ if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
+ && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
+ currentPw, PBKDF2_HASH_ROUNDS))) {
return false;
}
+ // Snap up to current on the pw file version
+ mPasswordVersion = BACKUP_PW_FILE_VERSION;
+ FileOutputStream pwFout = null;
+ DataOutputStream pwOut = null;
+ try {
+ pwFout = new FileOutputStream(mPasswordVersionFile);
+ pwOut = new DataOutputStream(pwFout);
+ pwOut.writeInt(mPasswordVersion);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to write backup pw version; password not changed");
+ return false;
+ } finally {
+ try {
+ if (pwOut != null) pwOut.close();
+ if (pwFout != null) pwFout.close();
+ } catch (IOException e) {
+ Slog.w(TAG, "Unable to close pw version record");
+ }
+ }
+
// Clearing the password is okay
if (newPw == null || newPw.isEmpty()) {
if (mPasswordHashFile.exists()) {
@@ -1187,7 +1308,7 @@ class BackupManagerService extends IBackupManager.Stub {
try {
// Okay, build the hash of the new backup password
byte[] salt = randomBytes(PBKDF2_SALT_SIZE);
- String newPwHash = buildPasswordHash(newPw, salt, PBKDF2_HASH_ROUNDS);
+ String newPwHash = buildPasswordHash(PBKDF_CURRENT, newPw, salt, PBKDF2_HASH_ROUNDS);
OutputStream pwf = null, buffer = null;
DataOutputStream out = null;
@@ -1230,34 +1351,65 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
+ private boolean backupPasswordMatches(String currentPw) {
+ if (hasBackupPassword()) {
+ final boolean pbkdf2Fallback = (mPasswordVersion < BACKUP_PW_FILE_VERSION);
+ if (!passwordMatchesSaved(PBKDF_CURRENT, currentPw, PBKDF2_HASH_ROUNDS)
+ && !(pbkdf2Fallback && passwordMatchesSaved(PBKDF_FALLBACK,
+ currentPw, PBKDF2_HASH_ROUNDS))) {
+ if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
+ return false;
+ }
+ }
+ return true;
+ }
+
// Maintain persistent state around whether need to do an initialize operation.
// Must be called with the queue lock held.
void recordInitPendingLocked(boolean isPending, String transportName) {
if (DEBUG) Slog.i(TAG, "recordInitPendingLocked: " + isPending
+ " on transport " + transportName);
+ mBackupHandler.removeMessages(MSG_RETRY_INIT);
+
try {
IBackupTransport transport = getTransport(transportName);
- String transportDirName = transport.transportDirName();
- File stateDir = new File(mBaseStateDir, transportDirName);
- File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
-
- if (isPending) {
- // We need an init before we can proceed with sending backup data.
- // Record that with an entry in our set of pending inits, as well as
- // journaling it via creation of a sentinel file.
- mPendingInits.add(transportName);
- try {
- (new FileOutputStream(initPendingFile)).close();
- } catch (IOException ioe) {
- // Something is badly wrong with our permissions; just try to move on
+ if (transport != null) {
+ String transportDirName = transport.transportDirName();
+ File stateDir = new File(mBaseStateDir, transportDirName);
+ File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+
+ if (isPending) {
+ // We need an init before we can proceed with sending backup data.
+ // Record that with an entry in our set of pending inits, as well as
+ // journaling it via creation of a sentinel file.
+ mPendingInits.add(transportName);
+ try {
+ (new FileOutputStream(initPendingFile)).close();
+ } catch (IOException ioe) {
+ // Something is badly wrong with our permissions; just try to move on
+ }
+ } else {
+ // No more initialization needed; wipe the journal and reset our state.
+ initPendingFile.delete();
+ mPendingInits.remove(transportName);
}
- } else {
- // No more initialization needed; wipe the journal and reset our state.
- initPendingFile.delete();
- mPendingInits.remove(transportName);
+ return; // done; don't fall through to the error case
}
} catch (RemoteException e) {
- // can't happen; the transport is local
+ // transport threw when asked its name; fall through to the lookup-failed case
+ }
+
+ // The named transport doesn't exist or threw. This operation is
+ // important, so we record the need for a an init and post a message
+ // to retry the init later.
+ if (isPending) {
+ mPendingInits.add(transportName);
+ mBackupHandler.sendMessageDelayed(
+ mBackupHandler.obtainMessage(MSG_RETRY_INIT,
+ (isPending ? 1 : 0),
+ 0,
+ transportName),
+ TRANSPORT_RETRY_INTERVAL);
}
}
@@ -1298,13 +1450,16 @@ class BackupManagerService extends IBackupManager.Stub {
// Add a transport to our set of available backends. If 'transport' is null, this
// is an unregistration, and the transport's entry is removed from our bookkeeping.
- private void registerTransport(String name, IBackupTransport transport) {
+ private void registerTransport(String name, String component, IBackupTransport transport) {
synchronized (mTransports) {
- if (DEBUG) Slog.v(TAG, "Registering transport " + name + " = " + transport);
+ if (DEBUG) Slog.v(TAG, "Registering transport "
+ + component + "::" + name + " = " + transport);
if (transport != null) {
mTransports.put(name, transport);
+ mTransportNames.put(component, name);
} else {
- mTransports.remove(name);
+ mTransports.remove(mTransportNames.get(component));
+ mTransportNames.remove(component);
// Nothing further to do in the unregistration case
return;
}
@@ -1330,7 +1485,10 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
} catch (RemoteException e) {
- // can't happen, the transport is local
+ // the transport threw when asked its file naming prefs; declare it invalid
+ Slog.e(TAG, "Unable to register transport as " + name);
+ mTransportNames.remove(component);
+ mTransports.remove(name);
}
}
@@ -1390,18 +1548,23 @@ class BackupManagerService extends IBackupManager.Stub {
}
};
- // ----- Track connection to GoogleBackupTransport service -----
- ServiceConnection mGoogleConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) Slog.v(TAG, "Connected to Google transport");
- mGoogleTransport = IBackupTransport.Stub.asInterface(service);
- registerTransport(name.flattenToShortString(), mGoogleTransport);
+ // ----- Track connection to transports service -----
+ class TransportConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+ if (DEBUG) Slog.v(TAG, "Connected to transport " + component);
+ try {
+ IBackupTransport transport = IBackupTransport.Stub.asInterface(service);
+ registerTransport(transport.name(), component.flattenToShortString(), transport);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to register transport " + component);
+ }
}
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Slog.v(TAG, "Disconnected from Google transport");
- mGoogleTransport = null;
- registerTransport(name.flattenToShortString(), null);
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component);
+ registerTransport(null, component.flattenToShortString(), null);
}
};
@@ -1645,7 +1808,7 @@ class BackupManagerService extends IBackupManager.Stub {
agent = mConnectedAgent;
}
} catch (RemoteException e) {
- // can't happen
+ // can't happen - ActivityManager is local
}
}
return agent;
@@ -1821,17 +1984,13 @@ class BackupManagerService extends IBackupManager.Stub {
int mStatus;
boolean mFinished;
- public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue,
- File journal) {
+ public PerformBackupTask(IBackupTransport transport, String dirName,
+ ArrayList<BackupRequest> queue, File journal) {
mTransport = transport;
mOriginalQueue = queue;
mJournal = journal;
- try {
- mStateDir = new File(mBaseStateDir, transport.transportDirName());
- } catch (RemoteException e) {
- // can't happen; the transport is local
- }
+ mStateDir = new File(mBaseStateDir, dirName);
mCurrentState = BackupState.INITIAL;
mFinished = false;
@@ -2077,8 +2236,12 @@ class BackupManagerService extends IBackupManager.Stub {
addBackupTrace("success; recording token");
try {
mCurrentToken = mTransport.getCurrentRestoreSet();
- } catch (RemoteException e) {} // can't happen
- writeRestoreTokens();
+ writeRestoreTokens();
+ } catch (RemoteException e) {
+ // nothing for it at this point, unfortunately, but this will be
+ // recorded the next time we fully succeed.
+ addBackupTrace("transport threw returning token");
+ }
}
// Set up the next backup pass - at this point we can set mBackupRunning
@@ -2299,7 +2462,7 @@ class BackupManagerService extends IBackupManager.Stub {
addBackupTrace("unbinding " + mCurrentPackage.packageName);
try { // unbind even on timeout, just in case
mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo);
- } catch (RemoteException e) {}
+ } catch (RemoteException e) { /* can't happen; activity manager is local */ }
}
}
@@ -2621,11 +2784,9 @@ class BackupManagerService extends IBackupManager.Stub {
// Verify that the given password matches the currently-active
// backup password, if any
- if (hasBackupPassword()) {
- if (!passwordMatchesSaved(mCurrentPassword, PBKDF2_HASH_ROUNDS)) {
- if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
- return;
- }
+ if (!backupPasswordMatches(mCurrentPassword)) {
+ if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
+ return;
}
// Write the global file header. All strings are UTF-8 encoded; lines end
@@ -2633,7 +2794,7 @@ class BackupManagerService extends IBackupManager.Stub {
// final '\n'.
//
// line 1: "ANDROID BACKUP"
- // line 2: backup file format version, currently "1"
+ // line 2: backup file format version, currently "2"
// line 3: compressed? "0" if not compressed, "1" if compressed.
// line 4: name of encryption algorithm [currently only "none" or "AES-256"]
//
@@ -2741,7 +2902,7 @@ class BackupManagerService extends IBackupManager.Stub {
OutputStream ofstream) throws Exception {
// User key will be used to encrypt the master key.
byte[] newUserSalt = randomBytes(PBKDF2_SALT_SIZE);
- SecretKey userKey = buildPasswordKey(mEncryptPassword, newUserSalt,
+ SecretKey userKey = buildPasswordKey(PBKDF_CURRENT, mEncryptPassword, newUserSalt,
PBKDF2_HASH_ROUNDS);
// the master key is random for each backup
@@ -2788,7 +2949,7 @@ class BackupManagerService extends IBackupManager.Stub {
// stated number of PBKDF2 rounds
IV = c.getIV();
byte[] mk = masterKeySpec.getEncoded();
- byte[] checksum = makeKeyChecksum(masterKeySpec.getEncoded(),
+ byte[] checksum = makeKeyChecksum(PBKDF_CURRENT, masterKeySpec.getEncoded(),
checksumSalt, PBKDF2_HASH_ROUNDS);
ByteArrayOutputStream blob = new ByteArrayOutputStream(IV.length + mk.length
@@ -3131,11 +3292,9 @@ class BackupManagerService extends IBackupManager.Stub {
FileInputStream rawInStream = null;
DataInputStream rawDataIn = null;
try {
- if (hasBackupPassword()) {
- if (!passwordMatchesSaved(mCurrentPassword, PBKDF2_HASH_ROUNDS)) {
- if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
- return;
- }
+ if (!backupPasswordMatches(mCurrentPassword)) {
+ if (DEBUG) Slog.w(TAG, "Backup password mismatch; aborting");
+ return;
}
mBytes = 0;
@@ -3156,8 +3315,12 @@ class BackupManagerService extends IBackupManager.Stub {
if (Arrays.equals(magicBytes, streamHeader)) {
// okay, header looks good. now parse out the rest of the fields.
String s = readHeaderLine(rawInStream);
- if (Integer.parseInt(s) == BACKUP_FILE_VERSION) {
- // okay, it's a version we recognize
+ final int archiveVersion = Integer.parseInt(s);
+ if (archiveVersion <= BACKUP_FILE_VERSION) {
+ // okay, it's a version we recognize. if it's version 1, we may need
+ // to try two different PBKDF2 regimes to compare checksums.
+ final boolean pbkdf2Fallback = (archiveVersion == 1);
+
s = readHeaderLine(rawInStream);
compressed = (Integer.parseInt(s) != 0);
s = readHeaderLine(rawInStream);
@@ -3165,7 +3328,8 @@ class BackupManagerService extends IBackupManager.Stub {
// no more header to parse; we're good to go
okay = true;
} else if (mDecryptPassword != null && mDecryptPassword.length() > 0) {
- preCompressStream = decodeAesHeaderAndInitialize(s, rawInStream);
+ preCompressStream = decodeAesHeaderAndInitialize(s, pbkdf2Fallback,
+ rawInStream);
if (preCompressStream != null) {
okay = true;
}
@@ -3225,7 +3389,71 @@ class BackupManagerService extends IBackupManager.Stub {
return buffer.toString();
}
- InputStream decodeAesHeaderAndInitialize(String encryptionName, InputStream rawInStream) {
+ InputStream attemptMasterKeyDecryption(String algorithm, byte[] userSalt, byte[] ckSalt,
+ int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream,
+ boolean doLog) {
+ InputStream result = null;
+
+ try {
+ Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ SecretKey userKey = buildPasswordKey(algorithm, mDecryptPassword, userSalt,
+ rounds);
+ byte[] IV = hexToByteArray(userIvHex);
+ IvParameterSpec ivSpec = new IvParameterSpec(IV);
+ c.init(Cipher.DECRYPT_MODE,
+ new SecretKeySpec(userKey.getEncoded(), "AES"),
+ ivSpec);
+ byte[] mkCipher = hexToByteArray(masterKeyBlobHex);
+ byte[] mkBlob = c.doFinal(mkCipher);
+
+ // first, the master key IV
+ int offset = 0;
+ int len = mkBlob[offset++];
+ IV = Arrays.copyOfRange(mkBlob, offset, offset + len);
+ offset += len;
+ // then the master key itself
+ len = mkBlob[offset++];
+ byte[] mk = Arrays.copyOfRange(mkBlob,
+ offset, offset + len);
+ offset += len;
+ // and finally the master key checksum hash
+ len = mkBlob[offset++];
+ byte[] mkChecksum = Arrays.copyOfRange(mkBlob,
+ offset, offset + len);
+
+ // now validate the decrypted master key against the checksum
+ byte[] calculatedCk = makeKeyChecksum(algorithm, mk, ckSalt, rounds);
+ if (Arrays.equals(calculatedCk, mkChecksum)) {
+ ivSpec = new IvParameterSpec(IV);
+ c.init(Cipher.DECRYPT_MODE,
+ new SecretKeySpec(mk, "AES"),
+ ivSpec);
+ // Only if all of the above worked properly will 'result' be assigned
+ result = new CipherInputStream(rawInStream, c);
+ } else if (doLog) Slog.w(TAG, "Incorrect password");
+ } catch (InvalidAlgorithmParameterException e) {
+ if (doLog) Slog.e(TAG, "Needed parameter spec unavailable!", e);
+ } catch (BadPaddingException e) {
+ // This case frequently occurs when the wrong password is used to decrypt
+ // the master key. Use the identical "incorrect password" log text as is
+ // used in the checksum failure log in order to avoid providing additional
+ // information to an attacker.
+ if (doLog) Slog.w(TAG, "Incorrect password");
+ } catch (IllegalBlockSizeException e) {
+ if (doLog) Slog.w(TAG, "Invalid block size in master key");
+ } catch (NoSuchAlgorithmException e) {
+ if (doLog) Slog.e(TAG, "Needed decryption algorithm unavailable!");
+ } catch (NoSuchPaddingException e) {
+ if (doLog) Slog.e(TAG, "Needed padding mechanism unavailable!");
+ } catch (InvalidKeyException e) {
+ if (doLog) Slog.w(TAG, "Illegal password; aborting");
+ }
+
+ return result;
+ }
+
+ InputStream decodeAesHeaderAndInitialize(String encryptionName, boolean pbkdf2Fallback,
+ InputStream rawInStream) {
InputStream result = null;
try {
if (encryptionName.equals(ENCRYPTION_ALGORITHM_NAME)) {
@@ -3242,59 +3470,13 @@ class BackupManagerService extends IBackupManager.Stub {
String masterKeyBlobHex = readHeaderLine(rawInStream); // 9
// decrypt the master key blob
- Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
- SecretKey userKey = buildPasswordKey(mDecryptPassword, userSalt,
- rounds);
- byte[] IV = hexToByteArray(userIvHex);
- IvParameterSpec ivSpec = new IvParameterSpec(IV);
- c.init(Cipher.DECRYPT_MODE,
- new SecretKeySpec(userKey.getEncoded(), "AES"),
- ivSpec);
- byte[] mkCipher = hexToByteArray(masterKeyBlobHex);
- byte[] mkBlob = c.doFinal(mkCipher);
-
- // first, the master key IV
- int offset = 0;
- int len = mkBlob[offset++];
- IV = Arrays.copyOfRange(mkBlob, offset, offset + len);
- offset += len;
- // then the master key itself
- len = mkBlob[offset++];
- byte[] mk = Arrays.copyOfRange(mkBlob,
- offset, offset + len);
- offset += len;
- // and finally the master key checksum hash
- len = mkBlob[offset++];
- byte[] mkChecksum = Arrays.copyOfRange(mkBlob,
- offset, offset + len);
-
- // now validate the decrypted master key against the checksum
- byte[] calculatedCk = makeKeyChecksum(mk, ckSalt, rounds);
- if (Arrays.equals(calculatedCk, mkChecksum)) {
- ivSpec = new IvParameterSpec(IV);
- c.init(Cipher.DECRYPT_MODE,
- new SecretKeySpec(mk, "AES"),
- ivSpec);
- // Only if all of the above worked properly will 'result' be assigned
- result = new CipherInputStream(rawInStream, c);
- } else Slog.w(TAG, "Incorrect password");
+ result = attemptMasterKeyDecryption(PBKDF_CURRENT, userSalt, ckSalt,
+ rounds, userIvHex, masterKeyBlobHex, rawInStream, false);
+ if (result == null && pbkdf2Fallback) {
+ result = attemptMasterKeyDecryption(PBKDF_FALLBACK, userSalt, ckSalt,
+ rounds, userIvHex, masterKeyBlobHex, rawInStream, true);
+ }
} else Slog.w(TAG, "Unsupported encryption method: " + encryptionName);
- } catch (InvalidAlgorithmParameterException e) {
- Slog.e(TAG, "Needed parameter spec unavailable!", e);
- } catch (BadPaddingException e) {
- // This case frequently occurs when the wrong password is used to decrypt
- // the master key. Use the identical "incorrect password" log text as is
- // used in the checksum failure log in order to avoid providing additional
- // information to an attacker.
- Slog.w(TAG, "Incorrect password");
- } catch (IllegalBlockSizeException e) {
- Slog.w(TAG, "Invalid block size in master key");
- } catch (NoSuchAlgorithmException e) {
- Slog.e(TAG, "Needed decryption algorithm unavailable!");
- } catch (NoSuchPaddingException e) {
- Slog.e(TAG, "Needed padding mechanism unavailable!");
- } catch (InvalidKeyException e) {
- Slog.w(TAG, "Illegal password; aborting");
} catch (NumberFormatException e) {
Slog.w(TAG, "Can't parse restore data header");
} catch (IOException e) {
@@ -4314,7 +4496,7 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
- PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer,
+ PerformRestoreTask(IBackupTransport transport, String dirName, IRestoreObserver observer,
long restoreSetToken, PackageInfo targetPackage, int pmToken,
boolean needFullBackup, String[] filterSet) {
mCurrentState = RestoreState.INITIAL;
@@ -4337,11 +4519,7 @@ class BackupManagerService extends IBackupManager.Stub {
mFilterSet = null;
}
- try {
- mStateDir = new File(mBaseStateDir, transport.transportDirName());
- } catch (RemoteException e) {
- // can't happen; the transport is local
- }
+ mStateDir = new File(mBaseStateDir, dirName);
}
// Execute one tick of whatever state machine the task implements
@@ -5067,8 +5245,8 @@ class BackupManagerService extends IBackupManager.Stub {
}
// Clear the given package's backup data from the current transport
- public void clearBackupData(String packageName) {
- if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName);
+ public void clearBackupData(String transportName, String packageName) {
+ if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName);
PackageInfo info;
try {
info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
@@ -5099,13 +5277,22 @@ class BackupManagerService extends IBackupManager.Stub {
// Is the given app an available participant?
if (apps.contains(packageName)) {
- if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
// found it; fire off the clear request
+ if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
+ mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
synchronized (mQueueLock) {
+ final IBackupTransport transport = getTransport(transportName);
+ if (transport == null) {
+ // transport is currently unavailable -- make sure to retry
+ Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR,
+ new ClearRetryParams(transportName, packageName));
+ mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL);
+ return;
+ }
long oldId = Binder.clearCallingIdentity();
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
- new ClearParams(getTransport(mCurrentTransport), info));
+ new ClearParams(transport, info));
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
}
@@ -5597,31 +5784,55 @@ class BackupManagerService extends IBackupManager.Stub {
return;
}
+ boolean skip = false;
+
long restoreSet = getAvailableRestoreToken(packageName);
if (DEBUG) Slog.v(TAG, "restoreAtInstall pkg=" + packageName
+ " token=" + Integer.toHexString(token)
+ " restoreSet=" + Long.toHexString(restoreSet));
+ if (restoreSet == 0) {
+ if (MORE_DEBUG) Slog.i(TAG, "No restore set");
+ skip = true;
+ }
- if (mAutoRestore && mProvisioned && restoreSet != 0) {
- // okay, we're going to attempt a restore of this package from this restore set.
- // The eventual message back into the Package Manager to run the post-install
- // steps for 'token' will be issued from the restore handling code.
+ // Do we have a transport to fetch data for us?
+ IBackupTransport transport = getTransport(mCurrentTransport);
+ if (transport == null) {
+ if (DEBUG) Slog.w(TAG, "No transport");
+ skip = true;
+ }
- // We can use a synthetic PackageInfo here because:
- // 1. We know it's valid, since the Package Manager supplied the name
- // 2. Only the packageName field will be used by the restore code
- PackageInfo pkg = new PackageInfo();
- pkg.packageName = packageName;
+ if (!skip && mAutoRestore && mProvisioned) {
+ try {
+ // okay, we're going to attempt a restore of this package from this restore set.
+ // The eventual message back into the Package Manager to run the post-install
+ // steps for 'token' will be issued from the restore handling code.
- mWakelock.acquire();
- Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
- msg.obj = new RestoreParams(getTransport(mCurrentTransport), null,
- restoreSet, pkg, token, true);
- mBackupHandler.sendMessage(msg);
- } else {
+ // This can throw and so *must* happen before the wakelock is acquired
+ String dirName = transport.transportDirName();
+
+ // We can use a synthetic PackageInfo here because:
+ // 1. We know it's valid, since the Package Manager supplied the name
+ // 2. Only the packageName field will be used by the restore code
+ PackageInfo pkg = new PackageInfo();
+ pkg.packageName = packageName;
+
+ mWakelock.acquire();
+ Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
+ msg.obj = new RestoreParams(transport, dirName, null,
+ restoreSet, pkg, token, true);
+ mBackupHandler.sendMessage(msg);
+ } catch (RemoteException e) {
+ // Binding to the transport broke; back off and proceed with the installation.
+ Slog.e(TAG, "Unable to contact transport");
+ skip = true;
+ }
+ }
+
+ if (skip) {
// Auto-restore disabled or no way to attempt a restore; just tell the Package
// Manager to proceed with the post-install handling for this package.
- if (DEBUG) Slog.v(TAG, "No restore set -- skipping restore");
+ if (DEBUG) Slog.v(TAG, "Skipping");
try {
mPackageManagerBinder.finishPackageInstall(token);
} catch (RemoteException e) { /* can't happen */ }
@@ -5774,13 +5985,23 @@ class BackupManagerService extends IBackupManager.Stub {
return -1;
}
+ String dirName;
+ try {
+ dirName = mRestoreTransport.transportDirName();
+ } catch (RemoteException e) {
+ // Transport went AWOL; fail.
+ Slog.e(TAG, "Unable to contact transport for restore");
+ return -1;
+ }
+
synchronized (mQueueLock) {
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
long oldId = Binder.clearCallingIdentity();
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
- msg.obj = new RestoreParams(mRestoreTransport, observer, token, true);
+ msg.obj = new RestoreParams(mRestoreTransport, dirName,
+ observer, token, true);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;
@@ -5834,13 +6055,22 @@ class BackupManagerService extends IBackupManager.Stub {
return -1;
}
+ String dirName;
+ try {
+ dirName = mRestoreTransport.transportDirName();
+ } catch (RemoteException e) {
+ // Transport went AWOL; fail.
+ Slog.e(TAG, "Unable to contact transport for restore");
+ return -1;
+ }
+
synchronized (mQueueLock) {
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
long oldId = Binder.clearCallingIdentity();
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
- msg.obj = new RestoreParams(mRestoreTransport, observer, token,
+ msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, token,
packages, true);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
@@ -5906,11 +6136,21 @@ class BackupManagerService extends IBackupManager.Stub {
return -1;
}
+ String dirName;
+ try {
+ dirName = mRestoreTransport.transportDirName();
+ } catch (RemoteException e) {
+ // Transport went AWOL; fail.
+ Slog.e(TAG, "Unable to contact transport for restore");
+ return -1;
+ }
+
// Ready to go: enqueue the restore request and claim success
long oldId = Binder.clearCallingIdentity();
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
- msg.obj = new RestoreParams(mRestoreTransport, observer, token, app, 0, false);
+ msg.obj = new RestoreParams(mRestoreTransport, dirName,
+ observer, token, app, 0, false);
mBackupHandler.sendMessage(msg);
Binder.restoreCallingIdentity(oldId);
return 0;
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 7c61c44..41a0e6b 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -32,6 +32,7 @@ import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -43,7 +44,9 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -77,6 +80,7 @@ import android.net.wifi.WifiStateTracker;
import android.net.wimax.WimaxManagerConstants;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Build;
import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
@@ -141,6 +145,7 @@ import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URL;
+import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -154,6 +159,10 @@ import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+
/**
* @hide
*/
@@ -406,6 +415,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private SettingsObserver mSettingsObserver;
+ private AppOpsManager mAppOpsManager;
+
NetworkConfig[] mNetConfigs;
int mNetworksDefined;
@@ -689,6 +700,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
filter = new IntentFilter();
filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
mContext.registerReceiver(mProvisioningReceiver, filter);
+
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
}
/**
@@ -1521,6 +1534,40 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
/**
+ * Check if the address falls into any of currently running VPN's route's.
+ */
+ private boolean isAddressUnderVpn(InetAddress address) {
+ synchronized (mVpns) {
+ synchronized (mRoutesLock) {
+ int uid = UserHandle.getCallingUserId();
+ Vpn vpn = mVpns.get(uid);
+ if (vpn == null) {
+ return false;
+ }
+
+ // Check if an exemption exists for this address.
+ for (LinkAddress destination : mExemptAddresses) {
+ if (!NetworkUtils.addressTypeMatches(address, destination.getAddress())) {
+ continue;
+ }
+
+ int prefix = destination.getNetworkPrefixLength();
+ InetAddress addrMasked = NetworkUtils.getNetworkPart(address, prefix);
+ InetAddress destMasked = NetworkUtils.getNetworkPart(destination.getAddress(),
+ prefix);
+
+ if (addrMasked.equals(destMasked)) {
+ return false;
+ }
+ }
+
+ // Finally check if the address is covered by the VPN.
+ return vpn.isAddressCovered(address);
+ }
+ }
+ }
+
+ /**
* @deprecated use requestRouteToHostAddress instead
*
* Ensure that a network route exists to deliver traffic to the specified
@@ -1531,14 +1578,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* desired
* @return {@code true} on success, {@code false} on failure
*/
- public boolean requestRouteToHost(int networkType, int hostAddress) {
+ public boolean requestRouteToHost(int networkType, int hostAddress, String packageName) {
InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
if (inetAddress == null) {
return false;
}
- return requestRouteToHostAddress(networkType, inetAddress.getAddress());
+ return requestRouteToHostAddress(networkType, inetAddress.getAddress(), packageName);
}
/**
@@ -1550,11 +1597,40 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* desired
* @return {@code true} on success, {@code false} on failure
*/
- public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
+ public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress,
+ String packageName) {
enforceChangePermission();
if (mProtectedNetworks.contains(networkType)) {
enforceConnectivityInternalPermission();
}
+ boolean exempt;
+ InetAddress addr;
+ try {
+ addr = InetAddress.getByAddress(hostAddress);
+ } catch (UnknownHostException e) {
+ if (DBG) log("requestRouteToHostAddress got " + e.toString());
+ return false;
+ }
+ // System apps may request routes bypassing the VPN to keep other networks working.
+ if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+ exempt = true;
+ } else {
+ mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
+ try {
+ ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(packageName,
+ 0);
+ exempt = (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Failed to find calling package details", e);
+ }
+ }
+
+ // Non-exempt routeToHost's can only be added if the host is not covered by the VPN.
+ // This can be either because the VPN's routes do not cover the destination or a
+ // system application added an exemption that covers this destination.
+ if (!exempt && isAddressUnderVpn(addr)) {
+ return false;
+ }
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
@@ -1578,18 +1654,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
final long token = Binder.clearCallingIdentity();
try {
- InetAddress addr = InetAddress.getByAddress(hostAddress);
LinkProperties lp = tracker.getLinkProperties();
- boolean ok = addRouteToAddress(lp, addr, EXEMPT);
+ boolean ok = addRouteToAddress(lp, addr, exempt);
if (DBG) log("requestRouteToHostAddress ok=" + ok);
return ok;
- } catch (UnknownHostException e) {
- if (DBG) log("requestRouteToHostAddress got " + e.toString());
} finally {
Binder.restoreCallingIdentity(token);
}
- if (DBG) log("requestRouteToHostAddress X bottom return false");
- return false;
}
private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
@@ -2299,9 +2370,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mInetConditionChangeInFlight = false;
// Don't do this - if we never sign in stay, grey
//reportNetworkCondition(mActiveDefaultNetwork, 100);
+ updateNetworkSettings(thisNet);
}
thisNet.setTeardownRequested(false);
- updateNetworkSettings(thisNet);
updateMtuSizeSettings(thisNet);
handleConnectivityChange(newNetType, false);
sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
@@ -2686,6 +2757,15 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
setBufferSize(bufferSizes);
}
+
+ final String defaultRwndKey = "net.tcp.default_init_rwnd";
+ int defaultRwndValue = SystemProperties.getInt(defaultRwndKey, 0);
+ Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.TCP_DEFAULT_INIT_RWND, defaultRwndValue);
+ final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd";
+ if (rwndValue != 0) {
+ SystemProperties.set(sysctlKey, rwndValue.toString());
+ }
}
/**
@@ -3028,7 +3108,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
info = (NetworkInfo) msg.obj;
int type = info.getType();
- updateNetworkSettings(mNetTrackers[type]);
+ if (mNetConfigs[type].isDefault()) updateNetworkSettings(mNetTrackers[type]);
break;
}
}
@@ -3374,6 +3454,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
String pacFileUrl = "";
if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
!TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) {
+ if (!proxyProperties.isValid()) {
+ if (DBG)
+ log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+ return;
+ }
mGlobalProxy = new ProxyProperties(proxyProperties);
host = mGlobalProxy.getHost();
port = mGlobalProxy.getPort();
@@ -3417,6 +3502,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} else {
proxyProperties = new ProxyProperties(host, port, exclList);
}
+ if (!proxyProperties.isValid()) {
+ if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+ return;
+ }
+
synchronized (mProxyLock) {
mGlobalProxy = proxyProperties;
}
@@ -3441,6 +3531,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
synchronized (mProxyLock) {
if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
if (mDefaultProxy == proxy) return; // catches repeated nulls
+ if (proxy != null && !proxy.isValid()) {
+ if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString());
+ return;
+ }
mDefaultProxy = proxy;
if (mGlobalProxy != null) return;
@@ -3573,8 +3667,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
int user = UserHandle.getUserId(Binder.getCallingUid());
if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
synchronized(mVpns) {
- mVpns.get(user).protect(socket,
- mNetTrackers[type].getLinkProperties().getInterfaceName());
+ mVpns.get(user).protect(socket);
}
return true;
}
@@ -3814,7 +3907,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
boolean forwardDns) {
try {
mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
- if (forwardDns) mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd);
+ if (forwardDns) mNetd.clearDnsInterfaceForUidRange(interfaze, uidStart, uidEnd);
} catch (RemoteException e) {
}
@@ -3969,6 +4062,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
private static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5;
+ /**
+ * The mobile network is provisioning
+ */
+ private static final int CMP_RESULT_CODE_IS_PROVISIONING = 6;
+
+ private AtomicBoolean mIsProvisioningNetwork = new AtomicBoolean(false);
+ private AtomicBoolean mIsStartingProvisioning = new AtomicBoolean(false);
+
private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false);
@Override
@@ -4039,11 +4140,25 @@ public class ConnectivityService extends IConnectivityManager.Stub {
setProvNotificationVisible(true,
ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(),
url);
+ // Mark that we've got a provisioning network and
+ // Disable Mobile Data until user actually starts provisioning.
+ mIsProvisioningNetwork.set(true);
+ MobileDataStateTracker mdst = (MobileDataStateTracker)
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ mdst.setInternalDataEnable(false);
} else {
if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url");
}
break;
}
+ case CMP_RESULT_CODE_IS_PROVISIONING: {
+ // FIXME: Need to know when provisioning is done. Probably we can
+ // check the completion status if successful we're done if we
+ // "timedout" or still connected to provisioning APN turn off data?
+ if (DBG) log("CheckMp.onComplete: provisioning started");
+ mIsStartingProvisioning.set(false);
+ break;
+ }
default: {
loge("CheckMp.onComplete: ignore unexpected result=" + result);
break;
@@ -4066,8 +4181,28 @@ public class ConnectivityService extends IConnectivityManager.Stub {
static class CheckMp extends
AsyncTask<CheckMp.Params, Void, Integer> {
private static final String CHECKMP_TAG = "CheckMp";
+
+ // adb shell setprop persist.checkmp.testfailures 1 to enable testing failures
+ private static boolean mTestingFailures;
+
+ // Choosing 4 loops as half of them will use HTTPS and the other half HTTP
+ private static final int MAX_LOOPS = 4;
+
+ // Number of milli-seconds to complete all of the retires
public static final int MAX_TIMEOUT_MS = 60000;
+
+ // The socket should retry only 5 seconds, the default is longer
private static final int SOCKET_TIMEOUT_MS = 5000;
+
+ // Sleep time for network errors
+ private static final int NET_ERROR_SLEEP_SEC = 3;
+
+ // Sleep time for network route establishment
+ private static final int NET_ROUTE_ESTABLISHMENT_SLEEP_SEC = 3;
+
+ // Short sleep time for polling :(
+ private static final int POLLING_SLEEP_SEC = 1;
+
private Context mContext;
private ConnectivityService mCs;
private TelephonyManager mTm;
@@ -4093,6 +4228,31 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
+ // As explained to me by Brian Carlstrom and Kenny Root, Certificates can be
+ // issued by name or ip address, for Google its by name so when we construct
+ // this HostnameVerifier we'll pass the original Uri and use it to verify
+ // the host. If the host name in the original uril fails we'll test the
+ // hostname parameter just incase things change.
+ static class CheckMpHostnameVerifier implements HostnameVerifier {
+ Uri mOrgUri;
+
+ CheckMpHostnameVerifier(Uri orgUri) {
+ mOrgUri = orgUri;
+ }
+
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
+ String orgUriHost = mOrgUri.getHost();
+ boolean retVal = hv.verify(orgUriHost, session) || hv.verify(hostname, session);
+ if (DBG) {
+ log("isMobileOk: hostnameVerify retVal=" + retVal + " hostname=" + hostname
+ + " orgUriHost=" + orgUriHost);
+ }
+ return retVal;
+ }
+ }
+
/**
* The call back object passed in Params. onComplete will be called
* on the main thread.
@@ -4103,6 +4263,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
public CheckMp(Context context, ConnectivityService cs) {
+ if (Build.IS_DEBUGGABLE) {
+ mTestingFailures =
+ SystemProperties.getInt("persist.checkmp.testfailures", 0) == 1;
+ } else {
+ mTestingFailures = false;
+ }
+
mContext = context;
mCs = cs;
@@ -4141,6 +4308,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return result;
}
+ if (mCs.mIsStartingProvisioning.get()) {
+ result = CMP_RESULT_CODE_IS_PROVISIONING;
+ log("isMobileOk: X is provisioning result=" + result);
+ return result;
+ }
+
// See if we've already determined we've got a provisioning connection,
// if so we don't need to do anything active.
MobileDataStateTracker mdstDefault = (MobileDataStateTracker)
@@ -4174,7 +4347,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
break;
}
- sleep(1);
+ sleep(POLLING_SLEEP_SEC);
}
}
@@ -4192,7 +4365,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
if (VDBG) log("isMobileOk: hipri not started yet");
result = CMP_RESULT_CODE_NO_CONNECTION;
- sleep(1);
+ sleep(POLLING_SLEEP_SEC);
}
// Continue trying to connect until time has run out
@@ -4208,7 +4381,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
log("isMobileOk: not connected ni=" +
mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
}
- sleep(1);
+ sleep(POLLING_SLEEP_SEC);
result = CMP_RESULT_CODE_NO_CONNECTION;
continue;
}
@@ -4226,7 +4399,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// Get of the addresses associated with the url host. We need to use the
// address otherwise HttpURLConnection object will use the name to get
- // the addresses and is will try every address but that will bypass the
+ // the addresses and will try every address but that will bypass the
// route to host we setup and the connection could succeed as the default
// interface might be connected to the internet via wifi or other interface.
InetAddress[] addresses;
@@ -4263,14 +4436,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
int addrTried = 0;
while (true) {
- // Loop through at most 3 valid addresses or until
+ // Loop through at most MAX_LOOPS valid addresses or until
// we run out of time
- if (addrTried++ >= 3) {
- log("too many loops tried - giving up");
+ if (addrTried++ >= MAX_LOOPS) {
+ log("isMobileOk: too many loops tried - giving up");
break;
}
if (SystemClock.elapsedRealtime() >= endTime) {
- log("spend too much time - giving up");
+ log("isMobileOk: spend too much time - giving up");
break;
}
@@ -4279,29 +4452,41 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// Make a route to host so we check the specific interface.
if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
- hostAddr.getAddress())) {
+ hostAddr.getAddress(), null)) {
// Wait a short time to be sure the route is established ??
log("isMobileOk:"
+ " wait to establish route to hostAddr=" + hostAddr);
- sleep(3);
+ sleep(NET_ROUTE_ESTABLISHMENT_SLEEP_SEC);
} else {
log("isMobileOk:"
+ " could not establish route to hostAddr=" + hostAddr);
+ // Wait a short time before the next attempt
+ sleep(NET_ERROR_SLEEP_SEC);
continue;
}
- // Rewrite the url to have numeric address to use the specific route.
- // Add a pointless random query param to fool proxies into not caching.
- URL newUrl = new URL(orgUri.getScheme(),
- hostAddr.getHostAddress(),
- orgUri.getPath() + "?q=" + rand.nextInt(Integer.MAX_VALUE));
+ // Rewrite the url to have numeric address to use the specific route
+ // using http for half the attempts and https for the other half.
+ // Doing https first and http second as on a redirected walled garden
+ // such as t-mobile uses we get a SocketTimeoutException: "SSL
+ // handshake timed out" which we declare as
+ // CMP_RESULT_CODE_NO_TCP_CONNECTION. We could change this, but by
+ // having http second we will be using logic used for some time.
+ URL newUrl;
+ String scheme = (addrTried <= (MAX_LOOPS/2)) ? "https" : "http";
+ newUrl = new URL(scheme, hostAddr.getHostAddress(),
+ orgUri.getPath());
log("isMobileOk: newUrl=" + newUrl);
HttpURLConnection urlConn = null;
try {
- // Open the connection set the request header and get the response
- urlConn = (HttpURLConnection) newUrl.openConnection(
+ // Open the connection set the request headers and get the response
+ urlConn = (HttpURLConnection)newUrl.openConnection(
java.net.Proxy.NO_PROXY);
+ if (scheme.equals("https")) {
+ ((HttpsURLConnection)urlConn).setHostnameVerifier(
+ new CheckMpHostnameVerifier(orgUri));
+ }
urlConn.setInstanceFollowRedirects(false);
urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConn.setReadTimeout(SOCKET_TIMEOUT_MS);
@@ -4320,10 +4505,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
urlConn.disconnect();
urlConn = null;
+ if (mTestingFailures) {
+ // Pretend no connection, this tests using http and https
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ log("isMobileOk: TESTING_FAILURES, pretend no connction");
+ continue;
+ }
+
if (responseCode == 204) {
// Return
result = CMP_RESULT_CODE_CONNECTABLE;
- log("isMobileOk: X expected responseCode=" + responseCode
+ log("isMobileOk: X got expected responseCode=" + responseCode
+ " result=" + result);
return result;
} else {
@@ -4337,12 +4529,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
result = CMP_RESULT_CODE_REDIRECTED;
}
} catch (Exception e) {
- log("isMobileOk: HttpURLConnection Exception e=" + e);
+ log("isMobileOk: HttpURLConnection Exception" + e);
result = CMP_RESULT_CODE_NO_TCP_CONNECTION;
if (urlConn != null) {
urlConn.disconnect();
urlConn = null;
}
+ sleep(NET_ERROR_SLEEP_SEC);
+ continue;
}
}
log("isMobileOk: X loops|timed out result=" + result);
@@ -4370,7 +4564,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
log("isMobileOk: connected ni=" +
mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
}
- sleep(1);
+ sleep(POLLING_SLEEP_SEC);
continue;
}
}
@@ -4435,7 +4629,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- private void log(String s) {
+ private static void log(String s) {
Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
}
}
@@ -4454,19 +4648,20 @@ public class ConnectivityService extends IConnectivityManager.Stub {
};
private void handleMobileProvisioningAction(String url) {
- // Notication mark notification as not visible
+ // Mark notification as not visible
setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
// If provisioning network handle as a special case,
// otherwise launch browser with the intent directly.
- NetworkInfo ni = getProvisioningNetworkInfo();
- if ((ni != null) && ni.isConnectedToProvisioningNetwork()) {
- if (DBG) log("handleMobileProvisioningAction: on provisioning network");
+ if (mIsProvisioningNetwork.get()) {
+ if (DBG) log("handleMobileProvisioningAction: on prov network enable then launch");
+ mIsStartingProvisioning.set(true);
MobileDataStateTracker mdst = (MobileDataStateTracker)
mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ mdst.setEnableFailFastMobileData(DctConstants.ENABLED);
mdst.enableMobileProvisioning(url);
} else {
- if (DBG) log("handleMobileProvisioningAction: on default network");
+ if (DBG) log("handleMobileProvisioningAction: not prov network, launch browser directly");
Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
Intent.CATEGORY_APP_BROWSER);
newIntent.setData(Uri.parse(url));
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 8cc80f7..2bb99d6 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -56,6 +56,7 @@ import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.net.ProxyProperties;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -522,6 +523,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|| 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
@@ -2463,6 +2465,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
exclusionList = exclusionList.trim();
ContentResolver res = mContext.getContentResolver();
+
+ ProxyProperties proxyProperties = new ProxyProperties(data[0], proxyPort, exclusionList);
+ if (!proxyProperties.isValid()) {
+ Slog.e(TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString());
+ return;
+ }
Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]);
Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort);
Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 8eaa91d..399e7d1 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -123,6 +123,18 @@ option java_package com.android.server
# ---------------------------
# Out of memory for surfaces.
31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3)
+# Task created.
+31001 wm_task_created (TaskId|1|5),(StackId|1|5)
+# Task moved to top (1) or bottom (0).
+31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1)
+# Task removed with source explanation.
+31003 wm_task_removed (TaskId|1|5),(Reason|3)
+# Stack created.
+31004 wm_stack_created (StackId|1|5),(RelativeBoxId|1|5),(Position|1),(Weight|1|6)
+# Home stack moved to top (1) or bottom (0).
+31005 wm_home_stack_moved (ToTop|1)
+# Stack removed.
+31006 wm_stack_removed (StackId|1|5)
# ---------------------------
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 562a50f..a996dbd 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -309,6 +309,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mShortcutInputMethodsAndSubtypes =
new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
+ // Was the keyguard locked when this client became current?
+ private boolean mCurClientInKeyguard;
+
/**
* Set to true if our ServiceConnection is currently actively bound to
* a service (whether or not we have gotten its IBinder back yet).
@@ -385,7 +388,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private Locale mLastSystemLocale;
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
private final IPackageManager mIPackageManager;
- private boolean mInputBoundToKeyguard;
class SettingsObserver extends ContentObserver {
String mLastEnabled = "";
@@ -874,12 +876,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final boolean hardKeyShown = haveHardKeyboard
&& conf.hardKeyboardHidden
!= Configuration.HARDKEYBOARDHIDDEN_YES;
- final boolean isScreenLocked =
- mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
- final boolean isScreenSecurelyLocked =
- isScreenLocked && mKeyguardManager.isKeyguardSecure();
- final boolean inputShown = mInputShown && (!isScreenLocked || mInputBoundToKeyguard);
- final boolean inputActive = !isScreenSecurelyLocked && (inputShown || hardKeyShown);
+
+ final boolean isScreenLocked = isKeyguardLocked();
+ final boolean inputActive = !isScreenLocked && (mInputShown || hardKeyShown);
// We assume the softkeyboard is shown when the input is active as long as the
// hard keyboard is not shown.
final boolean inputVisible = inputActive && !hardKeyShown;
@@ -1135,19 +1134,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return mNoBinding;
}
- if (mCurClient == null) {
- mInputBoundToKeyguard = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
- if (DEBUG) {
- Slog.v(TAG, "New bind. keyguard = " + mInputBoundToKeyguard);
- }
- }
-
if (mCurClient != cs) {
+ // Was the keyguard locked when switching over to the new client?
+ mCurClientInKeyguard = isKeyguardLocked();
// If the client is changing, we need to switch over to the new
// one.
unbindCurrentClientLocked();
if (DEBUG) Slog.v(TAG, "switching to client: client = "
- + cs.client.asBinder());
+ + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
// If the screen is on, inform the new client it is active
if (mScreenOn) {
@@ -1499,6 +1493,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ private boolean isKeyguardLocked() {
+ return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+ }
+
// Caution! This method is called in this class. Handle multi-user carefully
@SuppressWarnings("deprecation")
@Override
@@ -1510,8 +1508,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
return;
}
-
synchronized (mMethodMap) {
+ // apply policy for binder calls
+ if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
+ vis = 0;
+ }
mImeWindowVis = vis;
mBackDisposition = backDisposition;
if (mStatusBar != null) {
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 8f480dd..eebd1c5 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -1148,6 +1148,11 @@ public class LocationManagerService extends ILocationManager.Stub {
boolean shouldBeEnabled = isAllowedByCurrentUserSettingsLocked(name);
if (isEnabled && !shouldBeEnabled) {
updateProviderListenersLocked(name, false, mCurrentUserId);
+ // If any provider has been disabled, clear all last locations for all providers.
+ // This is to be on the safe side in case a provider has location derived from
+ // this disabled provider.
+ mLastLocation.clear();
+ mLastLocationCoarseInterval.clear();
changesMade = true;
} else if (!isEnabled && shouldBeEnabled) {
updateProviderListenersLocked(name, true, mCurrentUserId);
diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java
index cd746cf..35e7afa 100644
--- a/services/java/com/android/server/LockSettingsService.java
+++ b/services/java/com/android/server/LockSettingsService.java
@@ -154,11 +154,11 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private final void checkWritePermission(int userId) {
- mContext.checkCallingOrSelfPermission(PERMISSION);
+ mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
}
private final void checkPasswordReadPermission(int userId) {
- mContext.checkCallingOrSelfPermission(PERMISSION);
+ mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
}
private final void checkReadPermission(String requestedKey, int userId) {
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 92f99c2..72fce62 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -1567,10 +1567,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void clearDnsInterfaceForUidRange(int uid_start, int uid_end) {
+ public void clearDnsInterfaceForUidRange(String iface, int uid_start, int uid_end) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
- mConnector.execute("resolver", "clearifaceforuidrange", uid_start, uid_end);
+ mConnector.execute("resolver", "clearifaceforuidrange", iface, uid_start, uid_end);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 0438675..dedc9bd 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -260,10 +260,11 @@ public class NotificationManagerService extends INotificationManager.Stub
@Override
public void binderDied() {
- if (connection == null) {
- // This is not a service; it won't be recreated. We can give up this connection.
- unregisterListener(this.listener, this.userid);
- }
+ // Remove the listener, but don't unbind from the service. The system will bring the
+ // service back up, and the onServiceConnected handler will readd the listener with the
+ // new binding. If this isn't a bound service, and is just a registered
+ // INotificationListener, just removing it from the list is all we need to do anyway.
+ removeListenerImpl(this.listener, this.userid);
}
/** convenience method for looking in mEnabledListenersForCurrentUser */
@@ -757,26 +758,36 @@ public class NotificationManagerService extends INotificationManager.Stub
}
/**
- * Remove a listener binder directly
+ * Removes a listener from the list and unbinds from its service.
*/
- @Override
- public void unregisterListener(INotificationListener listener, int userid) {
- // no need to check permissions; if your listener binder is in the list,
- // that's proof that you had permission to add it in the first place
+ public void unregisterListener(final INotificationListener listener, final int userid) {
+ if (listener == null) return;
+
+ NotificationListenerInfo info = removeListenerImpl(listener, userid);
+ if (info != null && info.connection != null) {
+ mContext.unbindService(info.connection);
+ }
+ }
+ /**
+ * Removes a listener from the list but does not unbind from the listener's service.
+ *
+ * @return the removed listener.
+ */
+ NotificationListenerInfo removeListenerImpl(
+ final INotificationListener listener, final int userid) {
+ NotificationListenerInfo listenerInfo = null;
synchronized (mNotificationList) {
final int N = mListeners.size();
for (int i=N-1; i>=0; i--) {
final NotificationListenerInfo info = mListeners.get(i);
if (info.listener.asBinder() == listener.asBinder()
&& info.userid == userid) {
- mListeners.remove(i);
- if (info.connection != null) {
- mContext.unbindService(info.connection);
- }
+ listenerInfo = mListeners.remove(i);
}
}
}
+ return listenerInfo;
}
/**
diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java
index e0f415b..16d2468 100644
--- a/services/java/com/android/server/NsdService.java
+++ b/services/java/com/android/server/NsdService.java
@@ -483,10 +483,14 @@ public class NsdService extends INsdManager.Stub {
clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
stopResolveService(id);
- if (!getAddrInfo(id, cooked[3])) {
+ removeRequestMap(clientId, id, clientInfo);
+
+ int id2 = getUniqueId();
+ if (getAddrInfo(id2, cooked[3])) {
+ storeRequestMap(clientId, id2, clientInfo);
+ } else {
clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
NsdManager.FAILURE_INTERNAL_ERROR, clientId);
- removeRequestMap(clientId, id, clientInfo);
clientInfo.mResolvedService = null;
}
break;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0e0f156..a42cbcf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -55,6 +55,7 @@ import com.android.server.content.ContentService;
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.input.InputManagerService;
+import com.android.server.media.MediaRouterService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.os.SchedulingPolicyService;
@@ -356,6 +357,7 @@ class ServerThread {
DreamManagerService dreamy = null;
AssetAtlasService atlas = null;
PrintManagerService printManager = null;
+ MediaRouterService mediaRouter = null;
// Bring up services needed for UI.
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
@@ -804,6 +806,16 @@ class ServerThread {
} catch (Throwable e) {
reportWtf("starting Print Service", e);
}
+
+ if (!disableNonCoreServices) {
+ try {
+ Slog.i(TAG, "Media Router Service");
+ mediaRouter = new MediaRouterService(context);
+ ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
+ } catch (Throwable e) {
+ reportWtf("starting MediaRouterService", e);
+ }
+ }
}
// Before things start rolling, be sure we have decided whether
@@ -916,6 +928,7 @@ class ServerThread {
final InputManagerService inputManagerF = inputManager;
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final PrintManagerService printManagerF = printManager;
+ final MediaRouterService mediaRouterF = mediaRouter;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -1063,6 +1076,12 @@ class ServerThread {
} catch (Throwable e) {
reportWtf("Notifying PrintManagerService running", e);
}
+
+ try {
+ if (mediaRouterF != null) mediaRouterF.systemRunning();
+ } catch (Throwable e) {
+ reportWtf("Notifying MediaRouterService running", e);
+ }
}
});
@@ -1104,6 +1123,19 @@ public class SystemServer {
private static native void nativeInit();
public static void main(String[] args) {
+
+ /*
+ * In case the runtime switched since last boot (such as when
+ * the old runtime was removed in an OTA), set the system
+ * property so that it is in sync. We can't do this in
+ * libnativehelper's JniInvocation::Init code where we already
+ * had to fallback to a different runtime because it is
+ * running as root and we need to be the system user to set
+ * the property. http://b/11463182
+ */
+ SystemProperties.set("persist.sys.dalvik.vm.lib",
+ VMRuntime.getRuntime().vmLibrary());
+
if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
// If a device's clock is before 1970 (before 0), a lot of
// APIs crash dealing with negative numbers, notably
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index 6957bac..e6b6b93 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -40,6 +40,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.graphics.Point;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -637,6 +638,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
return false;
}
+ private Point getDefaultDisplaySize() {
+ Point p = new Point();
+ WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ Display d = wm.getDefaultDisplay();
+ d.getRealSize(p);
+ return p;
+ }
+
public void setDimensionHints(int width, int height) throws RemoteException {
checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
synchronized (mLock) {
@@ -648,6 +657,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
+ // Make sure it is at least as large as the display.
+ Point displaySize = getDefaultDisplaySize();
+ width = Math.max(width, displaySize.x);
+ height = Math.max(height, displaySize.y);
if (width != wallpaper.width || height != wallpaper.height) {
wallpaper.width = width;
@@ -1146,9 +1159,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
}
// We always want to have some reasonable width hint.
- WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
- Display d = wm.getDefaultDisplay();
- int baseSize = d.getMaximumSizeDimension();
+ int baseSize = getMaximumSizeDimension();
if (wallpaper.width < baseSize) {
wallpaper.width = baseSize;
}
@@ -1157,6 +1168,12 @@ class WallpaperManagerService extends IWallpaperManager.Stub {
}
}
+ private int getMaximumSizeDimension() {
+ WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ Display d = wm.getDefaultDisplay();
+ return d.getMaximumSizeDimension();
+ }
+
// Called by SystemBackupAgent after files are restored to disk.
void settingsRestored() {
// TODO: If necessary, make it work for secondary users as well. This currently assumes
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index a99b58a..43f12eb 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -76,7 +76,7 @@ class TouchExplorer implements EventStreamTransformation {
private static final int STATE_DELEGATING = 0x00000004;
private static final int STATE_GESTURE_DETECTING = 0x00000005;
- // The minimum of the cosine between the vectors of two moving
+ // The maximum of the cosine between the vectors of two moving
// pointers so they can be considered moving in the same direction.
private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
@@ -436,13 +436,19 @@ class TouchExplorer implements EventStreamTransformation {
final int pointerIdBits = (1 << pointerId);
mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits,
policyFlags);
+ } else {
+ // Cache the event until we discern exploration from gesturing.
+ mSendHoverEnterAndMoveDelayed.addEvent(event);
}
- // Cache the event until we discern exploration from gesturing.
- mSendHoverEnterAndMoveDelayed.addEvent(event);
}
} break;
case MotionEvent.ACTION_POINTER_DOWN: {
- /* do nothing - let the code for ACTION_MOVE decide what to do */
+ // Another finger down means that if we have not started to deliver
+ // hover events, we will not have to. The code for ACTION_MOVE will
+ // decide what we will actually do next.
+ mSendHoverEnterAndMoveDelayed.cancel();
+ mSendHoverExitDelayed.cancel();
+ mPerformLongPressDelayed.cancel();
} break;
case MotionEvent.ACTION_MOVE: {
final int pointerId = receivedTracker.getPrimaryPointerId();
@@ -518,14 +524,11 @@ class TouchExplorer implements EventStreamTransformation {
mPerformLongPressDelayed.cancel();
}
}
- // The user is either double tapping or performing a long
- // press, so do not send move events yet.
- if (mDoubleTapDetector.firstTapDetected()) {
- break;
+ if (mTouchExplorationInProgress) {
+ sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
+ policyFlags);
}
- sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
- policyFlags);
}
} break;
case 2: {
@@ -539,22 +542,24 @@ class TouchExplorer implements EventStreamTransformation {
mPerformLongPressDelayed.cancel();
} else {
mPerformLongPressDelayed.cancel();
- // If the user is touch exploring the second pointer may be
- // performing a double tap to activate an item without need
- // for the user to lift his exploring finger.
- // It is *important* to use the distance traveled by the pointers
- // on the screen which may or may not be magnified.
- final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
- - rawEvent.getX(pointerIndex);
- final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
- - rawEvent.getY(pointerIndex);
- final double moveDelta = Math.hypot(deltaX, deltaY);
- if (moveDelta < mDoubleTapSlop) {
- break;
+ if (mTouchExplorationInProgress) {
+ // If the user is touch exploring the second pointer may be
+ // performing a double tap to activate an item without need
+ // for the user to lift his exploring finger.
+ // It is *important* to use the distance traveled by the pointers
+ // on the screen which may or may not be magnified.
+ final float deltaX = receivedTracker.getReceivedPointerDownX(
+ pointerId) - rawEvent.getX(pointerIndex);
+ final float deltaY = receivedTracker.getReceivedPointerDownY(
+ pointerId) - rawEvent.getY(pointerIndex);
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ if (moveDelta < mDoubleTapSlop) {
+ break;
+ }
+ // We are sending events so send exit and gesture
+ // end since we transition to another state.
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
- // We are sending events so send exit and gesture
- // end since we transition to another state.
- sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
}
// We know that a new state transition is to happen and the
@@ -736,20 +741,34 @@ class TouchExplorer implements EventStreamTransformation {
+ "there is at least one pointer down!");
}
case MotionEvent.ACTION_UP: {
- mAms.onTouchInteractionEnd();
+ // Offset the event if we are doing a long press as the
+ // target is not necessarily under the user's finger.
+ if (mLongPressingPointerId >= 0) {
+ event = offsetEvent(event, - mLongPressingPointerDeltaX,
+ - mLongPressingPointerDeltaY);
+ // Clear the long press state.
+ mLongPressingPointerId = -1;
+ mLongPressingPointerDeltaX = 0;
+ mLongPressingPointerDeltaY = 0;
+ }
+
+ // Deliver the event.
+ sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+
// Announce the end of a the touch interaction.
+ mAms.onTouchInteractionEnd();
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
- mLongPressingPointerId = -1;
- mLongPressingPointerDeltaX = 0;
- mLongPressingPointerDeltaY = 0;
+
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
case MotionEvent.ACTION_CANCEL: {
clear(event, policyFlags);
} break;
+ default: {
+ // Deliver the event.
+ sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
+ }
}
- // Deliver the event.
- sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
}
private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
@@ -959,27 +978,8 @@ class TouchExplorer implements EventStreamTransformation {
// long press it, or even worse to avoid the user long pressing
// on the wrong item since click and long press behave differently.
if (mLongPressingPointerId >= 0) {
- final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
- final int pointerCount = event.getPointerCount();
- PointerProperties[] props = PointerProperties.createArray(pointerCount);
- PointerCoords[] coords = PointerCoords.createArray(pointerCount);
- for (int i = 0; i < pointerCount; i++) {
- event.getPointerProperties(i, props[i]);
- event.getPointerCoords(i, coords[i]);
- if (i == remappedIndex) {
- coords[i].x -= mLongPressingPointerDeltaX;
- coords[i].y -= mLongPressingPointerDeltaY;
- }
- }
- MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
- event.getEventTime(), event.getAction(), event.getPointerCount(),
- props, coords, event.getMetaState(), event.getButtonState(),
- 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
- event.getSource(), event.getFlags());
- if (event != prototype) {
- event.recycle();
- }
- event = remapped;
+ event = offsetEvent(event, - mLongPressingPointerDeltaX,
+ - mLongPressingPointerDeltaY);
}
if (DEBUG) {
@@ -1004,6 +1004,39 @@ class TouchExplorer implements EventStreamTransformation {
}
/**
+ * Offsets all pointers in the given event by adding the specified X and Y
+ * offsets.
+ *
+ * @param event The event to offset.
+ * @param offsetX The X offset.
+ * @param offsetY The Y offset.
+ * @return An event with the offset pointers or the original event if both
+ * offsets are zero.
+ */
+ private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) {
+ if (offsetX == 0 && offsetY == 0) {
+ return event;
+ }
+ final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
+ final int pointerCount = event.getPointerCount();
+ PointerProperties[] props = PointerProperties.createArray(pointerCount);
+ PointerCoords[] coords = PointerCoords.createArray(pointerCount);
+ for (int i = 0; i < pointerCount; i++) {
+ event.getPointerProperties(i, props[i]);
+ event.getPointerCoords(i, coords[i]);
+ if (i == remappedIndex) {
+ coords[i].x += offsetX;
+ coords[i].y += offsetY;
+ }
+ }
+ return MotionEvent.obtain(event.getDownTime(),
+ event.getEventTime(), event.getAction(), event.getPointerCount(),
+ props, coords, event.getMetaState(), event.getButtonState(),
+ 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
+ event.getSource(), event.getFlags());
+ }
+
+ /**
* Computes the action for an injected event based on a masked action
* and a pointer index.
*
@@ -1674,7 +1707,6 @@ class TouchExplorer implements EventStreamTransformation {
// Keep track of the last up pointer data.
private long mLastReceivedUpPointerDownTime;
- private int mLastReceivedUpPointerId;
private float mLastReceivedUpPointerDownX;
private float mLastReceivedUpPointerDownY;
@@ -1690,7 +1722,6 @@ class TouchExplorer implements EventStreamTransformation {
mReceivedPointersDown = 0;
mPrimaryPointerId = 0;
mLastReceivedUpPointerDownTime = 0;
- mLastReceivedUpPointerId = 0;
mLastReceivedUpPointerDownX = 0;
mLastReceivedUpPointerDownY = 0;
}
@@ -1823,7 +1854,6 @@ class TouchExplorer implements EventStreamTransformation {
final int pointerId = event.getPointerId(pointerIndex);
final int pointerFlag = (1 << pointerId);
- mLastReceivedUpPointerId = 0;
mLastReceivedUpPointerDownTime = 0;
mLastReceivedUpPointerDownX = 0;
mLastReceivedUpPointerDownX = 0;
@@ -1848,7 +1878,6 @@ class TouchExplorer implements EventStreamTransformation {
final int pointerId = event.getPointerId(pointerIndex);
final int pointerFlag = (1 << pointerId);
- mLastReceivedUpPointerId = pointerId;
mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId];
mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId];
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index f972f70..aa9849e 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -190,10 +190,10 @@ public class AccountManagerService
private final HashMap<String, Account[]> accountCache =
new LinkedHashMap<String, Account[]>();
/** protected by the {@link #cacheLock} */
- private HashMap<Account, HashMap<String, String>> userDataCache =
+ private final HashMap<Account, HashMap<String, String>> userDataCache =
new HashMap<Account, HashMap<String, String>>();
/** protected by the {@link #cacheLock} */
- private HashMap<Account, HashMap<String, String>> authTokenCache =
+ private final HashMap<Account, HashMap<String, String>> authTokenCache =
new HashMap<Account, HashMap<String, String>>();
UserAccounts(Context context, int userId) {
@@ -475,6 +475,7 @@ public class AccountManagerService
validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
}
+ @Override
public String getPassword(Account account) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getPassword: " + account
@@ -514,6 +515,7 @@ public class AccountManagerService
}
}
+ @Override
public String getUserData(Account account, String key) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getUserData: " + account
@@ -533,6 +535,7 @@ public class AccountManagerService
}
}
+ @Override
public AuthenticatorDescription[] getAuthenticatorTypes() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAuthenticatorTypes: "
@@ -763,6 +766,7 @@ public class AccountManagerService
return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
}
+ @Override
public void hasFeatures(IAccountManagerResponse response,
Account account, String[] features) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -840,6 +844,7 @@ public class AccountManagerService
}
}
+ @Override
public void removeAccount(IAccountManagerResponse response, Account account) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "removeAccount: " + account
@@ -1049,6 +1054,7 @@ public class AccountManagerService
}
}
+ @Override
public String peekAuthToken(Account account, String authTokenType) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "peekAuthToken: " + account
@@ -1068,6 +1074,7 @@ public class AccountManagerService
}
}
+ @Override
public void setAuthToken(Account account, String authTokenType, String authToken) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setAuthToken: " + account
@@ -1087,6 +1094,7 @@ public class AccountManagerService
}
}
+ @Override
public void setPassword(Account account, String password) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setAuthToken: " + account
@@ -1135,6 +1143,7 @@ public class AccountManagerService
mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
}
+ @Override
public void clearPassword(Account account) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "clearPassword: " + account
@@ -1152,6 +1161,7 @@ public class AccountManagerService
}
}
+ @Override
public void setUserData(Account account, String key, String value) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setUserData: " + account
@@ -1225,6 +1235,7 @@ public class AccountManagerService
}
}
+ @Override
public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
final String authTokenType)
throws RemoteException {
@@ -1271,6 +1282,7 @@ public class AccountManagerService
}
}
+ @Override
public void getAuthToken(IAccountManagerResponse response, final Account account,
final String authTokenType, final boolean notifyOnAuthFailure,
final boolean expectActivityLaunch, Bundle loginOptionsIn) {
@@ -1284,8 +1296,22 @@ public class AccountManagerService
+ ", pid " + Binder.getCallingPid());
}
if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+ try {
+ if (account == null) {
+ Slog.w(TAG, "getAuthToken called with null account");
+ response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "account is null");
+ return;
+ }
+ if (authTokenType == null) {
+ Slog.w(TAG, "getAuthToken called with null authTokenType");
+ response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "authTokenType is null");
+ return;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to report error back to the client." + e);
+ return;
+ }
+
checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
final UserAccounts accounts = getUserAccountsForCaller();
final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
@@ -1294,11 +1320,6 @@ public class AccountManagerService
final boolean customTokens =
authenticatorInfo != null && authenticatorInfo.type.customTokens;
- // Check to see that the app is authorized to access the account, in case it's a
- // restricted account.
- if (!ArrayUtils.contains(getAccounts((String) null), account)) {
- throw new IllegalArgumentException("no such account");
- }
// skip the check if customTokens
final int callerUid = Binder.getCallingUid();
final boolean permissionGranted = customTokens ||
@@ -1472,6 +1493,7 @@ public class AccountManagerService
return id;
}
+ @Override
public void addAccount(final IAccountManagerResponse response, final String accountType,
final String authTokenType, final String[] requiredFeatures,
final boolean expectActivityLaunch, final Bundle optionsIn) {
@@ -1582,6 +1604,7 @@ public class AccountManagerService
}
}
+ @Override
public void updateCredentials(IAccountManagerResponse response, final Account account,
final String authTokenType, final boolean expectActivityLaunch,
final Bundle loginOptions) {
@@ -1620,6 +1643,7 @@ public class AccountManagerService
}
}
+ @Override
public void editProperties(IAccountManagerResponse response, final String accountType,
final boolean expectActivityLaunch) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1657,7 +1681,7 @@ public class AccountManagerService
private volatile Account[] mAccountsOfType = null;
private volatile ArrayList<Account> mAccountsWithFeatures = null;
private volatile int mCurrentAccount = 0;
- private int mCallingUid;
+ private final int mCallingUid;
public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
IAccountManagerResponse response, String type, String[] features, int callingUid) {
@@ -1941,6 +1965,7 @@ public class AccountManagerService
return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid);
}
+ @Override
public void getAccountsByFeatures(IAccountManagerResponse response,
String type, String[] features) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2069,6 +2094,7 @@ public class AccountManagerService
unbind();
}
+ @Override
public void binderDied() {
mResponse = null;
close();
@@ -2112,6 +2138,7 @@ public class AccountManagerService
mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
+ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
try {
@@ -2122,6 +2149,7 @@ public class AccountManagerService
}
}
+ @Override
public void onServiceDisconnected(ComponentName name) {
mAuthenticator = null;
IAccountManagerResponse response = getResponseAndClose();
@@ -2217,7 +2245,14 @@ public class AccountManagerService
Log.v(TAG, getClass().getSimpleName()
+ " calling onResult() on response " + response);
}
- response.onResult(result);
+ if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) &&
+ (intent == null)) {
+ // All AccountManager error codes are greater than 0
+ response.onError(result.getInt(AccountManager.KEY_ERROR_CODE),
+ result.getString(AccountManager.KEY_ERROR_MESSAGE));
+ } else {
+ response.onResult(result);
+ }
}
} catch (RemoteException e) {
// if the caller is dead then there is no one to care about remote exceptions
@@ -2228,10 +2263,12 @@ public class AccountManagerService
}
}
+ @Override
public void onRequestContinued() {
mNumRequestContinued++;
}
+ @Override
public void onError(int errorCode, String errorMessage) {
mNumErrors++;
IAccountManagerResponse response = getResponseAndClose();
@@ -2731,6 +2768,7 @@ public class AccountManagerService
return true;
}
+ @Override
public void updateAppPermission(Account account, String authTokenType, int uid, boolean value)
throws RemoteException {
final int callingUid = getCallingUid();
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index a64940c..17bb3e0 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,7 @@ import java.util.List;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemProperties;
import android.util.ArrayMap;
import com.android.internal.app.ProcessStats;
import com.android.internal.os.BatteryStatsImpl;
@@ -63,7 +64,7 @@ public final class ActiveServices {
static final boolean DEBUG_SERVICE = ActivityManagerService.DEBUG_SERVICE;
static final boolean DEBUG_SERVICE_EXECUTING = ActivityManagerService.DEBUG_SERVICE_EXECUTING;
static final boolean DEBUG_DELAYED_SERVICE = ActivityManagerService.DEBUG_SERVICE;
- static final boolean DEBUG_DELAYED_STATS = DEBUG_DELAYED_SERVICE;
+ static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE;
static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU;
static final String TAG = ActivityManagerService.TAG;
static final String TAG_MU = ActivityManagerService.TAG_MU;
@@ -76,7 +77,7 @@ public final class ActiveServices {
// How long a service needs to be running until restarting its process
// is no longer considered to be a relaunch of the service.
- static final int SERVICE_RESTART_DURATION = 5*1000;
+ static final int SERVICE_RESTART_DURATION = 1*1000;
// How long a service needs to be running until it will start back at
// SERVICE_RESTART_DURATION after being killed.
@@ -185,11 +186,11 @@ public final class ActiveServices {
void ensureNotStartingBackground(ServiceRecord r) {
if (mStartingBackground.remove(r)) {
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer background starting: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer background starting: " + r);
rescheduleDelayedStarts();
}
if (mDelayedStartList.remove(r)) {
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer delaying start: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "No longer delaying start: " + r);
}
}
@@ -207,7 +208,7 @@ public final class ActiveServices {
while (mDelayedStartList.size() > 0
&& mStartingBackground.size() < mMaxStartingBackground) {
ServiceRecord r = mDelayedStartList.remove(0);
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r);
if (r.pendingStarts.size() <= 0) {
Slog.w(TAG, "**** NO PENDING STARTS! " + r + " startReq=" + r.startRequested
+ " delayedStop=" + r.delayedStop);
@@ -239,7 +240,12 @@ public final class ActiveServices {
public ActiveServices(ActivityManagerService service) {
mAm = service;
- mMaxStartingBackground = ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
+ int maxBg = 0;
+ try {
+ maxBg = Integer.parseInt(SystemProperties.get("ro.config.max_starting_bg", "0"));
+ } catch(RuntimeException e) {
+ }
+ mMaxStartingBackground = maxBg > 0 ? maxBg : ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
}
ServiceRecord getServiceByName(ComponentName name, int callingUser) {
@@ -270,7 +276,7 @@ public final class ActiveServices {
ComponentName startServiceLocked(IApplicationThread caller,
Intent service, String resolvedType,
int callingPid, int callingUid, int userId) {
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "startService: " + service
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
final boolean callerFg;
@@ -301,7 +307,7 @@ public final class ActiveServices {
ServiceRecord r = res.record;
NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
callingUid, r.packageName, service, service.getFlags(), null);
- if (unscheduleServiceRestartLocked(r)) {
+ if (unscheduleServiceRestartLocked(r, callingUid, false)) {
if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r);
}
r.lastActivity = SystemClock.uptimeMillis();
@@ -330,7 +336,7 @@ public final class ActiveServices {
if (r.delayed) {
// This service is already scheduled for a delayed start; just leave
// it still waiting.
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Continuing to delay: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Continuing to delay: " + r);
return r.name;
}
if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
@@ -340,15 +346,15 @@ public final class ActiveServices {
r.delayed = true;
return r.name;
}
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying: " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying: " + r);
addToStarting = true;
} else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
// We slightly loosen when we will enqueue this new service as a background
// starting service we are waiting for, to also include processes that are
// currently running other services or receivers.
addToStarting = true;
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying, but counting as bg: " + r);
- } else if (DEBUG_DELAYED_STATS) {
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Not delaying, but counting as bg: " + r);
+ } else if (DEBUG_DELAYED_STARTS) {
StringBuilder sb = new StringBuilder(128);
sb.append("Not potential delay (state=").append(proc.curProcState)
.append(' ').append(proc.adjType);
@@ -361,7 +367,7 @@ public final class ActiveServices {
sb.append(r.toString());
Slog.v(TAG, sb.toString());
}
- } else if (DEBUG_DELAYED_STATS) {
+ } else if (DEBUG_DELAYED_STARTS) {
if (callerFg) {
Slog.v(TAG, "Not potential delay (callerFg=" + callerFg + " uid="
+ callingUid + " pid=" + callingPid + "): " + r);
@@ -398,7 +404,7 @@ public final class ActiveServices {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.v(TAG, "Starting background (first=" + first + "): " + r, here);
- } else if (DEBUG_DELAYED_STATS) {
+ } else if (DEBUG_DELAYED_STARTS) {
Slog.v(TAG, "Starting background (first=" + first + "): " + r);
}
if (first) {
@@ -416,7 +422,7 @@ public final class ActiveServices {
// If service isn't actually running, but is is being held in the
// delayed list, then we need to keep it started but note that it
// should be stopped once no longer delayed.
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Delaying stop of pending: " + service);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Delaying stop of pending: " + service);
service.delayedStop = true;
return;
}
@@ -564,7 +570,7 @@ public final class ActiveServices {
if (r.isForeground) {
r.isForeground = false;
if (r.app != null) {
- mAm.updateLruProcessLocked(r.app, false, false);
+ mAm.updateLruProcessLocked(r.app, false, null);
updateServiceForegroundLocked(r.app, true);
}
}
@@ -597,6 +603,42 @@ public final class ActiveServices {
}
}
+ private boolean updateServiceClientActivitiesLocked(ProcessRecord proc,
+ ConnectionRecord modCr) {
+ if (modCr != null && modCr.binding.client != null) {
+ if (modCr.binding.client.activities.size() <= 0) {
+ // This connection is from a client without activities, so adding
+ // and removing is not interesting.
+ return false;
+ }
+ }
+
+ boolean anyClientActivities = false;
+ for (int i=proc.services.size()-1; i>=0 && !anyClientActivities; i--) {
+ ServiceRecord sr = proc.services.valueAt(i);
+ for (int conni=sr.connections.size()-1; conni>=0 && !anyClientActivities; conni--) {
+ ArrayList<ConnectionRecord> clist = sr.connections.valueAt(conni);
+ for (int cri=clist.size()-1; cri>=0; cri--) {
+ ConnectionRecord cr = clist.get(cri);
+ if (cr.binding.client == null || cr.binding.client == proc) {
+ // Binding to ourself is not interesting.
+ continue;
+ }
+ if (cr.binding.client.activities.size() > 0) {
+ anyClientActivities = true;
+ break;
+ }
+ }
+ }
+ }
+ if (anyClientActivities != proc.hasClientActivities) {
+ proc.hasClientActivities = anyClientActivities;
+ mAm.updateLruProcessLocked(proc, anyClientActivities, null);
+ return true;
+ }
+ return false;
+ }
+
int bindServiceLocked(IApplicationThread caller, IBinder token,
Intent service, String resolvedType,
IServiceConnection connection, int flags, int userId) {
@@ -659,7 +701,7 @@ public final class ActiveServices {
final long origId = Binder.clearCallingIdentity();
try {
- if (unscheduleServiceRestartLocked(s)) {
+ if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) {
if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: "
+ s);
}
@@ -698,6 +740,9 @@ public final class ActiveServices {
if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
b.client.hasAboveClient = true;
}
+ if (s.app != null) {
+ updateServiceClientActivitiesLocked(s.app, c);
+ }
clist = mServiceConnections.get(binder);
if (clist == null) {
clist = new ArrayList<ConnectionRecord>();
@@ -714,6 +759,7 @@ public final class ActiveServices {
if (s.app != null) {
// This could have made the service more important.
+ mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities, b.client);
mAm.updateOomAdjLocked(s.app);
}
@@ -956,14 +1002,11 @@ public final class ActiveServices {
smap.mServicesByIntent.put(filter, r);
// Make sure this component isn't in the pending list.
- int N = mPendingServices.size();
- for (int i=0; i<N; i++) {
+ for (int i=mPendingServices.size()-1; i>=0; i--) {
ServiceRecord pr = mPendingServices.get(i);
if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid
&& pr.name.equals(name)) {
mPendingServices.remove(i);
- i--;
- N--;
}
}
}
@@ -1055,6 +1098,14 @@ public final class ActiveServices {
boolean allowCancel) {
boolean canceled = false;
+ ServiceMap smap = getServiceMap(r.userId);
+ if (smap.mServicesByName.get(r.name) != r) {
+ ServiceRecord cur = smap.mServicesByName.get(r.name);
+ Slog.wtf(TAG, "Attempting to schedule restart of " + r
+ + " when found in map: " + cur);
+ return false;
+ }
+
final long now = SystemClock.uptimeMillis();
if ((r.serviceInfo.applicationInfo.flags
@@ -1101,16 +1152,9 @@ public final class ActiveServices {
r.restartCount = 1;
r.restartDelay = minDuration;
} else {
- if ((r.serviceInfo.applicationInfo.flags
- &ApplicationInfo.FLAG_PERSISTENT) != 0) {
- // Services in peristent processes will restart much more
- // quickly, since they are pretty important. (Think SystemUI).
- r.restartDelay += minDuration/2;
- } else {
- r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR;
- if (r.restartDelay < minDuration) {
- r.restartDelay = minDuration;
- }
+ r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR;
+ if (r.restartDelay < minDuration) {
+ r.restartDelay = minDuration;
}
}
}
@@ -1137,7 +1181,7 @@ public final class ActiveServices {
} while (repeat);
} else {
- // Persistent processes are immediately restrted, so there is no
+ // Persistent processes are immediately restarted, so there is no
// reason to hold of on restarting their services.
r.totalRestartCount++;
r.restartCount = 0;
@@ -1148,6 +1192,7 @@ public final class ActiveServices {
if (!mRestartingServices.contains(r)) {
r.createdFromFg = false;
mRestartingServices.add(r);
+ r.makeRestarting(mAm.mProcessStats.getMemFactorLocked(), now);
}
r.cancelNotification();
@@ -1170,16 +1215,44 @@ public final class ActiveServices {
bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
}
- private final boolean unscheduleServiceRestartLocked(ServiceRecord r) {
- if (r.restartDelay == 0) {
+ private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid,
+ boolean force) {
+ if (!force && r.restartDelay == 0) {
return false;
}
- r.resetRestartCounter();
- mRestartingServices.remove(r);
+ // Remove from the restarting list; if the service is currently on the
+ // restarting list, or the call is coming from another app, then this
+ // service has become of much more interest so we reset the restart interval.
+ boolean removed = mRestartingServices.remove(r);
+ if (removed || callingUid != r.appInfo.uid) {
+ r.resetRestartCounter();
+ }
+ if (removed) {
+ clearRestartingIfNeededLocked(r);
+ }
mAm.mHandler.removeCallbacks(r.restarter);
return true;
}
+ private void clearRestartingIfNeededLocked(ServiceRecord r) {
+ if (r.restartTracker != null) {
+ // If this is the last restarting record with this tracker, then clear
+ // the tracker's restarting state.
+ boolean stillTracking = false;
+ for (int i=mRestartingServices.size()-1; i>=0; i--) {
+ if (mRestartingServices.get(i).restartTracker == r.restartTracker) {
+ stillTracking = true;
+ break;
+ }
+ }
+ if (!stillTracking) {
+ r.restartTracker.setRestarting(false, mAm.mProcessStats.getMemFactorLocked(),
+ SystemClock.uptimeMillis());
+ r.restartTracker = null;
+ }
+ }
+ }
+
private final String bringUpServiceLocked(ServiceRecord r,
int intentFlags, boolean execInFg, boolean whileRestarting) {
//Slog.i(TAG, "Bring up service:");
@@ -1199,11 +1272,13 @@ public final class ActiveServices {
// We are now bringing the service up, so no longer in the
// restarting state.
- mRestartingServices.remove(r);
+ if (mRestartingServices.remove(r)) {
+ clearRestartingIfNeededLocked(r);
+ }
// Make sure this service is no longer considered delayed, we are starting it now.
if (r.delayed) {
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r);
getServiceMap(r.userId).mDelayedStartList.remove(r);
r.delayed = false;
}
@@ -1286,7 +1361,7 @@ public final class ActiveServices {
// Oh and hey we've already been asked to stop!
r.delayedStop = false;
if (r.startRequested) {
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r);
stopServiceLocked(r);
}
}
@@ -1316,7 +1391,8 @@ public final class ActiveServices {
app.services.add(r);
bumpServiceExecutingLocked(r, execInFg, "create");
- mAm.updateLruProcessLocked(app, true, false);
+ mAm.updateLruProcessLocked(app, false, null);
+ mAm.updateOomAdjLocked();
boolean created = false;
try {
@@ -1338,6 +1414,7 @@ public final class ActiveServices {
} finally {
if (!created) {
app.services.remove(r);
+ r.app = null;
scheduleServiceRestartLocked(r, false);
}
}
@@ -1355,7 +1432,7 @@ public final class ActiveServices {
sendServiceArgsLocked(r, execInFg, true);
if (r.delayed) {
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r);
getServiceMap(r.userId).mDelayedStartList.remove(r);
r.delayed = false;
}
@@ -1364,7 +1441,7 @@ public final class ActiveServices {
// Oh and hey we've already been asked to stop!
r.delayedStop = false;
if (r.startRequested) {
- if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (from start): " + r);
+ if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (from start): " + r);
stopServiceLocked(r);
}
}
@@ -1508,16 +1585,13 @@ public final class ActiveServices {
smap.mServicesByName.remove(r.name);
smap.mServicesByIntent.remove(r.intent);
r.totalRestartCount = 0;
- unscheduleServiceRestartLocked(r);
+ unscheduleServiceRestartLocked(r, 0, true);
// Also make sure it is not on the pending list.
- int N = mPendingServices.size();
- for (int i=0; i<N; i++) {
+ for (int i=mPendingServices.size()-1; i>=0; i--) {
if (mPendingServices.get(i) == r) {
mPendingServices.remove(i);
if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r);
- i--;
- N--;
}
}
@@ -1536,6 +1610,7 @@ public final class ActiveServices {
}
r.app.services.remove(r);
if (r.app.thread != null) {
+ updateServiceForegroundLocked(r.app, false);
try {
bumpServiceExecutingLocked(r, false, "destroy");
mDestroyingServices.add(r);
@@ -1546,7 +1621,6 @@ public final class ActiveServices {
+ r.shortName, e);
serviceProcessGoneLocked(r);
}
- updateServiceForegroundLocked(r.app, false);
} else {
if (DEBUG_SERVICE) Slog.v(
TAG, "Removed service that has no process: " + r);
@@ -1601,6 +1675,9 @@ public final class ActiveServices {
if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
b.client.updateHasAboveClientLocked();
}
+ if (s.app != null) {
+ updateServiceClientActivitiesLocked(s.app, c);
+ }
}
clist = mServiceConnections.get(binder);
if (clist != null) {
@@ -1621,6 +1698,13 @@ public final class ActiveServices {
&& b.intent.hasBound) {
try {
bumpServiceExecutingLocked(s, false, "unbind");
+ if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
+ && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+ // If this service's process is not already in the cached list,
+ // then update it in the LRU list here because this may be causing
+ // it to go down there and we want it to start out near the top.
+ mAm.updateLruProcessLocked(s.app, false, null);
+ }
mAm.updateOomAdjLocked(s.app);
b.intent.hasBound = false;
// Assume the client doesn't want to know about a rebind;
@@ -1714,6 +1798,7 @@ public final class ActiveServices {
long now = SystemClock.uptimeMillis();
r.tracker.setExecuting(false, memFactor, now);
r.tracker.setBound(false, memFactor, now);
+ r.tracker.setStarted(false, memFactor, now);
}
serviceDoneExecutingLocked(r, true, true);
}
@@ -1761,6 +1846,12 @@ public final class ActiveServices {
r.tracker = null;
}
}
+ if (finishing) {
+ if (r.app != null && !r.app.persistent) {
+ r.app.services.remove(r);
+ }
+ r.app = null;
+ }
}
}
@@ -1839,6 +1930,9 @@ public final class ActiveServices {
Slog.i(TAG, " Force stopping service " + service);
if (service.app != null) {
service.app.removed = true;
+ if (!service.app.persistent) {
+ service.app.services.remove(service);
+ }
}
service.app = null;
service.isolatedProc = null;
@@ -1905,8 +1999,7 @@ public final class ActiveServices {
}
}
- final void killServicesLocked(ProcessRecord app,
- boolean allowRestart) {
+ final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
// Report disconnected services.
if (false) {
// XXX we are letting the client link to the service for
@@ -1935,20 +2028,15 @@ public final class ActiveServices {
}
}
- // Clean up any connections this application has to other services.
- for (int i=app.connections.size()-1; i>=0; i--) {
- ConnectionRecord r = app.connections.valueAt(i);
- removeConnectionLocked(r, app, null);
- }
- app.connections.clear();
-
+ // First clear app state from services.
for (int i=app.services.size()-1; i>=0; i--) {
- // Any services running in the application need to be placed
- // back in the pending list.
ServiceRecord sr = app.services.valueAt(i);
synchronized (sr.stats.getBatteryStats()) {
sr.stats.stopLaunchedLocked();
}
+ if (sr.app != app && sr.app != null && !sr.app.persistent) {
+ sr.app.services.remove(sr);
+ }
sr.app = null;
sr.isolatedProc = null;
sr.executeNesting = 0;
@@ -1965,8 +2053,39 @@ public final class ActiveServices {
b.binder = null;
b.requested = b.received = b.hasBound = false;
}
+ }
- if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
+ // Clean up any connections this application has to other services.
+ for (int i=app.connections.size()-1; i>=0; i--) {
+ ConnectionRecord r = app.connections.valueAt(i);
+ removeConnectionLocked(r, app, null);
+ }
+ app.connections.clear();
+
+ ServiceMap smap = getServiceMap(app.userId);
+
+ // Now do remaining service cleanup.
+ for (int i=app.services.size()-1; i>=0; i--) {
+ ServiceRecord sr = app.services.valueAt(i);
+
+ // Unless the process is persistent, this process record is going away,
+ // so make sure the service is cleaned out of it.
+ if (!app.persistent) {
+ app.services.removeAt(i);
+ }
+
+ // Sanity check: if the service listed for the app is not one
+ // we actually are maintaining, just let it drop.
+ if (smap.mServicesByName.get(sr.name) != sr) {
+ ServiceRecord cur = smap.mServicesByName.get(sr.name);
+ Slog.wtf(TAG, "Service " + sr + " in process " + app
+ + " not same as in map: " + cur);
+ continue;
+ }
+
+ // Any services running in the application may need to be placed
+ // back in the pending list.
+ if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
&ApplicationInfo.FLAG_PERSISTENT) == 0) {
Slog.w(TAG, "Service crashed " + sr.crashCount
+ " times, stopping: " + sr);
@@ -1999,6 +2118,23 @@ public final class ActiveServices {
if (!allowRestart) {
app.services.clear();
+
+ // Make sure there are no more restarting services for this process.
+ for (int i=mRestartingServices.size()-1; i>=0; i--) {
+ ServiceRecord r = mRestartingServices.get(i);
+ if (r.processName.equals(app.processName) &&
+ r.serviceInfo.applicationInfo.uid == app.info.uid) {
+ mRestartingServices.remove(i);
+ clearRestartingIfNeededLocked(r);
+ }
+ }
+ for (int i=mPendingServices.size()-1; i>=0; i--) {
+ ServiceRecord r = mPendingServices.get(i);
+ if (r.processName.equals(app.processName) &&
+ r.serviceInfo.applicationInfo.uid == app.info.uid) {
+ mPendingServices.remove(i);
+ }
+ }
}
// Make sure we have no more records on the stopping list.
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d75fe5e..e2050fc 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -34,7 +34,6 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.ProcessStats;
-import com.android.internal.app.ResolverActivity;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.os.ProcessCpuTracker;
@@ -218,6 +217,7 @@ public final class ActivityManagerService extends ActivityManagerNative
static final boolean DEBUG_IMMERSIVE = localLOGV || false;
static final boolean DEBUG_MU = localLOGV || false;
static final boolean DEBUG_OOM_ADJ = localLOGV || false;
+ static final boolean DEBUG_LRU = localLOGV || false;
static final boolean DEBUG_PAUSE = localLOGV || false;
static final boolean DEBUG_POWER = localLOGV || false;
static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
@@ -457,6 +457,23 @@ public final class ActivityManagerService extends ActivityManagerNative
final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<Long>();
/**
+ * Information about a process that is currently marked as bad.
+ */
+ static final class BadProcessInfo {
+ BadProcessInfo(long time, String shortMsg, String longMsg, String stack) {
+ this.time = time;
+ this.shortMsg = shortMsg;
+ this.longMsg = longMsg;
+ this.stack = stack;
+ }
+
+ final long time;
+ final String shortMsg;
+ final String longMsg;
+ final String stack;
+ }
+
+ /**
* Set of applications that we consider to be bad, and will reject
* incoming broadcasts from (which the user has no control over).
* Processes are added to this set when they have crashed twice within
@@ -464,7 +481,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* later restarted (hopefully due to some user action). The value is the
* time it was added to the list.
*/
- final ProcessMap<Long> mBadProcesses = new ProcessMap<Long>();
+ final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<BadProcessInfo>();
/**
* All of the processes we currently have running organized by pid.
@@ -1311,7 +1328,7 @@ public final class ActivityManagerService extends ActivityManagerNative
String pkg = bundle.getString("pkg");
String reason = bundle.getString("reason");
forceStopPackageLocked(pkg, appid, restart, false, true, false,
- UserHandle.USER_ALL, reason);
+ false, UserHandle.USER_ALL, reason);
}
} break;
case FINALIZE_PENDING_INTENT_MSG: {
@@ -1743,7 +1760,8 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (mSelf.mPidsSelfLocked) {
mSelf.mPidsSelfLocked.put(app.pid, app);
}
- mSelf.updateLruProcessLocked(app, true, false);
+ mSelf.updateLruProcessLocked(app, false, null);
+ mSelf.updateOomAdjLocked();
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(
@@ -2131,7 +2149,8 @@ public final class ActivityManagerService extends ActivityManagerNative
totalUTime += otherUTime;
totalSTime += otherSTime;
if (pr != null) {
- BatteryStatsImpl.Uid.Proc ps = pr.batteryStats;
+ BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(
+ st.name, st.pid);
ps.addCpuTimeLocked(st.rel_utime-otherUTime,
st.rel_stime-otherSTime);
ps.addSpeedStepTimes(cpuSpeedTimes);
@@ -2269,7 +2288,7 @@ public final class ActivityManagerService extends ActivityManagerNative
int lrui = mLruProcesses.lastIndexOf(app);
if (lrui < 0) {
- Log.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+ Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+ what + " " + obj + " from " + srcApp);
return index;
}
@@ -2289,6 +2308,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (index > 0) {
index--;
}
+ if (DEBUG_LRU) Slog.d(TAG, "Moving dep from " + lrui + " to " + index
+ + " in LRU list: " + app);
mLruProcesses.add(index, app);
return index;
}
@@ -2306,8 +2327,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- final void updateLruProcessLocked(ProcessRecord app, boolean oomAdj, boolean activityChange) {
- final boolean hasActivity = app.activities.size() > 0;
+ final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
+ ProcessRecord client) {
+ final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities;
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
// The process has activties, so we are only going to allow activity-based
@@ -2321,8 +2343,65 @@ public final class ActivityManagerService extends ActivityManagerNative
final long now = SystemClock.uptimeMillis();
app.lastActivityTime = now;
+ // First a quick reject: if the app is already at the position we will
+ // put it, then there is nothing to do.
+ if (hasActivity) {
+ final int N = mLruProcesses.size();
+ if (N > 0 && mLruProcesses.get(N-1) == app) {
+ if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top activity: " + app);
+ return;
+ }
+ } else {
+ if (mLruProcessServiceStart > 0
+ && mLruProcesses.get(mLruProcessServiceStart-1) == app) {
+ if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top other: " + app);
+ return;
+ }
+ }
+
int lrui = mLruProcesses.lastIndexOf(app);
+ if (app.persistent && lrui >= 0) {
+ // We don't care about the position of persistent processes, as long as
+ // they are in the list.
+ if (DEBUG_LRU) Slog.d(TAG, "Not moving, persistent: " + app);
+ return;
+ }
+
+ /* In progress: compute new position first, so we can avoid doing work
+ if the process is not actually going to move. Not yet working.
+ int addIndex;
+ int nextIndex;
+ boolean inActivity = false, inService = false;
+ if (hasActivity) {
+ // Process has activities, put it at the very tipsy-top.
+ addIndex = mLruProcesses.size();
+ nextIndex = mLruProcessServiceStart;
+ inActivity = true;
+ } else if (hasService) {
+ // Process has services, put it at the top of the service list.
+ addIndex = mLruProcessActivityStart;
+ nextIndex = mLruProcessServiceStart;
+ inActivity = true;
+ inService = true;
+ } else {
+ // Process not otherwise of interest, it goes to the top of the non-service area.
+ addIndex = mLruProcessServiceStart;
+ if (client != null) {
+ int clientIndex = mLruProcesses.lastIndexOf(client);
+ if (clientIndex < 0) Slog.d(TAG, "Unknown client " + client + " when updating "
+ + app);
+ if (clientIndex >= 0 && addIndex > clientIndex) {
+ addIndex = clientIndex;
+ }
+ }
+ nextIndex = addIndex > 0 ? addIndex-1 : addIndex;
+ }
+
+ Slog.d(TAG, "Update LRU at " + lrui + " to " + addIndex + " (act="
+ + mLruProcessActivityStart + "): " + app);
+ */
+
if (lrui >= 0) {
if (lrui < mLruProcessActivityStart) {
mLruProcessActivityStart--;
@@ -2330,23 +2409,91 @@ public final class ActivityManagerService extends ActivityManagerNative
if (lrui < mLruProcessServiceStart) {
mLruProcessServiceStart--;
}
+ /*
+ if (addIndex > lrui) {
+ addIndex--;
+ }
+ if (nextIndex > lrui) {
+ nextIndex--;
+ }
+ */
mLruProcesses.remove(lrui);
}
+ /*
+ mLruProcesses.add(addIndex, app);
+ if (inActivity) {
+ mLruProcessActivityStart++;
+ }
+ if (inService) {
+ mLruProcessActivityStart++;
+ }
+ */
+
int nextIndex;
if (hasActivity) {
- // Process has activities, put it at the very tipsy-top.
- mLruProcesses.add(app);
- nextIndex = mLruProcessActivityStart;
+ final int N = mLruProcesses.size();
+ if (app.activities.size() == 0 && mLruProcessActivityStart < (N-1)) {
+ // Process doesn't have activities, but has clients with
+ // activities... move it up, but one below the top (the top
+ // should always have a real activity).
+ if (DEBUG_LRU) Slog.d(TAG, "Adding to second-top of LRU activity list: " + app);
+ mLruProcesses.add(N-1, app);
+ // To keep it from spamming the LRU list (by making a bunch of clients),
+ // we will push down any other entries owned by the app.
+ final int uid = app.info.uid;
+ for (int i=N-2; i>mLruProcessActivityStart; i--) {
+ ProcessRecord subProc = mLruProcesses.get(i);
+ if (subProc.info.uid == uid) {
+ // We want to push this one down the list. If the process after
+ // it is for the same uid, however, don't do so, because we don't
+ // want them internally to be re-ordered.
+ if (mLruProcesses.get(i-1).info.uid != uid) {
+ if (DEBUG_LRU) Slog.d(TAG, "Pushing uid " + uid + " swapping at " + i
+ + ": " + mLruProcesses.get(i) + " : " + mLruProcesses.get(i-1));
+ ProcessRecord tmp = mLruProcesses.get(i);
+ mLruProcesses.set(i, mLruProcesses.get(i-1));
+ mLruProcesses.set(i-1, tmp);
+ i--;
+ }
+ } else {
+ // A gap, we can stop here.
+ break;
+ }
+ }
+ } else {
+ // Process has activities, put it at the very tipsy-top.
+ if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU activity list: " + app);
+ mLruProcesses.add(app);
+ }
+ nextIndex = mLruProcessServiceStart;
} else if (hasService) {
// Process has services, put it at the top of the service list.
+ if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU service list: " + app);
mLruProcesses.add(mLruProcessActivityStart, app);
nextIndex = mLruProcessServiceStart;
mLruProcessActivityStart++;
} else {
// Process not otherwise of interest, it goes to the top of the non-service area.
- mLruProcesses.add(mLruProcessServiceStart, app);
- nextIndex = mLruProcessServiceStart-1;
+ int index = mLruProcessServiceStart;
+ if (client != null) {
+ // If there is a client, don't allow the process to be moved up higher
+ // in the list than that client.
+ int clientIndex = mLruProcesses.lastIndexOf(client);
+ if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG, "Unknown client " + client
+ + " when updating " + app);
+ if (clientIndex <= lrui) {
+ // Don't allow the client index restriction to push it down farther in the
+ // list than it already is.
+ clientIndex = lrui;
+ }
+ if (clientIndex >= 0 && index > clientIndex) {
+ index = clientIndex;
+ }
+ }
+ if (DEBUG_LRU) Slog.d(TAG, "Adding at " + index + " of LRU list: " + app);
+ mLruProcesses.add(index, app);
+ nextIndex = index-1;
mLruProcessActivityStart++;
mLruProcessServiceStart++;
}
@@ -2357,23 +2504,19 @@ public final class ActivityManagerService extends ActivityManagerNative
ConnectionRecord cr = app.connections.valueAt(j);
if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
&& cr.binding.service.app != null
- && cr.binding.service.app.lruSeq != mLruSeq) {
+ && cr.binding.service.app.lruSeq != mLruSeq
+ && !cr.binding.service.app.persistent) {
nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex,
"service connection", cr, app);
}
}
for (int j=app.conProviders.size()-1; j>=0; j--) {
ContentProviderRecord cpr = app.conProviders.get(j).provider;
- if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) {
+ if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) {
nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,
"provider reference", cpr, app);
}
}
-
- //Slog.i(TAG, "Putting proc to front: " + app.processName);
- if (oomAdj) {
- updateOomAdjLocked();
- }
}
final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) {
@@ -2627,10 +2770,10 @@ public final class ActivityManagerService extends ActivityManagerNative
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, null);
- BatteryStatsImpl bs = app.batteryStats.getBatteryStats();
+ BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics();
synchronized (bs) {
if (bs.isOnBattery()) {
- app.batteryStats.incStartsLocked();
+ bs.getProcessStatsLocked(app.uid, app.processName).incStartsLocked();
}
}
@@ -4351,7 +4494,7 @@ public final class ActivityManagerService extends ActivityManagerNative
private void forceStopPackageLocked(final String packageName, int uid, String reason) {
forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
- false, true, false, UserHandle.getUserId(uid), reason);
+ false, true, false, false, UserHandle.getUserId(uid), reason);
Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
Uri.fromParts("package", packageName, null));
if (!mProcessesReady) {
@@ -4367,7 +4510,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private void forceStopUserLocked(int userId, String reason) {
- forceStopPackageLocked(null, -1, false, false, true, false, userId, reason);
+ forceStopPackageLocked(null, -1, false, false, true, false, false, userId, reason);
Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
@@ -4452,7 +4595,7 @@ public final class ActivityManagerService extends ActivityManagerNative
private final boolean forceStopPackageLocked(String name, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
- boolean evenPersistent, int userId, String reason) {
+ boolean evenPersistent, boolean uninstalling, int userId, String reason) {
int i;
int N;
@@ -4544,7 +4687,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Remove transient permissions granted from/to this package/user
removeUriPermissionsForPackageLocked(name, userId, false);
- if (name == null) {
+ if (name == null || uninstalling) {
// Remove pending intents. For now we only do this when force
// stopping users, because we have some problems when doing this
// for packages -- app widgets are not currently cleaned up for
@@ -4831,7 +4974,7 @@ public final class ActivityManagerService extends ActivityManagerNative
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
mCoreSettingsObserver.getCoreSettingsLocked());
- updateLruProcessLocked(app, false, false);
+ updateLruProcessLocked(app, false, null);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
} catch (Exception e) {
// todo: Yikes! What should we do? For now we will try to
@@ -4989,7 +5132,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (pkgs != null) {
for (String pkg : pkgs) {
synchronized (ActivityManagerService.this) {
- if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0,
+ if (forceStopPackageLocked(pkg, -1, false, false, false, false, false, 0,
"finished booting")) {
setResultCode(Activity.RESULT_OK);
return;
@@ -7419,7 +7562,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// make sure to count it as being accessed and thus
// back up on the LRU list. This is good because
// content providers are often expensive to start.
- updateLruProcessLocked(cpr.proc, false, false);
+ updateLruProcessLocked(cpr.proc, false, null);
}
}
@@ -8008,10 +8151,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
- synchronized (stats) {
- ps = stats.getProcessStatsLocked(info.uid, proc);
- }
- return new ProcessRecord(ps, info, proc, uid);
+ return new ProcessRecord(stats, info, proc, uid);
}
final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) {
@@ -8028,7 +8168,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (isolated) {
mIsolatedProcesses.put(app.uid, app);
}
- updateLruProcessLocked(app, true, false);
+ updateLruProcessLocked(app, false, null);
+ updateOomAdjLocked();
}
// This package really, really can not be stopped.
@@ -8304,7 +8445,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mDebugTransient = !persistent;
if (packageName != null) {
forceStopPackageLocked(packageName, -1, false, false, true, true,
- UserHandle.USER_ALL, "set debug app");
+ false, UserHandle.USER_ALL, "set debug app");
}
}
} finally {
@@ -9084,8 +9225,13 @@ public final class ActivityManagerService extends ActivityManagerNative
ActivityInfo ai = ris.get(i).activityInfo;
ComponentName comp = new ComponentName(ai.packageName, ai.name);
if (lastDoneReceivers.contains(comp)) {
+ // We already did the pre boot receiver for this app with the current
+ // platform version, so don't do it again...
ris.remove(i);
i--;
+ // ...however, do keep it as one that has been done, so we don't
+ // forget about it when rewriting the file of last done receivers.
+ doneReceivers.add(comp);
}
}
@@ -9292,7 +9438,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
startAppProblemLocked(app);
app.stopFreezingAllLocked();
- return handleAppCrashLocked(app);
+ return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace);
}
private void makeAppNotRespondingLocked(ProcessRecord app,
@@ -9347,13 +9493,14 @@ public final class ActivityManagerService extends ActivityManagerNative
app.waitDialog = null;
}
if (app.pid > 0 && app.pid != MY_PID) {
- handleAppCrashLocked(app);
+ handleAppCrashLocked(app, null, null, null);
killUnneededProcessLocked(app, "user request after error");
}
}
}
- private boolean handleAppCrashLocked(ProcessRecord app) {
+ private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg,
+ String stackTrace) {
if (mHeadless) {
Log.e(TAG, "handleAppCrashLocked: " + app.processName);
return false;
@@ -9383,7 +9530,8 @@ public final class ActivityManagerService extends ActivityManagerNative
if (!app.isolated) {
// XXX We don't have a way to mark isolated processes
// as bad, since they don't have a peristent identity.
- mBadProcesses.put(app.info.processName, app.uid, now);
+ mBadProcesses.put(app.info.processName, app.uid,
+ new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
mProcessCrashTimes.remove(app.info.processName, app.uid);
}
app.bad = true;
@@ -10101,7 +10249,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.persistent) {
outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT;
}
- if (app.hasActivities) {
+ if (app.activities.size() > 0) {
outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES;
}
outInfo.lastTrimLevel = app.trimMemoryLevel;
@@ -10645,11 +10793,11 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mBadProcesses.getMap().size() > 0) {
boolean printed = false;
- final ArrayMap<String, SparseArray<Long>> pmap = mBadProcesses.getMap();
+ final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
final int NP = pmap.size();
for (int ip=0; ip<NP; ip++) {
String pname = pmap.keyAt(ip);
- SparseArray<Long> uids = pmap.valueAt(ip);
+ SparseArray<BadProcessInfo> uids = pmap.valueAt(ip);
final int N = uids.size();
for (int i=0; i<N; i++) {
int puid = uids.keyAt(i);
@@ -10664,10 +10812,33 @@ public final class ActivityManagerService extends ActivityManagerNative
pw.println(" Bad processes:");
printedAnything = true;
}
+ BadProcessInfo info = uids.valueAt(i);
pw.print(" Bad process "); pw.print(pname);
pw.print(" uid "); pw.print(puid);
- pw.print(": crashed at time ");
- pw.println(uids.valueAt(i));
+ pw.print(": crashed at time "); pw.println(info.time);
+ if (info.shortMsg != null) {
+ pw.print(" Short msg: "); pw.println(info.shortMsg);
+ }
+ if (info.longMsg != null) {
+ pw.print(" Long msg: "); pw.println(info.longMsg);
+ }
+ if (info.stack != null) {
+ pw.println(" Stack:");
+ int lastPos = 0;
+ for (int pos=0; pos<info.stack.length(); pos++) {
+ if (info.stack.charAt(pos) == '\n') {
+ pw.print(" ");
+ pw.write(info.stack, lastPos, pos-lastPos);
+ pw.println();
+ lastPos = pos+1;
+ }
+ }
+ if (lastPos < info.stack.length()) {
+ pw.print(" ");
+ pw.write(info.stack, lastPos, info.stack.length()-lastPos);
+ pw.println();
+ }
+ }
}
}
}
@@ -11733,6 +11904,7 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean dumpDalvik = false;
boolean oomOnly = false;
boolean isCompact = false;
+ boolean localOnly = false;
int opti = 0;
while (opti < args.length) {
@@ -11751,12 +11923,15 @@ public final class ActivityManagerService extends ActivityManagerNative
isCompact = true;
} else if ("--oom".equals(opt)) {
oomOnly = true;
+ } else if ("--local".equals(opt)) {
+ localOnly = true;
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
pw.println(" -d: include dalvik details when dumping process details.");
pw.println(" -c: dump in a compact machine-parseable representation.");
pw.println(" --oom: only show processes organized by oom adj.");
+ pw.println(" --local: only collect details locally, don't call process.");
pw.println("If [process] is specified it can be the name or ");
pw.println("pid of a specific process to dump.");
return;
@@ -11857,7 +12032,7 @@ public final class ActivityManagerService extends ActivityManagerNative
thread = r.thread;
pid = r.pid;
oomAdj = r.getSetAdjWithServices();
- hasActivities = r.hasActivities;
+ hasActivities = r.activities.size() > 0;
}
if (thread != null) {
if (!isCheckinRequest && dumpDetails) {
@@ -11873,14 +12048,22 @@ public final class ActivityManagerService extends ActivityManagerNative
mi.dalvikPrivateDirty = (int)tmpLong[0];
}
if (dumpDetails) {
- try {
- pw.flush();
- thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, innerArgs);
- } catch (RemoteException e) {
- if (!isCheckinRequest) {
- pw.println("Got RemoteException!");
+ if (localOnly) {
+ ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
+ dumpDalvik, pid, r.processName, 0, 0, 0, 0, 0, 0);
+ if (isCheckinRequest) {
+ pw.println();
+ }
+ } else {
+ try {
pw.flush();
+ thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails,
+ dumpDalvik, innerArgs);
+ } catch (RemoteException e) {
+ if (!isCheckinRequest) {
+ pw.println("Got RemoteException!");
+ pw.flush();
+ }
}
}
}
@@ -13195,7 +13378,7 @@ public final class ActivityManagerService extends ActivityManagerNative
String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
if (list != null && (list.length > 0)) {
for (String pkg : list) {
- forceStopPackageLocked(pkg, -1, false, true, true, false, userId,
+ forceStopPackageLocked(pkg, -1, false, true, true, false, false, userId,
"storage unmount");
}
sendPackageBroadcastLocked(
@@ -13207,10 +13390,13 @@ public final class ActivityManagerService extends ActivityManagerNative
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, userId, removed ? "pkg removed" : "pkg changed");
+ false, fullUninstall, userId,
+ removed ? "pkg removed" : "pkg changed");
}
if (removed) {
sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED,
@@ -13688,7 +13874,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final long origId = Binder.clearCallingIdentity();
// Instrumentation can kill and relaunch even persistent processes
- forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId,
+ forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
"start instr");
ProcessRecord app = addAppLocked(ai, false);
app.instrumentationClass = className;
@@ -13756,7 +13942,7 @@ public final class ActivityManagerService extends ActivityManagerNative
app.instrumentationProfileFile = null;
app.instrumentationArguments = null;
- forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId,
+ forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false, app.userId,
"finished inst");
}
@@ -14079,7 +14265,6 @@ public final class ActivityManagerService extends ActivityManagerNative
app.adjTarget = null;
app.empty = false;
app.cached = false;
- app.hasClientActivities = false;
final int activitiesSize = app.activities.size();
@@ -14089,7 +14274,6 @@ public final class ActivityManagerService extends ActivityManagerNative
app.adjType = "fixed";
app.adjSeq = mAdjSeq;
app.curRawAdj = app.maxAdj;
- app.hasActivities = false;
app.foregroundActivities = false;
app.keeping = true;
app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
@@ -14101,16 +14285,12 @@ public final class ActivityManagerService extends ActivityManagerNative
app.systemNoUi = true;
if (app == TOP_APP) {
app.systemNoUi = false;
- app.hasActivities = true;
} else if (activitiesSize > 0) {
for (int j = 0; j < activitiesSize; j++) {
final ActivityRecord r = app.activities.get(j);
if (r.visible) {
app.systemNoUi = false;
}
- if (r.app == app) {
- app.hasActivities = true;
- }
}
}
if (!app.systemNoUi) {
@@ -14121,7 +14301,6 @@ public final class ActivityManagerService extends ActivityManagerNative
app.keeping = false;
app.systemNoUi = false;
- app.hasActivities = false;
// Determine the importance of the process, starting with most
// important to least, and assign an appropriate OOM adjustment.
@@ -14138,7 +14317,6 @@ public final class ActivityManagerService extends ActivityManagerNative
app.adjType = "top-activity";
foregroundActivities = true;
interesting = true;
- app.hasActivities = true;
procState = ActivityManager.PROCESS_STATE_TOP;
} else if (app.instrumentationClass != null) {
// Don't want to kill running instrumentation.
@@ -14187,7 +14365,6 @@ public final class ActivityManagerService extends ActivityManagerNative
+ app + "?!?");
continue;
}
- app.hasActivities = true;
if (r.visible) {
// App has a visible activity; only upgrade adjustment.
if (adj > ProcessList.VISIBLE_APP_ADJ) {
@@ -14436,27 +14613,6 @@ public final class ActivityManagerService extends ActivityManagerNative
clientAdj = adj;
}
}
- } else if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) {
- if ((cr.flags&Context.BIND_NOT_VISIBLE) == 0) {
- // If this connection is keeping the service
- // created, then we want to try to better follow
- // its memory management semantics for activities.
- // That is, if it is sitting in the background
- // LRU list as a cached process (with activities),
- // we don't want the service it is connected to
- // to go into the empty LRU and quickly get killed,
- // because all we'll do is just end up restarting
- // the service.
- if (client.hasActivities) {
- if (procState >
- ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT) {
- procState =
- ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
- app.adjType = "cch-client-act";
- }
- app.hasClientActivities = true;
- }
- }
}
if (adj > clientAdj) {
// If this process has recently shown UI, and
@@ -14674,6 +14830,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY && app.hasClientActivities) {
+ // This is a cached process, but with client activities. Mark it so.
+ procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+ app.adjType = "cch-client-act";
+ }
+
if (adj == ProcessList.SERVICE_ADJ) {
if (doingAll) {
app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
@@ -15302,7 +15464,6 @@ public final class ActivityManagerService extends ActivityManagerNative
// application processes based on their current state.
int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
int nextCachedAdj = curCachedAdj+1;
- int curClientCachedAdj = curCachedAdj+1;
int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
int nextEmptyAdj = curEmptyAdj+2;
for (int i=N-1; i>=0; i--) {
@@ -15317,11 +15478,15 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
switch (app.curProcState) {
case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
// This process is a cached process holding activities...
// assign it the next cached value for that type, and then
// step that cached level.
app.curRawAdj = curCachedAdj;
app.curAdj = app.modifyRawOomAdj(curCachedAdj);
+ if (DEBUG_LRU && false) Slog.d(TAG, "Assigning activity LRU #" + i
+ + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+ + ")");
if (curCachedAdj != nextCachedAdj) {
stepCached++;
if (stepCached >= cachedFactor) {
@@ -15331,25 +15496,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
}
- if (curClientCachedAdj <= curCachedAdj) {
- curClientCachedAdj = curCachedAdj + 1;
- if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
- curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
- }
- }
}
}
break;
- case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
- // Special case for cached client processes... just step
- // down from after regular cached processes.
- app.curRawAdj = curClientCachedAdj;
- app.curAdj = app.modifyRawOomAdj(curClientCachedAdj);
- curClientCachedAdj++;
- if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
- curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
- }
- break;
default:
// For everything else, assign next empty cached process
// level and bump that up. Note that this means that
@@ -15358,6 +15507,9 @@ public final class ActivityManagerService extends ActivityManagerNative
// state is still as a service), which is what we want.
app.curRawAdj = curEmptyAdj;
app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
+ if (DEBUG_LRU && false) Slog.d(TAG, "Assigning empty LRU #" + i
+ + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
+ + ")");
if (curEmptyAdj != nextEmptyAdj) {
stepEmpty++;
if (stepEmpty >= emptyFactor) {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 44ff3bc..596c84d 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -36,7 +36,6 @@ import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
-import android.os.Trace;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.util.Objects;
import com.android.server.Watchdog;
@@ -61,16 +60,17 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.EventLog;
import android.util.Slog;
@@ -566,7 +566,7 @@ final class ActivityStack {
// Move userId's tasks to the top.
int index = mTaskHistory.size();
- for (int i = 0; i < index; ++i) {
+ for (int i = 0; i < index; ) {
TaskRecord task = mTaskHistory.get(i);
if (task.userId == userId) {
if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() +
@@ -574,6 +574,9 @@ final class ActivityStack {
mTaskHistory.remove(i);
mTaskHistory.add(task);
--index;
+ // Use same value for i.
+ } else {
+ ++i;
}
}
if (VALIDATE_TOKENS) {
@@ -997,8 +1000,8 @@ final class ActivityStack {
if (r.isHomeActivity()) {
return true;
}
- if (!r.finishing && r.visible && r.fullscreen) {
- // Passed activity is over a visible fullscreen activity.
+ if (!r.finishing && r.fullscreen) {
+ // Passed activity is over a fullscreen activity.
return false;
}
}
@@ -1091,6 +1094,7 @@ final class ActivityStack {
if (!r.visible) {
if (DEBUG_VISBILITY) Slog.v(
TAG, "Starting and making visible: " + r);
+ r.visible = true;
mWindowManager.setAppVisibility(r.appToken, true);
}
if (r != starting) {
@@ -1141,7 +1145,7 @@ final class ActivityStack {
} else if (isActivityOverHome(r)) {
if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
showHomeBehindStack = true;
- behindFullscreen = true;
+ behindFullscreen = !isHomeStack();
}
} else {
if (DEBUG_VISBILITY) Slog.v(
@@ -1284,17 +1288,7 @@ final class ActivityStack {
if (prevTask != null && prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) {
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
if (prevTask == nextTask) {
- ArrayList<ActivityRecord> activities = prevTask.mActivities;
- final int numActivities = activities.size();
- for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
- // r is usually the same as next, but what if two activities were launched
- // before prev finished?
- if (!r.finishing) {
- r.frontOfTask = true;
- break;
- }
- }
+ prevTask.setFrontOfTask();
} else if (prevTask != topTask()) {
// This task is going away but it was supposed to return to the home task.
// Now the task above it has to return to the home task instead.
@@ -1398,7 +1392,7 @@ final class ActivityStack {
if (next.app != null && next.app.thread != null) {
// No reason to do full oom adj update here; we'll let that
// happen whenever it needs to later.
- mService.updateLruProcessLocked(next.app, false, true);
+ mService.updateLruProcessLocked(next.app, true, null);
}
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
return true;
@@ -1526,8 +1520,9 @@ final class ActivityStack {
mResumedActivity = next;
next.task.touchActiveTime();
mService.addRecentTaskLocked(next.task);
- mService.updateLruProcessLocked(next.app, true, true);
+ mService.updateLruProcessLocked(next.app, true, null);
updateLRUListLocked(next);
+ mService.updateOomAdjLocked();
// Have the window manager re-evaluate the orientation of
// the screen based on the new activity order.
@@ -1713,7 +1708,7 @@ final class ActivityStack {
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
(r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
- r.userId);
+ r.userId, r.info.configChanges);
if (VALIDATE_TOKENS) {
validateAppTokensLocked();
}
@@ -1745,9 +1740,9 @@ final class ActivityStack {
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to stack to task " + task,
new RuntimeException("here").fillInStackTrace());
task.addActivityToTop(r);
+ task.setFrontOfTask();
r.putInHistory();
- r.frontOfTask = newTask;
if (!isHomeStack() || numActivities() > 0) {
// We want to show the starting preview window if we are
// switching to a new task, or the next activity's process is
@@ -1774,7 +1769,8 @@ final class ActivityStack {
r.updateOptionsLocked(options);
mWindowManager.addAppToken(task.mActivities.indexOf(r),
r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId);
+ (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
+ r.info.configChanges);
boolean doShow = true;
if (newTask) {
// Even though this activity is starting fresh, we still need
@@ -1817,7 +1813,8 @@ final class ActivityStack {
// because there is nothing for it to animate on top of.
mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
- (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId);
+ (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
+ r.info.configChanges);
ActivityOptions.abort(options);
}
if (VALIDATE_TOKENS) {
@@ -1907,26 +1904,38 @@ final class ActivityStack {
// bottom of the activity stack. This also keeps it
// correctly ordered with any activities we previously
// moved.
+ final ThumbnailHolder newThumbHolder;
+ final TaskRecord targetTask;
final ActivityRecord bottom =
!mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ?
- mTaskHistory.get(0).mActivities.get(0) : null;
+ mTaskHistory.get(0).mActivities.get(0) : null;
if (bottom != null && target.taskAffinity != null
&& target.taskAffinity.equals(bottom.task.affinity)) {
// If the activity currently at the bottom has the
// same task affinity as the one we are moving,
// then merge it into the same task.
- target.setTask(bottom.task, bottom.thumbHolder, false);
+ targetTask = bottom.task;
+ newThumbHolder = bottom.thumbHolder == null ? targetTask : bottom.thumbHolder;
if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+ " out to bottom task " + bottom.task);
} else {
- target.setTask(createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
- null, false), null, false);
- target.task.affinityIntent = target.intent;
+ targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
+ null, false);
+ newThumbHolder = targetTask;
+ targetTask.affinityIntent = target.intent;
if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+ " out to new task " + target.task);
}
- final TaskRecord targetTask = target.task;
+ if (clearWhenTaskReset) {
+ // This is the start of a new sub-task.
+ if (target.thumbHolder == null) {
+ target.thumbHolder = new ThumbnailHolder();
+ }
+ } else {
+ target.thumbHolder = newThumbHolder;
+ }
+
final int targetTaskId = targetTask.taskId;
mWindowManager.setAppGroupId(target.appToken, targetTaskId);
@@ -1947,8 +1956,8 @@ final class ActivityStack {
}
}
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task="
- + task + " adding to task=" + targetTask,
- new RuntimeException("here").fillInStackTrace());
+ + task + " adding to task=" + targetTask
+ + " Callers=" + Debug.getCallers(4));
if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p
+ " out to target's task " + target.task);
p.setTask(targetTask, curThumbHolder, false);
@@ -2394,15 +2403,12 @@ final class ActivityStack {
final ArrayList<ActivityRecord> activities = r.task.mActivities;
final int index = activities.indexOf(r);
if (index < (activities.size() - 1)) {
- ActivityRecord next = activities.get(index+1);
- if (r.frontOfTask) {
- // The next activity is now the front of the task.
- next.frontOfTask = true;
- }
+ r.task.setFrontOfTask();
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
// If the caller asked that this activity (and all above it)
// be cleared when the task is reset, don't lose that information,
// but propagate it up to the next activity.
+ ActivityRecord next = activities.get(index+1);
next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
}
}
@@ -2781,7 +2787,7 @@ final class ActivityStack {
}
if (r.app.activities.isEmpty()) {
// No longer have activities, so update LRU list and oom adj.
- mService.updateLruProcessLocked(r.app, false, false);
+ mService.updateLruProcessLocked(r.app, false, null);
mService.updateOomAdjLocked();
}
}
@@ -3142,7 +3148,9 @@ final class ActivityStack {
final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
if (task == tr && task.mOnTopOfHome || numTasks <= 1) {
- task.mOnTopOfHome = false;
+ if (task != null) {
+ task.mOnTopOfHome = false;
+ }
return mStackSupervisor.resumeHomeActivity(null);
}
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 7650a65..d616f1b 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -905,7 +905,8 @@ public final class ActivityStackSupervisor {
if (idx < 0) {
app.activities.add(r);
}
- mService.updateLruProcessLocked(app, true, true);
+ mService.updateLruProcessLocked(app, true, null);
+ mService.updateOomAdjLocked();
final ActivityStack stack = r.task.stack;
try {
@@ -1133,6 +1134,19 @@ public final class ActivityStackSupervisor {
resultRecord.removeResultsLocked(
sourceRecord, resultWho, requestCode);
}
+ if (sourceRecord.launchedFromUid == callingUid) {
+ // The new activity is being launched from the same uid as the previous
+ // activity in the flow, and asking to forward its result back to the
+ // previous. In this case the activity is serving as a trampoline between
+ // the two, so we also want to update its launchedFromPackage to be the
+ // same as the previous activity. Note that this is safe, since we know
+ // these two packages come from the same uid; the caller could just as
+ // well have supplied that same package name itself. This specifially
+ // deals with the case of an intent picker/chooser being launched in the app
+ // flow to redirect to an activity picked by the user, where we want the final
+ // activity to consider it to have been launched by the previous app activity.
+ callingPackage = sourceRecord.launchedFromPackage;
+ }
}
if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
@@ -1385,17 +1399,22 @@ public final class ActivityStackSupervisor {
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
+ ActivityInfo newTaskInfo = null;
+ Intent newTaskIntent = null;
final ActivityStack sourceStack;
if (sourceRecord != null) {
if (sourceRecord.finishing) {
// If the source is finishing, we can't further count it as our source. This
// is because the task it is associated with may now be empty and on its way out,
// so we don't want to blindly throw it in to that task. Instead we will take
- // the NEW_TASK flow and try to find a task for it.
+ // the NEW_TASK flow and try to find a task for it. But save the task information
+ // so it can be used when creating the new task.
if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
Slog.w(TAG, "startActivity called from finishing " + sourceRecord
+ "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+ newTaskInfo = sourceRecord.info;
+ newTaskIntent = sourceRecord.task.intent;
}
sourceRecord = null;
sourceStack = null;
@@ -1667,8 +1686,10 @@ public final class ActivityStackSupervisor {
targetStack = adjustStackFocus(r);
moveHomeStack(targetStack.isHomeStack());
if (reuseTask == null) {
- r.setTask(targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),
- null, true);
+ r.setTask(targetStack.createTaskRecord(getNextTaskId(),
+ newTaskInfo != null ? newTaskInfo : r.info,
+ newTaskIntent != null ? newTaskIntent : intent,
+ true), null, true);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
} else {
@@ -1688,6 +1709,7 @@ public final class ActivityStackSupervisor {
TaskRecord sourceTask = sourceRecord.task;
targetStack = sourceTask.stack;
moveHomeStack(targetStack.isHomeStack());
+ mWindowManager.moveTaskToTop(sourceTask.taskId);
if (!addingToTask &&
(launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
// In this case, we are adding the activity to an existing
@@ -1746,6 +1768,7 @@ public final class ActivityStackSupervisor {
r.setTask(prev != null ? prev.task
: targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),
null, true);
+ mWindowManager.moveTaskToTop(r.task.taskId);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ " in new guessed " + r.task);
}
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 0dd950e..2d59678 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -419,6 +419,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
}
}
+ public void noteWifiBatchedScanStartedFromSource(WorkSource ws, int csph) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteWifiBatchedScanStartedFromSourceLocked(ws, csph);
+ }
+ }
+
+ public void noteWifiBatchedScanStoppedFromSource(WorkSource ws) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteWifiBatchedScanStoppedFromSourceLocked(ws);
+ }
+ }
+
public void noteWifiMulticastEnabledFromSource(WorkSource ws) {
enforceCallingPermission();
synchronized (mStats) {
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index 5e80135..bfb667f 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -27,6 +27,7 @@ import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
@@ -220,7 +221,8 @@ public final class BroadcastQueue {
r.curApp = app;
app.curReceiver = r;
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
- mService.updateLruProcessLocked(app, true, false);
+ mService.updateLruProcessLocked(app, false, null);
+ mService.updateOomAdjLocked();
// Tell the application to launch this receiver.
r.intent.setComponent(r.curComponent);
@@ -813,6 +815,26 @@ public final class BroadcastQueue {
+ " to " + r.curApp + ": process crashing");
skip = true;
}
+ if (!skip) {
+ boolean isAvailable = false;
+ try {
+ isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
+ info.activityInfo.packageName,
+ UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
+ } catch (Exception e) {
+ // all such failures mean we skip this receiver
+ Slog.w(TAG, "Exception getting recipient info for "
+ + info.activityInfo.packageName, e);
+ }
+ if (!isAvailable) {
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG, "Skipping delivery to " + info.activityInfo.packageName
+ + " / " + info.activityInfo.applicationInfo.uid
+ + " : package no longer available");
+ }
+ skip = true;
+ }
+ }
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG,
diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java
index 576adc2..423e540 100644
--- a/services/java/com/android/server/am/ConnectionRecord.java
+++ b/services/java/com/android/server/am/ConnectionRecord.java
@@ -27,7 +27,7 @@ import java.io.PrintWriter;
*/
final class ConnectionRecord {
final AppBindRecord binding; // The application/service binding.
- final ActivityRecord activity; // If non-null, the owning activity.
+ final ActivityRecord activity; // If non-null, the owning activity.
final IServiceConnection conn; // The client connection.
final int flags; // Binding options.
final int clientLabel; // String resource labeling this client.
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 486e916..217a8d6 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -46,7 +46,7 @@ import java.util.ArrayList;
* is currently running.
*/
final class ProcessRecord {
- final BatteryStatsImpl.Uid.Proc batteryStats; // where to collect runtime statistics
+ private final BatteryStatsImpl mBatteryStats; // where to collect runtime statistics
final ApplicationInfo info; // all about the first app in the process
final boolean isolated; // true if this is a special isolated process
final int uid; // uid of process; may be different from 'info' if isolated
@@ -86,7 +86,6 @@ final class ProcessRecord {
boolean keeping; // Actively running code so don't kill due to that?
boolean setIsForeground; // Running foreground UI when last set?
boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
- boolean hasActivities; // Are there any activities running in this process?
boolean hasClientActivities; // Are there any client services with activities?
boolean hasStartedServices; // Are there any started services running in this process?
boolean foregroundServices; // Running any services that are foreground?
@@ -265,9 +264,8 @@ final class ProcessRecord {
pw.print(prefix); pw.print("persistent="); pw.print(persistent);
pw.print(" removed="); pw.println(removed);
}
- if (hasActivities || hasClientActivities || foregroundActivities) {
- pw.print(prefix); pw.print("hasActivities="); pw.print(hasActivities);
- pw.print(" hasClientActivities="); pw.print(hasClientActivities);
+ if (hasClientActivities || foregroundActivities) {
+ pw.print(prefix); pw.print("hasClientActivities="); pw.print(hasClientActivities);
pw.print(" foregroundActivities="); pw.println(foregroundActivities);
}
if (hasStartedServices) {
@@ -275,8 +273,8 @@ final class ProcessRecord {
}
if (!keeping) {
long wtime;
- synchronized (batteryStats.getBatteryStats()) {
- wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid,
+ synchronized (mBatteryStats) {
+ wtime = mBatteryStats.getProcessWakeTime(info.uid,
pid, SystemClock.elapsedRealtime());
}
long timeUsed = wtime - lastWakeTime;
@@ -361,9 +359,9 @@ final class ProcessRecord {
}
}
- ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, ApplicationInfo _info,
+ ProcessRecord(BatteryStatsImpl _batteryStats, ApplicationInfo _info,
String _processName, int _uid) {
- batteryStats = _batteryStats;
+ mBatteryStats = _batteryStats;
info = _info;
isolated = _info.uid != _uid;
uid = _uid;
diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java
index 8d16880..e05fcda 100644
--- a/services/java/com/android/server/am/ProcessStatsService.java
+++ b/services/java/com/android/server/am/ProcessStatsService.java
@@ -750,23 +750,12 @@ public final class ProcessStatsService extends IProcessStats.Stub {
return;
} else {
// Not an option, last argument must be a package name.
- try {
- IPackageManager pm = AppGlobals.getPackageManager();
- if (pm.getPackageUid(arg, UserHandle.getCallingUserId()) >= 0) {
- reqPackage = arg;
- // Include all details, since we know we are only going to
- // be dumping a smaller set of data. In fact only the details
- // container per-package data, so that are needed to be able
- // to dump anything at all when filtering by package.
- dumpDetails = true;
- }
- } catch (RemoteException e) {
- }
- if (reqPackage == null) {
- pw.println("Unknown package: " + arg);
- dumpHelp(pw);
- return;
- }
+ reqPackage = arg;
+ // Include all details, since we know we are only going to
+ // be dumping a smaller set of data. In fact only the details
+ // container per-package data, so that are needed to be able
+ // to dump anything at all when filtering by package.
+ dumpDetails = true;
}
}
}
@@ -816,13 +805,14 @@ public final class ProcessStatsService extends IProcessStats.Stub {
}
return;
} else if (aggregateHours != 0) {
+ pw.print("AGGREGATED OVER LAST "); pw.print(aggregateHours); pw.println(" HOURS:");
dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
dumpDetails, dumpFullDetails, dumpAll, activeOnly);
return;
}
boolean sepNeeded = false;
- if (!currentOnly || isCheckin) {
+ if (dumpAll || isCheckin) {
mWriteLock.lock();
try {
ArrayList<String> files = getCommittedFiles(0, false, !isCheckin);
@@ -882,11 +872,11 @@ public final class ProcessStatsService extends IProcessStats.Stub {
}
}
if (!isCheckin) {
- if (dumpAll) {
+ if (!currentOnly) {
if (sepNeeded) {
pw.println();
- pw.println("AGGREGATED OVER LAST 24 HOURS:");
}
+ pw.println("AGGREGATED OVER LAST 24 HOURS:");
dumpAggregatedStats(pw, 24, now, reqPackage, isCompact,
dumpDetails, dumpFullDetails, dumpAll, activeOnly);
pw.println();
@@ -901,8 +891,8 @@ public final class ProcessStatsService extends IProcessStats.Stub {
} else {
if (sepNeeded) {
pw.println();
- pw.println("CURRENT STATS:");
}
+ pw.println("CURRENT STATS:");
if (dumpDetails || dumpFullDetails) {
mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
activeOnly);
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index cc1172a..80e6e94 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -85,6 +85,7 @@ final class ServiceRecord extends Binder {
ProcessRecord app; // where this service is running or null.
ProcessRecord isolatedProc; // keep track of isolated process, if requested
ProcessStats.ServiceState tracker; // tracking service execution, may be null
+ ProcessStats.ServiceState restartTracker; // tracking service restart
boolean delayed; // are we waiting to start this service in the background?
boolean isForeground; // is service currently in foreground mode?
int foregroundId; // Notification ID of last foreground req.
@@ -340,6 +341,19 @@ final class ServiceRecord extends Binder {
}
}
+ public void makeRestarting(int memFactor, long now) {
+ if (restartTracker == null) {
+ if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
+ restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
+ serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+ }
+ if (restartTracker == null) {
+ return;
+ }
+ }
+ restartTracker.setRestarting(true, memFactor, now);
+ }
+
public AppBindRecord retrieveAppBindingLocked(Intent intent,
ProcessRecord app) {
Intent.FilterComparison filter = new Intent.FilterComparison(intent);
diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java
index 3d568ff..9105103 100644
--- a/services/java/com/android/server/am/TaskRecord.java
+++ b/services/java/com/android/server/am/TaskRecord.java
@@ -159,18 +159,33 @@ final class TaskRecord extends ThumbnailHolder {
return null;
}
+ /** Call after activity movement or finish to make sure that frontOfTask is set correctly */
+ final void setFrontOfTask() {
+ boolean foundFront = false;
+ final int numActivities = mActivities.size();
+ for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
+ final ActivityRecord r = mActivities.get(activityNdx);
+ if (foundFront || r.finishing) {
+ r.frontOfTask = false;
+ } else {
+ r.frontOfTask = true;
+ // Set frontOfTask false for every following activity.
+ foundFront = true;
+ }
+ }
+ }
+
/**
- * Reorder the history stack so that the activity at the given index is
- * brought to the front.
+ * Reorder the history stack so that the passed activity is brought to the front.
*/
final void moveActivityToFrontLocked(ActivityRecord newTop) {
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + newTop
+ " to stack at top", new RuntimeException("here").fillInStackTrace());
- getTopActivity().frontOfTask = false;
mActivities.remove(newTop);
mActivities.add(newTop);
- newTop.frontOfTask = true;
+
+ setFrontOfTask();
}
void addActivityAtBottom(ActivityRecord r) {
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index f5a7039..8f25860 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -45,6 +45,7 @@ import android.net.LinkProperties;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.NetworkInfo;
+import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.NetworkInfo.DetailedState;
import android.os.Binder;
@@ -77,6 +78,7 @@ import com.android.server.net.BaseNetworkObserver;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
@@ -282,13 +284,12 @@ public class Vpn extends BaseNetworkStateTracker {
}
/**
- * Protect a socket from routing changes by binding it to the given
- * interface. The socket is NOT closed by this method.
+ * Protect a socket from VPN rules by binding it to the main routing table.
+ * The socket is NOT closed by this method.
*
* @param socket The socket to be bound.
- * @param interfaze The name of the interface.
*/
- public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
+ public void protect(ParcelFileDescriptor socket) throws Exception {
PackageManager pm = mContext.getPackageManager();
int appUid = pm.getPackageUid(mPackage, mUserId);
@@ -302,8 +303,6 @@ public class Vpn extends BaseNetworkStateTracker {
} finally {
Binder.restoreCallingIdentity(token);
}
- // bind the socket to the interface
- jniProtect(socket.getFd(), interfaze);
}
@@ -353,6 +352,12 @@ public class Vpn extends BaseNetworkStateTracker {
Binder.restoreCallingIdentity(token);
}
+ // Save the old config in case we need to go back.
+ VpnConfig oldConfig = mConfig;
+ String oldInterface = mInterface;
+ Connection oldConnection = mConnection;
+ SparseBooleanArray oldUsers = mVpnUsers;
+
// Configure the interface. Abort if any of these steps fails.
ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
try {
@@ -372,12 +377,7 @@ public class Vpn extends BaseNetworkStateTracker {
new UserHandle(mUserId))) {
throw new IllegalStateException("Cannot bind " + config.user);
}
- if (mConnection != null) {
- mContext.unbindService(mConnection);
- }
- if (mInterface != null && !mInterface.equals(interfaze)) {
- jniReset(mInterface);
- }
+
mConnection = connection;
mInterface = interfaze;
@@ -386,55 +386,91 @@ public class Vpn extends BaseNetworkStateTracker {
config.interfaze = mInterface;
config.startTime = SystemClock.elapsedRealtime();
mConfig = config;
+
// Set up forwarding and DNS rules.
mVpnUsers = new SparseBooleanArray();
token = Binder.clearCallingIdentity();
try {
mCallback.setMarkedForwarding(mInterface);
- mCallback.setRoutes(interfaze, config.routes);
+ mCallback.setRoutes(mInterface, config.routes);
mCallback.override(mInterface, config.dnsServers, config.searchDomains);
addVpnUserLocked(mUserId);
-
+ // If we are owner assign all Restricted Users to this VPN
+ if (mUserId == UserHandle.USER_OWNER) {
+ for (UserInfo user : mgr.getUsers()) {
+ if (user.isRestricted()) {
+ try {
+ addVpnUserLocked(user.id);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN");
+ }
+ }
+ }
+ }
} finally {
Binder.restoreCallingIdentity(token);
}
+ if (oldConnection != null) {
+ mContext.unbindService(oldConnection);
+ }
+ if (oldInterface != null && !oldInterface.equals(interfaze)) {
+ // Remove the old tun's user forwarding rules
+ // The new tun's user rules have already been added so they will take over
+ // as rules are deleted. This prevents data leakage as the rules are moved over.
+ token = Binder.clearCallingIdentity();
+ try {
+ final int size = oldUsers.size();
+ final boolean forwardDns = (oldConfig.dnsServers != null &&
+ oldConfig.dnsServers.size() != 0);
+ for (int i = 0; i < size; i++) {
+ int user = oldUsers.keyAt(i);
+ mCallback.clearUserForwarding(oldInterface, user, forwardDns);
+ }
+ mCallback.clearMarkedForwarding(oldInterface);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ jniReset(oldInterface);
+ }
} catch (RuntimeException e) {
updateState(DetailedState.FAILED, "establish");
IoUtils.closeQuietly(tun);
// make sure marked forwarding is cleared if it was set
+ token = Binder.clearCallingIdentity();
try {
mCallback.clearMarkedForwarding(mInterface);
} catch (Exception ingored) {
// ignored
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
+ // restore old state
+ mConfig = oldConfig;
+ mConnection = oldConnection;
+ mVpnUsers = oldUsers;
+ mInterface = oldInterface;
throw e;
}
Log.i(TAG, "Established by " + config.user + " on " + mInterface);
-
- // If we are owner assign all Restricted Users to this VPN
- if (mUserId == UserHandle.USER_OWNER) {
- token = Binder.clearCallingIdentity();
- try {
- for (UserInfo user : mgr.getUsers()) {
- if (user.isRestricted()) {
- try {
- addVpnUserLocked(user.id);
- } catch (Exception e) {
- Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN");
- }
- }
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
// TODO: ensure that contract class eventually marks as connected
updateState(DetailedState.AUTHENTICATING, "establish");
return tun;
}
+ /**
+ * Check if a given address is covered by the VPN's routing rules.
+ */
+ public boolean isAddressCovered(InetAddress address) {
+ synchronized (Vpn.this) {
+ if (!isRunningLocked()) {
+ return false;
+ }
+ return RouteInfo.selectBestRoute(mConfig.routes, address) != null;
+ }
+ }
+
private boolean isRunningLocked() {
return mVpnUsers != null;
}
@@ -670,7 +706,6 @@ public class Vpn extends BaseNetworkStateTracker {
private native int jniSetRoutes(String interfaze, String routes);
private native void jniReset(String interfaze);
private native int jniCheck(String interfaze);
- private native void jniProtect(int socket, String interfaze);
private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) {
for (RouteInfo route : prop.getAllRoutes()) {
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java
index cb35ef1..023bf2b 100644
--- a/services/java/com/android/server/content/ContentService.java
+++ b/services/java/com/android/server/content/ContentService.java
@@ -660,7 +660,7 @@ public final class ContentService extends IContentService.Stub {
int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
- return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
+ return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
} finally {
restoreCallingIdentity(identityToken);
}
diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java
index 71d8d99..9e3dad6 100644
--- a/services/java/com/android/server/content/SyncManager.java
+++ b/services/java/com/android/server/content/SyncManager.java
@@ -2352,7 +2352,7 @@ public class SyncManager {
Log.v(TAG, "canceling and rescheduling sync since an initialization "
+ "takes higher priority, " + conflict);
}
- } else if (candidate.expedited && !conflict.mSyncOperation.expedited
+ } else if (candidate.isExpedited() && !conflict.mSyncOperation.isExpedited()
&& (candidateIsInitialization
== conflict.mSyncOperation.isInitialization())) {
toReschedule = conflict;
diff --git a/services/java/com/android/server/content/SyncOperation.java b/services/java/com/android/server/content/SyncOperation.java
index 4856747..67e3b09 100644
--- a/services/java/com/android/server/content/SyncOperation.java
+++ b/services/java/com/android/server/content/SyncOperation.java
@@ -66,7 +66,8 @@ public class SyncOperation implements Comparable {
public final boolean allowParallelSyncs;
public Bundle extras;
public final String key;
- public boolean expedited;
+ /** Internal boolean to avoid reading a bundle everytime we want to compare operations. */
+ private final boolean expedited;
public SyncStorageEngine.PendingOperation pendingOperation;
/** Elapsed real time in millis at which to run this sync. */
public long latestRunTime;
@@ -79,7 +80,7 @@ public class SyncOperation implements Comparable {
* Depends on max(backoff, latestRunTime, and delayUntil).
*/
public long effectiveRunTime;
- /** Amount of time before {@link effectiveRunTime} from which this sync can run. */
+ /** Amount of time before {@link #effectiveRunTime} from which this sync can run. */
public long flexTime;
public SyncOperation(Account account, int userId, int reason, int source, String authority,
@@ -98,11 +99,16 @@ public class SyncOperation implements Comparable {
this.backoff = backoff;
final long now = SystemClock.elapsedRealtime();
// Checks the extras bundle. Must occur after we set the internal bundle.
- if (runTimeFromNow < 0 || isExpedited()) {
+ if (runTimeFromNow < 0) {
+ // Sanity check: Will always be true.
+ if (!this.extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
+ this.extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ }
this.expedited = true;
this.latestRunTime = now;
this.flexTime = 0;
} else {
+ this.extras.remove(ContentResolver.SYNC_EXTRAS_EXPEDITED);
this.expedited = false;
this.latestRunTime = now + runTimeFromNow;
this.flexTime = flexTime;
@@ -111,6 +117,24 @@ public class SyncOperation implements Comparable {
this.key = toKey();
}
+ /** Only used to immediately reschedule a sync. */
+ SyncOperation(SyncOperation other) {
+ this.service = other.service;
+ this.account = other.account;
+ this.authority = other.authority;
+ this.userId = other.userId;
+ this.reason = other.reason;
+ this.syncSource = other.syncSource;
+ this.extras = new Bundle(other.extras);
+ this.expedited = other.expedited;
+ this.latestRunTime = SystemClock.elapsedRealtime();
+ this.flexTime = 0L;
+ this.backoff = other.backoff;
+ this.allowParallelSyncs = other.allowParallelSyncs;
+ this.updateEffectiveRunTime();
+ this.key = toKey();
+ }
+
/**
* Make sure the bundle attached to this SyncOperation doesn't have unnecessary
* flags set.
@@ -138,24 +162,6 @@ public class SyncOperation implements Comparable {
}
}
- /** Only used to immediately reschedule a sync. */
- SyncOperation(SyncOperation other) {
- this.service = other.service;
- this.account = other.account;
- this.authority = other.authority;
- this.userId = other.userId;
- this.reason = other.reason;
- this.syncSource = other.syncSource;
- this.extras = new Bundle(other.extras);
- this.expedited = other.expedited;
- this.latestRunTime = SystemClock.elapsedRealtime();
- this.flexTime = 0L;
- this.backoff = other.backoff;
- this.allowParallelSyncs = other.allowParallelSyncs;
- this.updateEffectiveRunTime();
- this.key = toKey();
- }
-
@Override
public String toString() {
return dump(null, true);
@@ -220,7 +226,7 @@ public class SyncOperation implements Comparable {
}
public boolean isExpedited() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) || expedited;
+ return expedited;
}
public boolean ignoreBackoff() {
diff --git a/services/java/com/android/server/content/SyncQueue.java b/services/java/com/android/server/content/SyncQueue.java
index 6f3fe6e..22fa2de 100644
--- a/services/java/com/android/server/content/SyncQueue.java
+++ b/services/java/com/android/server/content/SyncQueue.java
@@ -73,10 +73,9 @@ public class SyncQueue {
}
SyncOperation syncOperation = new SyncOperation(
op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras,
- 0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0,
+ op.expedited ? -1: 0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0,
mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
syncAdapterInfo.type.allowParallelSyncs());
- syncOperation.expedited = op.expedited;
syncOperation.pendingOperation = op;
add(syncOperation, op);
}
@@ -104,7 +103,6 @@ public class SyncQueue {
if (existingOperation != null) {
boolean changed = false;
if (operation.compareTo(existingOperation) <= 0 ) {
- existingOperation.expedited = operation.expedited;
long newRunTime =
Math.min(existingOperation.latestRunTime, operation.latestRunTime);
// Take smaller runtime.
@@ -123,7 +121,7 @@ public class SyncQueue {
if (operation.pendingOperation == null) {
pop = new SyncStorageEngine.PendingOperation(
operation.account, operation.userId, operation.reason, operation.syncSource,
- operation.authority, operation.extras, operation.expedited);
+ operation.authority, operation.extras, operation.isExpedited());
pop = mSyncStorageEngine.insertIntoPending(pop);
if (pop == null) {
throw new IllegalStateException("error adding pending sync operation "
diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java
index 41ef229..781280e 100644
--- a/services/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/java/com/android/server/content/SyncStorageEngine.java
@@ -501,7 +501,7 @@ public class SyncStorageEngine extends Handler {
* @return amount of seconds before syncTimeSeconds that the sync can occur.
* I.e.
* earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds)
- * The flex time is capped at a percentage of the {@link DEFAULT_POLL_FREQUENCY_SECONDS}.
+ * The flex time is capped at a percentage of the {@link #DEFAULT_POLL_FREQUENCY_SECONDS}.
*/
public static long calculateDefaultFlexTime(long syncTimeSeconds) {
if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) {
@@ -1295,20 +1295,40 @@ public class SyncStorageEngine extends Handler {
}
/**
- * Return a list of the currently active syncs. Note that the returned items are the
- * real, live active sync objects, so be careful what you do with it.
+ * Return a list of the currently active syncs. Note that the returned
+ * items are the real, live active sync objects, so be careful what you do
+ * with it.
*/
- public List<SyncInfo> getCurrentSyncs(int userId) {
+ private List<SyncInfo> getCurrentSyncs(int userId) {
synchronized (mAuthorities) {
- ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
- if (syncs == null) {
- syncs = new ArrayList<SyncInfo>();
- mCurrentSyncs.put(userId, syncs);
+ return getCurrentSyncsLocked(userId);
+ }
+ }
+
+ /**
+ * @return a copy of the current syncs data structure. Will not return
+ * null.
+ */
+ public List<SyncInfo> getCurrentSyncsCopy(int userId) {
+ synchronized (mAuthorities) {
+ final List<SyncInfo> syncs = getCurrentSyncsLocked(userId);
+ final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>();
+ for (SyncInfo sync : syncs) {
+ syncsCopy.add(new SyncInfo(sync));
}
- return syncs;
+ return syncsCopy;
}
}
+ private List<SyncInfo> getCurrentSyncsLocked(int userId) {
+ ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
+ if (syncs == null) {
+ syncs = new ArrayList<SyncInfo>();
+ mCurrentSyncs.put(userId, syncs);
+ }
+ return syncs;
+ }
+
/**
* Return an array of the current sync status for all authorities. Note
* that the objects inside the array are the real, live status objects,
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 249c8b0..bcb677f 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -35,6 +35,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -173,6 +174,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
// The Wifi display adapter, or null if not registered.
private WifiDisplayAdapter mWifiDisplayAdapter;
+ // The number of active wifi display scan requests.
+ private int mWifiDisplayScanRequestCount;
+
// The virtual display adapter, or null if not registered.
private VirtualDisplayAdapter mVirtualDisplayAdapter;
@@ -458,38 +462,94 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
}
}
- private void onCallbackDied(int pid) {
+ private void onCallbackDied(CallbackRecord record) {
synchronized (mSyncRoot) {
- mCallbacks.remove(pid);
+ mCallbacks.remove(record.mPid);
+ stopWifiDisplayScanLocked(record);
}
}
@Override // Binder call
- public void scanWifiDisplays() {
+ public void startWifiDisplayScan() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to start wifi display scans");
+
+ final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
+ CallbackRecord record = mCallbacks.get(callingPid);
+ if (record == null) {
+ throw new IllegalStateException("The calling process has not "
+ + "registered an IDisplayManagerCallback.");
+ }
+ startWifiDisplayScanLocked(record);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void startWifiDisplayScanLocked(CallbackRecord record) {
+ if (!record.mWifiDisplayScanRequested) {
+ record.mWifiDisplayScanRequested = true;
+ if (mWifiDisplayScanRequestCount++ == 0) {
if (mWifiDisplayAdapter != null) {
- mWifiDisplayAdapter.requestScanLocked();
+ mWifiDisplayAdapter.requestStartScanLocked();
+ }
+ }
+ }
+ }
+
+ @Override // Binder call
+ public void stopWifiDisplayScan() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to stop wifi display scans");
+
+ final int callingPid = Binder.getCallingPid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ CallbackRecord record = mCallbacks.get(callingPid);
+ if (record == null) {
+ throw new IllegalStateException("The calling process has not "
+ + "registered an IDisplayManagerCallback.");
}
+ stopWifiDisplayScanLocked(record);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
+ private void stopWifiDisplayScanLocked(CallbackRecord record) {
+ if (record.mWifiDisplayScanRequested) {
+ record.mWifiDisplayScanRequested = false;
+ if (--mWifiDisplayScanRequestCount == 0) {
+ if (mWifiDisplayAdapter != null) {
+ mWifiDisplayAdapter.requestStopScanLocked();
+ }
+ } else if (mWifiDisplayScanRequestCount < 0) {
+ Log.wtf(TAG, "mWifiDisplayScanRequestCount became negative: "
+ + mWifiDisplayScanRequestCount);
+ mWifiDisplayScanRequestCount = 0;
+ }
+ }
+ }
+
@Override // Binder call
public void connectWifiDisplay(String address) {
if (address == null) {
throw new IllegalArgumentException("address must not be null");
}
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to connect to a wifi display");
- final boolean trusted = canCallerConfigureWifiDisplay();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
if (mWifiDisplayAdapter != null) {
- mWifiDisplayAdapter.requestConnectLocked(address, trusted);
+ mWifiDisplayAdapter.requestConnectLocked(address);
}
}
} finally {
@@ -499,12 +559,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
@Override
public void pauseWifiDisplay() {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY"
- + "permission to pause a wifi display session.");
- }
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to pause a wifi display session");
final long token = Binder.clearCallingIdentity();
try {
@@ -520,12 +576,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
@Override
public void resumeWifiDisplay() {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY"
- + "permission to resume a wifi display session.");
- }
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to resume a wifi display session");
final long token = Binder.clearCallingIdentity();
try {
@@ -541,6 +593,11 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
@Override // Binder call
public void disconnectWifiDisplay() {
+ // This request does not require special permissions.
+ // Any app can request disconnection from the currently active wifi display.
+ // This exception should no longer be needed once wifi display control moves
+ // to the media router service.
+
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
@@ -558,10 +615,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
if (address == null) {
throw new IllegalArgumentException("address must not be null");
}
- if (!canCallerConfigureWifiDisplay()) {
- throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission to "
- + "rename a wifi display.");
- }
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to rename to a wifi display");
final long token = Binder.clearCallingIdentity();
try {
@@ -580,10 +635,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
if (address == null) {
throw new IllegalArgumentException("address must not be null");
}
- if (!canCallerConfigureWifiDisplay()) {
- throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission to "
- + "forget a wifi display.");
- }
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to forget to a wifi display");
final long token = Binder.clearCallingIdentity();
try {
@@ -599,6 +652,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
@Override // Binder call
public WifiDisplayStatus getWifiDisplayStatus() {
+ // This request does not require special permissions.
+ // Any app can get information about available wifi displays.
+
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
@@ -612,11 +668,6 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
}
}
- private boolean canCallerConfigureWifiDisplay() {
- return mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
- == PackageManager.PERMISSION_GRANTED;
- }
-
@Override // Binder call
public int createVirtualDisplay(IBinder appToken, String packageName,
String name, int width, int height, int densityDpi, Surface surface, int flags) {
@@ -1112,6 +1163,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
pw.println(" mDefaultViewport=" + mDefaultViewport);
pw.println(" mExternalTouchViewport=" + mExternalTouchViewport);
pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
+ pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.increaseIndent();
@@ -1139,6 +1191,15 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
pw.println(" Display " + displayId + ":");
display.dumpLocked(ipw);
}
+
+ final int callbackCount = mCallbacks.size();
+ pw.println();
+ pw.println("Callbacks: size=" + callbackCount);
+ for (int i = 0; i < callbackCount; i++) {
+ CallbackRecord callback = mCallbacks.valueAt(i);
+ pw.println(" " + i + ": mPid=" + callback.mPid
+ + ", mWifiDisplayScanRequested=" + callback.mWifiDisplayScanRequested);
+ }
}
}
@@ -1239,9 +1300,11 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
}
private final class CallbackRecord implements DeathRecipient {
- private final int mPid;
+ public final int mPid;
private final IDisplayManagerCallback mCallback;
+ public boolean mWifiDisplayScanRequested;
+
public CallbackRecord(int pid, IDisplayManagerCallback callback) {
mPid = pid;
mCallback = callback;
@@ -1252,7 +1315,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub {
if (DEBUG) {
Slog.d(TAG, "Display listener for pid " + mPid + " died.");
}
- onCallbackDied(mPid);
+ onCallbackDied(this);
}
public void notifyDisplayEventAsync(int displayId, int event) {
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index f7bbdf8..cd57941 100644
--- a/services/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -127,7 +127,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate);
pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
-
+
// Try to dump the controller state.
if (mDisplayController == null) {
pw.println("mDisplayController=null");
@@ -157,34 +157,39 @@ final class WifiDisplayAdapter extends DisplayAdapter {
});
}
- public void requestScanLocked() {
+ public void requestStartScanLocked() {
if (DEBUG) {
- Slog.d(TAG, "requestScanLocked");
+ Slog.d(TAG, "requestStartScanLocked");
}
getHandler().post(new Runnable() {
@Override
public void run() {
if (mDisplayController != null) {
- mDisplayController.requestScan();
+ mDisplayController.requestStartScan();
}
}
});
}
- public void requestConnectLocked(final String address, final boolean trusted) {
+ public void requestStopScanLocked() {
if (DEBUG) {
- Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted);
+ Slog.d(TAG, "requestStopScanLocked");
}
- if (!trusted) {
- synchronized (getSyncRoot()) {
- if (!isRememberedDisplayLocked(address)) {
- Slog.w(TAG, "Ignoring request by an untrusted client to connect to "
- + "an unknown wifi display: " + address);
- return;
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (mDisplayController != null) {
+ mDisplayController.requestStopScan();
}
}
+ });
+ }
+
+ public void requestConnectLocked(final String address) {
+ if (DEBUG) {
+ Slog.d(TAG, "requestConnectLocked: address=" + address);
}
getHandler().post(new Runnable() {
@@ -197,15 +202,6 @@ final class WifiDisplayAdapter extends DisplayAdapter {
});
}
- private boolean isRememberedDisplayLocked(String address) {
- for (WifiDisplay display : mRememberedDisplays) {
- if (display.getDeviceAddress().equals(address)) {
- return true;
- }
- }
- return false;
- }
-
public void requestPauseLocked() {
if (DEBUG) {
Slog.d(TAG, "requestPauseLocked");
@@ -400,8 +396,6 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
refreshRate, deviceFlags, address, surface);
sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
-
- scheduleUpdateNotificationLocked();
}
private void removeDisplayDeviceLocked() {
@@ -409,8 +403,6 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mDisplayDevice.destroyLocked();
sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
mDisplayDevice = null;
-
- scheduleUpdateNotificationLocked();
}
}
@@ -457,21 +449,24 @@ final class WifiDisplayAdapter extends DisplayAdapter {
// Runs on the handler.
private void handleUpdateNotification() {
- final boolean isConnected;
+ final int state;
+ final WifiDisplay display;
synchronized (getSyncRoot()) {
if (!mPendingNotificationUpdate) {
return;
}
mPendingNotificationUpdate = false;
- isConnected = (mDisplayDevice != null);
+ state = mActiveDisplayState;
+ display = mActiveDisplay;
}
// Cancel the old notification if there is one.
mNotificationManager.cancelAsUser(null,
- R.string.wifi_display_notification_title, UserHandle.ALL);
+ R.string.wifi_display_notification_disconnect, UserHandle.ALL);
- if (isConnected) {
+ if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING
+ || state == WifiDisplayStatus.DISPLAY_STATE_CONNECTED) {
Context context = getContext();
// Initialize pending intents for the notification outside of the lock because
@@ -493,20 +488,38 @@ final class WifiDisplayAdapter extends DisplayAdapter {
// Post the notification.
Resources r = context.getResources();
- Notification notification = new Notification.Builder(context)
- .setContentTitle(r.getString(
- R.string.wifi_display_notification_title))
- .setContentText(r.getString(
- R.string.wifi_display_notification_message))
- .setContentIntent(mSettingsPendingIntent)
- .setSmallIcon(R.drawable.ic_notify_wifidisplay)
- .setOngoing(true)
- .addAction(android.R.drawable.ic_menu_close_clear_cancel,
- r.getString(R.string.wifi_display_notification_disconnect),
- mDisconnectPendingIntent)
- .build();
+ Notification notification;
+ if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING) {
+ notification = new Notification.Builder(context)
+ .setContentTitle(r.getString(
+ R.string.wifi_display_notification_connecting_title))
+ .setContentText(r.getString(
+ R.string.wifi_display_notification_connecting_message,
+ display.getFriendlyDisplayName()))
+ .setContentIntent(mSettingsPendingIntent)
+ .setSmallIcon(R.drawable.ic_notification_cast_connecting)
+ .setOngoing(true)
+ .addAction(android.R.drawable.ic_menu_close_clear_cancel,
+ r.getString(R.string.wifi_display_notification_disconnect),
+ mDisconnectPendingIntent)
+ .build();
+ } else {
+ notification = new Notification.Builder(context)
+ .setContentTitle(r.getString(
+ R.string.wifi_display_notification_connected_title))
+ .setContentText(r.getString(
+ R.string.wifi_display_notification_connected_message,
+ display.getFriendlyDisplayName()))
+ .setContentIntent(mSettingsPendingIntent)
+ .setSmallIcon(R.drawable.ic_notification_cast_on)
+ .setOngoing(true)
+ .addAction(android.R.drawable.ic_menu_close_clear_cancel,
+ r.getString(R.string.wifi_display_notification_disconnect),
+ mDisconnectPendingIntent)
+ .build();
+ }
mNotificationManager.notifyAsUser(null,
- R.string.wifi_display_notification_title,
+ R.string.wifi_display_notification_disconnect,
notification, UserHandle.ALL);
}
}
@@ -545,20 +558,20 @@ final class WifiDisplayAdapter extends DisplayAdapter {
}
@Override
- public void onScanFinished(WifiDisplay[] availableDisplays) {
+ public void onScanResults(WifiDisplay[] availableDisplays) {
synchronized (getSyncRoot()) {
availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
availableDisplays);
- // check if any of the available displays changed canConnect status
boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
+
+ // Check whether any of the available displays changed canConnect status.
for (int i = 0; !changed && i<availableDisplays.length; i++) {
changed = availableDisplays[i].canConnect()
!= mAvailableDisplays[i].canConnect();
}
- if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING || changed) {
- mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
+ if (changed) {
mAvailableDisplays = availableDisplays;
fixRememberedDisplayNamesFromAvailableDisplaysLocked();
updateDisplaysLocked();
@@ -568,6 +581,16 @@ final class WifiDisplayAdapter extends DisplayAdapter {
}
@Override
+ public void onScanFinished() {
+ synchronized (getSyncRoot()) {
+ if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING) {
+ mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
+ scheduleStatusChangedBroadcastLocked();
+ }
+ }
+ }
+
+ @Override
public void onDisplayConnecting(WifiDisplay display) {
synchronized (getSyncRoot()) {
display = mPersistentDataStore.applyWifiDisplayAlias(display);
@@ -578,6 +601,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
mActiveDisplay = display;
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
@@ -590,6 +614,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
mActiveDisplay = null;
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
@@ -607,6 +632,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
mActiveDisplay = display;
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
@@ -629,6 +655,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mActiveDisplay = display;
renameDisplayDeviceLocked(display.getFriendlyDisplayName());
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
@@ -644,6 +671,7 @@ final class WifiDisplayAdapter extends DisplayAdapter {
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
mActiveDisplay = null;
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index 9a4cfb7..dbb59b2 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -27,7 +27,6 @@ import android.database.ContentObserver;
import android.hardware.display.WifiDisplay;
import android.hardware.display.WifiDisplaySessionInfo;
import android.hardware.display.WifiDisplayStatus;
-import android.media.AudioManager;
import android.media.RemoteDisplay;
import android.net.NetworkInfo;
import android.net.Uri;
@@ -75,12 +74,19 @@ final class WifiDisplayController implements DumpUtils.Dump {
private static final int DEFAULT_CONTROL_PORT = 7236;
private static final int MAX_THROUGHPUT = 50;
- private static final int CONNECTION_TIMEOUT_SECONDS = 60;
- private static final int RTSP_TIMEOUT_SECONDS = 15;
+ private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+ private static final int RTSP_TIMEOUT_SECONDS = 30;
private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120;
- private static final int DISCOVER_PEERS_MAX_RETRIES = 10;
- private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
+ // We repeatedly issue calls to discover peers every so often for a few reasons.
+ // 1. The initial request may fail and need to retried.
+ // 2. Discovery will self-abort after any group is initiated, which may not necessarily
+ // be what we want to have happen.
+ // 3. Discovery will self-timeout after 2 minutes, whereas we want discovery to
+ // be occur for as long as a client is requesting it be.
+ // 4. We don't seem to get updated results for displays we've already found until
+ // we ask to discover again, particularly for the isSessionAvailable() property.
+ private static final int DISCOVER_PEERS_INTERVAL_MILLIS = 10000;
private static final int CONNECT_MAX_RETRIES = 3;
private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
@@ -103,12 +109,12 @@ final class WifiDisplayController implements DumpUtils.Dump {
// True if Wifi display is enabled by the user.
private boolean mWifiDisplayOnSetting;
+ // True if a scan was requested independent of whether one is actually in progress.
+ private boolean mScanRequested;
+
// True if there is a call to discoverPeers in progress.
private boolean mDiscoverPeersInProgress;
- // Number of discover peers retries remaining.
- private int mDiscoverPeersRetriesLeft;
-
// The device to which we want to connect, or null if we want to be disconnected.
private WifiP2pDevice mDesiredDevice;
@@ -209,8 +215,8 @@ final class WifiDisplayController implements DumpUtils.Dump {
pw.println("mWfdEnabled=" + mWfdEnabled);
pw.println("mWfdEnabling=" + mWfdEnabling);
pw.println("mNetworkInfo=" + mNetworkInfo);
+ pw.println("mScanRequested=" + mScanRequested);
pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
- pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft);
pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice));
@@ -232,8 +238,18 @@ final class WifiDisplayController implements DumpUtils.Dump {
}
}
- public void requestScan() {
- discoverPeers();
+ public void requestStartScan() {
+ if (!mScanRequested) {
+ mScanRequested = true;
+ updateScanState();
+ }
+ }
+
+ public void requestStopScan() {
+ if (mScanRequested) {
+ mScanRequested = false;
+ updateScanState();
+ }
}
public void requestConnect(String address) {
@@ -282,6 +298,7 @@ final class WifiDisplayController implements DumpUtils.Dump {
mWfdEnabling = false;
mWfdEnabled = true;
reportFeatureState();
+ updateScanState();
}
}
@@ -318,6 +335,7 @@ final class WifiDisplayController implements DumpUtils.Dump {
mWfdEnabling = false;
mWfdEnabled = false;
reportFeatureState();
+ updateScanState();
disconnect();
}
}
@@ -340,12 +358,29 @@ final class WifiDisplayController implements DumpUtils.Dump {
WifiDisplayStatus.FEATURE_STATE_OFF;
}
- private void discoverPeers() {
- if (!mDiscoverPeersInProgress) {
- mDiscoverPeersInProgress = true;
- mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;
- handleScanStarted();
- tryDiscoverPeers();
+ private void updateScanState() {
+ if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
+ if (!mDiscoverPeersInProgress) {
+ Slog.i(TAG, "Starting Wifi display scan.");
+ mDiscoverPeersInProgress = true;
+ handleScanStarted();
+ tryDiscoverPeers();
+ }
+ } else {
+ if (mDiscoverPeersInProgress) {
+ // Cancel automatic retry right away.
+ mHandler.removeCallbacks(mDiscoverPeers);
+
+ // Defer actually stopping discovery if we have a connection attempt in progress.
+ // The wifi display connection attempt often fails if we are not in discovery
+ // mode. So we allow discovery to continue until we give up trying to connect.
+ if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {
+ Slog.i(TAG, "Stopping Wifi display scan.");
+ mDiscoverPeersInProgress = false;
+ stopPeerDiscovery();
+ handleScanFinished();
+ }
+ }
}
}
@@ -357,8 +392,9 @@ final class WifiDisplayController implements DumpUtils.Dump {
Slog.d(TAG, "Discover peers succeeded. Requesting peers now.");
}
- mDiscoverPeersInProgress = false;
- requestPeers();
+ if (mDiscoverPeersInProgress) {
+ requestPeers();
+ }
}
@Override
@@ -367,30 +403,28 @@ final class WifiDisplayController implements DumpUtils.Dump {
Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
}
- if (mDiscoverPeersInProgress) {
- if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (mDiscoverPeersInProgress) {
- if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
- mDiscoverPeersRetriesLeft -= 1;
- if (DEBUG) {
- Slog.d(TAG, "Retrying discovery. Retries left: "
- + mDiscoverPeersRetriesLeft);
- }
- tryDiscoverPeers();
- } else {
- handleScanFinished();
- mDiscoverPeersInProgress = false;
- }
- }
- }
- }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
- } else {
- handleScanFinished();
- mDiscoverPeersInProgress = false;
- }
+ // Ignore the error.
+ // We will retry automatically in a little bit.
+ }
+ });
+
+ // Retry discover peers periodically until stopped.
+ mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);
+ }
+
+ private void stopPeerDiscovery() {
+ mWifiP2pManager.stopPeerDiscovery(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop peer discovery succeeded.");
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop peer discovery failed with reason " + reason + ".");
}
}
});
@@ -415,7 +449,9 @@ final class WifiDisplayController implements DumpUtils.Dump {
}
}
- handleScanFinished();
+ if (mDiscoverPeersInProgress) {
+ handleScanResults();
+ }
}
});
}
@@ -429,7 +465,7 @@ final class WifiDisplayController implements DumpUtils.Dump {
});
}
- private void handleScanFinished() {
+ private void handleScanResults() {
final int count = mAvailableWifiDisplayPeers.size();
final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
for (int i = 0; i < count; i++) {
@@ -441,7 +477,16 @@ final class WifiDisplayController implements DumpUtils.Dump {
mHandler.post(new Runnable() {
@Override
public void run() {
- mListener.onScanFinished(displays);
+ mListener.onScanResults(displays);
+ }
+ });
+ }
+
+ private void handleScanFinished() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onScanFinished();
}
});
}
@@ -484,6 +529,12 @@ final class WifiDisplayController implements DumpUtils.Dump {
return;
}
+ if (!mWfdEnabled) {
+ Slog.i(TAG, "Ignoring request to connect to Wifi display because the "
+ +" feature is currently disabled: " + device.deviceName);
+ return;
+ }
+
mDesiredDevice = device;
mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
updateConnection();
@@ -508,6 +559,10 @@ final class WifiDisplayController implements DumpUtils.Dump {
* connection is established (or not).
*/
private void updateConnection() {
+ // Step 0. Stop scans if necessary to prevent interference while connected.
+ // Resume scans later when no longer attempting to connect.
+ updateScanState();
+
// Step 1. Before we try to connect to a new device, tell the system we
// have disconnected from the old one.
if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
@@ -661,7 +716,7 @@ final class WifiDisplayController implements DumpUtils.Dump {
return; // wait for asynchronous callback
}
- // Step 6. Listen for incoming connections.
+ // Step 6. Listen for incoming RTSP connection.
if (mConnectedDevice != null && mRemoteDisplay == null) {
Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
if (addr == null) {
@@ -817,7 +872,11 @@ final class WifiDisplayController implements DumpUtils.Dump {
}
} else {
mConnectedDeviceGroupInfo = null;
- disconnect();
+
+ // Disconnect if we lost the network while connecting or connected to a display.
+ if (mConnectingDevice != null || mConnectedDevice != null) {
+ disconnect();
+ }
// After disconnection for a group, for some reason we have a tendency
// to get a peer change notification with an empty list of peers.
@@ -828,6 +887,13 @@ final class WifiDisplayController implements DumpUtils.Dump {
}
}
+ private final Runnable mDiscoverPeers = new Runnable() {
+ @Override
+ public void run() {
+ tryDiscoverPeers();
+ }
+ };
+
private final Runnable mConnectionTimeout = new Runnable() {
@Override
public void run() {
@@ -1033,7 +1099,8 @@ final class WifiDisplayController implements DumpUtils.Dump {
void onFeatureStateChanged(int featureState);
void onScanStarted();
- void onScanFinished(WifiDisplay[] availableDisplays);
+ void onScanResults(WifiDisplay[] availableDisplays);
+ void onScanFinished();
void onDisplayConnecting(WifiDisplay display);
void onDisplayConnectionFailed();
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index d749e6c..3145805 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -294,6 +294,7 @@ public class InputManagerService extends IInputManager.Stub
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addDataScheme("package");
mContext.registerReceiver(new BroadcastReceiver() {
@Override
diff --git a/services/java/com/android/server/media/MediaRouterService.java b/services/java/com/android/server/media/MediaRouterService.java
new file mode 100644
index 0000000..a31695b
--- /dev/null
+++ b/services/java/com/android/server/media/MediaRouterService.java
@@ -0,0 +1,1423 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import com.android.internal.util.Objects;
+import com.android.server.Watchdog;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioSystem;
+import android.media.IMediaRouterClient;
+import android.media.IMediaRouterService;
+import android.media.MediaRouter;
+import android.media.MediaRouterClientState;
+import android.media.RemoteDisplayState;
+import android.media.RemoteDisplayState.RemoteDisplayInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a mechanism for discovering media routes and manages media playback
+ * behalf of applications.
+ * <p>
+ * Currently supports discovering remote displays via remote display provider
+ * services that have been registered by applications.
+ * </p>
+ */
+public final class MediaRouterService extends IMediaRouterService.Stub
+ implements Watchdog.Monitor {
+ private static final String TAG = "MediaRouterService";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * Timeout in milliseconds for a selected route to transition from a
+ * disconnected state to a connecting state. If we don't observe any
+ * progress within this interval, then we will give up and unselect the route.
+ */
+ static final long CONNECTING_TIMEOUT = 5000;
+
+ /**
+ * Timeout in milliseconds for a selected route to transition from a
+ * connecting state to a connected state. If we don't observe any
+ * progress within this interval, then we will give up and unselect the route.
+ */
+ static final long CONNECTED_TIMEOUT = 60000;
+
+ private final Context mContext;
+
+ // State guarded by mLock.
+ private final Object mLock = new Object();
+ private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
+ private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
+ new ArrayMap<IBinder, ClientRecord>();
+ private int mCurrentUserId = -1;
+
+ public MediaRouterService(Context context) {
+ mContext = context;
+ Watchdog.getInstance().addMonitor(this);
+ }
+
+ public void systemRunning() {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
+ switchUser();
+ }
+ }
+ }, filter);
+
+ switchUser();
+ }
+
+ @Override
+ public void monitor() {
+ synchronized (mLock) { /* check for deadlock */ }
+ }
+
+ // Binder call
+ @Override
+ public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final int uid = Binder.getCallingUid();
+ if (!validatePackageName(uid, packageName)) {
+ throw new SecurityException("packageName must match the calling uid");
+ }
+
+ final int pid = Binder.getCallingPid();
+ final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
+ final boolean trusted = mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) ==
+ PackageManager.PERMISSION_GRANTED;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ registerClientLocked(client, pid, packageName, resolvedUserId, trusted);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void unregisterClient(IMediaRouterClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ unregisterClientLocked(client, false);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public MediaRouterClientState getState(IMediaRouterClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ return getStateLocked(client);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void setDiscoveryRequest(IMediaRouterClient client,
+ int routeTypes, boolean activeScan) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setDiscoveryRequestLocked(client, routeTypes, activeScan);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ // A null routeId means that the client wants to unselect its current route.
+ // The explicit flag indicates whether the change was explicitly requested by the
+ // user or the application which may cause changes to propagate out to the rest
+ // of the system. Should be false when the change is in response to a new globally
+ // selected route or a default selection.
+ @Override
+ public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setSelectedRouteLocked(client, routeId, explicit);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestSetVolumeLocked(client, routeId, volume);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestUpdateVolumeLocked(client, routeId, direction);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump MediaRouterService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
+ pw.println();
+ pw.println("Global state");
+ pw.println(" mCurrentUserId=" + mCurrentUserId);
+
+ synchronized (mLock) {
+ final int count = mUserRecords.size();
+ for (int i = 0; i < count; i++) {
+ UserRecord userRecord = mUserRecords.valueAt(i);
+ pw.println();
+ userRecord.dump(pw, "");
+ }
+ }
+ }
+
+ void switchUser() {
+ synchronized (mLock) {
+ int userId = ActivityManager.getCurrentUser();
+ if (mCurrentUserId != userId) {
+ final int oldUserId = mCurrentUserId;
+ mCurrentUserId = userId; // do this first
+
+ UserRecord oldUser = mUserRecords.get(oldUserId);
+ if (oldUser != null) {
+ oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
+ disposeUserIfNeededLocked(oldUser); // since no longer current user
+ }
+
+ UserRecord newUser = mUserRecords.get(userId);
+ if (newUser != null) {
+ newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+ }
+ }
+ }
+ }
+
+ void clientDied(ClientRecord clientRecord) {
+ synchronized (mLock) {
+ unregisterClientLocked(clientRecord.mClient, true);
+ }
+ }
+
+ private void registerClientLocked(IMediaRouterClient client,
+ int pid, String packageName, int userId, boolean trusted) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord == null) {
+ boolean newUser = false;
+ UserRecord userRecord = mUserRecords.get(userId);
+ if (userRecord == null) {
+ userRecord = new UserRecord(userId);
+ newUser = true;
+ }
+ clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted);
+ try {
+ binder.linkToDeath(clientRecord, 0);
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Media router client died prematurely.", ex);
+ }
+
+ if (newUser) {
+ mUserRecords.put(userId, userRecord);
+ initializeUserLocked(userRecord);
+ }
+
+ userRecord.mClientRecords.add(clientRecord);
+ mAllClientRecords.put(binder, clientRecord);
+ initializeClientLocked(clientRecord);
+ }
+ }
+
+ private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
+ ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
+ if (clientRecord != null) {
+ UserRecord userRecord = clientRecord.mUserRecord;
+ userRecord.mClientRecords.remove(clientRecord);
+ disposeClientLocked(clientRecord, died);
+ disposeUserIfNeededLocked(userRecord); // since client removed from user
+ }
+ }
+
+ private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
+ ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+ if (clientRecord != null) {
+ return clientRecord.getState();
+ }
+ return null;
+ }
+
+ private void setDiscoveryRequestLocked(IMediaRouterClient client,
+ int routeTypes, boolean activeScan) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ // Only let the system discover remote display routes for now.
+ if (!clientRecord.mTrusted) {
+ routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+ }
+
+ if (clientRecord.mRouteTypes != routeTypes
+ || clientRecord.mActiveScan != activeScan) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
+ + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
+ }
+ clientRecord.mRouteTypes = routeTypes;
+ clientRecord.mActiveScan = activeScan;
+ clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+ UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+ }
+ }
+ }
+
+ private void setSelectedRouteLocked(IMediaRouterClient client,
+ String routeId, boolean explicit) {
+ ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+ if (clientRecord != null) {
+ final String oldRouteId = clientRecord.mSelectedRouteId;
+ if (!Objects.equal(routeId, oldRouteId)) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
+ + ", oldRouteId=" + oldRouteId
+ + ", explicit=" + explicit);
+ }
+
+ clientRecord.mSelectedRouteId = routeId;
+ if (explicit) {
+ // Any app can disconnect from the globally selected route.
+ if (oldRouteId != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
+ }
+ // Only let the system connect to new global routes for now.
+ // A similar check exists in the display manager for wifi display.
+ if (routeId != null && clientRecord.mTrusted) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
+ }
+ }
+ }
+ }
+ }
+
+ private void requestSetVolumeLocked(IMediaRouterClient client,
+ String routeId, int volume) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
+ }
+ }
+
+ private void requestUpdateVolumeLocked(IMediaRouterClient client,
+ String routeId, int direction) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
+ }
+ }
+
+ private void initializeUserLocked(UserRecord userRecord) {
+ if (DEBUG) {
+ Slog.d(TAG, userRecord + ": Initialized");
+ }
+ if (userRecord.mUserId == mCurrentUserId) {
+ userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+ }
+ }
+
+ private void disposeUserIfNeededLocked(UserRecord userRecord) {
+ // If there are no records left and the user is no longer current then go ahead
+ // and purge the user record and all of its associated state. If the user is current
+ // then leave it alone since we might be connected to a route or want to query
+ // the same route information again soon.
+ if (userRecord.mUserId != mCurrentUserId
+ && userRecord.mClientRecords.isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, userRecord + ": Disposed");
+ }
+ mUserRecords.remove(userRecord.mUserId);
+ // Note: User already stopped (by switchUser) so no need to send stop message here.
+ }
+ }
+
+ private void initializeClientLocked(ClientRecord clientRecord) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Registered");
+ }
+ }
+
+ private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
+ if (DEBUG) {
+ if (died) {
+ Slog.d(TAG, clientRecord + ": Died!");
+ } else {
+ Slog.d(TAG, clientRecord + ": Unregistered");
+ }
+ }
+ if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
+ clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+ UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+ }
+ clientRecord.dispose();
+ }
+
+ private boolean validatePackageName(int uid, String packageName) {
+ if (packageName != null) {
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packageNames != null) {
+ for (String n : packageNames) {
+ if (n.equals(packageName)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Information about a particular client of the media router.
+ * The contents of this object is guarded by mLock.
+ */
+ final class ClientRecord implements DeathRecipient {
+ public final UserRecord mUserRecord;
+ public final IMediaRouterClient mClient;
+ public final int mPid;
+ public final String mPackageName;
+ public final boolean mTrusted;
+
+ public int mRouteTypes;
+ public boolean mActiveScan;
+ public String mSelectedRouteId;
+
+ public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
+ int pid, String packageName, boolean trusted) {
+ mUserRecord = userRecord;
+ mClient = client;
+ mPid = pid;
+ mPackageName = packageName;
+ mTrusted = trusted;
+ }
+
+ public void dispose() {
+ mClient.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ clientDied(this);
+ }
+
+ MediaRouterClientState getState() {
+ return mTrusted ? mUserRecord.mTrustedState : mUserRecord.mUntrustedState;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mTrusted=" + mTrusted);
+ pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
+ pw.println(indent + "mActiveScan=" + mActiveScan);
+ pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
+ }
+
+ @Override
+ public String toString() {
+ return "Client " + mPackageName + " (pid " + mPid + ")";
+ }
+ }
+
+ /**
+ * Information about a particular user.
+ * The contents of this object is guarded by mLock.
+ */
+ final class UserRecord {
+ public final int mUserId;
+ public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
+ public final UserHandler mHandler;
+ public MediaRouterClientState mTrustedState;
+ public MediaRouterClientState mUntrustedState;
+
+ public UserRecord(int userId) {
+ mUserId = userId;
+ mHandler = new UserHandler(MediaRouterService.this, this);
+ }
+
+ public void dump(final PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ final int clientCount = mClientRecords.size();
+ if (clientCount != 0) {
+ for (int i = 0; i < clientCount; i++) {
+ mClientRecords.get(i).dump(pw, indent);
+ }
+ } else {
+ pw.println(indent + "<no clients>");
+ }
+
+ pw.println(indent + "State");
+ pw.println(indent + "mTrustedState=" + mTrustedState);
+ pw.println(indent + "mUntrustedState=" + mUntrustedState);
+
+ if (!mHandler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ mHandler.dump(pw, indent);
+ }
+ }, 1000)) {
+ pw.println(indent + "<could not dump handler state>");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "User " + mUserId;
+ }
+ }
+
+ /**
+ * Media router handler
+ * <p>
+ * Since remote display providers are designed to be single-threaded by nature,
+ * this class encapsulates all of the associated functionality and exports state
+ * to the service as it evolves.
+ * </p><p>
+ * One important task of this class is to keep track of the current globally selected
+ * route id for certain routes that have global effects, such as remote displays.
+ * Global route selections override local selections made within apps. The change
+ * is propagated to all apps so that they are all in sync. Synchronization works
+ * both ways. Whenever the globally selected route is explicitly unselected by any
+ * app, then it becomes unselected globally and all apps are informed.
+ * </p><p>
+ * This class is currently hardcoded to work with remote display providers but
+ * it is intended to be eventually extended to support more general route providers
+ * similar to the support library media router.
+ * </p>
+ */
+ static final class UserHandler extends Handler
+ implements RemoteDisplayProviderWatcher.Callback,
+ RemoteDisplayProviderProxy.Callback {
+ public static final int MSG_START = 1;
+ public static final int MSG_STOP = 2;
+ public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
+ public static final int MSG_SELECT_ROUTE = 4;
+ public static final int MSG_UNSELECT_ROUTE = 5;
+ public static final int MSG_REQUEST_SET_VOLUME = 6;
+ public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
+ private static final int MSG_UPDATE_CLIENT_STATE = 8;
+ private static final int MSG_CONNECTION_TIMED_OUT = 9;
+
+ private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
+ private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
+ private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
+ private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
+
+ // The relative order of these constants is important and expresses progress
+ // through the process of connecting to a route.
+ private static final int PHASE_NOT_AVAILABLE = -1;
+ private static final int PHASE_NOT_CONNECTED = 0;
+ private static final int PHASE_CONNECTING = 1;
+ private static final int PHASE_CONNECTED = 2;
+
+ private final MediaRouterService mService;
+ private final UserRecord mUserRecord;
+ private final RemoteDisplayProviderWatcher mWatcher;
+ private final ArrayList<ProviderRecord> mProviderRecords =
+ new ArrayList<ProviderRecord>();
+ private final ArrayList<IMediaRouterClient> mTempClients =
+ new ArrayList<IMediaRouterClient>();
+
+ private boolean mRunning;
+ private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+ private RouteRecord mGloballySelectedRouteRecord;
+ private int mConnectionPhase = PHASE_NOT_AVAILABLE;
+ private int mConnectionTimeoutReason;
+ private long mConnectionTimeoutStartTime;
+ private boolean mClientStateUpdateScheduled;
+
+ public UserHandler(MediaRouterService service, UserRecord userRecord) {
+ super(Looper.getMainLooper(), null, true);
+ mService = service;
+ mUserRecord = userRecord;
+ mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
+ this, mUserRecord.mUserId);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START: {
+ start();
+ break;
+ }
+ case MSG_STOP: {
+ stop();
+ break;
+ }
+ case MSG_UPDATE_DISCOVERY_REQUEST: {
+ updateDiscoveryRequest();
+ break;
+ }
+ case MSG_SELECT_ROUTE: {
+ selectRoute((String)msg.obj);
+ break;
+ }
+ case MSG_UNSELECT_ROUTE: {
+ unselectRoute((String)msg.obj);
+ break;
+ }
+ case MSG_REQUEST_SET_VOLUME: {
+ requestSetVolume((String)msg.obj, msg.arg1);
+ break;
+ }
+ case MSG_REQUEST_UPDATE_VOLUME: {
+ requestUpdateVolume((String)msg.obj, msg.arg1);
+ break;
+ }
+ case MSG_UPDATE_CLIENT_STATE: {
+ updateClientState();
+ break;
+ }
+ case MSG_CONNECTION_TIMED_OUT: {
+ connectionTimedOut();
+ break;
+ }
+ }
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "Handler");
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mRunning=" + mRunning);
+ pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
+ pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord);
+ pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
+ pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
+ pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
+ TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
+
+ mWatcher.dump(pw, prefix);
+
+ final int providerCount = mProviderRecords.size();
+ if (providerCount != 0) {
+ for (int i = 0; i < providerCount; i++) {
+ mProviderRecords.get(i).dump(pw, prefix);
+ }
+ } else {
+ pw.println(indent + "<no providers>");
+ }
+ }
+
+ private void start() {
+ if (!mRunning) {
+ mRunning = true;
+ mWatcher.start(); // also starts all providers
+ }
+ }
+
+ private void stop() {
+ if (mRunning) {
+ mRunning = false;
+ unselectGloballySelectedRoute();
+ mWatcher.stop(); // also stops all providers
+ }
+ }
+
+ private void updateDiscoveryRequest() {
+ int routeTypes = 0;
+ boolean activeScan = false;
+ synchronized (mService.mLock) {
+ final int count = mUserRecord.mClientRecords.size();
+ for (int i = 0; i < count; i++) {
+ ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
+ routeTypes |= clientRecord.mRouteTypes;
+ activeScan |= clientRecord.mActiveScan;
+ }
+ }
+
+ final int newDiscoveryMode;
+ if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
+ if (activeScan) {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
+ } else {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
+ }
+ } else {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+ }
+
+ if (mDiscoveryMode != newDiscoveryMode) {
+ mDiscoveryMode = newDiscoveryMode;
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
+ }
+ }
+ }
+
+ private void selectRoute(String routeId) {
+ if (routeId != null
+ && (mGloballySelectedRouteRecord == null
+ || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) {
+ RouteRecord routeRecord = findRouteRecord(routeId);
+ if (routeRecord != null) {
+ unselectGloballySelectedRoute();
+
+ Slog.i(TAG, "Selected global route:" + routeRecord);
+ mGloballySelectedRouteRecord = routeRecord;
+ checkGloballySelectedRouteState();
+ routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
+
+ scheduleUpdateClientState();
+ }
+ }
+ }
+
+ private void unselectRoute(String routeId) {
+ if (routeId != null
+ && mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ unselectGloballySelectedRoute();
+ }
+ }
+
+ private void unselectGloballySelectedRoute() {
+ if (mGloballySelectedRouteRecord != null) {
+ Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord);
+ mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null);
+ mGloballySelectedRouteRecord = null;
+ checkGloballySelectedRouteState();
+
+ scheduleUpdateClientState();
+ }
+ }
+
+ private void requestSetVolume(String routeId, int volume) {
+ if (mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume);
+ }
+ }
+
+ private void requestUpdateVolume(String routeId, int direction) {
+ if (mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
+ }
+ }
+
+ @Override
+ public void addProvider(RemoteDisplayProviderProxy provider) {
+ provider.setCallback(this);
+ provider.setDiscoveryMode(mDiscoveryMode);
+ provider.setSelectedDisplay(null); // just to be safe
+
+ ProviderRecord providerRecord = new ProviderRecord(provider);
+ mProviderRecords.add(providerRecord);
+ providerRecord.updateDescriptor(provider.getDisplayState());
+
+ scheduleUpdateClientState();
+ }
+
+ @Override
+ public void removeProvider(RemoteDisplayProviderProxy provider) {
+ int index = findProviderRecord(provider);
+ if (index >= 0) {
+ ProviderRecord providerRecord = mProviderRecords.remove(index);
+ providerRecord.updateDescriptor(null); // mark routes invalid
+ provider.setCallback(null);
+ provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
+
+ checkGloballySelectedRouteState();
+ scheduleUpdateClientState();
+ }
+ }
+
+ @Override
+ public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
+ RemoteDisplayState state) {
+ updateProvider(provider, state);
+ }
+
+ private void updateProvider(RemoteDisplayProviderProxy provider,
+ RemoteDisplayState state) {
+ int index = findProviderRecord(provider);
+ if (index >= 0) {
+ ProviderRecord providerRecord = mProviderRecords.get(index);
+ if (providerRecord.updateDescriptor(state)) {
+ checkGloballySelectedRouteState();
+ scheduleUpdateClientState();
+ }
+ }
+ }
+
+ /**
+ * This function is called whenever the state of the globally selected route
+ * may have changed. It checks the state and updates timeouts or unselects
+ * the route as appropriate.
+ */
+ private void checkGloballySelectedRouteState() {
+ // Unschedule timeouts when the route is unselected.
+ if (mGloballySelectedRouteRecord == null) {
+ mConnectionPhase = PHASE_NOT_AVAILABLE;
+ updateConnectionTimeout(0);
+ return;
+ }
+
+ // Ensure that the route is still present and enabled.
+ if (!mGloballySelectedRouteRecord.isValid()
+ || !mGloballySelectedRouteRecord.isEnabled()) {
+ updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+ return;
+ }
+
+ // Make sure we haven't lost our connection.
+ final int oldPhase = mConnectionPhase;
+ mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus());
+ if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
+ updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
+ return;
+ }
+
+ // Check the route status.
+ switch (mConnectionPhase) {
+ case PHASE_CONNECTED:
+ if (oldPhase != PHASE_CONNECTED) {
+ Slog.i(TAG, "Connected to global route: "
+ + mGloballySelectedRouteRecord);
+ }
+ updateConnectionTimeout(0);
+ break;
+ case PHASE_CONNECTING:
+ if (oldPhase != PHASE_CONNECTING) {
+ Slog.i(TAG, "Connecting to global route: "
+ + mGloballySelectedRouteRecord);
+ }
+ updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
+ break;
+ case PHASE_NOT_CONNECTED:
+ updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
+ break;
+ case PHASE_NOT_AVAILABLE:
+ default:
+ updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+ break;
+ }
+ }
+
+ private void updateConnectionTimeout(int reason) {
+ if (reason != mConnectionTimeoutReason) {
+ if (mConnectionTimeoutReason != 0) {
+ removeMessages(MSG_CONNECTION_TIMED_OUT);
+ }
+ mConnectionTimeoutReason = reason;
+ mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
+ switch (reason) {
+ case TIMEOUT_REASON_NOT_AVAILABLE:
+ case TIMEOUT_REASON_CONNECTION_LOST:
+ // Route became unavailable or connection lost.
+ // Unselect it immediately.
+ sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+ // Waiting for route to start connecting.
+ sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+ // Waiting for route to complete connection.
+ sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
+ break;
+ }
+ }
+ }
+
+ private void connectionTimedOut() {
+ if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) {
+ // Shouldn't get here. There must be a bug somewhere.
+ Log.wtf(TAG, "Handled connection timeout for no reason.");
+ return;
+ }
+
+ switch (mConnectionTimeoutReason) {
+ case TIMEOUT_REASON_NOT_AVAILABLE:
+ Slog.i(TAG, "Global route no longer available: "
+ + mGloballySelectedRouteRecord);
+ break;
+ case TIMEOUT_REASON_CONNECTION_LOST:
+ Slog.i(TAG, "Global route connection lost: "
+ + mGloballySelectedRouteRecord);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+ Slog.i(TAG, "Global route timed out while waiting for "
+ + "connection attempt to begin after "
+ + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+ + " ms: " + mGloballySelectedRouteRecord);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+ Slog.i(TAG, "Global route timed out while connecting after "
+ + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+ + " ms: " + mGloballySelectedRouteRecord);
+ break;
+ }
+ mConnectionTimeoutReason = 0;
+
+ unselectGloballySelectedRoute();
+ }
+
+ private void scheduleUpdateClientState() {
+ if (!mClientStateUpdateScheduled) {
+ mClientStateUpdateScheduled = true;
+ sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
+ }
+ }
+
+ private void updateClientState() {
+ mClientStateUpdateScheduled = false;
+
+ final String globallySelectedRouteId = mGloballySelectedRouteRecord != null ?
+ mGloballySelectedRouteRecord.getUniqueId() : null;
+
+ // Build a new client state for trusted clients.
+ MediaRouterClientState trustedState = new MediaRouterClientState();
+ trustedState.globallySelectedRouteId = globallySelectedRouteId;
+ final int providerCount = mProviderRecords.size();
+ for (int i = 0; i < providerCount; i++) {
+ mProviderRecords.get(i).appendClientState(trustedState);
+ }
+
+ // Build a new client state for untrusted clients that can only see
+ // the currently selected route.
+ MediaRouterClientState untrustedState = new MediaRouterClientState();
+ untrustedState.globallySelectedRouteId = globallySelectedRouteId;
+ if (globallySelectedRouteId != null) {
+ untrustedState.routes.add(trustedState.getRoute(globallySelectedRouteId));
+ }
+
+ try {
+ synchronized (mService.mLock) {
+ // Update the UserRecord.
+ mUserRecord.mTrustedState = trustedState;
+ mUserRecord.mUntrustedState = untrustedState;
+
+ // Collect all clients.
+ final int count = mUserRecord.mClientRecords.size();
+ for (int i = 0; i < count; i++) {
+ mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
+ }
+ }
+
+ // Notify all clients (outside of the lock).
+ final int count = mTempClients.size();
+ for (int i = 0; i < count; i++) {
+ try {
+ mTempClients.get(i).onStateChanged();
+ } catch (RemoteException ex) {
+ // ignore errors, client probably died
+ }
+ }
+ } finally {
+ // Clear the list in preparation for the next time.
+ mTempClients.clear();
+ }
+ }
+
+ private int findProviderRecord(RemoteDisplayProviderProxy provider) {
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ ProviderRecord record = mProviderRecords.get(i);
+ if (record.getProvider() == provider) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private RouteRecord findRouteRecord(String uniqueId) {
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
+ if (record != null) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ private static int getConnectionPhase(int status) {
+ switch (status) {
+ case MediaRouter.RouteInfo.STATUS_NONE:
+ case MediaRouter.RouteInfo.STATUS_CONNECTED:
+ return PHASE_CONNECTED;
+ case MediaRouter.RouteInfo.STATUS_CONNECTING:
+ return PHASE_CONNECTING;
+ case MediaRouter.RouteInfo.STATUS_SCANNING:
+ case MediaRouter.RouteInfo.STATUS_AVAILABLE:
+ return PHASE_NOT_CONNECTED;
+ case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
+ case MediaRouter.RouteInfo.STATUS_IN_USE:
+ default:
+ return PHASE_NOT_AVAILABLE;
+ }
+ }
+
+ static final class ProviderRecord {
+ private final RemoteDisplayProviderProxy mProvider;
+ private final String mUniquePrefix;
+ private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
+ private RemoteDisplayState mDescriptor;
+
+ public ProviderRecord(RemoteDisplayProviderProxy provider) {
+ mProvider = provider;
+ mUniquePrefix = provider.getFlattenedComponentName() + ":";
+ }
+
+ public RemoteDisplayProviderProxy getProvider() {
+ return mProvider;
+ }
+
+ public String getUniquePrefix() {
+ return mUniquePrefix;
+ }
+
+ public boolean updateDescriptor(RemoteDisplayState descriptor) {
+ boolean changed = false;
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+
+ // Update all existing routes and reorder them to match
+ // the order of their descriptors.
+ int targetIndex = 0;
+ if (descriptor != null) {
+ if (descriptor.isValid()) {
+ final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
+ final int routeCount = routeDescriptors.size();
+ for (int i = 0; i < routeCount; i++) {
+ final RemoteDisplayInfo routeDescriptor =
+ routeDescriptors.get(i);
+ final String descriptorId = routeDescriptor.id;
+ final int sourceIndex = findRouteByDescriptorId(descriptorId);
+ if (sourceIndex < 0) {
+ // Add the route to the provider.
+ String uniqueId = assignRouteUniqueId(descriptorId);
+ RouteRecord route =
+ new RouteRecord(this, descriptorId, uniqueId);
+ mRoutes.add(targetIndex++, route);
+ route.updateDescriptor(routeDescriptor);
+ changed = true;
+ } else if (sourceIndex < targetIndex) {
+ // Ignore route with duplicate id.
+ Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
+ + routeDescriptor);
+ } else {
+ // Reorder existing route within the list.
+ RouteRecord route = mRoutes.get(sourceIndex);
+ Collections.swap(mRoutes, sourceIndex, targetIndex++);
+ changed |= route.updateDescriptor(routeDescriptor);
+ }
+ }
+ } else {
+ Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
+ + mProvider.getFlattenedComponentName());
+ }
+ }
+
+ // Dispose all remaining routes that do not have matching descriptors.
+ for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
+ RouteRecord route = mRoutes.remove(i);
+ route.updateDescriptor(null); // mark route invalid
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ public void appendClientState(MediaRouterClientState state) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ state.routes.add(mRoutes.get(i).getInfo());
+ }
+ }
+
+ public RouteRecord findRouteByUniqueId(String uniqueId) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ RouteRecord route = mRoutes.get(i);
+ if (route.getUniqueId().equals(uniqueId)) {
+ return route;
+ }
+ }
+ return null;
+ }
+
+ private int findRouteByDescriptorId(String descriptorId) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ RouteRecord route = mRoutes.get(i);
+ if (route.getDescriptorId().equals(descriptorId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ mProvider.dump(pw, indent);
+
+ final int routeCount = mRoutes.size();
+ if (routeCount != 0) {
+ for (int i = 0; i < routeCount; i++) {
+ mRoutes.get(i).dump(pw, indent);
+ }
+ } else {
+ pw.println(indent + "<no routes>");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Provider " + mProvider.getFlattenedComponentName();
+ }
+
+ private String assignRouteUniqueId(String descriptorId) {
+ return mUniquePrefix + descriptorId;
+ }
+ }
+
+ static final class RouteRecord {
+ private final ProviderRecord mProviderRecord;
+ private final String mDescriptorId;
+ private final MediaRouterClientState.RouteInfo mMutableInfo;
+ private MediaRouterClientState.RouteInfo mImmutableInfo;
+ private RemoteDisplayInfo mDescriptor;
+
+ public RouteRecord(ProviderRecord providerRecord,
+ String descriptorId, String uniqueId) {
+ mProviderRecord = providerRecord;
+ mDescriptorId = descriptorId;
+ mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
+ }
+
+ public RemoteDisplayProviderProxy getProvider() {
+ return mProviderRecord.getProvider();
+ }
+
+ public ProviderRecord getProviderRecord() {
+ return mProviderRecord;
+ }
+
+ public String getDescriptorId() {
+ return mDescriptorId;
+ }
+
+ public String getUniqueId() {
+ return mMutableInfo.id;
+ }
+
+ public MediaRouterClientState.RouteInfo getInfo() {
+ if (mImmutableInfo == null) {
+ mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
+ }
+ return mImmutableInfo;
+ }
+
+ public boolean isValid() {
+ return mDescriptor != null;
+ }
+
+ public boolean isEnabled() {
+ return mMutableInfo.enabled;
+ }
+
+ public int getStatus() {
+ return mMutableInfo.statusCode;
+ }
+
+ public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
+ boolean changed = false;
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+ if (descriptor != null) {
+ final String name = computeName(descriptor);
+ if (!Objects.equal(mMutableInfo.name, name)) {
+ mMutableInfo.name = name;
+ changed = true;
+ }
+ final String description = computeDescription(descriptor);
+ if (!Objects.equal(mMutableInfo.description, description)) {
+ mMutableInfo.description = description;
+ changed = true;
+ }
+ final int supportedTypes = computeSupportedTypes(descriptor);
+ if (mMutableInfo.supportedTypes != supportedTypes) {
+ mMutableInfo.supportedTypes = supportedTypes;
+ changed = true;
+ }
+ final boolean enabled = computeEnabled(descriptor);
+ if (mMutableInfo.enabled != enabled) {
+ mMutableInfo.enabled = enabled;
+ changed = true;
+ }
+ final int statusCode = computeStatusCode(descriptor);
+ if (mMutableInfo.statusCode != statusCode) {
+ mMutableInfo.statusCode = statusCode;
+ changed = true;
+ }
+ final int playbackType = computePlaybackType(descriptor);
+ if (mMutableInfo.playbackType != playbackType) {
+ mMutableInfo.playbackType = playbackType;
+ changed = true;
+ }
+ final int playbackStream = computePlaybackStream(descriptor);
+ if (mMutableInfo.playbackStream != playbackStream) {
+ mMutableInfo.playbackStream = playbackStream;
+ changed = true;
+ }
+ final int volume = computeVolume(descriptor);
+ if (mMutableInfo.volume != volume) {
+ mMutableInfo.volume = volume;
+ changed = true;
+ }
+ final int volumeMax = computeVolumeMax(descriptor);
+ if (mMutableInfo.volumeMax != volumeMax) {
+ mMutableInfo.volumeMax = volumeMax;
+ changed = true;
+ }
+ final int volumeHandling = computeVolumeHandling(descriptor);
+ if (mMutableInfo.volumeHandling != volumeHandling) {
+ mMutableInfo.volumeHandling = volumeHandling;
+ changed = true;
+ }
+ final int presentationDisplayId = computePresentationDisplayId(descriptor);
+ if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
+ mMutableInfo.presentationDisplayId = presentationDisplayId;
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ mImmutableInfo = null;
+ }
+ return changed;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mMutableInfo=" + mMutableInfo);
+ pw.println(indent + "mDescriptorId=" + mDescriptorId);
+ pw.println(indent + "mDescriptor=" + mDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
+ }
+
+ private static String computeName(RemoteDisplayInfo descriptor) {
+ // Note that isValid() already ensures the name is non-empty.
+ return descriptor.name;
+ }
+
+ private static String computeDescription(RemoteDisplayInfo descriptor) {
+ final String description = descriptor.description;
+ return TextUtils.isEmpty(description) ? null : description;
+ }
+
+ private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
+ return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
+ | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
+ | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+ }
+
+ private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
+ switch (descriptor.status) {
+ case RemoteDisplayInfo.STATUS_CONNECTED:
+ case RemoteDisplayInfo.STATUS_CONNECTING:
+ case RemoteDisplayInfo.STATUS_AVAILABLE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static int computeStatusCode(RemoteDisplayInfo descriptor) {
+ switch (descriptor.status) {
+ case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
+ return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
+ case RemoteDisplayInfo.STATUS_AVAILABLE:
+ return MediaRouter.RouteInfo.STATUS_AVAILABLE;
+ case RemoteDisplayInfo.STATUS_IN_USE:
+ return MediaRouter.RouteInfo.STATUS_IN_USE;
+ case RemoteDisplayInfo.STATUS_CONNECTING:
+ return MediaRouter.RouteInfo.STATUS_CONNECTING;
+ case RemoteDisplayInfo.STATUS_CONNECTED:
+ return MediaRouter.RouteInfo.STATUS_CONNECTED;
+ default:
+ return MediaRouter.RouteInfo.STATUS_NONE;
+ }
+ }
+
+ private static int computePlaybackType(RemoteDisplayInfo descriptor) {
+ return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+ }
+
+ private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
+ return AudioSystem.STREAM_MUSIC;
+ }
+
+ private static int computeVolume(RemoteDisplayInfo descriptor) {
+ final int volume = descriptor.volume;
+ final int volumeMax = descriptor.volumeMax;
+ if (volume < 0) {
+ return 0;
+ } else if (volume > volumeMax) {
+ return volumeMax;
+ }
+ return volume;
+ }
+
+ private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
+ final int volumeMax = descriptor.volumeMax;
+ return volumeMax > 0 ? volumeMax : 0;
+ }
+
+ private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
+ final int volumeHandling = descriptor.volumeHandling;
+ switch (volumeHandling) {
+ case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
+ return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+ case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
+ default:
+ return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+ }
+ }
+
+ private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
+ // The MediaRouter class validates that the id corresponds to an extant
+ // presentation display. So all we do here is canonicalize the null case.
+ final int displayId = descriptor.presentationDisplayId;
+ return displayId < 0 ? -1 : displayId;
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
new file mode 100644
index 0000000..b248ee0
--- /dev/null
+++ b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import com.android.internal.util.Objects;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.IRemoteDisplayCallback;
+import android.media.IRemoteDisplayProvider;
+import android.media.RemoteDisplayState;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.IBinder.DeathRecipient;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
+/**
+ * Maintains a connection to a particular remote display provider service.
+ */
+final class RemoteDisplayProviderProxy implements ServiceConnection {
+ private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final ComponentName mComponentName;
+ private final int mUserId;
+ private final Handler mHandler;
+
+ private Callback mDisplayStateCallback;
+
+ // Connection state
+ private boolean mRunning;
+ private boolean mBound;
+ private Connection mActiveConnection;
+ private boolean mConnectionReady;
+
+ // Logical state
+ private int mDiscoveryMode;
+ private String mSelectedDisplayId;
+ private RemoteDisplayState mDisplayState;
+ private boolean mScheduledDisplayStateChangedCallback;
+
+ public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
+ int userId) {
+ mContext = context;
+ mComponentName = componentName;
+ mUserId = userId;
+ mHandler = new Handler();
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "Proxy");
+ pw.println(prefix + " mUserId=" + mUserId);
+ pw.println(prefix + " mRunning=" + mRunning);
+ pw.println(prefix + " mBound=" + mBound);
+ pw.println(prefix + " mActiveConnection=" + mActiveConnection);
+ pw.println(prefix + " mConnectionReady=" + mConnectionReady);
+ pw.println(prefix + " mDiscoveryMode=" + mDiscoveryMode);
+ pw.println(prefix + " mSelectedDisplayId=" + mSelectedDisplayId);
+ pw.println(prefix + " mDisplayState=" + mDisplayState);
+ }
+
+ public void setCallback(Callback callback) {
+ mDisplayStateCallback = callback;
+ }
+
+ public RemoteDisplayState getDisplayState() {
+ return mDisplayState;
+ }
+
+ public void setDiscoveryMode(int mode) {
+ if (mDiscoveryMode != mode) {
+ mDiscoveryMode = mode;
+ if (mConnectionReady) {
+ mActiveConnection.setDiscoveryMode(mode);
+ }
+ updateBinding();
+ }
+ }
+
+ public void setSelectedDisplay(String id) {
+ if (!Objects.equal(mSelectedDisplayId, id)) {
+ if (mConnectionReady && mSelectedDisplayId != null) {
+ mActiveConnection.disconnect(mSelectedDisplayId);
+ }
+ mSelectedDisplayId = id;
+ if (mConnectionReady && id != null) {
+ mActiveConnection.connect(id);
+ }
+ updateBinding();
+ }
+ }
+
+ public void setDisplayVolume(int volume) {
+ if (mConnectionReady && mSelectedDisplayId != null) {
+ mActiveConnection.setVolume(mSelectedDisplayId, volume);
+ }
+ }
+
+ public void adjustDisplayVolume(int delta) {
+ if (mConnectionReady && mSelectedDisplayId != null) {
+ mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
+ }
+ }
+
+ public boolean hasComponentName(String packageName, String className) {
+ return mComponentName.getPackageName().equals(packageName)
+ && mComponentName.getClassName().equals(className);
+ }
+
+ public String getFlattenedComponentName() {
+ return mComponentName.flattenToShortString();
+ }
+
+ public void start() {
+ if (!mRunning) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Starting");
+ }
+
+ mRunning = true;
+ updateBinding();
+ }
+ }
+
+ public void stop() {
+ if (mRunning) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Stopping");
+ }
+
+ mRunning = false;
+ updateBinding();
+ }
+ }
+
+ public void rebindIfDisconnected() {
+ if (mActiveConnection == null && shouldBind()) {
+ unbind();
+ bind();
+ }
+ }
+
+ private void updateBinding() {
+ if (shouldBind()) {
+ bind();
+ } else {
+ unbind();
+ }
+ }
+
+ private boolean shouldBind() {
+ if (mRunning) {
+ // Bind whenever there is a discovery request or selected display.
+ if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE
+ || mSelectedDisplayId != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void bind() {
+ if (!mBound) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Binding");
+ }
+
+ Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
+ service.setComponent(mComponentName);
+ try {
+ mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE,
+ new UserHandle(mUserId));
+ if (!mBound && DEBUG) {
+ Slog.d(TAG, this + ": Bind failed");
+ }
+ } catch (SecurityException ex) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Bind failed", ex);
+ }
+ }
+ }
+ }
+
+ private void unbind() {
+ if (mBound) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Unbinding");
+ }
+
+ mBound = false;
+ disconnect();
+ mContext.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Connected");
+ }
+
+ if (mBound) {
+ disconnect();
+
+ IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service);
+ if (provider != null) {
+ Connection connection = new Connection(provider);
+ if (connection.register()) {
+ mActiveConnection = connection;
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Registration failed");
+ }
+ }
+ } else {
+ Slog.e(TAG, this + ": Service returned invalid remote display provider binder");
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Service disconnected");
+ }
+ disconnect();
+ }
+
+ private void onConnectionReady(Connection connection) {
+ if (mActiveConnection == connection) {
+ mConnectionReady = true;
+
+ if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) {
+ mActiveConnection.setDiscoveryMode(mDiscoveryMode);
+ }
+ if (mSelectedDisplayId != null) {
+ mActiveConnection.connect(mSelectedDisplayId);
+ }
+ }
+ }
+
+ private void onConnectionDied(Connection connection) {
+ if (mActiveConnection == connection) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Service connection died");
+ }
+ disconnect();
+ }
+ }
+
+ private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) {
+ if (mActiveConnection == connection) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": State changed, state=" + state);
+ }
+ setDisplayState(state);
+ }
+ }
+
+ private void disconnect() {
+ if (mActiveConnection != null) {
+ if (mSelectedDisplayId != null) {
+ mActiveConnection.disconnect(mSelectedDisplayId);
+ }
+ mConnectionReady = false;
+ mActiveConnection.dispose();
+ mActiveConnection = null;
+ setDisplayState(null);
+ }
+ }
+
+ private void setDisplayState(RemoteDisplayState state) {
+ if (!Objects.equal(mDisplayState, state)) {
+ mDisplayState = state;
+ if (!mScheduledDisplayStateChangedCallback) {
+ mScheduledDisplayStateChangedCallback = true;
+ mHandler.post(mDisplayStateChanged);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Service connection " + mComponentName.flattenToShortString();
+ }
+
+ private final Runnable mDisplayStateChanged = new Runnable() {
+ @Override
+ public void run() {
+ mScheduledDisplayStateChangedCallback = false;
+ if (mDisplayStateCallback != null) {
+ mDisplayStateCallback.onDisplayStateChanged(
+ RemoteDisplayProviderProxy.this, mDisplayState);
+ }
+ }
+ };
+
+ public interface Callback {
+ void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
+ }
+
+ private final class Connection implements DeathRecipient {
+ private final IRemoteDisplayProvider mProvider;
+ private final ProviderCallback mCallback;
+
+ public Connection(IRemoteDisplayProvider provider) {
+ mProvider = provider;
+ mCallback = new ProviderCallback(this);
+ }
+
+ public boolean register() {
+ try {
+ mProvider.asBinder().linkToDeath(this, 0);
+ mProvider.setCallback(mCallback);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onConnectionReady(Connection.this);
+ }
+ });
+ return true;
+ } catch (RemoteException ex) {
+ binderDied();
+ }
+ return false;
+ }
+
+ public void dispose() {
+ mProvider.asBinder().unlinkToDeath(this, 0);
+ mCallback.dispose();
+ }
+
+ public void setDiscoveryMode(int mode) {
+ try {
+ mProvider.setDiscoveryMode(mode);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
+ }
+ }
+
+ public void connect(String id) {
+ try {
+ mProvider.connect(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to connect to display.", ex);
+ }
+ }
+
+ public void disconnect(String id) {
+ try {
+ mProvider.disconnect(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex);
+ }
+ }
+
+ public void setVolume(String id, int volume) {
+ try {
+ mProvider.setVolume(id, volume);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to set display volume.", ex);
+ }
+ }
+
+ public void adjustVolume(String id, int volume) {
+ try {
+ mProvider.adjustVolume(id, volume);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onConnectionDied(Connection.this);
+ }
+ });
+ }
+
+ void postStateChanged(final RemoteDisplayState state) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onDisplayStateChanged(Connection.this, state);
+ }
+ });
+ }
+ }
+
+ /**
+ * Receives callbacks from the service.
+ * <p>
+ * This inner class is static and only retains a weak reference to the connection
+ * to prevent the client from being leaked in case the service is holding an
+ * active reference to the client's callback.
+ * </p>
+ */
+ private static final class ProviderCallback extends IRemoteDisplayCallback.Stub {
+ private final WeakReference<Connection> mConnectionRef;
+
+ public ProviderCallback(Connection connection) {
+ mConnectionRef = new WeakReference<Connection>(connection);
+ }
+
+ public void dispose() {
+ mConnectionRef.clear();
+ }
+
+ @Override
+ public void onStateChanged(RemoteDisplayState state) throws RemoteException {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ connection.postStateChanged(state);
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java
new file mode 100644
index 0000000..6a5f563
--- /dev/null
+++ b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.media.RemoteDisplayState;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Watches for remote display provider services to be installed.
+ * Adds a provider to the media router for each registered service.
+ *
+ * @see RemoteDisplayProviderProxy
+ */
+public final class RemoteDisplayProviderWatcher {
+ private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final Callback mCallback;
+ private final Handler mHandler;
+ private final int mUserId;
+ private final PackageManager mPackageManager;
+
+ private final ArrayList<RemoteDisplayProviderProxy> mProviders =
+ new ArrayList<RemoteDisplayProviderProxy>();
+ private boolean mRunning;
+
+ public RemoteDisplayProviderWatcher(Context context,
+ Callback callback, Handler handler, int userId) {
+ mContext = context;
+ mCallback = callback;
+ mHandler = handler;
+ mUserId = userId;
+ mPackageManager = context.getPackageManager();
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "Watcher");
+ pw.println(prefix + " mUserId=" + mUserId);
+ pw.println(prefix + " mRunning=" + mRunning);
+ pw.println(prefix + " mProviders.size()=" + mProviders.size());
+ }
+
+ public void start() {
+ if (!mRunning) {
+ mRunning = true;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mScanPackagesReceiver,
+ new UserHandle(mUserId), filter, null, mHandler);
+
+ // Scan packages.
+ // Also has the side-effect of restarting providers if needed.
+ mHandler.post(mScanPackagesRunnable);
+ }
+ }
+
+ public void stop() {
+ if (mRunning) {
+ mRunning = false;
+
+ mContext.unregisterReceiver(mScanPackagesReceiver);
+ mHandler.removeCallbacks(mScanPackagesRunnable);
+
+ // Stop all providers.
+ for (int i = mProviders.size() - 1; i >= 0; i--) {
+ mProviders.get(i).stop();
+ }
+ }
+ }
+
+ private void scanPackages() {
+ if (!mRunning) {
+ return;
+ }
+
+ // Add providers for all new services.
+ // Reorder the list so that providers left at the end will be the ones to remove.
+ int targetIndex = 0;
+ Intent intent = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
+ for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
+ intent, 0, mUserId)) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) {
+ int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
+ if (sourceIndex < 0) {
+ RemoteDisplayProviderProxy provider =
+ new RemoteDisplayProviderProxy(mContext,
+ new ComponentName(serviceInfo.packageName, serviceInfo.name),
+ mUserId);
+ provider.start();
+ mProviders.add(targetIndex++, provider);
+ mCallback.addProvider(provider);
+ } else if (sourceIndex >= targetIndex) {
+ RemoteDisplayProviderProxy provider = mProviders.get(sourceIndex);
+ provider.start(); // restart the provider if needed
+ provider.rebindIfDisconnected();
+ Collections.swap(mProviders, sourceIndex, targetIndex++);
+ }
+ }
+ }
+
+ // Remove providers for missing services.
+ if (targetIndex < mProviders.size()) {
+ for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
+ RemoteDisplayProviderProxy provider = mProviders.get(i);
+ mCallback.removeProvider(provider);
+ mProviders.remove(provider);
+ provider.stop();
+ }
+ }
+ }
+
+ private boolean verifyServiceTrusted(ServiceInfo serviceInfo) {
+ if (serviceInfo.permission == null || !serviceInfo.permission.equals(
+ Manifest.permission.BIND_REMOTE_DISPLAY)) {
+ // If the service does not require this permission then any app could
+ // potentially bind to it and cause the remote display service to
+ // misbehave. So we only want to trust providers that require the
+ // correct permissions.
+ Slog.w(TAG, "Ignoring remote display provider service because it did not "
+ + "require the BIND_REMOTE_DISPLAY permission in its manifest: "
+ + serviceInfo.packageName + "/" + serviceInfo.name);
+ return false;
+ }
+ if (!hasCaptureVideoPermission(serviceInfo.packageName)) {
+ // If the service does not have permission to capture video then it
+ // isn't going to be terribly useful as a remote display, is it?
+ // Kind of makes you wonder what it's doing there in the first place.
+ Slog.w(TAG, "Ignoring remote display provider service because it does not "
+ + "have the CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT "
+ + "permission: " + serviceInfo.packageName + "/" + serviceInfo.name);
+ return false;
+ }
+ // Looks good.
+ return true;
+ }
+
+ private boolean hasCaptureVideoPermission(String packageName) {
+ if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT,
+ packageName) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT,
+ packageName) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ return false;
+ }
+
+ private int findProvider(String packageName, String className) {
+ int count = mProviders.size();
+ for (int i = 0; i < count; i++) {
+ RemoteDisplayProviderProxy provider = mProviders.get(i);
+ if (provider.hasComponentName(packageName, className)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received package manager broadcast: " + intent);
+ }
+ scanPackages();
+ }
+ };
+
+ private final Runnable mScanPackagesRunnable = new Runnable() {
+ @Override
+ public void run() {
+ scanPackages();
+ }
+ };
+
+ public interface Callback {
+ void addProvider(RemoteDisplayProviderProxy provider);
+ void removeProvider(RemoteDisplayProviderProxy provider);
+ }
+}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 3d6b3c9..dc63ca2 100755
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -217,6 +217,7 @@ public class PackageManagerService extends IPackageManager.Stub {
static final int SCAN_UPDATE_TIME = 1<<6;
static final int SCAN_DEFER_DEX = 1<<7;
static final int SCAN_BOOTING = 1<<8;
+ static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<9;
static final int REMOVE_CHATTY = 1<<16;
@@ -332,7 +333,6 @@ public class PackageManagerService extends IPackageManager.Stub {
new HashMap<String, PackageParser.Package>();
// Information for the parser to write more useful error messages.
- File mScanningPath;
int mLastScanError;
// ----------------------------------------------------------------
@@ -867,7 +867,7 @@ public class PackageManagerService extends IPackageManager.Stub {
int[] uidArray = new int[] { res.pkg.applicationInfo.uid };
ArrayList<String> pkgList = new ArrayList<String>(1);
pkgList.add(res.pkg.applicationInfo.packageName);
- sendResourcesChangedBroadcast(true, false,
+ sendResourcesChangedBroadcast(true, true,
pkgList,uidArray, null);
}
}
@@ -1278,7 +1278,8 @@ public class PackageManagerService extends IPackageManager.Stub {
frameworkDir.getPath(), OBSERVER_EVENTS, true, false);
mFrameworkInstallObserver.startWatching();
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR,
+ | PackageParser.PARSE_IS_SYSTEM_DIR
+ | PackageParser.PARSE_IS_PRIVILEGED,
scanMode | SCAN_NO_DEX, 0);
// Collected privileged system packages.
@@ -1771,6 +1772,24 @@ public class PackageManagerService extends IPackageManager.Stub {
state, userId);
}
+ public boolean isPackageAvailable(String packageName, int userId) {
+ if (!sUserManager.exists(userId)) return false;
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "is package available");
+ synchronized (mPackages) {
+ PackageParser.Package p = mPackages.get(packageName);
+ if (p != null) {
+ final PackageSetting ps = (PackageSetting) p.mExtras;
+ if (ps != null) {
+ final PackageUserState state = ps.readUserState(userId);
+ if (state != null) {
+ return PackageParser.isAvailable(state);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
@Override
public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
@@ -3638,6 +3657,12 @@ public class PackageManagerService extends IPackageManager.Stub {
// An updated system app will not have the PARSE_IS_SYSTEM flag set
// initially
parseFlags |= PackageParser.PARSE_IS_SYSTEM;
+
+ // An updated privileged app will not have the PARSE_IS_PRIVILEGED
+ // flag set initially
+ if ((updatedPkg.pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+ parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+ }
}
// Verify certificates against what was last scanned
if (!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags)) {
@@ -4119,7 +4144,6 @@ public class PackageManagerService extends IPackageManager.Stub {
mLastScanError = PackageManager.INSTALL_FAILED_INVALID_APK;
return null;
}
- mScanningPath = scanFile;
if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
@@ -4139,7 +4163,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (mAndroidApplication != null) {
Slog.w(TAG, "*************************************************");
Slog.w(TAG, "Core android package being redefined. Skipping.");
- Slog.w(TAG, " file=" + mScanningPath);
+ Slog.w(TAG, " file=" + scanFile);
Slog.w(TAG, "*************************************************");
mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
return null;
@@ -4619,6 +4643,10 @@ public class PackageManagerService extends IPackageManager.Stub {
if ((scanMode&SCAN_NO_DEX) == 0) {
if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
== DEX_OPT_FAILED) {
+ if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
+ removeDataDirsLI(pkg.packageName);
+ }
+
mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
return null;
}
@@ -4696,6 +4724,10 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageParser.Package clientPkg = clientLibPkgs.get(i);
if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
== DEX_OPT_FAILED) {
+ if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
+ removeDataDirsLI(pkg.packageName);
+ }
+
mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
return null;
}
@@ -4959,6 +4991,18 @@ public class PackageManagerService extends IPackageManager.Stub {
permissionMap.put(p.info.name, bp);
}
if (bp.perm == null) {
+ if (bp.sourcePackage != null
+ && !bp.sourcePackage.equals(p.info.packageName)) {
+ // If this is a permission that was formerly defined by a non-system
+ // app, but is now defined by a system app (following an upgrade),
+ // discard the previous declaration and consider the system's to be
+ // canonical.
+ if (isSystemApp(p.owner)) {
+ Slog.i(TAG, "New decl " + p.owner + " of permission "
+ + p.info.name + " is system");
+ bp.sourcePackage = null;
+ }
+ }
if (bp.sourcePackage == null
|| bp.sourcePackage.equals(p.info.packageName)) {
BasePermission tree = findPermissionTreeLP(p.info.name);
@@ -9038,7 +9082,7 @@ public class PackageManagerService extends IPackageManager.Stub {
replacePackageLI(pkg, parseFlags, scanMode, args.user,
installerPackageName, res);
} else {
- installNewPackageLI(pkg, parseFlags, scanMode, args.user,
+ installNewPackageLI(pkg, parseFlags, scanMode | SCAN_DELETE_DATA_ON_FAILURES, args.user,
installerPackageName, res);
}
synchronized (mPackages) {
@@ -9999,11 +10043,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if (filter.countDataAuthorities() != 0
|| filter.countDataPaths() != 0
- || filter.countDataSchemes() != 0
+ || filter.countDataSchemes() > 1
|| filter.countDataTypes() != 0) {
throw new IllegalArgumentException(
"replacePreferredActivity expects filter to have no data authorities, " +
- "paths, schemes or types.");
+ "paths, or types; and at most one scheme.");
}
synchronized (mPackages) {
if (mContext.checkCallingOrSelfPermission(
@@ -10020,33 +10064,27 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final int callingUserId = UserHandle.getCallingUserId();
- ArrayList<PreferredActivity> removed = null;
PreferredIntentResolver pir = mSettings.mPreferredActivities.get(callingUserId);
if (pir != null) {
- Iterator<PreferredActivity> it = pir.filterIterator();
- String action = filter.getAction(0);
- String category = filter.getCategory(0);
- while (it.hasNext()) {
- PreferredActivity pa = it.next();
- if ((pa.countActions() == 0) || (pa.countCategories() == 0)
- || (pa.getAction(0).equals(action)
- && pa.getCategory(0).equals(category))) {
- if (removed == null) {
- removed = new ArrayList<PreferredActivity>();
- }
- removed.add(pa);
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Removing preferred activity "
- + pa.mPref.mComponent + ":");
- filter.dump(new LogPrinter(Log.INFO, TAG), " ");
- }
- }
- }
- if (removed != null) {
- for (int i=0; i<removed.size(); i++) {
- PreferredActivity pa = removed.get(i);
- pir.removeFilter(pa);
+ Intent intent = new Intent(filter.getAction(0)).addCategory(filter.getCategory(0));
+ if (filter.countDataSchemes() == 1) {
+ Uri.Builder builder = new Uri.Builder();
+ builder.scheme(filter.getDataScheme(0));
+ intent.setData(builder.build());
+ }
+ List<PreferredActivity> matches = pir.queryIntent(
+ intent, null, true, callingUserId);
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, matches.size() + " preferred matches for " + intent);
+ }
+ for (int i = 0; i < matches.size(); i++) {
+ PreferredActivity pa = matches.get(i);
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Removing preferred activity "
+ + pa.mPref.mComponent + ":");
+ filter.dump(new LogPrinter(Log.INFO, TAG), " ");
}
+ pir.removeFilter(pa);
}
}
addPreferredActivityInternal(filter, match, set, activity, true, callingUserId);
@@ -10551,7 +10589,8 @@ public class PackageManagerService extends IPackageManager.Stub {
DumpState dumpState = new DumpState();
boolean fullPreferred = false;
-
+ boolean checkin = false;
+
String packageName = null;
int opti = 0;
@@ -10565,7 +10604,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// Right now we only know how to print all.
} else if ("-h".equals(opt)) {
pw.println("Package manager dump options:");
- pw.println(" [-h] [-f] [cmd] ...");
+ pw.println(" [-h] [-f] [--checkin] [cmd] ...");
+ pw.println(" --checkin: dump for a checkin");
pw.println(" -f: print details of intent filters");
pw.println(" -h: print this help");
pw.println(" cmd may be one of:");
@@ -10583,13 +10623,15 @@ public class PackageManagerService extends IPackageManager.Stub {
pw.println(" <package.name>: info about given package");
pw.println(" k[eysets]: print known keysets");
return;
+ } else if ("--checkin".equals(opt)) {
+ checkin = true;
} else if ("-f".equals(opt)) {
dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
}
}
-
+
// Is the caller requesting to dump a particular piece of data?
if (opti < args.length) {
String cmd = args[opti];
@@ -10631,17 +10673,26 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ if (checkin) {
+ pw.println("vers,1");
+ }
+
// reader
synchronized (mPackages) {
if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
- if (dumpState.onTitlePrinted())
- pw.println();
- pw.println("Verifiers:");
- pw.print(" Required: ");
- pw.print(mRequiredVerifierPackage);
- pw.print(" (uid=");
- pw.print(getPackageUid(mRequiredVerifierPackage, 0));
- pw.println(")");
+ if (!checkin) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Verifiers:");
+ pw.print(" Required: ");
+ pw.print(mRequiredVerifierPackage);
+ pw.print(" (uid=");
+ pw.print(getPackageUid(mRequiredVerifierPackage, 0));
+ pw.println(")");
+ } else if (mRequiredVerifierPackage != null) {
+ pw.print("vrfy,"); pw.print(mRequiredVerifierPackage);
+ pw.print(","); pw.println(getPackageUid(mRequiredVerifierPackage, 0));
+ }
}
if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
@@ -10650,21 +10701,37 @@ public class PackageManagerService extends IPackageManager.Stub {
while (it.hasNext()) {
String name = it.next();
SharedLibraryEntry ent = mSharedLibraries.get(name);
- if (!printedHeader) {
- if (dumpState.onTitlePrinted())
- pw.println();
- pw.println("Libraries:");
- printedHeader = true;
+ if (!checkin) {
+ if (!printedHeader) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Libraries:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ } else {
+ pw.print("lib,");
}
- pw.print(" ");
pw.print(name);
- pw.print(" -> ");
+ if (!checkin) {
+ pw.print(" -> ");
+ }
if (ent.path != null) {
- pw.print("(jar) ");
- pw.print(ent.path);
+ if (!checkin) {
+ pw.print("(jar) ");
+ pw.print(ent.path);
+ } else {
+ pw.print(",jar,");
+ pw.print(ent.path);
+ }
} else {
- pw.print("(apk) ");
- pw.print(ent.apk);
+ if (!checkin) {
+ pw.print("(apk) ");
+ pw.print(ent.apk);
+ } else {
+ pw.print(",apk,");
+ pw.print(ent.apk);
+ }
}
pw.println();
}
@@ -10673,16 +10740,22 @@ public class PackageManagerService extends IPackageManager.Stub {
if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) {
if (dumpState.onTitlePrinted())
pw.println();
- pw.println("Features:");
+ if (!checkin) {
+ pw.println("Features:");
+ }
Iterator<String> it = mAvailableFeatures.keySet().iterator();
while (it.hasNext()) {
String name = it.next();
- pw.print(" ");
+ if (!checkin) {
+ pw.print(" ");
+ } else {
+ pw.print("feat,");
+ }
pw.println(name);
}
}
- if (dumpState.isDumping(DumpState.DUMP_RESOLVERS)) {
+ 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))) {
@@ -10705,7 +10778,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- if (dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
int user = mSettings.mPreferredActivities.keyAt(i);
@@ -10719,7 +10792,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- if (dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) {
pw.flush();
FileOutputStream fout = new FileOutputStream(fd);
BufferedOutputStream str = new BufferedOutputStream(fout);
@@ -10741,11 +10814,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- if (dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
mSettings.dumpPermissionsLPr(pw, packageName, dumpState);
}
- if (dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
boolean printedSomething = false;
for (PackageParser.Provider p : mProviders.mProviders.values()) {
if (packageName != null && !packageName.equals(p.info.packageName)) {
@@ -10782,19 +10855,19 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- if (dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
mSettings.mKeySetManager.dump(pw, packageName, dumpState);
}
if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
- mSettings.dumpPackagesLPr(pw, packageName, dumpState);
+ mSettings.dumpPackagesLPr(pw, packageName, dumpState, checkin);
}
- if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
mSettings.dumpSharedUsersLPr(pw, packageName, dumpState);
}
- if (dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
if (dumpState.onTitlePrinted())
pw.println();
mSettings.dumpReadMessagesLPr(pw, dumpState);
@@ -11033,7 +11106,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (uidArr != null) {
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
}
- if (replacing && !mediaStatus) {
+ if (replacing) {
extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
}
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index d3ccba6..19bfe01 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -1959,10 +1959,14 @@ final class Settings {
}
boolean doNonData = true;
+ boolean hasSchemes = false;
for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) {
boolean doScheme = true;
String scheme = tmpPa.getDataScheme(ischeme);
+ if (scheme != null && !scheme.isEmpty()) {
+ hasSchemes = true;
+ }
for (int issp=0; issp<tmpPa.countDataSchemeSpecificParts(); issp++) {
Uri.Builder builder = new Uri.Builder();
builder.scheme(scheme);
@@ -2016,11 +2020,25 @@ final class Settings {
}
for (int idata=0; idata<tmpPa.countDataTypes(); idata++) {
- Intent finalIntent = new Intent(intent);
String mimeType = tmpPa.getDataType(idata);
- finalIntent.setType(mimeType);
- applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
- null, null, null, null, mimeType, userId);
+ if (hasSchemes) {
+ Uri.Builder builder = new Uri.Builder();
+ for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) {
+ String scheme = tmpPa.getDataScheme(ischeme);
+ if (scheme != null && !scheme.isEmpty()) {
+ Intent finalIntent = new Intent(intent);
+ builder.scheme(scheme);
+ finalIntent.setDataAndType(builder.build(), mimeType);
+ applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+ scheme, null, null, null, mimeType, userId);
+ }
+ }
+ } else {
+ Intent finalIntent = new Intent(intent);
+ finalIntent.setType(mimeType);
+ applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+ null, null, null, null, mimeType, userId);
+ }
doNonData = false;
}
@@ -2873,8 +2891,44 @@ final class Settings {
ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
};
- void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf,
- Date date, List<UserInfo> users) {
+ void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag, PackageSetting ps,
+ SimpleDateFormat sdf, Date date, List<UserInfo> users) {
+ if (checkinTag != null) {
+ pw.print(checkinTag);
+ pw.print(",");
+ pw.print(ps.realName != null ? ps.realName : ps.name);
+ pw.print(",");
+ pw.print(ps.appId);
+ pw.print(",");
+ pw.print(ps.versionCode);
+ pw.print(",");
+ pw.print(ps.firstInstallTime);
+ pw.print(",");
+ pw.print(ps.lastUpdateTime);
+ pw.print(",");
+ pw.print(ps.installerPackageName != null ? ps.installerPackageName : "?");
+ pw.println();
+ for (UserInfo user : users) {
+ pw.print(checkinTag);
+ pw.print("-");
+ pw.print("usr");
+ pw.print(",");
+ pw.print(user.id);
+ pw.print(",");
+ pw.print(ps.getInstalled(user.id) ? "I" : "i");
+ pw.print(ps.getBlocked(user.id) ? "B" : "b");
+ pw.print(ps.getStopped(user.id) ? "S" : "s");
+ pw.print(ps.getNotLaunched(user.id) ? "l" : "L");
+ pw.print(",");
+ pw.print(ps.getEnabled(user.id));
+ String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
+ pw.print(",");
+ pw.print(lastDisabledAppCaller != null ? lastDisabledAppCaller : "?");
+ pw.println();
+ }
+ return;
+ }
+
pw.print(prefix); pw.print("Package [");
pw.print(ps.realName != null ? ps.realName : ps.name);
pw.print("] (");
@@ -3036,7 +3090,7 @@ final class Settings {
}
}
- void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
+ void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState, boolean checkin) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final Date date = new Date();
boolean printedSomething = false;
@@ -3047,35 +3101,39 @@ final class Settings {
continue;
}
- if (packageName != null) {
+ if (!checkin && packageName != null) {
dumpState.setSharedUser(ps.sharedUser);
}
- if (!printedSomething) {
+ if (!checkin && !printedSomething) {
if (dumpState.onTitlePrinted())
pw.println();
pw.println("Packages:");
printedSomething = true;
}
- dumpPackageLPr(pw, " ", ps, sdf, date, users);
+ dumpPackageLPr(pw, " ", checkin ? "pkg" : null, ps, sdf, date, users);
}
printedSomething = false;
- if (mRenamedPackages.size() > 0) {
+ if (!checkin && mRenamedPackages.size() > 0) {
for (final Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
if (packageName != null && !packageName.equals(e.getKey())
&& !packageName.equals(e.getValue())) {
continue;
}
- if (!printedSomething) {
- if (dumpState.onTitlePrinted())
- pw.println();
- pw.println("Renamed packages:");
- printedSomething = true;
+ if (!checkin) {
+ if (!printedSomething) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Renamed packages:");
+ printedSomething = true;
+ }
+ pw.print(" ");
+ } else {
+ pw.print("ren,");
}
- pw.print(" ");
pw.print(e.getKey());
- pw.print(" -> ");
+ pw.print(checkin ? " -> " : ",");
pw.println(e.getValue());
}
}
@@ -3087,13 +3145,13 @@ final class Settings {
&& !packageName.equals(ps.name)) {
continue;
}
- if (!printedSomething) {
+ if (!checkin && !printedSomething) {
if (dumpState.onTitlePrinted())
pw.println();
pw.println("Hidden system packages:");
printedSomething = true;
}
- dumpPackageLPr(pw, " ", ps, sdf, date, users);
+ dumpPackageLPr(pw, " ", checkin ? "dis" : null, ps, sdf, date, users);
}
}
}
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index 976a328..30bc922 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -298,6 +298,10 @@ final class DisplayPowerController {
// True if mAmbientLux holds a valid value.
private boolean mAmbientLuxValid;
+ // The ambient light level threshold at which to brighten or darken the screen.
+ private float mBrighteningLuxThreshold;
+ private float mDarkeningLuxThreshold;
+
// The most recent light sample.
private float mLastObservedLux;
@@ -606,7 +610,6 @@ final class DisplayPowerController {
&& mProximity == PROXIMITY_POSITIVE) {
mScreenOffBecauseOfProximity = true;
sendOnProximityPositiveWithWakelock();
- setScreenOn(false);
}
} else if (mWaitingForNegativeProximity
&& mScreenOffBecauseOfProximity
@@ -666,59 +669,62 @@ final class DisplayPowerController {
mUsingScreenAutoBrightness = false;
}
- // Animate the screen on or off.
- if (!mScreenOffBecauseOfProximity) {
- if (wantScreenOn(mPowerRequest.screenState)) {
- // Want screen on.
- // Wait for previous off animation to complete beforehand.
- // It is relatively short but if we cancel it and switch to the
- // on animation immediately then the results are pretty ugly.
- if (!mElectronBeamOffAnimator.isStarted()) {
- // Turn the screen on. The contents of the screen may not yet
- // be visible if the electron beam has not been dismissed because
- // its last frame of animation is solid black.
- setScreenOn(true);
-
- if (mPowerRequest.blockScreenOn
- && mPowerState.getElectronBeamLevel() == 0.0f) {
- blockScreenOn();
- } else {
- unblockScreenOn();
- if (USE_ELECTRON_BEAM_ON_ANIMATION) {
- if (!mElectronBeamOnAnimator.isStarted()) {
- if (mPowerState.getElectronBeamLevel() == 1.0f) {
- mPowerState.dismissElectronBeam();
- } else if (mPowerState.prepareElectronBeam(
- mElectronBeamFadesConfig ?
- ElectronBeam.MODE_FADE :
- ElectronBeam.MODE_WARM_UP)) {
- mElectronBeamOnAnimator.start();
- } else {
- mElectronBeamOnAnimator.end();
- }
+ // Animate the screen on or off unless blocked.
+ if (mScreenOffBecauseOfProximity) {
+ // Screen off due to proximity.
+ setScreenOn(false);
+ unblockScreenOn();
+ } else if (wantScreenOn(mPowerRequest.screenState)) {
+ // Want screen on.
+ // Wait for previous off animation to complete beforehand.
+ // It is relatively short but if we cancel it and switch to the
+ // on animation immediately then the results are pretty ugly.
+ if (!mElectronBeamOffAnimator.isStarted()) {
+ // Turn the screen on. The contents of the screen may not yet
+ // be visible if the electron beam has not been dismissed because
+ // its last frame of animation is solid black.
+ setScreenOn(true);
+
+ if (mPowerRequest.blockScreenOn
+ && mPowerState.getElectronBeamLevel() == 0.0f) {
+ blockScreenOn();
+ } else {
+ unblockScreenOn();
+ if (USE_ELECTRON_BEAM_ON_ANIMATION) {
+ if (!mElectronBeamOnAnimator.isStarted()) {
+ if (mPowerState.getElectronBeamLevel() == 1.0f) {
+ mPowerState.dismissElectronBeam();
+ } else if (mPowerState.prepareElectronBeam(
+ mElectronBeamFadesConfig ?
+ ElectronBeam.MODE_FADE :
+ ElectronBeam.MODE_WARM_UP)) {
+ mElectronBeamOnAnimator.start();
+ } else {
+ mElectronBeamOnAnimator.end();
}
- } else {
- mPowerState.setElectronBeamLevel(1.0f);
- mPowerState.dismissElectronBeam();
}
+ } else {
+ mPowerState.setElectronBeamLevel(1.0f);
+ mPowerState.dismissElectronBeam();
}
}
- } else {
- // Want screen off.
- // Wait for previous on animation to complete beforehand.
- if (!mElectronBeamOnAnimator.isStarted()) {
- if (!mElectronBeamOffAnimator.isStarted()) {
- if (mPowerState.getElectronBeamLevel() == 0.0f) {
- setScreenOn(false);
- } else if (mPowerState.prepareElectronBeam(
- mElectronBeamFadesConfig ?
- ElectronBeam.MODE_FADE :
- ElectronBeam.MODE_COOL_DOWN)
- && mPowerState.isScreenOn()) {
- mElectronBeamOffAnimator.start();
- } else {
- mElectronBeamOffAnimator.end();
- }
+ }
+ } else {
+ // Want screen off.
+ // Wait for previous on animation to complete beforehand.
+ unblockScreenOn();
+ if (!mElectronBeamOnAnimator.isStarted()) {
+ if (!mElectronBeamOffAnimator.isStarted()) {
+ if (mPowerState.getElectronBeamLevel() == 0.0f) {
+ setScreenOn(false);
+ } else if (mPowerState.prepareElectronBeam(
+ mElectronBeamFadesConfig ?
+ ElectronBeam.MODE_FADE :
+ ElectronBeam.MODE_COOL_DOWN)
+ && mPowerState.isScreenOn()) {
+ mElectronBeamOffAnimator.start();
+ } else {
+ mElectronBeamOffAnimator.end();
}
}
}
@@ -758,15 +764,15 @@ final class DisplayPowerController {
private void unblockScreenOn() {
if (mScreenOnWasBlocked) {
mScreenOnWasBlocked = false;
- if (DEBUG) {
- Slog.d(TAG, "Unblocked screen on after " +
- (SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime) + " ms");
+ long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
+ if (delay > 1000 || DEBUG) {
+ Slog.d(TAG, "Unblocked screen on after " + delay + " ms");
}
}
}
private void setScreenOn(boolean on) {
- if (!mPowerState.isScreenOn() == on) {
+ if (mPowerState.isScreenOn() != on) {
mPowerState.setScreenOn(on);
if (on) {
mNotifier.onScreenOn();
@@ -945,12 +951,24 @@ final class DisplayPowerController {
mLastObservedLuxTime = time;
}
+ private void setAmbientLux(float lux) {
+ mAmbientLux = lux;
+ mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
+ mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
+ }
+
private void updateAmbientLux(long time) {
// If the light sensor was just turned on then immediately update our initial
// estimate of the current ambient light level.
- if (!mAmbientLuxValid
- || (time - mLightSensorEnableTime) < mLightSensorWarmUpTimeConfig) {
- mAmbientLux = mRecentShortTermAverageLux;
+ if (!mAmbientLuxValid) {
+ final long timeWhenSensorWarmedUp =
+ mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
+ if (time < timeWhenSensorWarmedUp) {
+ mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED,
+ timeWhenSensorWarmedUp);
+ return;
+ }
+ setAmbientLux(mRecentShortTermAverageLux);
mAmbientLuxValid = true;
mDebounceLuxDirection = 0;
mDebounceLuxTime = time;
@@ -961,98 +979,90 @@ final class DisplayPowerController {
+ ", mAmbientLux=" + mAmbientLux);
}
updateAutoBrightness(true);
- return;
- }
-
- // Determine whether the ambient environment appears to be brightening.
- float brighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
- if (mRecentShortTermAverageLux > brighteningLuxThreshold
- && mRecentLongTermAverageLux > brighteningLuxThreshold) {
+ } else if (mRecentShortTermAverageLux > mBrighteningLuxThreshold
+ && mRecentLongTermAverageLux > mBrighteningLuxThreshold) {
+ // The ambient environment appears to be brightening.
if (mDebounceLuxDirection <= 0) {
mDebounceLuxDirection = 1;
mDebounceLuxTime = time;
if (DEBUG) {
Slog.d(TAG, "updateAmbientLux: Possibly brightened, waiting for "
+ BRIGHTENING_LIGHT_DEBOUNCE + " ms: "
- + "brighteningLuxThreshold=" + brighteningLuxThreshold
+ + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+ ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+ ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+ ", mAmbientLux=" + mAmbientLux);
}
}
long debounceTime = mDebounceLuxTime + BRIGHTENING_LIGHT_DEBOUNCE;
- if (time >= debounceTime) {
- mAmbientLux = mRecentShortTermAverageLux;
- if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: Brightened: "
- + "brighteningLuxThreshold=" + brighteningLuxThreshold
- + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
- + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
- + ", mAmbientLux=" + mAmbientLux);
- }
- updateAutoBrightness(true);
- } else {
+ if (time < debounceTime) {
mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime);
+ return;
}
- return;
- }
-
- // Determine whether the ambient environment appears to be darkening.
- float darkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
- if (mRecentShortTermAverageLux < darkeningLuxThreshold
- && mRecentLongTermAverageLux < darkeningLuxThreshold) {
+ setAmbientLux(mRecentShortTermAverageLux);
+ if (DEBUG) {
+ Slog.d(TAG, "updateAmbientLux: Brightened: "
+ + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+ + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+ + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+ + ", mAmbientLux=" + mAmbientLux);
+ }
+ updateAutoBrightness(true);
+ } else if (mRecentShortTermAverageLux < mDarkeningLuxThreshold
+ && mRecentLongTermAverageLux < mDarkeningLuxThreshold) {
+ // The ambient environment appears to be darkening.
if (mDebounceLuxDirection >= 0) {
mDebounceLuxDirection = -1;
mDebounceLuxTime = time;
if (DEBUG) {
Slog.d(TAG, "updateAmbientLux: Possibly darkened, waiting for "
+ DARKENING_LIGHT_DEBOUNCE + " ms: "
- + "darkeningLuxThreshold=" + darkeningLuxThreshold
+ + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
+ ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+ ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+ ", mAmbientLux=" + mAmbientLux);
}
}
long debounceTime = mDebounceLuxTime + DARKENING_LIGHT_DEBOUNCE;
- if (time >= debounceTime) {
- // Be conservative about reducing the brightness, only reduce it a little bit
- // at a time to avoid having to bump it up again soon.
- mAmbientLux = Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux);
- if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: Darkened: "
- + "darkeningLuxThreshold=" + darkeningLuxThreshold
- + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
- + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
- + ", mAmbientLux=" + mAmbientLux);
- }
- updateAutoBrightness(true);
- } else {
+ if (time < debounceTime) {
mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime);
+ return;
}
- return;
- }
-
- // No change or change is within the hysteresis thresholds.
- if (mDebounceLuxDirection != 0) {
+ // Be conservative about reducing the brightness, only reduce it a little bit
+ // at a time to avoid having to bump it up again soon.
+ setAmbientLux(Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux));
+ if (DEBUG) {
+ Slog.d(TAG, "updateAmbientLux: Darkened: "
+ + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
+ + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+ + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+ + ", mAmbientLux=" + mAmbientLux);
+ }
+ updateAutoBrightness(true);
+ } else if (mDebounceLuxDirection != 0) {
+ // No change or change is within the hysteresis thresholds.
mDebounceLuxDirection = 0;
mDebounceLuxTime = time;
if (DEBUG) {
Slog.d(TAG, "updateAmbientLux: Canceled debounce: "
- + "brighteningLuxThreshold=" + brighteningLuxThreshold
- + ", darkeningLuxThreshold=" + darkeningLuxThreshold
+ + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+ + ", mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
+ ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+ ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+ ", mAmbientLux=" + mAmbientLux);
}
}
- // If the light level does not change, then the sensor may not report
- // a new value. This can cause problems for the auto-brightness algorithm
- // because the filters might not be updated. To work around it, we want to
- // make sure to update the filters whenever the observed light level could
- // possibly exceed one of the hysteresis thresholds.
- if (mLastObservedLux > brighteningLuxThreshold
- || mLastObservedLux < darkeningLuxThreshold) {
+ // Now that we've done all of that, we haven't yet posted a debounce
+ // message. So consider the case where current lux is beyond the
+ // threshold. It's possible that the light sensor may not report values
+ // if the light level does not change, so we need to occasionally
+ // synthesize sensor readings in order to make sure the brightness is
+ // adjusted accordingly. Note these thresholds may have changed since
+ // we entered the function because we called setAmbientLux and
+ // updateAutoBrightness along the way.
+ if (mLastObservedLux > mBrighteningLuxThreshold
+ || mLastObservedLux < mDarkeningLuxThreshold) {
mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED,
time + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS);
}
diff --git a/services/java/com/android/server/power/DisplayPowerState.java b/services/java/com/android/server/power/DisplayPowerState.java
index fa318f8..5c048f1 100644
--- a/services/java/com/android/server/power/DisplayPowerState.java
+++ b/services/java/com/android/server/power/DisplayPowerState.java
@@ -304,8 +304,15 @@ final class DisplayPowerState {
int brightness = mScreenOn && mElectronBeamLevel > 0f ? mScreenBrightness : 0;
if (mPhotonicModulator.setState(mScreenOn, brightness)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Screen ready");
+ }
mScreenReady = true;
invokeCleanListenerIfNeeded();
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Screen not ready");
+ }
}
}
};
@@ -355,7 +362,7 @@ final class DisplayPowerState {
AsyncTask.THREAD_POOL_EXECUTOR.execute(mTask);
}
}
- return mChangeInProgress;
+ return !mChangeInProgress;
}
}
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index da9548f..134718b 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -1135,6 +1135,9 @@ public final class PowerManagerService extends IPowerManager.Stub
if (!mSystemReady || mDirty == 0) {
return;
}
+ if (!Thread.holdsLock(mLock)) {
+ Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked");
+ }
// Phase 0: Basic state updates.
updateIsPoweredLocked(mDirty);
diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java
index 8a3997a..98acc27 100644
--- a/services/java/com/android/server/print/PrintManagerService.java
+++ b/services/java/com/android/server/print/PrintManagerService.java
@@ -366,7 +366,7 @@ public final class PrintManagerService extends IPrintManager.Stub {
pw.println("PRINT MANAGER STATE (dumpsys print)");
final int userStateCount = mUserStates.size();
for (int i = 0; i < userStateCount; i++) {
- UserState userState = mUserStates.get(i);
+ UserState userState = mUserStates.valueAt(i);
userState.dump(fd, pw, "");
pw.println();
}
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index 5a60de0..bb61e49 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -98,6 +98,13 @@ public class UsbDeviceManager {
// which need debouncing.
private static final int UPDATE_DELAY = 1000;
+ // Time we received a request to enter USB accessory mode
+ private long mAccessoryModeRequestTime = 0;
+
+ // Timeout for entering USB request mode.
+ // Request is cancelled if host does not configure device within 10 seconds.
+ private static final int ACCESSORY_REQUEST_TIMEOUT = 10 * 1000;
+
private static final String BOOT_MODE_PROPERTY = "ro.bootmode";
private UsbHandler mHandler;
@@ -205,6 +212,8 @@ public class UsbDeviceManager {
}
private void startAccessoryMode() {
+ if (!mHasUsbAccessory) return;
+
mAccessoryStrings = nativeGetAccessoryStrings();
boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
// don't start accessory mode if our mandatory strings have not been set
@@ -223,6 +232,7 @@ public class UsbDeviceManager {
}
if (functions != null) {
+ mAccessoryModeRequestTime = SystemClock.elapsedRealtime();
setCurrentFunctions(functions, false);
}
}
@@ -456,6 +466,8 @@ public class UsbDeviceManager {
}
private void setEnabledFunctions(String functions, boolean makeDefault) {
+ if (DEBUG) Slog.d(TAG, "setEnabledFunctions " + functions
+ + " makeDefault: " + makeDefault);
// Do not update persystent.sys.usb.config if the device is booted up
// with OEM specific mode.
@@ -517,9 +529,16 @@ public class UsbDeviceManager {
}
private void updateCurrentAccessory() {
- if (!mHasUsbAccessory) return;
+ // We are entering accessory mode if we have received a request from the host
+ // and the request has not timed out yet.
+ boolean enteringAccessoryMode =
+ mAccessoryModeRequestTime > 0 &&
+ SystemClock.elapsedRealtime() <
+ mAccessoryModeRequestTime + ACCESSORY_REQUEST_TIMEOUT;
+
+ if (mConfigured && enteringAccessoryMode) {
+ // successfully entered accessory mode
- if (mConfigured) {
if (mAccessoryStrings != null) {
mCurrentAccessory = new UsbAccessory(mAccessoryStrings);
Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
@@ -530,7 +549,7 @@ public class UsbDeviceManager {
} else {
Slog.e(TAG, "nativeGetAccessoryStrings failed");
}
- } else if (!mConnected) {
+ } else if (!enteringAccessoryMode) {
// make sure accessory mode is off
// and restore default functions
Slog.d(TAG, "exited USB accessory mode");
@@ -560,6 +579,8 @@ public class UsbDeviceManager {
}
}
+ if (DEBUG) Slog.d(TAG, "broadcasting " + intent + " connected: " + mConnected
+ + " configured: " + mConfigured);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -599,9 +620,7 @@ public class UsbDeviceManager {
if (containsFunction(mCurrentFunctions,
UsbManager.USB_FUNCTION_ACCESSORY)) {
updateCurrentAccessory();
- }
-
- if (!mConnected) {
+ } else if (!mConnected) {
// restore defaults when USB is disconnected
setEnabledFunctions(mDefaultFunctions, false);
}
diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java
index a3d514e..bdb0a5e 100644
--- a/services/java/com/android/server/wifi/WifiController.java
+++ b/services/java/com/android/server/wifi/WifiController.java
@@ -152,11 +152,21 @@ class WifiController extends StateMachine {
addState(mStaDisabledWithScanState, mDefaultState);
addState(mApEnabledState, mDefaultState);
addState(mEcmState, mDefaultState);
- if (mSettingsStore.isScanAlwaysAvailable()) {
+
+ boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
+ boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
+ boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
+
+ log("isAirplaneModeOn = " + isAirplaneModeOn +
+ ", isWifiEnabled = " + isWifiEnabled +
+ ", isScanningAvailable = " + isScanningAlwaysAvailable);
+
+ if (isWifiEnabled && isScanningAlwaysAvailable) {
setInitialState(mStaDisabledWithScanState);
} else {
setInitialState(mApStaDisabledState);
}
+
setLogRecSize(100);
setLogOnlyTransitions(false);
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index d471b57..aecf9ae 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -369,15 +369,17 @@ public final class WifiService extends IWifiManager.Stub {
}
private class BatchedScanRequest extends DeathRecipient {
- BatchedScanSettings settings;
- int uid;
- int pid;
+ final BatchedScanSettings settings;
+ final int uid;
+ final int pid;
+ final WorkSource workSource;
- BatchedScanRequest(BatchedScanSettings settings, IBinder binder) {
+ BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) {
super(0, null, binder, null);
this.settings = settings;
this.uid = getCallingUid();
this.pid = getCallingPid();
+ workSource = ws;
}
public void binderDied() {
stopBatchedScan(settings, uid, pid);
@@ -406,12 +408,19 @@ public final class WifiService extends IWifiManager.Stub {
/**
* see {@link android.net.wifi.WifiManager#requestBatchedScan()}
*/
- public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) {
+ public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder,
+ WorkSource workSource) {
enforceChangePermission();
+ if (workSource != null) {
+ enforceWorkSourcePermission();
+ // WifiManager currently doesn't use names, so need to clear names out of the
+ // supplied WorkSource to allow future WorkSource combining.
+ workSource.clearNames();
+ }
if (mBatchedScanSupported == false) return false;
requested = new BatchedScanSettings(requested);
if (requested.isInvalid()) return false;
- BatchedScanRequest r = new BatchedScanRequest(requested, binder);
+ BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource);
synchronized(mBatchedScanners) {
mBatchedScanners.add(r);
resolveBatchedScannersLocked();
@@ -468,18 +477,48 @@ public final class WifiService extends IWifiManager.Stub {
private void resolveBatchedScannersLocked() {
BatchedScanSettings setting = new BatchedScanSettings();
+ WorkSource responsibleWorkSource = null;
int responsibleUid = 0;
+ double responsibleCsph = 0; // Channel Scans Per Hour
if (mBatchedScanners.size() == 0) {
- mWifiStateMachine.setBatchedScanSettings(null, 0);
+ mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null);
return;
}
for (BatchedScanRequest r : mBatchedScanners) {
BatchedScanSettings s = r.settings;
+
+ // evaluate responsibility
+ int currentChannelCount;
+ int currentScanInterval;
+ double currentCsph;
+
+ if (s.channelSet == null || s.channelSet.isEmpty()) {
+ // all channels - 11 B and 9 A channels roughly.
+ currentChannelCount = 9 + 11;
+ } else {
+ currentChannelCount = s.channelSet.size();
+ // these are rough est - no real need to correct for reg-domain;
+ if (s.channelSet.contains("A")) currentChannelCount += (9 - 1);
+ if (s.channelSet.contains("B")) currentChannelCount += (11 - 1);
+
+ }
+ if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
+ currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+ } else {
+ currentScanInterval = s.scanIntervalSec;
+ }
+ currentCsph = 60 * 60 * currentChannelCount / currentScanInterval;
+
+ if (currentCsph > responsibleCsph) {
+ responsibleUid = r.uid;
+ responsibleWorkSource = r.workSource;
+ responsibleCsph = currentCsph;
+ }
+
if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
s.maxScansPerBatch < setting.maxScansPerBatch) {
setting.maxScansPerBatch = s.maxScansPerBatch;
- responsibleUid = r.uid;
}
if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
(setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED ||
@@ -489,7 +528,6 @@ public final class WifiService extends IWifiManager.Stub {
if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
s.scanIntervalSec < setting.scanIntervalSec) {
setting.scanIntervalSec = s.scanIntervalSec;
- responsibleUid = r.uid;
}
if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
(setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED ||
@@ -511,7 +549,8 @@ public final class WifiService extends IWifiManager.Stub {
}
setting.constrain();
- mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid);
+ mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph,
+ responsibleWorkSource);
}
private void enforceAccessPermission() {
@@ -831,7 +870,7 @@ public final class WifiService extends IWifiManager.Stub {
public void setCountryCode(String countryCode, boolean persist) {
Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
" with persist set to " + persist);
- enforceChangePermission();
+ enforceConnectivityInternalPermission();
final long token = Binder.clearCallingIdentity();
try {
mWifiStateMachine.setCountryCode(countryCode, persist);
diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java
index 8cc1d02..e98014b 100644
--- a/services/java/com/android/server/wm/AppWindowToken.java
+++ b/services/java/com/android/server/wm/AppWindowToken.java
@@ -53,6 +53,7 @@ class AppWindowToken extends WindowToken {
int groupId = -1;
boolean appFullscreen;
int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ boolean layoutConfigChanges;
boolean showWhenLocked;
// The input dispatching timeout for this application token in nanoseconds.
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 52f2325..d358b4c 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -25,9 +25,11 @@ import android.app.ActivityManager.StackBoxInfo;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Debug;
+import android.util.EventLog;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
+import com.android.server.EventLogTags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -97,9 +99,6 @@ class DisplayContent {
/** True when the home StackBox is at the top of mStackBoxes, false otherwise. */
private TaskStack mHomeStack = null;
- /** Sorted most recent at top, oldest at [0]. */
- ArrayList<TaskStack> mStackHistory = new ArrayList<TaskStack>();
-
/** Detect user tapping outside of current focused stack bounds .*/
StackTapPointerEventListener mTapDetector;
@@ -107,7 +106,7 @@ class DisplayContent {
Region mTouchExcludeRegion = new Region();
/** Save allocating when retrieving tasks */
- ArrayList<Task> mTaskHistory = new ArrayList<Task>();
+ private ArrayList<Task> mTaskHistory = new ArrayList<Task>();
/** Save allocating when calculating rects */
Rect mTmpRect = new Rect();
@@ -160,12 +159,6 @@ class DisplayContent {
return mStackBoxes.get(0).mStack != mHomeStack;
}
- void moveStack(TaskStack stack, boolean toTop) {
- mStackHistory.remove(stack);
- mStackHistory.add(toTop ? mStackHistory.size() : 0, stack);
- mService.moveStackWindowsLocked(this);
- }
-
public boolean isPrivate() {
return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0;
}
@@ -200,6 +193,7 @@ class DisplayContent {
}
mTaskHistory.add(taskNdx, task);
+ EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, taskNdx);
}
void removeTask(Task task) {
@@ -277,6 +271,8 @@ class DisplayContent {
if (newStack != null) {
layoutNeeded = true;
}
+ EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, relativeStackBoxId, position,
+ (int)(weight * 100 + 0.5));
return newStack;
}
@@ -345,6 +341,7 @@ class DisplayContent {
boolean moveHomeStackBox(boolean toTop) {
if (DEBUG_STACK) Slog.d(TAG, "moveHomeStackBox: toTop=" + toTop + " Callers=" +
Debug.getCallers(4));
+ EventLog.writeEvent(EventLogTags.WM_HOME_STACK_MOVED, toTop ? 1 : 0);
switch (mStackBoxes.size()) {
case 0: throw new RuntimeException("moveHomeStackBox: No home StackBox!");
case 1: return false; // Only the home StackBox exists.
diff --git a/services/java/com/android/server/wm/StackBox.java b/services/java/com/android/server/wm/StackBox.java
index d054e9a..d351925 100644
--- a/services/java/com/android/server/wm/StackBox.java
+++ b/services/java/com/android/server/wm/StackBox.java
@@ -243,10 +243,6 @@ public class StackBox {
/** Remove this box and propagate its sibling's content up to their parent.
* @return The first stackId of the resulting StackBox. */
int remove() {
- if (mStack != null) {
- if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing stackId=" + mStack.mStackId);
- mDisplayContent.mStackHistory.remove(mStack);
- }
mDisplayContent.layoutNeeded = true;
if (mParent == null) {
diff --git a/services/java/com/android/server/wm/Task.java b/services/java/com/android/server/wm/Task.java
index d9acbb9..13fdbc8 100644
--- a/services/java/com/android/server/wm/Task.java
+++ b/services/java/com/android/server/wm/Task.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import android.util.EventLog;
+import com.android.server.EventLogTags;
+
class Task {
// private final String TAG = "TaskGroup";
TaskStack mStack;
@@ -41,6 +44,8 @@ class Task {
boolean removeAppToken(AppWindowToken wtoken) {
mAppTokens.remove(wtoken);
if (mAppTokens.size() == 0) {
+ EventLog.writeEvent(com.android.server.EventLogTags.WM_TASK_REMOVED, taskId,
+ "removeAppToken: last token");
mStack.removeTask(this);
return true;
}
diff --git a/services/java/com/android/server/wm/TaskStack.java b/services/java/com/android/server/wm/TaskStack.java
index 34bef68..cb29df4 100644
--- a/services/java/com/android/server/wm/TaskStack.java
+++ b/services/java/com/android/server/wm/TaskStack.java
@@ -21,8 +21,10 @@ import static com.android.server.wm.WindowManagerService.TAG;
import android.graphics.Rect;
import android.os.Debug;
+import android.util.EventLog;
import android.util.Slog;
import android.util.TypedValue;
+import com.android.server.EventLogTags;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
@@ -45,7 +47,7 @@ public class TaskStack {
/** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match
* mTaskHistory in the ActivityStack with the same mStackId */
- private ArrayList<Task> mTasks = new ArrayList<Task>();
+ private final ArrayList<Task> mTasks = new ArrayList<Task>();
/** The StackBox this sits in. */
StackBox mStackBox;
@@ -70,7 +72,6 @@ public class TaskStack {
mService = service;
mStackId = stackId;
mDisplayContent = displayContent;
- final int displayId = displayContent.getDisplayId();
mDimLayer = new DimLayer(service, this);
mAnimationBackgroundSurface = new DimLayer(service, this);
}
@@ -152,6 +153,7 @@ public class TaskStack {
int remove() {
mAnimationBackgroundSurface.destroySurface();
mDimLayer.destroySurface();
+ EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
return mStackBox.remove();
}
@@ -269,6 +271,8 @@ public class TaskStack {
for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
final WindowState win = windows.get(winNdx);
if (!resizingWindows.contains(win)) {
+ if (WindowManagerService.DEBUG_RESIZE) Slog.d(TAG,
+ "setBounds: Resizing " + win);
resizingWindows.add(win);
}
win.mUnderStatusBar = underStatusBar;
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index cd46bb8..91f15f3 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -245,7 +245,7 @@ public class WindowAnimator {
mForceHiding = KEYGUARD_ANIMATING_OUT;
}
} else {
- mForceHiding = KEYGUARD_SHOWN;
+ mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN;
}
}
if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 63e09db..737f384 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -437,8 +437,15 @@ public class WindowManagerService extends IWindowManager.Stub
int mRotation = 0;
int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
boolean mAltOrientation = false;
- ArrayList<IRotationWatcher> mRotationWatchers
- = new ArrayList<IRotationWatcher>();
+ class RotationWatcher {
+ IRotationWatcher watcher;
+ IBinder.DeathRecipient dr;
+ RotationWatcher(IRotationWatcher w, IBinder.DeathRecipient d) {
+ watcher = w;
+ dr = d;
+ }
+ }
+ ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<RotationWatcher>();
int mDeferredRotationPauseCount;
int mSystemDecorLayer = 0;
@@ -578,10 +585,13 @@ public class WindowManagerService extends IWindowManager.Stub
private boolean mUpdateRotation = false;
boolean mWallpaperActionPending = false;
- private static final int DISPLAY_CONTENT_UNKNOWN = 0;
- private static final int DISPLAY_CONTENT_MIRROR = 1;
- private static final int DISPLAY_CONTENT_UNIQUE = 2;
- private int mDisplayHasContent = DISPLAY_CONTENT_UNKNOWN;
+ // Set to true when the display contains content to show the user.
+ // When false, the display manager may choose to mirror or blank the display.
+ boolean mDisplayHasContent = false;
+
+ // Only set while traversing the default display based on its content.
+ // Affects the behavior of mirroring on secondary displays.
+ boolean mObscureApplicationContentOnSecondaryDisplays = false;
}
final LayoutFields mInnerFields = new LayoutFields();
@@ -2370,6 +2380,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
public void removeWindowLocked(Session session, WindowState win) {
+ removeWindowLocked(session, win, false);
+ }
+
+ private void removeWindowLocked(Session session, WindowState win,
+ boolean forceRemove) {
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win);
removeStartingWindowTimeout(win.mAppToken);
@@ -2420,7 +2435,7 @@ public class WindowManagerService extends IWindowManager.Stub
mDisplayMagnifier.onWindowTransitionLocked(win, transit);
}
}
- if (win.mExiting || win.mWinAnimator.isAnimating()) {
+ if (!forceRemove && (win.mExiting || win.mWinAnimator.isAnimating())) {
// The exit animation is running... wait for it!
//Slog.i(TAG, "*** Running exit animation...");
win.mExiting = true;
@@ -3394,16 +3409,17 @@ public class WindowManagerService extends IWindowManager.Stub
if (stack == null) {
throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
}
+ EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
Task task = new Task(atoken, stack, userId);
mTaskIdToTask.put(taskId, task);
stack.addTask(task, true);
- stack.getDisplayContent().moveStack(stack, true);
return task;
}
@Override
public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
- int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId) {
+ int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
+ int configChanges) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"addAppToken()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3435,6 +3451,8 @@ public class WindowManagerService extends IWindowManager.Stub
atoken.appFullscreen = fullscreen;
atoken.showWhenLocked = showWhenLocked;
atoken.requestedOrientation = requestedOrientation;
+ atoken.layoutConfigChanges = (configChanges &
+ (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken
+ " to stack=" + stackId + " task=" + taskId + " at " + addPos);
@@ -4303,10 +4321,6 @@ public class WindowManagerService extends IWindowManager.Stub
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
if (okToDisplay() && mAppTransition.isTransitionSet()) {
- // Already in requested state, don't do anything more.
- if (wtoken.hiddenRequested != visible) {
- return;
- }
wtoken.hiddenRequested = !visible;
if (!wtoken.startingDisplayed) {
@@ -4792,7 +4806,6 @@ public class WindowManagerService extends IWindowManager.Stub
displayContent.moveHomeStackBox(isHomeStackTask);
}
stack.moveTaskToTop(task);
- displayContent.moveStack(stack, true);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4846,7 +4859,6 @@ public class WindowManagerService extends IWindowManager.Stub
weight);
if (stack != null) {
mStackIdToStack.put(stackId, stack);
- displayContent.moveStack(stack, true);
performLayoutAndPlaceSurfacesLocked();
return;
}
@@ -4878,6 +4890,7 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
final TaskStack stack = task.mStack;
+ EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, taskId, "removeTask");
stack.removeTask(task);
stack.getDisplayContent().layoutNeeded = true;
}
@@ -5992,7 +6005,7 @@ public class WindowManagerService extends IWindowManager.Stub
for (int i=mRotationWatchers.size()-1; i>=0; i--) {
try {
- mRotationWatchers.get(i).onRotationChanged(rotation);
+ mRotationWatchers.get(i).watcher.onRotationChanged(rotation);
} catch (RemoteException e) {
}
}
@@ -6024,10 +6037,10 @@ public class WindowManagerService extends IWindowManager.Stub
public void binderDied() {
synchronized (mWindowMap) {
for (int i=0; i<mRotationWatchers.size(); i++) {
- if (watcherBinder == mRotationWatchers.get(i).asBinder()) {
- IRotationWatcher removed = mRotationWatchers.remove(i);
+ if (watcherBinder == mRotationWatchers.get(i).watcher.asBinder()) {
+ RotationWatcher removed = mRotationWatchers.remove(i);
if (removed != null) {
- removed.asBinder().unlinkToDeath(this, 0);
+ removed.watcher.asBinder().unlinkToDeath(this, 0);
}
i--;
}
@@ -6039,7 +6052,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
try {
watcher.asBinder().linkToDeath(dr, 0);
- mRotationWatchers.add(watcher);
+ mRotationWatchers.add(new RotationWatcher(watcher, dr));
} catch (RemoteException e) {
// Client died, no cleanup needed.
}
@@ -6053,9 +6066,13 @@ public class WindowManagerService extends IWindowManager.Stub
final IBinder watcherBinder = watcher.asBinder();
synchronized (mWindowMap) {
for (int i=0; i<mRotationWatchers.size(); i++) {
- if (watcherBinder == mRotationWatchers.get(i).asBinder()) {
- mRotationWatchers.remove(i);
- i--;
+ RotationWatcher rotationWatcher = mRotationWatchers.get(i);
+ if (watcherBinder == rotationWatcher.watcher.asBinder()) {
+ RotationWatcher removed = mRotationWatchers.remove(i);
+ if (removed != null) {
+ removed.watcher.asBinder().unlinkToDeath(removed.dr, 0);
+ i--;
+ }
}
}
}
@@ -8269,7 +8286,9 @@ public class WindowManagerService extends IWindowManager.Stub
// windows, since that means "perform layout as normal,
// just don't display").
if (!gone || !win.mHaveFrame || win.mLayoutNeeded
- || (win.mAttrs.type == TYPE_KEYGUARD && win.isConfigChanged())
+ || ((win.isConfigChanged() || win.setInsetsChanged()) &&
+ (win.mAttrs.type == TYPE_KEYGUARD ||
+ win.mAppToken != null && win.mAppToken.layoutConfigChanges))
|| win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
if (!win.mLayoutAttached) {
if (initial) {
@@ -8703,12 +8722,7 @@ public class WindowManagerService extends IWindowManager.Stub
private void updateResizingWindows(final WindowState w) {
final WindowStateAnimator winAnimator = w.mWinAnimator;
if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq) {
- w.mOverscanInsetsChanged |=
- !w.mLastOverscanInsets.equals(w.mOverscanInsets);
- w.mContentInsetsChanged |=
- !w.mLastContentInsets.equals(w.mContentInsets);
- w.mVisibleInsetsChanged |=
- !w.mLastVisibleInsets.equals(w.mVisibleInsets);
+ w.setInsetsChanged();
boolean configChanged = w.isConfigChanged();
if (DEBUG_CONFIGURATION && configChanged) {
Slog.v(TAG, "Win " + w + " config changed: "
@@ -8783,6 +8797,14 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowManager.LayoutParams attrs = w.mAttrs;
final int attrFlags = attrs.flags;
final boolean canBeSeen = w.isDisplayedLw();
+ final boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
+
+ if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
+ // This window completely covers everything behind it,
+ // so we want to leave all of them as undimmed (for
+ // performance reasons).
+ mInnerFields.mObscured = true;
+ }
if (w.mHasSurface) {
if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) {
@@ -8811,22 +8833,24 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (canBeSeen) {
- if (type == TYPE_DREAM || type == TYPE_KEYGUARD) {
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_MIRROR;
- } else if (mInnerFields.mDisplayHasContent
- == LayoutFields.DISPLAY_CONTENT_UNKNOWN) {
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNIQUE;
+ // This function assumes that the contents of the default display are
+ // processed first before secondary displays.
+ if (w.mDisplayContent.isDefaultDisplay) {
+ // While a dream or keyguard is showing, obscure ordinary application
+ // content on secondary displays (by forcibly enabling mirroring unless
+ // there is other content we want to show) but still allow opaque
+ // keyguard dialogs to be shown.
+ if (type == TYPE_DREAM || type == TYPE_KEYGUARD) {
+ mInnerFields.mObscureApplicationContentOnSecondaryDisplays = true;
+ }
+ mInnerFields.mDisplayHasContent = true;
+ } else if (!mInnerFields.mObscureApplicationContentOnSecondaryDisplays
+ || (mInnerFields.mObscured && type == TYPE_KEYGUARD_DIALOG)) {
+ // Allow full screen keyguard presentation dialogs to be seen.
+ mInnerFields.mDisplayHasContent = true;
}
}
}
-
- boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
- if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
- // This window completely covers everything behind it,
- // so we want to leave all of them as undimmed (for
- // performance reasons).
- mInnerFields.mObscured = true;
- }
}
private void handleFlagDimBehind(WindowState w, int innerDw, int innerDh) {
@@ -8904,7 +8928,7 @@ public class WindowManagerService extends IWindowManager.Stub
mInnerFields.mScreenBrightness = -1;
mInnerFields.mButtonBrightness = -1;
mInnerFields.mUserActivityTimeout = -1;
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN;
+ mInnerFields.mObscureApplicationContentOnSecondaryDisplays = false;
mTransactionSequence++;
@@ -8939,10 +8963,8 @@ public class WindowManagerService extends IWindowManager.Stub
final int innerDh = displayInfo.appHeight;
final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
- // Reset for each display unless we are forcing mirroring.
- if (mInnerFields.mDisplayHasContent != LayoutFields.DISPLAY_CONTENT_MIRROR) {
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN;
- }
+ // Reset for each display.
+ mInnerFields.mDisplayHasContent = false;
int repeats = 0;
do {
@@ -9151,20 +9173,8 @@ public class WindowManagerService extends IWindowManager.Stub
updateResizingWindows(w);
}
- final boolean hasUniqueContent;
- switch (mInnerFields.mDisplayHasContent) {
- case LayoutFields.DISPLAY_CONTENT_MIRROR:
- hasUniqueContent = isDefaultDisplay;
- break;
- case LayoutFields.DISPLAY_CONTENT_UNIQUE:
- hasUniqueContent = true;
- break;
- case LayoutFields.DISPLAY_CONTENT_UNKNOWN:
- default:
- hasUniqueContent = false;
- break;
- }
- mDisplayManagerService.setDisplayHasContent(displayId, hasUniqueContent,
+ mDisplayManagerService.setDisplayHasContent(displayId,
+ mInnerFields.mDisplayHasContent,
true /* inTraversal, must call performTraversalInTrans... below */);
getDisplayContentLocked(displayId).stopDimmingIfNeeded();
@@ -10846,7 +10856,7 @@ public class WindowManagerService extends IWindowManager.Stub
WindowList windows = displayContent.getWindowList();
while (!windows.isEmpty()) {
final WindowState win = windows.get(windows.size() - 1);
- removeWindowLocked(win.mSession, win);
+ removeWindowLocked(win.mSession, win, true);
}
}
mAnimator.removeDisplayLocked(displayId);
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 2d08792..4d53cea 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -701,6 +701,13 @@ final class WindowState implements WindowManagerPolicy.WindowState {
return mAppToken != null ? mAppToken.appToken : null;
}
+ boolean setInsetsChanged() {
+ mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets);
+ mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets);
+ mVisibleInsetsChanged |= !mLastVisibleInsets.equals(mVisibleInsets);
+ return mOverscanInsetsChanged || mContentInsetsChanged || mVisibleInsetsChanged;
+ }
+
public int getDisplayId() {
return mDisplayContent.getDisplayId();
}