diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2013-12-05 13:10:46 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2013-12-05 13:10:46 -0800 |
commit | ebcb32f58a6220802ca129ea33f47b4b69931a10 (patch) | |
tree | 32b57c1d6ba9180ae63979e06d7421e107a9aa6c /services/java/com/android | |
parent | 6e2d0c1d91f644ab50e0c0b7cae4306262a4ca41 (diff) | |
parent | bac61807d3bcfff957b358cb9ad77850bd373689 (diff) | |
download | frameworks_base-ebcb32f58a6220802ca129ea33f47b4b69931a10.zip frameworks_base-ebcb32f58a6220802ca129ea33f47b4b69931a10.tar.gz frameworks_base-ebcb32f58a6220802ca129ea33f47b4b69931a10.tar.bz2 |
Merge commit 'bac61807d3bcfff957b358cb9ad77850bd373689' into HEAD
Change-Id: I29374270c8e0c2f2859efaf1d55af9f73da0f8d7
Diffstat (limited to 'services/java/com/android')
51 files changed, 3984 insertions, 864 deletions
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 3d804ef..5ae9a6d 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -385,10 +385,19 @@ class AlarmManagerService extends IAlarmManager.Stub { for (int i = 0; i < N; i++) { Alarm a = batch.get(i); long whenElapsed = convertToElapsed(a.when, a.type); - long maxElapsed = (a.whenElapsed == a.maxWhen) - ? whenElapsed - : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval); - setImplLocked(a.type, a.when, whenElapsed, maxElapsed, + final long maxElapsed; + if (a.whenElapsed == a.maxWhen) { + // Exact + maxElapsed = whenElapsed; + } else { + // Not exact. Preserve any explicit window, otherwise recalculate + // the window based on the alarm's new futurity. Note that this + // reflects a policy of preferring timely to deferred delivery. + maxElapsed = (a.windowLength > 0) + ? (whenElapsed + a.windowLength) + : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval); + } + setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed, a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource); } } @@ -556,27 +565,26 @@ class AlarmManagerService extends IAlarmManager.Stub { + " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed + " interval=" + interval + " standalone=" + isStandalone); } - setImplLocked(type, triggerAtTime, triggerElapsed, maxElapsed, + setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed, interval, operation, isStandalone, true, workSource); } } - private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval, - PendingIntent operation, boolean isStandalone, boolean doValidate, - WorkSource workSource) { - Alarm a = new Alarm(type, when, whenElapsed, maxWhen, interval, operation, workSource); + private void setImplLocked(int type, long when, long whenElapsed, long windowLength, + long maxWhen, long interval, PendingIntent operation, boolean isStandalone, + boolean doValidate, WorkSource workSource) { + Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval, + operation, workSource); removeLocked(operation); - boolean reschedule; int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen); if (whichBatch < 0) { Batch batch = new Batch(a); batch.standalone = isStandalone; - reschedule = addBatchLocked(mAlarmBatches, batch); + addBatchLocked(mAlarmBatches, batch); } else { Batch batch = mAlarmBatches.get(whichBatch); - reschedule = batch.add(a); - if (reschedule) { + if (batch.add(a)) { // The start time of this batch advanced, so batch ordering may // have just been broken. Move it to where it now belongs. mAlarmBatches.remove(whichBatch); @@ -592,13 +600,10 @@ class AlarmManagerService extends IAlarmManager.Stub { + " interval=" + interval + " op=" + operation + " standalone=" + isStandalone); rebatchAllAlarmsLocked(false); - reschedule = true; } } - if (reschedule) { - rescheduleKernelAlarmsLocked(); - } + rescheduleKernelAlarmsLocked(); } private void logBatchesLocked() { @@ -1046,7 +1051,7 @@ class AlarmManagerService extends IAlarmManager.Stub { // Also schedule its next recurrence final long delta = alarm.count * alarm.repeatInterval; final long nextElapsed = alarm.whenElapsed + delta; - setImplLocked(alarm.type, alarm.when + delta, nextElapsed, + setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength, maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval), alarm.repeatInterval, alarm.operation, batch.standalone, true, alarm.workSource); @@ -1077,17 +1082,19 @@ class AlarmManagerService extends IAlarmManager.Stub { public int type; public int count; public long when; + public long windowLength; public long whenElapsed; // 'when' in the elapsed time base public long maxWhen; // also in the elapsed time base public long repeatInterval; public PendingIntent operation; public WorkSource workSource; - public Alarm(int _type, long _when, long _whenElapsed, long _maxWhen, + public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen, long _interval, PendingIntent _op, WorkSource _ws) { type = _type; when = _when; whenElapsed = _whenElapsed; + windowLength = _windowLength; maxWhen = _maxWhen; repeatInterval = _interval; operation = _op; @@ -1112,6 +1119,7 @@ class AlarmManagerService extends IAlarmManager.Stub { pw.print(prefix); pw.print("type="); pw.print(type); pw.print(" whenElapsed="); pw.print(whenElapsed); pw.print(" when="); TimeUtils.formatDuration(when, now, pw); + pw.print(" window="); pw.print(windowLength); pw.print(" repeatInterval="); pw.print(repeatInterval); pw.print(" count="); pw.println(count); pw.print(prefix); pw.print("operation="); pw.println(operation); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index a04ee14..baef607 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; @@ -146,6 +148,7 @@ class BackupManagerService extends IBackupManager.Stub { 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. @@ -251,10 +254,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 @@ -815,13 +821,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 +829,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 @@ -1298,13 +1313,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; } @@ -1390,18 +1408,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); } }; diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index a9b4f19..d42ae3a 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -77,6 +77,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 +142,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 +156,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 */ @@ -3454,7 +3460,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized (mProxyLock) { if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return; if (mDefaultProxy == proxy) return; // catches repeated nulls - if (!proxy.isValid()) { + if (proxy != null && !proxy.isValid()) { if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString()); return; } @@ -4083,8 +4089,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; @@ -4110,6 +4136,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. @@ -4120,6 +4171,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; @@ -4191,7 +4249,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mCs.setEnableFailFastMobileData(DctConstants.ENABLED); break; } - sleep(1); + sleep(POLLING_SLEEP_SEC); } } @@ -4209,7 +4267,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 @@ -4225,7 +4283,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; } @@ -4243,7 +4301,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; @@ -4280,14 +4338,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; } @@ -4300,25 +4358,37 @@ public class ConnectivityService extends IConnectivityManager.Stub { // 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); @@ -4337,10 +4407,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 { @@ -4354,12 +4431,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); @@ -4387,7 +4466,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("isMobileOk: connected ni=" + mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI)); } - sleep(1); + sleep(POLLING_SLEEP_SEC); continue; } } @@ -4452,7 +4531,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); } } @@ -4484,8 +4563,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { mdst.enableMobileProvisioning(url); } else { if (DBG) log("handleMobileProvisioningAction: on default network"); - Intent newIntent = - new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, + Intent.CATEGORY_APP_BROWSER); + newIntent.setData(Uri.parse(url)); newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); try { diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index b0fe6a7..2bb99d6 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -55,8 +55,8 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; -import android.net.ProxyProperties; import android.content.pm.UserInfo; +import android.net.ProxyProperties; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -523,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 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 794d274..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,13 +876,14 @@ 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); - mImeWindowVis = (!isScreenSecurelyLocked && (inputShown || hardKeyShown)) ? - (InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE) : 0; + + 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; + mImeWindowVis = (inputActive ? InputMethodService.IME_ACTIVE : 0) + | (inputVisible ? InputMethodService.IME_VISIBLE : 0); updateImeWindowStatusLocked(); } @@ -1131,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) { @@ -1495,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 @@ -1506,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/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/MountService.java b/services/java/com/android/server/MountService.java index c7ca1ea..e60231a 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -62,6 +62,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IMediaContainerService; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.NativeDaemonConnector.Command; @@ -173,6 +174,8 @@ class MountService extends IMountService.Stub * 600 series - Unsolicited broadcasts. */ public static final int VolumeStateChange = 605; + public static final int VolumeUuidChange = 613; + public static final int VolumeUserLabelChange = 614; public static final int VolumeDiskInserted = 630; public static final int VolumeDiskRemoved = 631; public static final int VolumeBadRemoval = 632; @@ -661,6 +664,7 @@ class MountService extends IMountService.Stub final String oldState; synchronized (mVolumesLock) { oldState = mVolumeStates.put(path, state); + volume.setState(state); } if (state.equals(oldState)) { @@ -801,6 +805,26 @@ class MountService extends IMountService.Stub notifyVolumeStateChange( cooked[2], cooked[3], Integer.parseInt(cooked[7]), Integer.parseInt(cooked[10])); + } else if (code == VoldResponseCode.VolumeUuidChange) { + // Format: nnn <label> <path> <uuid> + final String path = cooked[2]; + final String uuid = (cooked.length > 3) ? cooked[3] : null; + + final StorageVolume vol = mVolumesByPath.get(path); + if (vol != null) { + vol.setUuid(uuid); + } + + } else if (code == VoldResponseCode.VolumeUserLabelChange) { + // Format: nnn <label> <path> <label> + final String path = cooked[2]; + final String userLabel = (cooked.length > 3) ? cooked[3] : null; + + final StorageVolume vol = mVolumesByPath.get(path); + if (vol != null) { + vol.setUserLabel(userLabel); + } + } else if ((code == VoldResponseCode.VolumeDiskInserted) || (code == VoldResponseCode.VolumeDiskRemoved) || (code == VoldResponseCode.VolumeBadRemoval)) { @@ -1230,6 +1254,7 @@ class MountService extends IMountService.Stub // Until we hear otherwise, treat as unmounted mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED); + volume.setState(Environment.MEDIA_UNMOUNTED); } } @@ -1273,6 +1298,7 @@ class MountService extends IMountService.Stub } else { // Place stub status for early callers to find mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED); + volume.setState(Environment.MEDIA_MOUNTED); } } @@ -2743,54 +2769,59 @@ class MountService extends IMountService.Stub } @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump ActivityManager from from pid=" - + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " without permission " + android.Manifest.permission.DUMP); - return; - } + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); - synchronized (mObbMounts) { - pw.println(" mObbMounts:"); + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); - final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet().iterator(); + synchronized (mObbMounts) { + pw.println("mObbMounts:"); + pw.increaseIndent(); + final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet() + .iterator(); while (binders.hasNext()) { Entry<IBinder, List<ObbState>> e = binders.next(); - pw.print(" Key="); pw.println(e.getKey().toString()); + pw.println(e.getKey() + ":"); + pw.increaseIndent(); final List<ObbState> obbStates = e.getValue(); for (final ObbState obbState : obbStates) { - pw.print(" "); pw.println(obbState.toString()); + pw.println(obbState); } + pw.decreaseIndent(); } + pw.decreaseIndent(); - pw.println(""); - pw.println(" mObbPathToStateMap:"); + pw.println(); + pw.println("mObbPathToStateMap:"); + pw.increaseIndent(); final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator(); while (maps.hasNext()) { final Entry<String, ObbState> e = maps.next(); - pw.print(" "); pw.print(e.getKey()); - pw.print(" -> "); pw.println(e.getValue().toString()); + pw.print(e.getKey()); + pw.print(" -> "); + pw.println(e.getValue()); } + pw.decreaseIndent(); } - pw.println(""); - synchronized (mVolumesLock) { - pw.println(" mVolumes:"); - - final int N = mVolumes.size(); - for (int i = 0; i < N; i++) { - final StorageVolume v = mVolumes.get(i); - pw.print(" "); - pw.println(v.toString()); - pw.println(" state=" + mVolumeStates.get(v.getPath())); + pw.println(); + pw.println("mVolumes:"); + pw.increaseIndent(); + for (StorageVolume volume : mVolumes) { + pw.println(volume); + pw.increaseIndent(); + pw.println("Current state: " + mVolumeStates.get(volume.getPath())); + pw.decreaseIndent(); } + pw.decreaseIndent(); } pw.println(); - pw.println(" mConnection:"); + pw.println("mConnection:"); + pw.increaseIndent(); mConnector.dump(fd, pw, args); + pw.decreaseIndent(); } /** {@inheritDoc} */ 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/Watchdog.java b/services/java/com/android/server/Watchdog.java index 616090e..e17f42d 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -58,9 +58,17 @@ public class Watchdog extends Thread { // Set this to true to have the watchdog record kernel thread stacks when it fires static final boolean RECORD_KERNEL_THREADS = true; - static final int TIME_TO_WAIT = DB ? 5*1000 : 30*1000; + static final long DEFAULT_TIMEOUT = DB ? 10*1000 : 60*1000; + static final long CHECK_INTERVAL = DEFAULT_TIMEOUT / 2; - static final String[] NATIVE_STACKS_OF_INTEREST = new String[] { + // These are temporally ordered: larger values as lateness increases + static final int COMPLETED = 0; + static final int WAITING = 1; + static final int WAITED_HALF = 2; + static final int OVERDUE = 3; + + // Which native processes to dump into dropbox's stack traces + public static final String[] NATIVE_STACKS_OF_INTEREST = new String[] { "/system/bin/mediaserver", "/system/bin/sdcard", "/system/bin/surfaceflinger" @@ -87,13 +95,17 @@ public class Watchdog extends Thread { public final class HandlerChecker implements Runnable { private final Handler mHandler; private final String mName; + private final long mWaitMax; private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>(); private boolean mCompleted; private Monitor mCurrentMonitor; + private long mStartTime; - HandlerChecker(Handler handler, String name) { + HandlerChecker(Handler handler, String name, long waitMaxMillis) { mHandler = handler; mName = name; + mWaitMax = waitMaxMillis; + mCompleted = true; } public void addMonitor(Monitor monitor) { @@ -111,13 +123,34 @@ public class Watchdog extends Thread { mCompleted = true; return; } + + if (!mCompleted) { + // we already have a check in flight, so no need + return; + } + mCompleted = false; mCurrentMonitor = null; + mStartTime = SystemClock.uptimeMillis(); mHandler.postAtFrontOfQueue(this); } - public boolean isCompletedLocked() { - return mCompleted; + public boolean isOverdueLocked() { + return (!mCompleted) && (SystemClock.uptimeMillis() > mStartTime + mWaitMax); + } + + public int getCompletionStateLocked() { + if (mCompleted) { + return COMPLETED; + } else { + long latency = SystemClock.uptimeMillis() - mStartTime; + if (latency < mWaitMax/2) { + return WAITING; + } else if (latency < mWaitMax) { + return WAITED_HALF; + } + } + return OVERDUE; } public Thread getThread() { @@ -186,16 +219,19 @@ public class Watchdog extends Thread { // The shared foreground thread is the main checker. It is where we // will also dispatch monitor checks and do other work. - mMonitorChecker = new HandlerChecker(FgThread.getHandler(), "foreground thread"); + mMonitorChecker = new HandlerChecker(FgThread.getHandler(), + "foreground thread", DEFAULT_TIMEOUT); mHandlerCheckers.add(mMonitorChecker); // Add checker for main thread. We only do a quick check since there // can be UI running on the thread. mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()), - "main thread")); + "main thread", DEFAULT_TIMEOUT)); // Add checker for shared UI thread. - mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(), "ui thread")); + mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(), + "ui thread", DEFAULT_TIMEOUT)); // And also check IO thread. - mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(), "i/o thread")); + mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(), + "i/o thread", DEFAULT_TIMEOUT)); } public void init(Context context, BatteryService battery, @@ -242,11 +278,15 @@ public class Watchdog extends Thread { } public void addThread(Handler thread, String name) { + addThread(thread, name, DEFAULT_TIMEOUT); + } + + public void addThread(Handler thread, String name, long timeoutMillis) { synchronized (this) { if (isAlive()) { throw new RuntimeException("Threads can't be added once the Watchdog is running"); } - mHandlerCheckers.add(new HandlerChecker(thread, name)); + mHandlerCheckers.add(new HandlerChecker(thread, name, timeoutMillis)); } } @@ -259,21 +299,20 @@ public class Watchdog extends Thread { pms.reboot(false, reason, false); } - private boolean haveAllCheckersCompletedLocked() { + private int evaluateCheckerCompletionLocked() { + int state = COMPLETED; for (int i=0; i<mHandlerCheckers.size(); i++) { HandlerChecker hc = mHandlerCheckers.get(i); - if (!hc.isCompletedLocked()) { - return false; - } + state = Math.max(state, hc.getCompletionStateLocked()); } - return true; + return state; } private ArrayList<HandlerChecker> getBlockedCheckersLocked() { ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>(); for (int i=0; i<mHandlerCheckers.size(); i++) { HandlerChecker hc = mHandlerCheckers.get(i); - if (!hc.isCompletedLocked()) { + if (hc.isOverdueLocked()) { checkers.add(hc); } } @@ -299,14 +338,12 @@ public class Watchdog extends Thread { final String subject; final boolean allowRestart; synchronized (this) { - long timeout = TIME_TO_WAIT; - if (!waitedHalf) { - // If we are not at the half-point of waiting, perform a - // new set of checks. Otherwise we are still waiting for a previous set. - for (int i=0; i<mHandlerCheckers.size(); i++) { - HandlerChecker hc = mHandlerCheckers.get(i); - hc.scheduleCheckLocked(); - } + long timeout = CHECK_INTERVAL; + // Make sure we (re)spin the checkers that have become idle within + // this wait-and-check interval + for (int i=0; i<mHandlerCheckers.size(); i++) { + HandlerChecker hc = mHandlerCheckers.get(i); + hc.scheduleCheckLocked(); } // NOTE: We use uptimeMillis() here because we do not want to increment the time we @@ -320,26 +357,31 @@ public class Watchdog extends Thread { } catch (InterruptedException e) { Log.wtf(TAG, e); } - timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start); + timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start); } - if (haveAllCheckersCompletedLocked()) { - // The monitors have returned. + final int waitState = evaluateCheckerCompletionLocked(); + if (waitState == COMPLETED) { + // The monitors have returned; reset waitedHalf = false; continue; - } - - if (!waitedHalf) { - // We've waited half the deadlock-detection interval. Pull a stack - // trace and wait another half. - ArrayList<Integer> pids = new ArrayList<Integer>(); - pids.add(Process.myPid()); - ActivityManagerService.dumpStackTraces(true, pids, null, null, - NATIVE_STACKS_OF_INTEREST); - waitedHalf = true; + } else if (waitState == WAITING) { + // still waiting but within their configured intervals; back off and recheck + continue; + } else if (waitState == WAITED_HALF) { + if (!waitedHalf) { + // We've waited half the deadlock-detection interval. Pull a stack + // trace and wait another half. + ArrayList<Integer> pids = new ArrayList<Integer>(); + pids.add(Process.myPid()); + ActivityManagerService.dumpStackTraces(true, pids, null, null, + NATIVE_STACKS_OF_INTEREST); + waitedHalf = true; + } continue; } + // something is overdue! blockedCheckers = getBlockedCheckersLocked(); subject = describeCheckersLocked(blockedCheckers); allowRestart = mAllowRestart; 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..933247e 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; @@ -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. @@ -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) { @@ -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(); @@ -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,7 +1272,9 @@ 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) { @@ -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); } } @@ -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.services.remove(r); + } + r.app = null; + } } } @@ -1839,6 +1930,7 @@ public final class ActiveServices { Slog.i(TAG, " Force stopping service " + service); if (service.app != null) { service.app.removed = true; + service.app.services.remove(service); } service.app = null; service.isolatedProc = null; @@ -1905,8 +1997,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 +2026,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 != null) { + sr.app.services.remove(sr); + } sr.app = null; sr.isolatedProc = null; sr.executeNesting = 0; @@ -1965,8 +2051,33 @@ 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); + // Sanity check: if the service listed for the app is not one + // we actually are maintaining, drop it. + 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); + app.services.removeAt(i); + 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 +2110,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 cc5a0b7..fc66e45 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -21,6 +21,7 @@ import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; +import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -33,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; @@ -217,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; @@ -322,6 +323,9 @@ public final class ActivityManagerService extends ActivityManagerNative static final String[] EMPTY_STRING_ARRAY = new String[0]; + // How many bytes to write into the dropbox log before truncating + static final int DROPBOX_MAX_SIZE = 256 * 1024; + /** Run all ActivityStacks through this */ ActivityStackSupervisor mStackSupervisor; @@ -453,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 @@ -460,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. @@ -1739,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( @@ -2127,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); @@ -2265,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; } @@ -2285,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; } @@ -2302,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 @@ -2317,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--; @@ -2326,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++; } @@ -2353,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) { @@ -2623,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(); } } @@ -3687,7 +3834,17 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // Next measure CPU usage. + // Next collect the stacks of the native pids + if (nativeProcs != null) { + int[] pids = Process.getPidsForCommands(nativeProcs); + if (pids != null) { + for (int pid : pids) { + Debug.dumpNativeBacktraceToFile(pid, tracesPath); + } + } + } + + // Lastly, measure CPU usage. if (processCpuTracker != null) { processCpuTracker.init(); System.gc(); @@ -3719,19 +3876,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - } finally { observer.stopWatching(); } - - if (nativeProcs != null) { - int[] pids = Process.getPidsForCommands(nativeProcs); - if (pids != null) { - for (int pid : pids) { - Debug.dumpNativeBacktraceToFile(pid, tracesPath); - } - } - } } final void logAppTooSlow(ProcessRecord app, long startTime, String msg) { @@ -3895,7 +4042,8 @@ public final class ActivityManagerService extends ActivityManagerNative final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); - File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids, null); + File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids, + NATIVE_STACKS_OF_INTEREST); String cpuInfo = null; if (MONITOR_CPU_USAGE) { @@ -4826,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 @@ -7224,7 +7372,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_MU) Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid); app.pubProviders.put(cpi.name, cpr); - app.addPackage(cpi.applicationInfo.packageName, mProcessStats); + if (!cpi.multiprocess || !"android".equals(cpi.packageName)) { + // Don't add this if it is a platform component that is marked + // to run in multiple processes, because this is actually + // part of the framework so doesn't make sense to track as a + // separate apk in the process. + app.addPackage(cpi.applicationInfo.packageName, mProcessStats); + } ensurePackageDexOpt(cpi.applicationInfo.packageName); } } @@ -7408,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); } } @@ -7997,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) { @@ -8017,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. @@ -9281,7 +9433,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, @@ -9336,13 +9488,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; @@ -9372,7 +9525,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; @@ -9820,7 +9974,8 @@ public final class ActivityManagerService extends ActivityManagerNative } if (logFile != null) { try { - sb.append(FileUtils.readTextFile(logFile, 128 * 1024, "\n\n[[TRUNCATED]]")); + sb.append(FileUtils.readTextFile(logFile, DROPBOX_MAX_SIZE, + "\n\n[[TRUNCATED]]")); } catch (IOException e) { Slog.e(TAG, "Error reading " + logFile, e); } @@ -10089,7 +10244,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; @@ -10633,11 +10788,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); @@ -10652,10 +10807,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(); + } + } } } } @@ -11497,7 +11675,6 @@ public final class ActivityManagerService extends ActivityManagerNative try { pid = Integer.parseInt(args[start]); } catch (NumberFormatException e) { - } for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord proc = mLruProcesses.get(i); @@ -11508,7 +11685,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (procs.size() <= 0) { - pw.println("No process found for: " + args[start]); return null; } } else { @@ -11522,6 +11698,7 @@ public final class ActivityManagerService extends ActivityManagerNative PrintWriter pw, String[] args) { ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, args); if (procs == null) { + pw.println("No process found for: " + args[0]); return; } @@ -11557,6 +11734,7 @@ public final class ActivityManagerService extends ActivityManagerNative final void dumpDbInfo(FileDescriptor fd, PrintWriter pw, String[] args) { ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, args); if (procs == null) { + pw.println("No process found for: " + args[0]); return; } @@ -11703,6 +11881,17 @@ public final class ActivityManagerService extends ActivityManagerNative "prev", "serviceb", "cached" }; + private final void dumpApplicationMemoryUsageHeader(PrintWriter pw, long uptime, + long realtime, boolean isCheckinRequest, boolean isCompact) { + if (isCheckinRequest || isCompact) { + // short checkin version + pw.print("time,"); pw.print(uptime); pw.print(","); pw.println(realtime); + } else { + pw.println("Applications Memory Usage (kB):"); + pw.println("Uptime: " + uptime + " Realtime: " + realtime); + } + } + final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) { boolean dumpDetails = false; @@ -11710,6 +11899,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) { @@ -11728,12 +11918,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; @@ -11742,26 +11935,71 @@ public final class ActivityManagerService extends ActivityManagerNative } } + final boolean isCheckinRequest = scanArgs(args, "--checkin"); + long uptime = SystemClock.uptimeMillis(); + long realtime = SystemClock.elapsedRealtime(); + final long[] tmpLong = new long[1]; + ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, args); if (procs == null) { + // No Java processes. Maybe they want to print a native process. + if (args != null && args.length > opti + && args[opti].charAt(0) != '-') { + ArrayList<ProcessCpuTracker.Stats> nativeProcs + = new ArrayList<ProcessCpuTracker.Stats>(); + updateCpuStatsNow(); + int findPid = -1; + try { + findPid = Integer.parseInt(args[opti]); + } catch (NumberFormatException e) { + } + synchronized (mProcessCpuThread) { + final int N = mProcessCpuTracker.countStats(); + for (int i=0; i<N; i++) { + ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); + if (st.pid == findPid || (st.baseName != null + && st.baseName.equals(args[opti]))) { + nativeProcs.add(st); + } + } + } + if (nativeProcs.size() > 0) { + dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest, + isCompact); + Debug.MemoryInfo mi = null; + for (int i = nativeProcs.size() - 1 ; i >= 0 ; i--) { + final ProcessCpuTracker.Stats r = nativeProcs.get(i); + final int pid = r.pid; + if (!isCheckinRequest && dumpDetails) { + pw.println("\n** MEMINFO in pid " + pid + " [" + r.baseName + "] **"); + } + if (mi == null) { + mi = new Debug.MemoryInfo(); + } + if (dumpDetails || (!brief && !oomOnly)) { + Debug.getMemoryInfo(pid, mi); + } else { + mi.dalvikPss = (int)Debug.getPss(pid, tmpLong); + mi.dalvikPrivateDirty = (int)tmpLong[0]; + } + ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails, + dumpDalvik, pid, r.baseName, 0, 0, 0, 0, 0, 0); + if (isCheckinRequest) { + pw.println(); + } + } + return; + } + } + pw.println("No process found for: " + args[opti]); return; } - final boolean isCheckinRequest = scanArgs(args, "--checkin"); - long uptime = SystemClock.uptimeMillis(); - long realtime = SystemClock.elapsedRealtime(); - if (!brief && !oomOnly && (procs.size() == 1 || isCheckinRequest)) { dumpDetails = true; } - if (isCheckinRequest || isCompact) { - // short checkin version - pw.print("time,"); pw.print(uptime); pw.print(","); pw.println(realtime); - } else { - pw.println("Applications Memory Usage (kB):"); - pw.println("Uptime: " + uptime + " Realtime: " + realtime); - } + dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest, isCompact); String[] innerArgs = new String[args.length-opti]; System.arraycopy(args, opti, innerArgs, 0, args.length-opti); @@ -11774,7 +12012,6 @@ public final class ActivityManagerService extends ActivityManagerNative long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length]; ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[]) new ArrayList[DUMP_MEM_OOM_LABEL.length]; - final long[] tmpLong = new long[1]; long totalPss = 0; long cachedPss = 0; @@ -11790,7 +12027,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) { @@ -11806,14 +12043,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(); + } } } } @@ -12361,7 +12606,6 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); - checkValidCaller(callingUid, userId); final long origId = Binder.clearCallingIdentity(); ComponentName res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid, userId); @@ -12391,8 +12635,6 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Intent"); } - checkValidCaller(Binder.getCallingUid(), userId); - synchronized(this) { return mServices.stopServiceLocked(caller, service, resolvedType, userId); } @@ -12777,7 +13019,8 @@ public final class ActivityManagerService extends ActivityManagerNative + ") when registering receiver " + receiver); } if (callerApp.info.uid != Process.SYSTEM_UID && - !callerApp.pkgList.containsKey(callerPackage)) { + !callerApp.pkgList.containsKey(callerPackage) && + !"android".equals(callerPackage)) { throw new SecurityException("Given caller package " + callerPackage + " is not running in process " + callerApp); } @@ -14014,7 +14257,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(); @@ -14024,7 +14266,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; @@ -14036,16 +14277,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) { @@ -14056,7 +14293,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. @@ -14073,7 +14309,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. @@ -14122,7 +14357,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) { @@ -14371,27 +14605,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 @@ -14609,6 +14822,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); @@ -15237,7 +15456,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--) { @@ -15252,11 +15470,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) { @@ -15266,25 +15488,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 @@ -15293,6 +15499,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) { @@ -16382,13 +16591,6 @@ public final class ActivityManagerService extends ActivityManagerNative return mUserManager; } - private void checkValidCaller(int uid, int userId) { - if (UserHandle.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0) return; - - throw new SecurityException("Caller uid=" + uid - + " is not privileged to communicate with user=" + userId); - } - private int applyUserId(int uid, int userId) { return UserHandle.getUid(userId, uid); } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index cf68667..49f29fe 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -480,7 +480,12 @@ final class ActivityRecord { void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { if (task != null && task.removeActivity(this)) { - mStackSupervisor.removeTask(task); + if (task != newTask) { + mStackSupervisor.removeTask(task); + } else { + Slog.d(TAG, "!!! REMOVE THIS LOG !!! setTask: nearly removed stack=" + + (newTask == null ? null : newTask.stack)); + } } if (inHistory && !finishing) { if (task != null) { diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 0397fd5..0e15261 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) { @@ -740,7 +743,10 @@ final class ActivityStack { prev.state = ActivityState.PAUSING; prev.task.touchActiveTime(); clearLaunchTime(prev); - prev.updateThumbnail(screenshotActivities(prev), null); + final ActivityRecord next = mStackSupervisor.topRunningActivityLocked(); + if (next == null || next.task != prev.task) { + prev.updateThumbnail(screenshotActivities(prev), null); + } stopFullyDrawnTraceIfNeeded(); mService.updateCpuStats(); @@ -977,6 +983,40 @@ final class ActivityStack { } /** + * Determine if home should be visible below the passed record. + * @param record activity we are querying for. + * @return true if home is visible below the passed activity, false otherwise. + */ + boolean isActivityOverHome(ActivityRecord record) { + // Start at record and go down, look for either home or a visible fullscreen activity. + final TaskRecord recordTask = record.task; + for (int taskNdx = mTaskHistory.indexOf(recordTask); taskNdx >= 0; --taskNdx) { + TaskRecord task = mTaskHistory.get(taskNdx); + final ArrayList<ActivityRecord> activities = task.mActivities; + final int startNdx = + task == recordTask ? activities.indexOf(record) : activities.size() - 1; + for (int activityNdx = startNdx; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.isHomeActivity()) { + return true; + } + if (!r.finishing && r.fullscreen) { + // Passed activity is over a fullscreen activity. + return false; + } + } + if (task.mOnTopOfHome) { + // Got to the bottom of a task on top of home without finding a visible fullscreen + // activity. Home is visible. + return true; + } + } + // Got to the bottom of this stack and still don't know. If this is over the home stack + // then record is over home. May not work if we ever get more than two layers. + return mStackSupervisor.isFrontStack(this); + } + + /** * Version of ensureActivitiesVisible that can easily be called anywhere. */ final boolean ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) { @@ -1101,24 +1141,10 @@ final class ActivityStack { // At this point, nothing else needs to be shown if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r); behindFullscreen = true; - } else if (task.mOnTopOfHome) { - // Work our way down from r to bottom of task and see if there are any - // visible activities below r. - int rIndex = task.mActivities.indexOf(r); - for ( --rIndex; rIndex >= 0; --rIndex) { - final ActivityRecord blocker = task.mActivities.get(rIndex); - if (!blocker.finishing) { - if (DEBUG_VISBILITY) Slog.v(TAG, "Home visibility for " + - r + " blocked by " + blocker); - break; - } - } - if (rIndex < 0) { - // Got to task bottom without finding a visible activity, show home. - if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r); - showHomeBehindStack = true; - behindFullscreen = true; - } + } else if (isActivityOverHome(r)) { + if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r); + showHomeBehindStack = true; + behindFullscreen = !isHomeStack(); } } else { if (DEBUG_VISBILITY) Slog.v( @@ -1375,7 +1401,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; @@ -1503,8 +1529,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. @@ -1690,7 +1717,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(); } @@ -1751,7 +1778,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 @@ -1794,7 +1822,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) { @@ -1884,26 +1913,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); @@ -1924,8 +1965,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); @@ -2174,6 +2215,19 @@ final class ActivityStack { r.addResultLocked(null, resultWho, requestCode, resultCode, data); } + private void adjustFocusedActivityLocked(ActivityRecord r) { + if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) { + ActivityRecord next = topRunningActivityLocked(null); + if (next != r) { + final TaskRecord task = r.task; + if (r.frontOfTask && task == topTask() && task.mOnTopOfHome) { + mStackSupervisor.moveHomeToTop(); + } + } + mService.setFocusedActivityLocked(mStackSupervisor.topRunningActivityLocked()); + } + } + final void stopActivityLocked(ActivityRecord r) { if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r); if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 @@ -2193,11 +2247,7 @@ final class ActivityStack { } if (r.app != null && r.app.thread != null) { - if (mStackSupervisor.isFrontStack(this)) { - if (mService.mFocusedActivity == r) { - mService.setFocusedActivityLocked(topRunningActivityLocked(null)); - } - } + adjustFocusedActivityLocked(r); r.resumeKeyDispatchingLocked(); try { r.stopped = false; @@ -2376,11 +2426,8 @@ final class ActivityStack { } r.pauseKeyDispatchingLocked(); - if (mStackSupervisor.isFrontStack(this)) { - if (mService.mFocusedActivity == r) { - mService.setFocusedActivityLocked(mStackSupervisor.topRunningActivityLocked()); - } - } + + adjustFocusedActivityLocked(r); finishActivityResultsLocked(r, resultCode, resultData); @@ -2752,7 +2799,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(); } } @@ -3113,7 +3160,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); } @@ -3379,6 +3428,9 @@ final class ActivityStack { int numActivities = 0; int numRunning = 0; final ArrayList<ActivityRecord> activities = task.mActivities; + if (activities.isEmpty()) { + continue; + } for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { r = activities.get(activityNdx); diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java index 523015d..483b4a0 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 { @@ -1051,7 +1052,14 @@ public final class ActivityStackSupervisor { if (app != null && app.thread != null) { try { - app.addPackage(r.info.packageName, mService.mProcessStats); + if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0 + || !"android".equals(r.info.packageName)) { + // Don't add this if it is a platform component that is marked + // to run in multiple processes, because this is actually + // part of the framework so doesn't make sense to track as a + // separate apk in the process. + app.addPackage(r.info.packageName, mService.mProcessStats); + } realStartActivityLocked(r, app, andResume, checkConfig); return; } catch (RemoteException e) { @@ -1254,15 +1262,16 @@ public final class ActivityStackSupervisor { final TaskRecord task = r.task; if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) { if (task != null) { - if (mFocusedStack != task.stack) { + final ActivityStack taskStack = task.stack; + if (mFocusedStack != taskStack) { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Setting focused stack to r=" + r + " task=" + task); - mFocusedStack = task.stack; + mFocusedStack = taskStack.isHomeStack() ? null : taskStack; } else { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Focused stack already=" + mFocusedStack); } - return mFocusedStack; + return taskStack; } if (mFocusedStack != null) { @@ -1282,8 +1291,8 @@ public final class ActivityStackSupervisor { } // Time to create the first app stack for this user. - int stackId = mService.createStack(-1, HOME_STACK_ID, - StackBox.TASK_STACK_GOES_OVER, 1.0f); + int stackId = + mService.createStack(-1, HOME_STACK_ID, StackBox.TASK_STACK_GOES_OVER, 1.0f); if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r + " stackId=" + stackId); mFocusedStack = getStack(stackId); @@ -1308,7 +1317,8 @@ public final class ActivityStackSupervisor { if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "setFocusedStack: Setting focused stack to r=" + r + " task=" + r.task + " Callers=" + Debug.getCallers(3)); - mFocusedStack = r.task.stack; + final ActivityStack taskStack = r.task.stack; + mFocusedStack = taskStack.isHomeStack() ? null : taskStack; if (mStackState != STACK_STATE_HOME_IN_BACK) { if (DEBUG_STACK) Slog.d(TAG, "setFocusedStack: mStackState old=" + stackStateToString(mStackState) + " new=" + @@ -1376,17 +1386,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; @@ -1462,13 +1477,13 @@ public final class ActivityStackSupervisor { // We really do want to push this one into the // user's face, right now. movedHome = true; + targetStack.moveTaskToFrontLocked(intentActivity.task, r, options); if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { // Caller wants to appear on home activity. intentActivity.task.mOnTopOfHome = true; } - targetStack.moveTaskToFrontLocked(intentActivity.task, r, options); options = null; } } @@ -1658,8 +1673,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 { @@ -2367,12 +2384,13 @@ public final class ActivityStackSupervisor { } public void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("mDismissKeyguardOnNextActivity:"); + pw.print(prefix); pw.print("mDismissKeyguardOnNextActivity="); pw.println(mDismissKeyguardOnNextActivity); - pw.print(prefix); pw.print("mStackState="); pw.println(stackStateToString(mStackState)); - pw.print(prefix); pw.println("mSleepTimeout: " + mSleepTimeout); - pw.print(prefix); pw.println("mCurTaskId: " + mCurTaskId); - pw.print(prefix); pw.println("mUserStackInFront: " + mUserStackInFront); + pw.print(prefix); pw.print("mFocusedStack=" + mFocusedStack); + pw.print(" mStackState="); pw.println(stackStateToString(mStackState)); + pw.print(prefix); pw.println("mSleepTimeout=" + mSleepTimeout); + pw.print(prefix); pw.println("mCurTaskId=" + mCurTaskId); + pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront); } ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) { 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 1d6970f..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; @@ -54,9 +55,9 @@ public final class BroadcastQueue { static final boolean DEBUG_BROADCAST_LIGHT = ActivityManagerService.DEBUG_BROADCAST_LIGHT; static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU; - static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 25; + static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50; static final int MAX_BROADCAST_SUMMARY_HISTORY - = ActivityManager.isLowRamDeviceStatic() ? 25 : 100; + = ActivityManager.isLowRamDeviceStatic() ? 25 : 300; final ActivityManagerService mService; @@ -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 50a7b5c..e05fcda 100644 --- a/services/java/com/android/server/am/ProcessStatsService.java +++ b/services/java/com/android/server/am/ProcessStatsService.java @@ -529,6 +529,33 @@ public final class ProcessStatsService extends IProcessStats.Stub { } } + private void dumpAggregatedStats(PrintWriter pw, long aggregateHours, long now, + String reqPackage, boolean isCompact, boolean dumpDetails, boolean dumpFullDetails, + boolean dumpAll, boolean activeOnly) { + ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000 + - (ProcessStats.COMMIT_PERIOD/2)); + if (pfd == null) { + pw.println("Unable to build stats!"); + return; + } + ProcessStats stats = new ProcessStats(false); + InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + stats.read(stream); + if (stats.mReadError != null) { + pw.print("Failure reading: "); pw.println(stats.mReadError); + return; + } + if (isCompact) { + stats.dumpCheckinLocked(pw, reqPackage); + } else { + if (dumpDetails || dumpFullDetails) { + stats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, activeOnly); + } else { + stats.dumpSummaryLocked(pw, reqPackage, now, activeOnly); + } + } + } + static private void dumpHelp(PrintWriter pw) { pw.println("Process stats (procstats) dump options:"); pw.println(" [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]"); @@ -723,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; } } } @@ -789,33 +805,14 @@ public final class ProcessStatsService extends IProcessStats.Stub { } return; } else if (aggregateHours != 0) { - ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000 - - (ProcessStats.COMMIT_PERIOD/2)); - if (pfd == null) { - pw.println("Unable to build stats!"); - return; - } - ProcessStats stats = new ProcessStats(false); - InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); - stats.read(stream); - if (stats.mReadError != null) { - pw.print("Failure reading: "); pw.println(stats.mReadError); - return; - } - if (isCompact) { - stats.dumpCheckinLocked(pw, reqPackage); - } else { - if (dumpDetails || dumpFullDetails) { - stats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, activeOnly); - } else { - stats.dumpSummaryLocked(pw, reqPackage, now, activeOnly); - } - } + 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); @@ -875,14 +872,27 @@ public final class ProcessStatsService extends IProcessStats.Stub { } } if (!isCheckin) { + if (!currentOnly) { + if (sepNeeded) { + pw.println(); + } + pw.println("AGGREGATED OVER LAST 24 HOURS:"); + dumpAggregatedStats(pw, 24, now, reqPackage, isCompact, + dumpDetails, dumpFullDetails, dumpAll, activeOnly); + pw.println(); + pw.println("AGGREGATED OVER LAST 3 HOURS:"); + dumpAggregatedStats(pw, 3, now, reqPackage, isCompact, + dumpDetails, dumpFullDetails, dumpAll, activeOnly); + sepNeeded = true; + } synchronized (mAm) { if (isCompact) { mProcessStats.dumpCheckinLocked(pw, reqPackage); } 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/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index f5a7039..2ca2cc5 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -597,10 +597,10 @@ public class Vpn extends BaseNetworkStateTracker { int appId = UserHandle.getAppId(Binder.getCallingUid()); final long token = Binder.clearCallingIdentity(); try { - // System dialogs are also allowed to control VPN. + // System VPN dialogs are also allowed to control VPN. PackageManager pm = mContext.getPackageManager(); ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0); - if (appId == app.uid) { + if (((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) && (appId == app.uid)) { return; } } catch (Exception e) { 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/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java index 41ef229..5ebf9ea 100644 --- a/services/java/com/android/server/content/SyncStorageEngine.java +++ b/services/java/com/android/server/content/SyncStorageEngine.java @@ -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..02f26b3 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -466,6 +466,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override // Binder call public void scanWifiDisplays() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to scan wifi displays"); + final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { @@ -483,13 +486,14 @@ public final class DisplayManagerService extends IDisplayManager.Stub { 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 +503,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 +520,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 +537,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 +559,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 +579,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 +596,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 +612,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) { diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java index f7bbdf8..fdef039 100644 --- a/services/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/java/com/android/server/display/WifiDisplayAdapter.java @@ -172,19 +172,9 @@ final class WifiDisplayAdapter extends DisplayAdapter { }); } - public void requestConnectLocked(final String address, final boolean trusted) { + public void requestConnectLocked(final String address) { if (DEBUG) { - Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted); - } - - 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; - } - } + Slog.d(TAG, "requestConnectLocked: address=" + address); } getHandler().post(new Runnable() { @@ -400,8 +390,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 +397,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { mDisplayDevice.destroyLocked(); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED); mDisplayDevice = null; - - scheduleUpdateNotificationLocked(); } } @@ -457,21 +443,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 +482,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); } } @@ -578,6 +585,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING; mActiveDisplay = display; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -590,6 +598,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; mActiveDisplay = null; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -607,6 +616,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED; mActiveDisplay = display; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -629,6 +639,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplay = display; renameDisplayDeviceLocked(display.getFriendlyDisplayName()); scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -644,6 +655,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..b2939fe 100644 --- a/services/java/com/android/server/display/WifiDisplayController.java +++ b/services/java/com/android/server/display/WifiDisplayController.java @@ -76,7 +76,7 @@ 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 RTSP_TIMEOUT_SECONDS = 30; private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120; private static final int DISCOVER_PEERS_MAX_RETRIES = 10; 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 2a93dfc..5761f6c 100755 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -221,6 +221,14 @@ public class PackageManagerService extends IPackageManager.Stub { static final int REMOVE_CHATTY = 1<<16; /** + * Timeout (in milliseconds) after which the watchdog should declare that + * our handler thread is wedged. The usual default for such things is one + * minute but we sometimes do very lengthy I/O operations on this thread, + * such as installing multi-gigabyte applications, so ours needs to be longer. + */ + private static final long WATCHDOG_TIMEOUT = 1000*60*10; // ten minutes + + /** * Whether verification is enabled by default. */ private static final boolean DEFAULT_VERIFY_ENABLE = true; @@ -1115,7 +1123,8 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { mHandlerThread.start(); mHandler = new PackageHandler(mHandlerThread.getLooper()); - Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName()); + Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName(), + WATCHDOG_TIMEOUT); File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); @@ -1269,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. @@ -1762,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; @@ -3585,7 +3613,13 @@ public class PackageManagerService extends IPackageManager.Stub { + ps.name + " changing from " + updatedPkg.codePathString + " to " + scanFile); updatedPkg.codePath = scanFile; - updatedPkg.codePathString = scanFile.toString(); + updatedPkg.codePathString = scanFile.toString(); + // This is the point at which we know that the system-disk APK + // for this package has moved during a reboot (e.g. due to an OTA), + // so we need to reevaluate it for privilege policy. + if (locationIsPrivileged(scanFile)) { + updatedPkg.pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED; + } } updatedPkg.pkg = pkg; mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; @@ -4944,6 +4978,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); @@ -5566,9 +5612,9 @@ public class PackageManagerService extends IPackageManager.Stub { // version of the one on the data partition, but which // granted a new system permission that it didn't have // before. In this case we do want to allow the app to - // now get the new permission if the new system-partition - // apk is privileged to get it. - if (sysPs.pkg != null && isPrivilegedApp(pkg)) { + // now get the new permission if the ancestral apk is + // privileged to get it. + if (sysPs.pkg != null && sysPs.isPrivileged()) { for (int j=0; j<sysPs.pkg.requestedPermissions.size(); j++) { if (perm.equals( @@ -9370,7 +9416,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - boolean locationIsPrivileged(File path) { + static boolean locationIsPrivileged(File path) { try { final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app") .getCanonicalPath(); @@ -9950,6 +9996,10 @@ public class PackageManagerService extends IPackageManager.Stub { // writer int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true, "add preferred activity"); + if (filter.countActions() == 0) { + Slog.w(TAG, "Cannot set a preferred activity with no filter actions"); + return; + } synchronized (mPackages) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) @@ -9980,11 +10030,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( @@ -10001,31 +10051,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.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); diff --git a/services/java/com/android/server/pm/PackageSetting.java b/services/java/com/android/server/pm/PackageSetting.java index b6f9f5b..b447861 100644 --- a/services/java/com/android/server/pm/PackageSetting.java +++ b/services/java/com/android/server/pm/PackageSetting.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageParser; import java.io.File; @@ -56,4 +57,8 @@ final class PackageSetting extends PackageSettingBase { public int[] getGids() { return sharedUser != null ? sharedUser.gids : gids; } + + public boolean isPrivileged() { + return (pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0; + } } diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 5bc4d05..e599409 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -426,12 +426,12 @@ final class Settings { + "; replacing with new"); p = null; } else { - if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0) { - // If what we are scanning is a system package, then - // make it so, regardless of whether it was previously - // installed only in the data partition. - p.pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - } + // If what we are scanning is a system (and possibly privileged) package, + // then make it so, regardless of whether it was previously installed only + // in the data partition. + final int sysPrivFlags = pkgFlags + & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PRIVILEGED); + p.pkgFlags |= sysPrivFlags; } } if (p == null) { @@ -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; } @@ -2070,8 +2088,10 @@ final class Settings { if (intent.getAction() != null) { filter.addAction(intent.getAction()); } - for (String cat : intent.getCategories()) { - filter.addCategory(cat); + if (intent.getCategories() != null) { + for (String cat : intent.getCategories()) { + filter.addCategory(cat); + } } if ((flags&PackageManager.MATCH_DEFAULT_ONLY) != 0) { filter.addCategory(Intent.CATEGORY_DEFAULT); @@ -2088,6 +2108,13 @@ final class Settings { if (path != null) { filter.addDataPath(path); } + if (intent.getType() != null) { + try { + filter.addDataType(intent.getType()); + } catch (IntentFilter.MalformedMimeTypeException ex) { + Slog.w(TAG, "Malformed mimetype " + intent.getType() + " for " + cn); + } + } PreferredActivity pa = new PreferredActivity(filter, match, set, cn, true); editPreferredActivitiesLPw(userId).addFilter(pa); } else if (!haveNonSys) { @@ -2210,7 +2237,11 @@ final class Settings { int pkgFlags = 0; pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), + final File codePathFile = new File(codePathStr); + if (PackageManagerService.locationIsPrivileged(codePathFile)) { + pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED; + } + PackageSetting ps = new PackageSetting(name, realName, codePathFile, new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags); String timeStampStr = parser.getAttributeValue(null, "ft"); if (timeStampStr != null) { @@ -2266,6 +2297,7 @@ final class Settings { XmlUtils.skipCurrentTag(parser); } } + mDisabledSysPackages.put(name, ps); } diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index 976a328..60d44c7 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; @@ -945,12 +949,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 +977,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/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index 8fbde14..da9548f 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -747,6 +747,21 @@ public final class PowerManagerService extends IPowerManager.Stub } @Override // Binder call + public void updateWakeLockUids(IBinder lock, int[] uids) { + WorkSource ws = null; + + if (uids != null) { + ws = new WorkSource(); + // XXX should WorkSource have a way to set uids as an int[] instead of adding them + // one at a time? + for (int i = 0; i < uids.length; i++) { + ws.add(uids[i]); + } + } + updateWakeLockWorkSource(lock, ws); + } + + @Override // Binder call public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) { if (lock == null) { throw new IllegalArgumentException("lock must not be null"); diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java index edd6b25..98acc27 100644 --- a/services/java/com/android/server/print/PrintManagerService.java +++ b/services/java/com/android/server/print/PrintManagerService.java @@ -361,12 +361,17 @@ public final class PrintManagerService extends IPrintManager.Stub { } synchronized (mLock) { - pw.println("PRINT MANAGER STATE (dumpsys print)"); - final int userStateCount = mUserStates.size(); - for (int i = 0; i < userStateCount; i++) { - UserState userState = mUserStates.get(i); - userState.dump(fd, pw, ""); - pw.println(); + final long identity = Binder.clearCallingIdentity(); + try { + pw.println("PRINT MANAGER STATE (dumpsys print)"); + final int userStateCount = mUserStates.size(); + for (int i = 0; i < userStateCount; i++) { + UserState userState = mUserStates.valueAt(i); + userState.dump(fd, pw, ""); + pw.println(); + } + } finally { + Binder.restoreCallingIdentity(identity); } } } diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java index b69dcee..f23a992 100644 --- a/services/java/com/android/server/print/UserState.java +++ b/services/java/com/android/server/print/UserState.java @@ -231,10 +231,10 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { for (int i = 0; i < cachedPrintJobCount; i++) { PrintJobInfo cachedPrintJob = cachedPrintJobs.get(i); result.put(cachedPrintJob.getId(), cachedPrintJob); - // Strip out the tag - it is visible only to print services. - // Also the cached print jobs are delivered only to apps, so - // stripping the tag of a cached print job is fine. + // Strip out the tag and the advanced print options. + // They are visible only to print services. cachedPrintJob.setTag(null); + cachedPrintJob.setAdvancedOptions(null); } // Add everything else the spooler knows about. @@ -245,8 +245,10 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { for (int i = 0; i < printJobCount; i++) { PrintJobInfo printJob = printJobs.get(i); result.put(printJob.getId(), printJob); - // Strip out the tag - it is visible only to print services. + // Strip out the tag and the advanced print options. + // They are visible only to print services. printJob.setTag(null); + printJob.setAdvancedOptions(null); } } @@ -255,10 +257,16 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) { PrintJobInfo printJob = mPrintJobForAppCache.getPrintJob(printJobId, appId); + if (printJob == null) { + printJob = mSpooler.getPrintJobInfo(printJobId, appId); + } if (printJob != null) { - return printJob; + // Strip out the tag and the advanced print options. + // They are visible only to print services. + printJob.setTag(null); + printJob.setAdvancedOptions(null); } - return mSpooler.getPrintJobInfo(printJobId, appId); + return printJob; } public void cancelPrintJob(PrintJobId printJobId, int appId) { diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java index 86c68f3..f2efde1 100644 --- a/services/java/com/android/server/wifi/WifiService.java +++ b/services/java/com/android/server/wifi/WifiService.java @@ -35,6 +35,7 @@ import android.net.wifi.ScanResult; import android.net.wifi.BatchedScanResult; import android.net.wifi.BatchedScanSettings; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.ProxySettings; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiStateMachine; @@ -175,8 +176,19 @@ public final class WifiService extends IWifiManager.Stub { WifiConfiguration config = (WifiConfiguration) msg.obj; int networkId = msg.arg1; if (config != null && config.isValid()) { - if (DBG) Slog.d(TAG, "Connect with config" + config); - mWifiStateMachine.sendMessage(Message.obtain(msg)); + // This is restricted because there is no UI for the user to + // monitor/control PAC. + if (config.proxySettings != ProxySettings.PAC) { + if (DBG) Slog.d(TAG, "Connect with config" + config); + mWifiStateMachine.sendMessage(Message.obtain(msg)); + } else { + Slog.e(TAG, "ClientHandler.handleMessage cannot process msg with PAC"); + if (msg.what == WifiManager.CONNECT_NETWORK) { + replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED); + } else { + replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED); + } + } } else if (config == null && networkId != WifiConfiguration.INVALID_NETWORK_ID) { if (DBG) Slog.d(TAG, "Connect with networkId" + networkId); @@ -357,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); @@ -394,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(); @@ -456,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 || @@ -477,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 || @@ -499,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() { @@ -685,6 +736,9 @@ public final class WifiService extends IWifiManager.Stub { */ public int addOrUpdateNetwork(WifiConfiguration config) { enforceChangePermission(); + if (config.proxySettings == ProxySettings.PAC) { + enforceConnectivityInternalPermission(); + } if (config.isValid()) { if (mWifiStateMachineChannel != null) { return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config); diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index 8cc1d02..b1d67de 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; + int configChanges; 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 afa4f78..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> mTmpTasks = 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(stack); - } - public boolean isPrivate() { return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0; } @@ -175,14 +168,36 @@ class DisplayContent { * @return All the Tasks, in order, on this display. */ ArrayList<Task> getTasks() { - mTmpTasks.clear(); - final int numStacks = mStackHistory.size(); - for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { - mTmpTasks.addAll(mStackHistory.get(stackNdx).getTasks()); + return mTaskHistory; + } + + void addTask(Task task, boolean toTop) { + mTaskHistory.remove(task); + + final int userId = task.mUserId; + int taskNdx; + final int numTasks = mTaskHistory.size(); + if (toTop) { + for (taskNdx = numTasks - 1; taskNdx >= 0; --taskNdx) { + if (mTaskHistory.get(taskNdx).mUserId == userId) { + break; + } + } + ++taskNdx; + } else { + for (taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + if (mTaskHistory.get(taskNdx).mUserId == userId) { + break; + } + } } - if (WindowManagerService.DEBUG_LAYERS) Slog.i(TAG, "getTasks: mStackHistory=" + - mStackHistory); - return mTmpTasks; + + mTaskHistory.add(taskNdx, task); + EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, taskNdx); + } + + void removeTask(Task task) { + mTaskHistory.remove(task); } TaskStack getHomeStack() { @@ -205,10 +220,9 @@ class DisplayContent { /** @return The number of tokens in all of the Tasks on this display. */ int numTokens() { - getTasks(); int count = 0; - for (int taskNdx = mTmpTasks.size() - 1; taskNdx >= 0; --taskNdx) { - count += mTmpTasks.get(taskNdx).mAppTokens.size(); + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + count += mTaskHistory.get(taskNdx).mAppTokens.size(); } return count; } @@ -257,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; } @@ -325,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. @@ -469,8 +486,8 @@ class DisplayContent { pw.println(); pw.println(" Application tokens in Z order:"); getTasks(); - for (int taskNdx = mTmpTasks.size() - 1; taskNdx >= 0; --taskNdx) { - AppTokenList tokens = mTmpTasks.get(taskNdx).mAppTokens; + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + AppTokenList tokens = mTaskHistory.get(taskNdx).mAppTokens; for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { final AppWindowToken wtoken = tokens.get(tokenNdx); pw.print(" App #"); pw.print(ndx--); 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 2347a19..e65aecb 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); } @@ -120,6 +121,7 @@ public class TaskStack { mTasks.add(stackNdx, task); task.mStack = this; + mDisplayContent.addTask(task, toTop); return mDisplayContent.moveHomeStackBox(mStackId == HOME_STACK_ID); } @@ -145,11 +147,13 @@ public class TaskStack { if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "removeTask: task=" + task); mStackBox.makeDirty(); mTasks.remove(task); + mDisplayContent.removeTask(task); } int remove() { mAnimationBackgroundSurface.destroySurface(); mDimLayer.destroySurface(); + EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); return mStackBox.remove(); } diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index ca87b4f..91f15f3 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -121,12 +121,6 @@ public class WindowAnimator { mDisplayContentsAnimators.delete(displayId); } - AppWindowAnimator getWallpaperAppAnimator() { - return mService.mWallpaperTarget == null - ? null : mService.mWallpaperTarget.mAppToken == null - ? null : mService.mWallpaperTarget.mAppToken.mAppAnimator; - } - void hideWallpapersLocked(final WindowState w) { final WindowState wallpaperTarget = mService.mWallpaperTarget; final WindowState lowerWallpaperTarget = mService.mLowerWallpaperTarget; @@ -251,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 15bf8c8..6d47fcf 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -530,6 +530,7 @@ public class WindowManagerService extends IWindowManager.Stub static final long WALLPAPER_TIMEOUT = 150; // Time we wait after a timeout before trying to wait again. static final long WALLPAPER_TIMEOUT_RECOVERY = 10000; + boolean mAnimateWallpaperWithTarget; AppWindowToken mFocusedApp = null; @@ -577,10 +578,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(); @@ -3393,16 +3397,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"); @@ -3434,6 +3439,7 @@ public class WindowManagerService extends IWindowManager.Stub atoken.appFullscreen = fullscreen; atoken.showWhenLocked = showWhenLocked; atoken.requestedOrientation = requestedOrientation; + atoken.configChanges = configChanges; if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken + " to stack=" + stackId + " task=" + taskId + " at " + addPos); @@ -4302,10 +4308,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) { @@ -4732,11 +4734,9 @@ public class WindowManagerService extends IWindowManager.Stub return index; } - void moveStackWindowsLocked(TaskStack stack) { - DisplayContent displayContent = stack.getDisplayContent(); - + void moveStackWindowsLocked(DisplayContent displayContent) { // First remove all of the windows from the list. - final ArrayList<Task> tasks = stack.getTasks(); + final ArrayList<Task> tasks = displayContent.getTasks(); final int numTasks = tasks.size(); for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { AppTokenList tokens = tasks.get(taskNdx).mAppTokens; @@ -4793,7 +4793,6 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.moveHomeStackBox(isHomeStackTask); } stack.moveTaskToTop(task); - displayContent.moveStack(stack, true); } } finally { Binder.restoreCallingIdentity(origId); @@ -4812,7 +4811,7 @@ public class WindowManagerService extends IWindowManager.Stub } final TaskStack stack = task.mStack; stack.moveTaskToBottom(task); - moveStackWindowsLocked(stack); + moveStackWindowsLocked(stack.getDisplayContent()); } } finally { Binder.restoreCallingIdentity(origId); @@ -4847,7 +4846,6 @@ public class WindowManagerService extends IWindowManager.Stub weight); if (stack != null) { mStackIdToStack.put(stackId, stack); - displayContent.moveStack(stack, true); performLayoutAndPlaceSurfacesLocked(); return; } @@ -4879,6 +4877,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; } @@ -5717,7 +5716,7 @@ public class WindowManagerService extends IWindowManager.Stub canvas.drawBitmap(rawss, matrix, null); canvas.setBitmap(null); - if (true || DEBUG_SCREENSHOT) { + if (DEBUG_SCREENSHOT) { // TEST IF IT's ALL BLACK int[] buffer = new int[bm.getWidth() * bm.getHeight()]; bm.getPixels(buffer, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight()); @@ -7630,22 +7629,27 @@ public class WindowManagerService extends IWindowManager.Stub if (displayId != Display.DEFAULT_DISPLAY) { throw new IllegalArgumentException("Can only set the default display"); } - synchronized(mWindowMap) { - // Set some sort of reasonable bounds on the size of the display that we - // will try to emulate. - final int MIN_WIDTH = 200; - final int MIN_HEIGHT = 200; - final int MAX_SCALE = 2; - final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { - width = Math.min(Math.max(width, MIN_WIDTH), - displayContent.mInitialDisplayWidth * MAX_SCALE); - height = Math.min(Math.max(height, MIN_HEIGHT), - displayContent.mInitialDisplayHeight * MAX_SCALE); - setForcedDisplaySizeLocked(displayContent, width, height); - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.DISPLAY_SIZE_FORCED, width + "," + height); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized(mWindowMap) { + // Set some sort of reasonable bounds on the size of the display that we + // will try to emulate. + final int MIN_WIDTH = 200; + final int MIN_HEIGHT = 200; + final int MAX_SCALE = 2; + final DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + width = Math.min(Math.max(width, MIN_WIDTH), + displayContent.mInitialDisplayWidth * MAX_SCALE); + height = Math.min(Math.max(height, MIN_HEIGHT), + displayContent.mInitialDisplayHeight * MAX_SCALE); + setForcedDisplaySizeLocked(displayContent, width, height); + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.DISPLAY_SIZE_FORCED, width + "," + height); + } } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -7716,14 +7720,19 @@ public class WindowManagerService extends IWindowManager.Stub if (displayId != Display.DEFAULT_DISPLAY) { throw new IllegalArgumentException("Can only set the default display"); } - synchronized(mWindowMap) { - final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { - setForcedDisplaySizeLocked(displayContent, displayContent.mInitialDisplayWidth, - displayContent.mInitialDisplayHeight); - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.DISPLAY_SIZE_FORCED, ""); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized(mWindowMap) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + setForcedDisplaySizeLocked(displayContent, displayContent.mInitialDisplayWidth, + displayContent.mInitialDisplayHeight); + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.DISPLAY_SIZE_FORCED, ""); + } } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -7764,13 +7773,18 @@ public class WindowManagerService extends IWindowManager.Stub if (displayId != Display.DEFAULT_DISPLAY) { throw new IllegalArgumentException("Can only set the default display"); } - synchronized(mWindowMap) { - final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { - setForcedDisplayDensityLocked(displayContent, density); - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.DISPLAY_DENSITY_FORCED, Integer.toString(density)); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized(mWindowMap) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + setForcedDisplayDensityLocked(displayContent, density); + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.DISPLAY_DENSITY_FORCED, Integer.toString(density)); + } } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -7795,13 +7809,19 @@ public class WindowManagerService extends IWindowManager.Stub if (displayId != Display.DEFAULT_DISPLAY) { throw new IllegalArgumentException("Can only set the default display"); } - synchronized(mWindowMap) { - final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { - setForcedDisplayDensityLocked(displayContent, displayContent.mInitialDisplayDensity); - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.DISPLAY_DENSITY_FORCED, ""); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized(mWindowMap) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + setForcedDisplayDensityLocked(displayContent, + displayContent.mInitialDisplayDensity); + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.DISPLAY_DENSITY_FORCED, ""); + } } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -7849,11 +7869,16 @@ public class WindowManagerService extends IWindowManager.Stub throw new SecurityException("Must hold permission " + android.Manifest.permission.WRITE_SECURE_SETTINGS); } - synchronized(mWindowMap) { - DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { - setOverscanLocked(displayContent, left, top, right, bottom); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized(mWindowMap) { + DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + setOverscanLocked(displayContent, left, top, right, bottom); + } } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -8238,7 +8263,10 @@ 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.mAttrs.type == TYPE_KEYGUARD || + (win.mAppToken != null && (win.mAppToken.configChanges & + (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) + != 0)) || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) { if (!win.mLayoutAttached) { if (initial) { @@ -8470,6 +8498,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + mAnimateWallpaperWithTarget = false; if (closingAppHasWallpaper && openingAppHasWallpaper) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!"); switch (transit) { @@ -8485,7 +8514,8 @@ public class WindowManagerService extends IWindowManager.Stub break; } if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit: " + transit); - } else if ((oldWallpaper != null) && !mOpeningApps.contains(oldWallpaper.mAppToken)) { + } else if ((oldWallpaper != null) && !mOpeningApps.isEmpty() + && !mOpeningApps.contains(oldWallpaper.mAppToken)) { // We are transitioning from an activity with // a wallpaper to one without. transit = AppTransition.TRANSIT_WALLPAPER_CLOSE; @@ -8497,6 +8527,8 @@ public class WindowManagerService extends IWindowManager.Stub transit = AppTransition.TRANSIT_WALLPAPER_OPEN; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit into wallpaper: " + transit); + } else { + mAnimateWallpaperWithTarget = true; } // If all closing windows are obscured, then there is @@ -8552,8 +8584,7 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.mAppAnimator.clearThumbnail(); wtoken.inPendingTransaction = false; wtoken.mAppAnimator.animation = null; - setTokenVisibilityLocked(wtoken, animLp, false, - transit, false); + setTokenVisibilityLocked(wtoken, animLp, false, transit, false); wtoken.updateReportedVisibilityLocked(); wtoken.waitingToHide = false; // Force the allDrawn flag, because we want to start @@ -8749,6 +8780,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) { @@ -8777,22 +8816,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) { @@ -8870,7 +8911,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++; @@ -8905,10 +8946,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 { @@ -9117,20 +9156,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(); diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index e2fae89..c405170 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -360,6 +360,10 @@ class WindowStateAnimator { + mWin.mToken + ": first real window done animating"); mService.mFinishedStarting.add(mWin.mAppToken); mService.mH.sendEmptyMessage(H.FINISHED_STARTING); + } else if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.mPolicyVisibility) { + // Upon completion of a not-visible to visible status bar animation a relayout is + // required. + mWin.mDisplayContent.layoutNeeded = true; } finishExit(); @@ -845,9 +849,9 @@ class WindowStateAnimator { // Wallpapers are animated based on the "real" window they // are currently targeting. - if (mIsWallpaper && mService.mLowerWallpaperTarget == null - && mService.mWallpaperTarget != null) { - final WindowStateAnimator wallpaperAnimator = mService.mWallpaperTarget.mWinAnimator; + final WindowState wallpaperTarget = mService.mWallpaperTarget; + if (mIsWallpaper && wallpaperTarget != null && mService.mAnimateWallpaperWithTarget) { + final WindowStateAnimator wallpaperAnimator = wallpaperTarget.mWinAnimator; if (wallpaperAnimator.mHasLocalTransformation && wallpaperAnimator.mAnimation != null && !wallpaperAnimator.mAnimation.getDetachWallpaper()) { @@ -856,8 +860,9 @@ class WindowStateAnimator { Slog.v(TAG, "WP target attached xform: " + attachedTransformation); } } - final AppWindowAnimator wpAppAnimator = mAnimator.getWallpaperAppAnimator(); - if (wpAppAnimator != null && wpAppAnimator.hasTransformation + final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ? + null : wallpaperTarget.mAppToken.mAppAnimator; + if (wpAppAnimator != null && wpAppAnimator.hasTransformation && wpAppAnimator.animation != null && !wpAppAnimator.animation.getDetachWallpaper()) { appTransformation = wpAppAnimator.transformation; |