diff options
Diffstat (limited to 'services/java')
145 files changed, 30472 insertions, 14572 deletions
diff --git a/services/java/Android.mk b/services/java/Android.mk index e70a6c9..95b28d9 100644 --- a/services/java/Android.mk +++ b/services/java/Android.mk @@ -13,9 +13,6 @@ LOCAL_MODULE:= services LOCAL_JAVA_LIBRARIES := android.policy telephony-common -LOCAL_NO_EMMA_INSTRUMENT := true -LOCAL_NO_EMMA_COMPILE := true - include $(BUILD_JAVA_LIBRARY) include $(BUILD_DROIDDOC) diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 32ac8e1..f960833 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -34,6 +34,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.WorkSource; import android.text.TextUtils; import android.text.format.Time; @@ -242,32 +243,39 @@ class AlarmManagerService extends IAlarmManager.Stub { "android.permission.SET_TIME_ZONE", "setTimeZone"); - if (TextUtils.isEmpty(tz)) return; - TimeZone zone = TimeZone.getTimeZone(tz); - // Prevent reentrant calls from stepping on each other when writing - // the time zone property - boolean timeZoneWasChanged = false; - synchronized (this) { - String current = SystemProperties.get(TIMEZONE_PROPERTY); - if (current == null || !current.equals(zone.getID())) { - if (localLOGV) Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID()); - timeZoneWasChanged = true; - SystemProperties.set(TIMEZONE_PROPERTY, zone.getID()); + long oldId = Binder.clearCallingIdentity(); + try { + if (TextUtils.isEmpty(tz)) return; + TimeZone zone = TimeZone.getTimeZone(tz); + // Prevent reentrant calls from stepping on each other when writing + // the time zone property + boolean timeZoneWasChanged = false; + synchronized (this) { + String current = SystemProperties.get(TIMEZONE_PROPERTY); + if (current == null || !current.equals(zone.getID())) { + if (localLOGV) { + Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID()); + } + timeZoneWasChanged = true; + SystemProperties.set(TIMEZONE_PROPERTY, zone.getID()); + } + + // Update the kernel timezone information + // Kernel tracks time offsets as 'minutes west of GMT' + int gmtOffset = zone.getOffset(System.currentTimeMillis()); + setKernelTimezone(mDescriptor, -(gmtOffset / 60000)); } - - // Update the kernel timezone information - // Kernel tracks time offsets as 'minutes west of GMT' - int gmtOffset = zone.getOffset(System.currentTimeMillis()); - setKernelTimezone(mDescriptor, -(gmtOffset / 60000)); - } - TimeZone.setDefault(null); - - if (timeZoneWasChanged) { - Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra("time-zone", zone.getID()); - mContext.sendBroadcast(intent); + TimeZone.setDefault(null); + + if (timeZoneWasChanged) { + Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("time-zone", zone.getID()); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + } finally { + Binder.restoreCallingIdentity(oldId); } } @@ -303,7 +311,7 @@ class AlarmManagerService extends IAlarmManager.Stub { } } } - + public void removeLocked(String packageName) { removeLocked(mRtcWakeupAlarms, packageName); removeLocked(mRtcAlarms, packageName); @@ -327,6 +335,29 @@ class AlarmManagerService extends IAlarmManager.Stub { } } } + + public void removeUserLocked(int userHandle) { + removeUserLocked(mRtcWakeupAlarms, userHandle); + removeUserLocked(mRtcAlarms, userHandle); + removeUserLocked(mElapsedRealtimeWakeupAlarms, userHandle); + removeUserLocked(mElapsedRealtimeAlarms, userHandle); + } + + private void removeUserLocked(ArrayList<Alarm> alarmList, int userHandle) { + if (alarmList.size() <= 0) { + return; + } + + // iterator over the list removing any it where the intent match + Iterator<Alarm> it = alarmList.iterator(); + + while (it.hasNext()) { + Alarm alarm = it.next(); + if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) { + it.remove(); + } + } + } public boolean lookForPackageLocked(String packageName) { return lookForPackageLocked(mRtcWakeupAlarms, packageName) @@ -637,7 +668,7 @@ class AlarmManagerService extends IAlarmManager.Stub { Intent intent = new Intent(Intent.ACTION_TIME_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } synchronized (mLock) { @@ -822,6 +853,7 @@ class AlarmManagerService extends IAlarmManager.Stub { // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + sdFilter.addAction(Intent.ACTION_USER_STOPPED); mContext.registerReceiver(this, sdFilter); } @@ -841,6 +873,11 @@ class AlarmManagerService extends IAlarmManager.Stub { return; } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + removeUserLocked(userHandle); + } } else { if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 38f4554..385681e 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -27,10 +27,10 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; -import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -150,13 +150,13 @@ class AppWidgetService extends IAppWidgetService.Stub // Register for the boot completed broadcast, so we can send the // ENABLE broacasts. If we try to send them now, they time out, // because the system isn't ready to handle them yet. - mContext.registerReceiver(mBroadcastReceiver, + mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); // Register for configuration changes so we can update the names // of the widgets when the locale changes. - mContext.registerReceiver(mBroadcastReceiver, new IntentFilter( - Intent.ACTION_CONFIGURATION_CHANGED), null, null); + mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, + new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null); // Register for broadcasts about package install, etc., so we can // update the provider list. @@ -165,75 +165,95 @@ class AppWidgetService extends IAppWidgetService.Stub filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); - mContext.registerReceiver(mBroadcastReceiver, filter); + mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, + filter, null, null); // Register for events related to sdcard installation. IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mBroadcastReceiver, sdFilter); + mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, + sdFilter, null, null); IntentFilter userFilter = new IntentFilter(); userFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - onUserRemoved(intent.getIntExtra(Intent.EXTRA_USERID, -1)); + onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); } }, userFilter); + + IntentFilter userStopFilter = new IntentFilter(); + userStopFilter.addAction(Intent.ACTION_USER_STOPPED); + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onUserStopped(getSendingUserId()); + } + }, UserHandle.ALL, userFilter, null, null); } @Override public int allocateAppWidgetId(String packageName, int hostId) throws RemoteException { - return getImplForUser().allocateAppWidgetId(packageName, hostId); + return getImplForUser(UserHandle.getCallingUserId()).allocateAppWidgetId( + packageName, hostId); } @Override public void deleteAppWidgetId(int appWidgetId) throws RemoteException { - getImplForUser().deleteAppWidgetId(appWidgetId); + getImplForUser(UserHandle.getCallingUserId()).deleteAppWidgetId(appWidgetId); } @Override public void deleteHost(int hostId) throws RemoteException { - getImplForUser().deleteHost(hostId); + getImplForUser(UserHandle.getCallingUserId()).deleteHost(hostId); } @Override public void deleteAllHosts() throws RemoteException { - getImplForUser().deleteAllHosts(); + getImplForUser(UserHandle.getCallingUserId()).deleteAllHosts(); } @Override - public void bindAppWidgetId(int appWidgetId, ComponentName provider) throws RemoteException { - getImplForUser().bindAppWidgetId(appWidgetId, provider); + public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) + throws RemoteException { + getImplForUser(UserHandle.getCallingUserId()).bindAppWidgetId(appWidgetId, provider, + options); } @Override public boolean bindAppWidgetIdIfAllowed( - String packageName, int appWidgetId, ComponentName provider) throws RemoteException { - return getImplForUser().bindAppWidgetIdIfAllowed(packageName, appWidgetId, provider); + String packageName, int appWidgetId, ComponentName provider, Bundle options) + throws RemoteException { + return getImplForUser(UserHandle.getCallingUserId()).bindAppWidgetIdIfAllowed( + packageName, appWidgetId, provider, options); } @Override public boolean hasBindAppWidgetPermission(String packageName) throws RemoteException { - return getImplForUser().hasBindAppWidgetPermission(packageName); + return getImplForUser(UserHandle.getCallingUserId()).hasBindAppWidgetPermission( + packageName); } @Override public void setBindAppWidgetPermission(String packageName, boolean permission) throws RemoteException { - getImplForUser().setBindAppWidgetPermission(packageName, permission); + getImplForUser(UserHandle.getCallingUserId()).setBindAppWidgetPermission( + packageName, permission); } @Override public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) throws RemoteException { - getImplForUser().bindRemoteViewsService(appWidgetId, intent, connection); + getImplForUser(UserHandle.getCallingUserId()).bindRemoteViewsService( + appWidgetId, intent, connection); } @Override public int[] startListening(IAppWidgetHost host, String packageName, int hostId, List<RemoteViews> updatedViews) throws RemoteException { - return getImplForUser().startListening(host, packageName, hostId, updatedViews); + return getImplForUser(UserHandle.getCallingUserId()).startListening(host, + packageName, hostId, updatedViews); } public void onUserRemoved(int userId) { @@ -247,8 +267,10 @@ class AppWidgetService extends IAppWidgetService.Stub } } - private AppWidgetServiceImpl getImplForUser() { - final int userId = Binder.getOrigCallingUser(); + public void onUserStopped(int userId) { + } + + private AppWidgetServiceImpl getImplForUser(int userId) { AppWidgetServiceImpl service = mAppWidgetServices.get(userId); if (service == null) { Slog.e(TAG, "Unable to find AppWidgetServiceImpl for the current user"); @@ -265,27 +287,27 @@ class AppWidgetService extends IAppWidgetService.Stub @Override public int[] getAppWidgetIds(ComponentName provider) throws RemoteException { - return getImplForUser().getAppWidgetIds(provider); + return getImplForUser(UserHandle.getCallingUserId()).getAppWidgetIds(provider); } @Override public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) throws RemoteException { - return getImplForUser().getAppWidgetInfo(appWidgetId); + return getImplForUser(UserHandle.getCallingUserId()).getAppWidgetInfo(appWidgetId); } @Override public RemoteViews getAppWidgetViews(int appWidgetId) throws RemoteException { - return getImplForUser().getAppWidgetViews(appWidgetId); + return getImplForUser(UserHandle.getCallingUserId()).getAppWidgetViews(appWidgetId); } @Override public void updateAppWidgetOptions(int appWidgetId, Bundle options) { - getImplForUser().updateAppWidgetOptions(appWidgetId, options); + getImplForUser(UserHandle.getCallingUserId()).updateAppWidgetOptions(appWidgetId, options); } @Override public Bundle getAppWidgetOptions(int appWidgetId) { - return getImplForUser().getAppWidgetOptions(appWidgetId); + return getImplForUser(UserHandle.getCallingUserId()).getAppWidgetOptions(appWidgetId); } static int[] getAppWidgetIds(Provider p) { @@ -299,40 +321,43 @@ class AppWidgetService extends IAppWidgetService.Stub @Override public List<AppWidgetProviderInfo> getInstalledProviders() throws RemoteException { - return getImplForUser().getInstalledProviders(); + return getImplForUser(UserHandle.getCallingUserId()).getInstalledProviders(); } @Override public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) throws RemoteException { - getImplForUser().notifyAppWidgetViewDataChanged(appWidgetIds, viewId); + getImplForUser(UserHandle.getCallingUserId()).notifyAppWidgetViewDataChanged( + appWidgetIds, viewId); } @Override public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) throws RemoteException { - getImplForUser().partiallyUpdateAppWidgetIds(appWidgetIds, views); + getImplForUser(UserHandle.getCallingUserId()).partiallyUpdateAppWidgetIds( + appWidgetIds, views); } @Override public void stopListening(int hostId) throws RemoteException { - getImplForUser().stopListening(hostId); + getImplForUser(UserHandle.getCallingUserId()).stopListening(hostId); } @Override public void unbindRemoteViewsService(int appWidgetId, Intent intent) throws RemoteException { - getImplForUser().unbindRemoteViewsService(appWidgetId, intent); + getImplForUser(UserHandle.getCallingUserId()).unbindRemoteViewsService( + appWidgetId, intent); } @Override public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) throws RemoteException { - getImplForUser().updateAppWidgetIds(appWidgetIds, views); + getImplForUser(UserHandle.getCallingUserId()).updateAppWidgetIds(appWidgetIds, views); } @Override public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) throws RemoteException { - getImplForUser().updateAppWidgetProvider(provider, views); + getImplForUser(UserHandle.getCallingUserId()).updateAppWidgetProvider(provider, views); } @Override @@ -349,16 +374,29 @@ class AppWidgetService extends IAppWidgetService.Stub String action = intent.getAction(); // Slog.d(TAG, "received " + action); if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { - getImplForUser().sendInitialBroadcasts(); + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId >= 0) { + getImplForUser(userId).sendInitialBroadcasts(); + } else { + Slog.w(TAG, "Not user handle supplied in " + intent); + } } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { for (int i = 0; i < mAppWidgetServices.size(); i++) { AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); service.onConfigurationChanged(); } } else { - for (int i = 0; i < mAppWidgetServices.size(); i++) { - AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); - service.onBroadcastReceived(intent); + int sendingUser = getSendingUserId(); + if (sendingUser == UserHandle.USER_ALL) { + for (int i = 0; i < mAppWidgetServices.size(); i++) { + AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); + service.onBroadcastReceived(intent); + } + } else { + AppWidgetServiceImpl service = mAppWidgetServices.get(sendingUser); + if (service != null) { + service.onBroadcastReceived(intent); + } } } } diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java index f9c432b..499c15e 100644 --- a/services/java/com/android/server/AppWidgetServiceImpl.java +++ b/services/java/com/android/server/AppWidgetServiceImpl.java @@ -36,24 +36,28 @@ import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.graphics.Point; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.Environment; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserId; +import android.os.UserHandle; +import android.util.AtomicFile; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.TypedValue; import android.util.Xml; +import android.view.Display; import android.view.WindowManager; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetHost; -import com.android.internal.os.AtomicFile; import com.android.internal.util.FastXmlSerializer; import com.android.internal.widget.IRemoteViewsAdapterConnection; import com.android.internal.widget.IRemoteViewsFactory; @@ -188,11 +192,12 @@ class AppWidgetServiceImpl { void computeMaximumWidgetBitmapMemory() { WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - int height = wm.getDefaultDisplay().getRawHeight(); - int width = wm.getDefaultDisplay().getRawWidth(); + Display display = wm.getDefaultDisplay(); + Point size = new Point(); + display.getRealSize(size); // Cap memory usage at 1.5 times the size of the display // 1.5 * 4 bytes/pixel * w * h ==> 6 * w * h - mMaxWidgetBitmapMemory = 6 * width * height; + mMaxWidgetBitmapMemory = 6 * size.x * size.y; } public void systemReady(boolean safeMode) { @@ -210,11 +215,19 @@ class AppWidgetServiceImpl { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); - int N = mInstalledProviders.size(); + // Note: updateProvidersForPackageLocked() may remove providers, so we must copy the + // list of installed providers and skip providers that we don't need to update. + // Also note that remove the provider does not clear the Provider component data. + ArrayList<Provider> installedProviders = + new ArrayList<Provider>(mInstalledProviders); + HashSet<ComponentName> removedProviders = new HashSet<ComponentName>(); + int N = installedProviders.size(); for (int i = N - 1; i >= 0; i--) { - Provider p = mInstalledProviders.get(i); - String pkgName = p.info.provider.getPackageName(); - updateProvidersForPackageLocked(pkgName); + Provider p = installedProviders.get(i); + ComponentName cn = p.info.provider; + if (!removedProviders.contains(cn)) { + updateProvidersForPackageLocked(cn.getPackageName(), removedProviders); + } } saveStateLocked(); } @@ -225,6 +238,7 @@ class AppWidgetServiceImpl { final String action = intent.getAction(); boolean added = false; boolean changed = false; + boolean providersModified = false; String pkgList[] = null; if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); @@ -256,12 +270,12 @@ class AppWidgetServiceImpl { || (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false))) { for (String pkgName : pkgList) { // The package was just upgraded - updateProvidersForPackageLocked(pkgName); + providersModified |= updateProvidersForPackageLocked(pkgName, null); } } else { // The package was just added for (String pkgName : pkgList) { - addProvidersForPackageLocked(pkgName); + providersModified |= addProvidersForPackageLocked(pkgName); } } saveStateLocked(); @@ -274,12 +288,20 @@ class AppWidgetServiceImpl { synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); for (String pkgName : pkgList) { - removeProvidersForPackageLocked(pkgName); + providersModified |= removeProvidersForPackageLocked(pkgName); saveStateLocked(); } } } } + + if (providersModified) { + // If the set of providers has been modified, notify each active AppWidgetHost + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + notifyHostsForProvidersChangedLocked(); + } + } } private void dumpProvider(Provider p, int index, PrintWriter pw) { @@ -295,6 +317,8 @@ class AppWidgetServiceImpl { pw.print(info.updatePeriodMillis); pw.print(" resizeMode="); pw.print(info.resizeMode); + pw.print(info.widgetCategory); + pw.print(info.widgetFeatures); pw.print(" autoAdvanceViewId="); pw.print(info.autoAdvanceViewId); pw.print(" initialLayout=#"); @@ -386,7 +410,7 @@ class AppWidgetServiceImpl { } public int allocateAppWidgetId(String packageName, int hostId) { - int callingUid = enforceCallingUid(packageName); + int callingUid = enforceSystemOrCallingUid(packageName); synchronized (mAppWidgetIds) { ensureStateLoadedLocked(); int appWidgetId = mNextAppWidgetId++; @@ -479,7 +503,7 @@ class AppWidgetServiceImpl { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); intent.setComponent(p.info.provider); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); - mContext.sendBroadcast(intent, mUserId); + mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId)); if (p.instances.size() == 0) { // cancel the future updates cancelBroadcasts(p); @@ -487,7 +511,7 @@ class AppWidgetServiceImpl { // send the broacast saying that the provider is not in use any more intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED); intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent, mUserId); + mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId)); } } } @@ -506,7 +530,7 @@ class AppWidgetServiceImpl { } } - private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider) { + private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider, Bundle options) { final long ident = Binder.clearCallingIdentity(); try { synchronized (mAppWidgetIds) { @@ -529,6 +553,17 @@ class AppWidgetServiceImpl { } id.provider = p; + if (options == null) { + options = new Bundle(); + } + id.options = options; + + // We need to provide a default value for the widget category if it is not specified + if (!options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) { + options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, + AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN); + } + p.instances.add(id); int instancesSize = p.instances.size(); if (instancesSize == 1) { @@ -551,14 +586,14 @@ class AppWidgetServiceImpl { } } - public void bindAppWidgetId(int appWidgetId, ComponentName provider) { + public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) { mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, "bindAppWidgetId appWidgetId=" + appWidgetId + " provider=" + provider); - bindAppWidgetIdImpl(appWidgetId, provider); + bindAppWidgetIdImpl(appWidgetId, provider, options); } public boolean bindAppWidgetIdIfAllowed( - String packageName, int appWidgetId, ComponentName provider) { + String packageName, int appWidgetId, ComponentName provider, Bundle options) { try { mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, null); } catch (SecurityException se) { @@ -566,14 +601,14 @@ class AppWidgetServiceImpl { return false; } } - bindAppWidgetIdImpl(appWidgetId, provider); + bindAppWidgetIdImpl(appWidgetId, provider, options); return true; } private boolean callerHasBindAppWidgetPermission(String packageName) { int callingUid = Binder.getCallingUid(); try { - if (!UserId.isSameApp(callingUid, getUidForPackage(packageName))) { + if (!UserHandle.isSameApp(callingUid, getUidForPackage(packageName))) { return false; } } catch (Exception e) { @@ -645,7 +680,7 @@ class AppWidgetServiceImpl { mBoundRemoteViewsServices.remove(key); } - int userId = UserId.getUserId(id.provider.uid); + int userId = UserHandle.getUserId(id.provider.uid); // Bind to the RemoteViewsService (which will trigger a callback to the // RemoteViewsAdapter.onServiceConnected()) final long token = Binder.clearCallingIdentity(); @@ -736,7 +771,7 @@ class AppWidgetServiceImpl { } }; - int userId = UserId.getUserId(id.provider.uid); + int userId = UserHandle.getUserId(id.provider.uid); // Bind to the service and remove the static intent->factory mapping in the // RemoteViewsService. final long token = Binder.clearCallingIdentity(); @@ -852,15 +887,18 @@ class AppWidgetServiceImpl { if (id == null) { return; } + Provider p = id.provider; - id.options = options; + // Merge the options + id.options.putAll(options); // send the broacast saying that this appWidgetId has been deleted Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED); intent.setComponent(p.info.provider); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options); - mContext.sendBroadcast(intent, mUserId); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, id.options); + mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId)); + saveStateLocked(); } } @@ -1006,7 +1044,7 @@ class AppWidgetServiceImpl { } }; - int userId = UserId.getUserId(id.provider.uid); + int userId = UserHandle.getUserId(id.provider.uid); // Bind to the service and call onDataSetChanged() final long token = Binder.clearCallingIdentity(); try { @@ -1185,7 +1223,7 @@ class AppWidgetServiceImpl { void sendEnableIntentLocked(Provider p) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent, mUserId); + mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId)); } void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { @@ -1193,7 +1231,7 @@ class AppWidgetServiceImpl { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent, mUserId); + mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId)); } } @@ -1304,6 +1342,8 @@ class AppWidgetServiceImpl { com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); info.initialLayout = sa.getResourceId( com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); + info.initialKeyguardLayout = sa.getResourceId(com.android.internal.R.styleable. + AppWidgetProviderInfo_initialKeyguardLayout, 0); String className = sa .getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure); if (className != null) { @@ -1318,6 +1358,12 @@ class AppWidgetServiceImpl { info.resizeMode = sa.getInt( com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, AppWidgetProviderInfo.RESIZE_NONE); + info.widgetCategory = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_widgetCategory, + AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN); + info.widgetFeatures = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_widgetFeatures, + AppWidgetProviderInfo.WIDGET_FEATURES_NONE); sa.recycle(); } catch (Exception e) { @@ -1346,6 +1392,15 @@ class AppWidgetServiceImpl { return pkgInfo.applicationInfo.uid; } + int enforceSystemOrCallingUid(String packageName) throws IllegalArgumentException { + int callingUid = Binder.getCallingUid(); + int uid = Process.myUid(); + if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) { + return callingUid; + } + return enforceCallingUid(packageName); + } + int enforceCallingUid(String packageName) throws IllegalArgumentException { int callingUid = Binder.getCallingUid(); int packageUid; @@ -1355,7 +1410,7 @@ class AppWidgetServiceImpl { throw new IllegalArgumentException("packageName and uid don't match packageName=" + packageName); } - if (!UserId.isSameApp(callingUid, packageUid)) { + if (!UserHandle.isSameApp(callingUid, packageUid)) { throw new IllegalArgumentException("packageName and uid don't match packageName=" + packageName); } @@ -1455,6 +1510,18 @@ class AppWidgetServiceImpl { if (id.provider != null) { out.attribute(null, "p", Integer.toHexString(id.provider.tag)); } + if (id.options != null) { + out.attribute(null, "min_width", Integer.toHexString(id.options.getInt( + AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH))); + out.attribute(null, "min_height", Integer.toHexString(id.options.getInt( + AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT))); + out.attribute(null, "max_width", Integer.toHexString(id.options.getInt( + AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH))); + out.attribute(null, "max_height", Integer.toHexString(id.options.getInt( + AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT))); + out.attribute(null, "host_category", Integer.toHexString(id.options.getInt( + AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY))); + } out.endTag(null, "g"); } @@ -1475,6 +1542,7 @@ class AppWidgetServiceImpl { } } + @SuppressWarnings("unused") void readStateFromFileLocked(FileInputStream stream) { boolean success = false; try { @@ -1494,11 +1562,12 @@ class AppWidgetServiceImpl { String pkg = parser.getAttributeValue(null, "pkg"); String cl = parser.getAttributeValue(null, "cl"); - final PackageManager packageManager = mContext.getPackageManager(); + final IPackageManager packageManager = AppGlobals.getPackageManager(); try { - packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); - } catch (PackageManager.NameNotFoundException e) { - String[] pkgs = packageManager + packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0, + UserHandle.getCallingUserId()); + } catch (RemoteException e) { + String[] pkgs = mContext.getPackageManager() .currentToCanonicalPackageNames(new String[] { pkg }); pkg = pkgs[0]; } @@ -1547,6 +1616,34 @@ class AppWidgetServiceImpl { mNextAppWidgetId = id.appWidgetId + 1; } + Bundle options = new Bundle(); + String minWidthString = parser.getAttributeValue(null, "min_width"); + if (minWidthString != null) { + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, + Integer.parseInt(minWidthString, 16)); + } + String minHeightString = parser.getAttributeValue(null, "min_height"); + if (minWidthString != null) { + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, + Integer.parseInt(minHeightString, 16)); + } + String maxWidthString = parser.getAttributeValue(null, "max_height"); + if (minWidthString != null) { + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, + Integer.parseInt(maxWidthString, 16)); + } + String maxHeightString = parser.getAttributeValue(null, "max_height"); + if (minWidthString != null) { + options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, + Integer.parseInt(maxHeightString, 16)); + } + String categoryString = parser.getAttributeValue(null, "host_category"); + if (minWidthString != null) { + options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, + Integer.parseInt(categoryString, 16)); + } + id.options = options; + String providerString = parser.getAttributeValue(null, "p"); if (providerString != null) { // there's no provider if it hasn't been bound yet. @@ -1613,11 +1710,11 @@ class AppWidgetServiceImpl { } static File getSettingsFile(int userId) { - return new File("/data/system/users/" + userId + "/" + SETTINGS_FILENAME); + return new File(Environment.getUserSystemDirectory(userId), SETTINGS_FILENAME); } AtomicFile savedStateFile() { - File dir = new File("/data/system/users/" + mUserId); + File dir = Environment.getUserSystemDirectory(mUserId); File settingsFile = getSettingsFile(mUserId); if (!settingsFile.exists() && mUserId == 0) { if (!dir.exists()) { @@ -1642,7 +1739,8 @@ class AppWidgetServiceImpl { getSettingsFile(mUserId).delete(); } - void addProvidersForPackageLocked(String pkgName) { + boolean addProvidersForPackageLocked(String pkgName) { + boolean providersAdded = false; Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.setPackage(pkgName); List<ResolveInfo> broadcastReceivers; @@ -1652,7 +1750,7 @@ class AppWidgetServiceImpl { PackageManager.GET_META_DATA, mUserId); } catch (RemoteException re) { // Shouldn't happen, local call - return; + return false; } final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); for (int i = 0; i < N; i++) { @@ -1663,11 +1761,21 @@ class AppWidgetServiceImpl { } if (pkgName.equals(ai.packageName)) { addProviderLocked(ri); + providersAdded = true; } } + + return providersAdded; } - void updateProvidersForPackageLocked(String pkgName) { + /** + * Updates all providers with the specified package names, and records any providers that were + * pruned. + * + * @return whether any providers were updated + */ + boolean updateProvidersForPackageLocked(String pkgName, Set<ComponentName> removedProviders) { + boolean providersUpdated = false; HashSet<String> keep = new HashSet<String>(); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.setPackage(pkgName); @@ -1678,7 +1786,7 @@ class AppWidgetServiceImpl { PackageManager.GET_META_DATA, mUserId); } catch (RemoteException re) { // Shouldn't happen, local call - return; + return false; } // add the missing ones and collect which ones to keep @@ -1695,6 +1803,7 @@ class AppWidgetServiceImpl { if (p == null) { if (addProviderLocked(ri)) { keep.add(ai.name); + providersUpdated = true; } } else { Provider parsed = parseProviderInfoXml(component, ri); @@ -1729,6 +1838,7 @@ class AppWidgetServiceImpl { } // Now that we've told the host, push out an update. sendUpdateIntentLocked(p, appWidgetIds); + providersUpdated = true; } } } @@ -1741,17 +1851,25 @@ class AppWidgetServiceImpl { Provider p = mInstalledProviders.get(i); if (pkgName.equals(p.info.provider.getPackageName()) && !keep.contains(p.info.provider.getClassName())) { + if (removedProviders != null) { + removedProviders.add(p.info.provider); + } removeProviderLocked(i, p); + providersUpdated = true; } } + + return providersUpdated; } - void removeProvidersForPackageLocked(String pkgName) { + boolean removeProvidersForPackageLocked(String pkgName) { + boolean providersRemoved = false; int N = mInstalledProviders.size(); for (int i = N - 1; i >= 0; i--) { Provider p = mInstalledProviders.get(i); if (pkgName.equals(p.info.provider.getPackageName())) { removeProviderLocked(i, p); + providersRemoved = true; } } @@ -1766,5 +1884,24 @@ class AppWidgetServiceImpl { deleteHostLocked(host); } } + + return providersRemoved; + } + + void notifyHostsForProvidersChangedLocked() { + final int N = mHosts.size(); + for (int i = N - 1; i >= 0; i--) { + Host host = mHosts.get(i); + try { + if (host.callbacks != null) { + host.callbacks.providersChanged(); + } + } catch (RemoteException ex) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this + // instance. + host.callbacks = null; + } + } } } diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 2167c49..5e2b425 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -65,7 +65,9 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.os.WorkSource; +import android.os.Environment.UserEnvironment; import android.os.storage.IMountService; import android.provider.Settings; import android.util.EventLog; @@ -1662,8 +1664,7 @@ class BackupManagerService extends IBackupManager.Stub { synchronized(mClearDataLock) { mClearingData = true; try { - mActivityManager.clearApplicationUserData(packageName, observer, - Binder.getOrigCallingUser()); + mActivityManager.clearApplicationUserData(packageName, observer, 0); } catch (RemoteException e) { // can't happen because the activity manager is in this process } @@ -2720,9 +2721,13 @@ class BackupManagerService extends IBackupManager.Stub { FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null, apkDir, appSourceDir, output); + // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM + // doesn't have access to external storage. + // Save associated .obb content if it exists and we did save the apk // check for .obb and save those too - final File obbDir = Environment.getExternalStorageAppObbDirectory(pkg.packageName); + final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER); + final File obbDir = userEnv.getExternalStorageAppObbDirectory(pkg.packageName); if (obbDir != null) { if (MORE_DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); File[] obbFiles = obbDir.listFiles(); @@ -4401,6 +4406,18 @@ class BackupManagerService extends IBackupManager.Stub { return; } + if (packageInfo.applicationInfo.backupAgentName == null + || "".equals(packageInfo.applicationInfo.backupAgentName)) { + if (DEBUG) { + Slog.i(TAG, "Data exists for package " + packageName + + " but app has no agent; skipping"); + } + EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName, + "Package has no agent"); + executeNextState(RestoreState.RUNNING_QUEUE); + return; + } + if (metaInfo.versionCode > packageInfo.versionCode) { // Data is from a "newer" version of the app than we have currently // installed. If the app has not declared that it is prepared to @@ -4845,6 +4862,18 @@ class BackupManagerService extends IBackupManager.Stub { // ----- IBackupManager binder interface ----- public void dataChanged(final String packageName) { + final int callingUserHandle = UserHandle.getCallingUserId(); + if (callingUserHandle != UserHandle.USER_OWNER) { + // App is running under a non-owner user profile. For now, we do not back + // up data from secondary user profiles. + // TODO: backups for all user profiles. + if (MORE_DEBUG) { + Slog.v(TAG, "dataChanged(" + packageName + ") ignored because it's user " + + callingUserHandle); + } + return; + } + final HashSet<String> targets = dataChangedTargets(packageName); if (targets == null) { Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'" @@ -4937,6 +4966,11 @@ class BackupManagerService extends IBackupManager.Stub { boolean doAllApps, boolean includeSystem, String[] pkgList) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup"); + final int callingUserHandle = UserHandle.getCallingUserId(); + if (callingUserHandle != UserHandle.USER_OWNER) { + throw new IllegalStateException("Backup supported only for the device owner"); + } + // Validate if (!doAllApps) { if (!includeShared) { @@ -5001,6 +5035,11 @@ class BackupManagerService extends IBackupManager.Stub { public void fullRestore(ParcelFileDescriptor fd) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullRestore"); + final int callingUserHandle = UserHandle.getCallingUserId(); + if (callingUserHandle != UserHandle.USER_OWNER) { + throw new IllegalStateException("Restore supported only for the device owner"); + } + long oldId = Binder.clearCallingIdentity(); try { diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index ab9ae69..fe8529b 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -33,6 +33,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UEventObserver; +import android.os.UserHandle; import android.provider.Settings; import android.util.EventLog; import android.util.Slog; @@ -68,7 +69,7 @@ import java.util.Arrays; * a degree Centigrade</p> * <p>"technology" - String, the type of battery installed, e.g. "Li-ion"</p> */ -class BatteryService extends Binder { +public class BatteryService extends Binder { private static final String TAG = BatteryService.class.getSimpleName(); private static final boolean LOCAL_LOGV = false; @@ -93,6 +94,7 @@ class BatteryService extends Binder { private boolean mAcOnline; private boolean mUsbOnline; + private boolean mWirelessOnline; private int mBatteryStatus; private int mBatteryHealth; private boolean mBatteryPresent; @@ -114,6 +116,7 @@ class BatteryService extends Binder { private int mLowBatteryWarningLevel; private int mLowBatteryCloseWarningLevel; + private int mShutdownBatteryTemperature; private int mPlugType; private int mLastPlugType = -1; // Extra state so we can detect first run @@ -136,6 +139,8 @@ class BatteryService extends Binder { com.android.internal.R.integer.config_lowBatteryWarningLevel); mLowBatteryCloseWarningLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryCloseWarningLevel); + mShutdownBatteryTemperature = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shutdownBatteryTemperature); mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply"); @@ -148,12 +153,13 @@ class BatteryService extends Binder { update(); } - final boolean isPowered() { + public final boolean isPowered() { // assume we are powered if battery state is unknown so the "stay on while plugged in" option will work. - return (mAcOnline || mUsbOnline || mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN); + return (mAcOnline || mUsbOnline || mWirelessOnline + || mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN); } - final boolean isPowered(int plugTypeSet) { + public final boolean isPowered(int plugTypeSet) { // assume we are powered if battery state is unknown so // the "stay on while plugged in" option will work. if (mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { @@ -169,10 +175,13 @@ class BatteryService extends Binder { if (mUsbOnline) { plugTypeBit |= BatteryManager.BATTERY_PLUGGED_USB; } + if (mWirelessOnline) { + plugTypeBit |= BatteryManager.BATTERY_PLUGGED_WIRELESS; + } return (plugTypeSet & plugTypeBit) != 0; } - final int getPlugType() { + public final int getPlugType() { return mPlugType; } @@ -195,10 +204,15 @@ class BatteryService extends Binder { }; // returns battery level as a percentage - final int getBatteryLevel() { + public final int getBatteryLevel() { return mBatteryLevel; } + // true if battery level is below the first warning threshold + public final boolean isBatteryLow() { + return mBatteryPresent && mBatteryLevel <= mLowBatteryWarningLevel; + } + void systemReady() { // check our power situation now that it is safe to display the shutdown dialog. shutdownIfNoPower(); @@ -217,9 +231,11 @@ class BatteryService extends Binder { } private final void shutdownIfOverTemp() { - // shut down gracefully if temperature is too high (> 68.0C) - // wait until the system has booted before attempting to display the shutdown dialog. - if (mBatteryTemperature > 680 && ActivityManagerNative.isSystemReady()) { + // shut down gracefully if temperature is too high (> 68.0C by default) + // wait until the system has booted before attempting to display the + // shutdown dialog. + if (mBatteryTemperature > mShutdownBatteryTemperature + && ActivityManagerNative.isSystemReady()) { Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN); intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -243,10 +259,12 @@ class BatteryService extends Binder { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; } else if (mUsbOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_USB; + } else if (mWirelessOnline) { + mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS; } else { mPlugType = BATTERY_PLUGGED_NONE; } - + // Let the battery stats keep track of the current level. try { mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth, @@ -255,7 +273,7 @@ class BatteryService extends Binder { } catch (RemoteException e) { // Should never happen. } - + shutdownIfNoPower(); shutdownIfOverTemp(); @@ -333,21 +351,21 @@ class BatteryService extends Binder { statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); if (mPlugType != 0 && mLastPlugType == 0) { statusIntent.setAction(Intent.ACTION_POWER_CONNECTED); - mContext.sendBroadcast(statusIntent); + mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); } else if (mPlugType == 0 && mLastPlugType != 0) { statusIntent.setAction(Intent.ACTION_POWER_DISCONNECTED); - mContext.sendBroadcast(statusIntent); + mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); } if (sendBatteryLow) { mSentLowBatteryBroadcast = true; statusIntent.setAction(Intent.ACTION_BATTERY_LOW); - mContext.sendBroadcast(statusIntent); + mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) { mSentLowBatteryBroadcast = false; statusIntent.setAction(Intent.ACTION_BATTERY_OKAY); - mContext.sendBroadcast(statusIntent); + mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL); } // Update the battery LED @@ -398,10 +416,11 @@ class BatteryService extends Binder { " temperature: " + mBatteryTemperature + " technology: " + mBatteryTechnology + " AC powered:" + mAcOnline + " USB powered:" + mUsbOnline + + " Wireless powered:" + mWirelessOnline + " icon:" + icon + " invalid charger:" + mInvalidCharger); } - ActivityManagerNative.broadcastStickyIntent(intent, null); + ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL); } private final void logBatteryStats() { @@ -503,6 +522,7 @@ class BatteryService extends Binder { pw.println("Current Battery Service state:"); pw.println(" AC powered: " + mAcOnline); pw.println(" USB powered: " + mUsbOnline); + pw.println(" Wireless powered: " + mWirelessOnline); pw.println(" status: " + mBatteryStatus); pw.println(" health: " + mBatteryHealth); pw.println(" present: " + mBatteryPresent); @@ -523,6 +543,8 @@ class BatteryService extends Binder { mAcOnline = Integer.parseInt(value) != 0; } else if ("usb".equals(key)) { mUsbOnline = Integer.parseInt(value) != 0; + } else if ("wireless".equals(key)) { + mWirelessOnline = Integer.parseInt(value) != 0; } else if ("status".equals(key)) { mBatteryStatus = Integer.parseInt(value); } else if ("level".equals(key)) { @@ -603,4 +625,3 @@ class BatteryService extends Binder { } } } - diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java new file mode 100755 index 0000000..e68686d --- /dev/null +++ b/services/java/com/android/server/BluetoothManagerService.java @@ -0,0 +1,773 @@ +/* + * Copyright (C) 2012 Google Inc. + */ + +package com.android.server; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.IBluetooth; +import android.bluetooth.IBluetoothCallback; +import android.bluetooth.IBluetoothManager; +import android.bluetooth.IBluetoothManagerCallback; +import android.bluetooth.IBluetoothStateChangeCallback; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.Binder; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; +import java.util.List; +import java.util.ArrayList; +class BluetoothManagerService extends IBluetoothManager.Stub { + private static final String TAG = "BluetoothManagerService"; + private static final boolean DBG = true; + + private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; + private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + private static final String ACTION_SERVICE_STATE_CHANGED="com.android.bluetooth.btservice.action.STATE_CHANGED"; + private static final String EXTRA_ACTION="action"; + private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS="bluetooth_address"; + private static final String SECURE_SETTINGS_BLUETOOTH_NAME="bluetooth_name"; + private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind + private static final int TIMEOUT_SAVE_MS = 500; //Maximum msec to wait for a save + //Maximum msec to wait for service restart + private static final int SERVICE_RESTART_TIME_MS = 200; + + private static final int MESSAGE_ENABLE = 1; + private static final int MESSAGE_DISABLE = 2; + private static final int MESSAGE_REGISTER_ADAPTER = 20; + private static final int MESSAGE_UNREGISTER_ADAPTER = 21; + private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30; + private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31; + private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40; + private static final int MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED = 41; + private static final int MESSAGE_RESTART_BLUETOOTH_SERVICE = 42; + private static final int MESSAGE_BLUETOOTH_STATE_CHANGE=60; + private static final int MESSAGE_TIMEOUT_BIND =100; + private static final int MESSAGE_TIMEOUT_UNBIND =101; + private static final int MESSAGE_GET_NAME_AND_ADDRESS=200; + private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201; + private static final int MAX_SAVE_RETRIES=3; + + private final Context mContext; + + // Locks are not provided for mName and mAddress. + // They are accessed in handler or broadcast receiver, same thread context. + private String mAddress; + private String mName; + private final ContentResolver mContentResolver; + private final RemoteCallbackList<IBluetoothManagerCallback> mCallbacks; + private final RemoteCallbackList<IBluetoothStateChangeCallback> mStateChangeCallbacks; + private IBluetooth mBluetooth; + private boolean mBinding; + private boolean mUnbinding; + private boolean mQuietEnable = false; + + private void registerForAirplaneMode(IntentFilter filter) { + final ContentResolver resolver = mContext.getContentResolver(); + final String airplaneModeRadios = Settings.Global.getString(resolver, + Settings.Global.AIRPLANE_MODE_RADIOS); + final String toggleableRadios = Settings.Global.getString(resolver, + Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + boolean mIsAirplaneSensitive = airplaneModeRadios == null ? true : + airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH); + if (mIsAirplaneSensitive) { + filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + } + } + + private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() { + @Override + public void onBluetoothStateChange(int prevState, int newState) throws RemoteException { + Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE,prevState,newState); + mHandler.sendMessage(msg); + } + }; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) { + String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME); + if (DBG) Log.d(TAG, "Bluetooth Adapter name changed to " + newName); + if (newName != null) { + storeNameAndAddress(newName, null); + } + } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { + if (isAirplaneModeOn()) { + // disable without persisting the setting + handleDisable(false); + } else { + if (isBluetoothPersistedStateOn()) { + // enable without persisting the setting + handleEnable(false, false); + } + } + } + } + }; + + BluetoothManagerService(Context context) { + mContext = context; + mBluetooth = null; + mBinding = false; + mUnbinding = false; + mAddress = null; + mName = null; + mContentResolver = context.getContentResolver(); + mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>(); + mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>(); + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); + registerForAirplaneMode(filter); + mContext.registerReceiver(mReceiver, filter); + boolean airplaneModeOn = isAirplaneModeOn(); + boolean bluetoothOn = isBluetoothPersistedStateOn(); + loadStoredNameAndAddress(); + if (DBG) Log.d(TAG, "airplaneModeOn: " + airplaneModeOn + " bluetoothOn: " + bluetoothOn); + if (bluetoothOn) { + //Enable + if (DBG) Log.d(TAG, "Auto-enabling Bluetooth."); + enable(); + } else if (!isNameAndAddressSet()) { + //Sync the Bluetooth name and address from the Bluetooth Adapter + if (DBG) Log.d(TAG,"Retrieving Bluetooth Adapter name and address..."); + getNameAndAddress(); + } + } + + /** + * Returns true if airplane mode is currently on + */ + private final boolean isAirplaneModeOn() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + } + + /** + * Returns true if the Bluetooth saved state is "on" + */ + private final boolean isBluetoothPersistedStateOn() { + return Settings.Secure.getInt(mContentResolver, + Settings.Secure.BLUETOOTH_ON, 0) ==1; + } + + /** + * Save the Bluetooth on/off state + * + */ + private void persistBluetoothSetting(boolean setOn) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.BLUETOOTH_ON, + setOn ? 1 : 0); + } + + /** + * Returns true if the Bluetooth Adapter's name and address is + * locally cached + * @return + */ + private boolean isNameAndAddressSet() { + return mName !=null && mAddress!= null && mName.length()>0 && mAddress.length()>0; + } + + /** + * Retrieve the Bluetooth Adapter's name and address and save it in + * in the local cache + */ + private void loadStoredNameAndAddress() { + if (DBG) Log.d(TAG, "Loading stored name and address"); + mName = Settings.Secure.getString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME); + mAddress = Settings.Secure.getString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS); + if (mName == null || mAddress == null) { + if (DBG) Log.d(TAG, "Name or address not cached..."); + } + } + + /** + * Save the Bluetooth name and address in the persistent store. + * Only non-null values will be saved. + * @param name + * @param address + */ + private void storeNameAndAddress(String name, String address) { + if (name != null) { + Settings.Secure.putString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, name); + mName = name; + if (DBG) Log.d(TAG,"Stored Bluetooth name: " + + Settings.Secure.getString(mContentResolver,SECURE_SETTINGS_BLUETOOTH_NAME)); + } + + if (address != null) { + Settings.Secure.putString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, address); + mAddress=address; + if (DBG) Log.d(TAG,"Stored Bluetoothaddress: " + + Settings.Secure.getString(mContentResolver,SECURE_SETTINGS_BLUETOOTH_ADDRESS)); + } + } + + public IBluetooth registerAdapter(IBluetoothManagerCallback callback){ + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, + "Need BLUETOOTH permission"); + Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER); + msg.obj = callback; + mHandler.sendMessage(msg); + synchronized(mConnection) { + return mBluetooth; + } + } + + public void unregisterAdapter(IBluetoothManagerCallback callback) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, + "Need BLUETOOTH permission"); + Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_ADAPTER); + msg.obj = callback; + mHandler.sendMessage(msg); + } + + public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, + "Need BLUETOOTH permission"); + Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_STATE_CHANGE_CALLBACK); + msg.obj = callback; + mHandler.sendMessage(msg); + } + + public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, + "Need BLUETOOTH permission"); + Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK); + msg.obj = callback; + mHandler.sendMessage(msg); + } + + public boolean isEnabled() { + synchronized(mConnection) { + try { + return (mBluetooth != null && mBluetooth.isEnabled()); + } catch (RemoteException e) { + Log.e(TAG, "isEnabled()", e); + } + } + return false; + } + + public void getNameAndAddress() { + if (DBG) { + Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth + + " mBinding = " + mBinding); + } + synchronized(mConnection) { + if (mBinding) return; + if (mConnection == null) mBinding = true; + } + Message msg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); + mHandler.sendMessage(msg); + } + public boolean enableNoAutoConnect() + { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH ADMIN permission"); + if (DBG) { + Log.d(TAG,"enableNoAutoConnect(): mBluetooth =" + mBluetooth + + " mBinding = " + mBinding); + } + if (Binder.getCallingUid() != android.os.Process.NFC_UID) { + throw new SecurityException("no permission to enable Bluetooth quietly"); + } + synchronized(mConnection) { + if (mBinding) { + Log.w(TAG,"enableNoAutoConnect(): binding in progress. Returning.."); + return true; + } + if (mConnection == null) mBinding = true; + } + + Message msg = mHandler.obtainMessage(MESSAGE_ENABLE); + msg.arg1=0; //No persist + msg.arg2=1; //Quiet mode + mHandler.sendMessage(msg); + return true; + + } + public boolean enable() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH ADMIN permission"); + if (DBG) { + Log.d(TAG,"enable(): mBluetooth =" + mBluetooth + + " mBinding = " + mBinding); + } + + synchronized(mConnection) { + if (mBinding) { + Log.w(TAG,"enable(): binding in progress. Returning.."); + return true; + } + if (mConnection == null) mBinding = true; + } + + Message msg = mHandler.obtainMessage(MESSAGE_ENABLE); + msg.arg1=1; //persist + msg.arg2=0; //No Quiet Mode + mHandler.sendMessage(msg); + return true; + } + + public boolean disable(boolean persist) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH ADMIN permissicacheNameAndAddresson"); + if (DBG) { + Log.d(TAG,"disable(): mBluetooth = " + mBluetooth + + " mBinding = " + mBinding); + } + + synchronized(mConnection) { + if (mBluetooth == null) return false; + } + Message msg = mHandler.obtainMessage(MESSAGE_DISABLE); + msg.arg1=(persist?1:0); + mHandler.sendMessage(msg); + return true; + } + + public void unbindAndFinish() { + if (DBG) { + Log.d(TAG,"unbindAndFinish(): " + mBluetooth + + " mBinding = " + mBinding); + } + + synchronized (mConnection) { + if (mUnbinding) return; + mUnbinding = true; + if (mConnection != null) { + if (!mConnection.isGetNameAddressOnly()) { + //Unregister callback object + try { + mBluetooth.unregisterCallback(mBluetoothCallback); + } catch (RemoteException re) { + Log.e(TAG, "Unable to register BluetoothCallback",re); + } + } + if (DBG) Log.d(TAG, "Sending unbind request."); + mBluetooth = null; + //Unbind + mContext.unbindService(mConnection); + mUnbinding = false; + } else { + mUnbinding=false; + } + } + } + + private void sendBluetoothStateCallback(boolean isUp) { + int n = mStateChangeCallbacks.beginBroadcast(); + if (DBG) Log.d(TAG,"Broadcasting onBluetoothStateChange("+isUp+") to " + n + " receivers."); + for (int i=0; i <n;i++) { + try { + mStateChangeCallbacks.getBroadcastItem(i).onBluetoothStateChange(isUp); + } catch (RemoteException e) { + Log.e(TAG, "Unable to call onBluetoothStateChange() on callback #" + i , e); + } + } + mStateChangeCallbacks.finishBroadcast(); + } + + /** + * Inform BluetoothAdapter instances that Adapter service is down + */ + private void sendBluetoothServiceDownCallback() { + if (!mConnection.isGetNameAddressOnly()) { + if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks"); + int n = mCallbacks.beginBroadcast(); + Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers."); + for (int i=0; i <n;i++) { + try { + mCallbacks.getBroadcastItem(i).onBluetoothServiceDown(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e); + } + } + mCallbacks.finishBroadcast(); + } + } + public String getAddress() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH ADMIN permission"); + synchronized(mConnection) { + if (mBluetooth != null) { + try { + return mBluetooth.getAddress(); + } catch (RemoteException e) { + Log.e(TAG, "getAddress(): Unable to retrieve address remotely..Returning cached address",e); + } + } + } + // mAddress is accessed from outside. + // It is alright without a lock. Here, bluetooth is off, no other thread is + // changing mAddress + return mAddress; + } + + public String getName() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH ADMIN permission"); + synchronized(mConnection) { + if (mBluetooth != null) { + try { + return mBluetooth.getName(); + } catch (RemoteException e) { + Log.e(TAG, "getName(): Unable to retrieve name remotely..Returning cached name",e); + } + } + } + // mName is accessed from outside. + // It alright without a lock. Here, bluetooth is off, no other thread is + // changing mName + return mName; + } + + private class BluetoothServiceConnection implements ServiceConnection { + + private boolean mGetNameAddressOnly; + + public void setGetNameAddressOnly(boolean getOnly) { + mGetNameAddressOnly = getOnly; + } + + public boolean isGetNameAddressOnly() { + return mGetNameAddressOnly; + } + + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "BluetoothServiceConnection: connected to AdapterService"); + Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED); + msg.obj = service; + mHandler.sendMessage(msg); + } + + public void onServiceDisconnected(ComponentName className) { + // Called if we unexpected disconnected. + if (DBG) Log.d(TAG, "BluetoothServiceConnection: disconnected from AdapterService"); + Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED); + mHandler.sendMessage(msg); + } + } + + private BluetoothServiceConnection mConnection = new BluetoothServiceConnection(); + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (DBG) Log.d (TAG, "Message: " + msg.what); + switch (msg.what) { + case MESSAGE_GET_NAME_AND_ADDRESS: { + if (DBG) Log.d(TAG,"MESSAGE_GET_NAME_AND_ADDRESS"); + synchronized(mConnection) { + //Start bind request + if (mBluetooth == null) { + if (DBG) Log.d(TAG, "Binding to service to get name and address"); + mConnection.setGetNameAddressOnly(true); + //Start bind timeout and bind + Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND); + mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS); + Intent i = new Intent(IBluetooth.class.getName()); + if (!mContext.bindService(i, mConnection, + Context.BIND_AUTO_CREATE)) { + mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); + Log.e(TAG, "fail to bind to: " + IBluetooth.class.getName()); + } + } + else { + Message saveMsg= mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS); + mHandler.sendMessage(saveMsg); + } + } + break; + } + case MESSAGE_SAVE_NAME_AND_ADDRESS: { + if (DBG) Log.d(TAG,"MESSAGE_SAVE_NAME_AND_ADDRESS"); + synchronized(mConnection) { + if (mBluetooth != null) { + String name = null; + String address = null; + try { + name = mBluetooth.getName(); + address = mBluetooth.getAddress(); + } catch (RemoteException re) { + Log.e(TAG,"",re); + } + + if (name != null && address != null) { + storeNameAndAddress(name,address); + sendBluetoothServiceDownCallback(); + unbindAndFinish(); + } else { + if (msg.arg1 < MAX_SAVE_RETRIES) { + Message retryMsg = mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS); + retryMsg.arg1= 1+msg.arg1; + if (DBG) Log.d(TAG,"Retrying name/address remote retrieval and save.....Retry count =" + retryMsg.arg1); + mHandler.sendMessageDelayed(retryMsg, TIMEOUT_SAVE_MS); + } else { + Log.w(TAG,"Maximum name/address remote retrieval retry exceeded"); + sendBluetoothServiceDownCallback(); + unbindAndFinish(); + } + } + } + } + break; + } + case MESSAGE_ENABLE: + if (DBG) { + Log.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth); + } + + handleEnable(msg.arg1 == 1, msg.arg2 ==1); + break; + + case MESSAGE_DISABLE: + handleDisable(msg.arg1 == 1); + break; + + case MESSAGE_REGISTER_ADAPTER: + { + IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; + boolean added = mCallbacks.register(callback); + Log.d(TAG,"Added callback: " + (callback == null? "null": callback) +":" +added ); + } + break; + case MESSAGE_UNREGISTER_ADAPTER: + { + IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; + boolean removed = mCallbacks.unregister(callback); + Log.d(TAG,"Removed callback: " + (callback == null? "null": callback) +":" + removed); + break; + } + case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: + { + IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; + mStateChangeCallbacks.register(callback); + break; + } + case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK: + { + IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; + mStateChangeCallbacks.unregister(callback); + break; + } + case MESSAGE_BLUETOOTH_SERVICE_CONNECTED: + { + if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED"); + + //Remove timeout + mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); + + IBinder service = (IBinder) msg.obj; + synchronized(mConnection) { + mBinding = false; + mBluetooth = IBluetooth.Stub.asInterface(service); + + if (mConnection.isGetNameAddressOnly()) { + //Request GET NAME AND ADDRESS + Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); + mHandler.sendMessage(getMsg); + return; + } + + //Register callback object + try { + mBluetooth.registerCallback(mBluetoothCallback); + } catch (RemoteException re) { + Log.e(TAG, "Unable to register BluetoothCallback",re); + } + + //Inform BluetoothAdapter instances that service is up + int n = mCallbacks.beginBroadcast(); + Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers."); + for (int i=0; i <n;i++) { + try { + mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth); + } catch (RemoteException e) { + Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e); + } + } + mCallbacks.finishBroadcast(); + + //Do enable request + try { + if (mQuietEnable == false) { + if(!mBluetooth.enable()) { + Log.e(TAG,"IBluetooth.enable() returned false"); + } + } + else + { + if(!mBluetooth.enableNoAutoConnect()) { + Log.e(TAG,"IBluetooth.enableNoAutoConnect() returned false"); + } + } + } catch (RemoteException e) { + Log.e(TAG,"Unable to call enable()",e); + } + } + break; + } + case MESSAGE_TIMEOUT_BIND: { + Log.e(TAG, "MESSAGE_TIMEOUT_BIND"); + synchronized(mConnection) { + mBinding = false; + } + break; + } + case MESSAGE_BLUETOOTH_STATE_CHANGE: + { + int prevState = msg.arg1; + int newState = msg.arg2; + if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: prevState = " + prevState + ", newState=" + newState); + if (prevState != newState) { + //Notify all proxy objects first of adapter state change + if (newState == BluetoothAdapter.STATE_ON || newState == BluetoothAdapter.STATE_OFF) { + boolean isUp = (newState==BluetoothAdapter.STATE_ON); + sendBluetoothStateCallback(isUp); + + //If Bluetooth is off, send service down event to proxy objects, and unbind + if (!isUp) { + sendBluetoothServiceDownCallback(); + unbindAndFinish(); + } + } + + //Send broadcast message to everyone else + Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); + intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + if (DBG) Log.d(TAG,"Bluetooth State Change Intent: " + prevState + " -> " + newState); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + BLUETOOTH_PERM); + } + break; + } + case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: + { + if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED"); + sendBluetoothServiceDownCallback(); + + // Send BT state broadcast to update + // the BT icon correctly + Message stateChangeMsg = mHandler.obtainMessage( + MESSAGE_BLUETOOTH_STATE_CHANGE); + stateChangeMsg.arg1 = BluetoothAdapter.STATE_ON; + stateChangeMsg.arg2 = + BluetoothAdapter.STATE_TURNING_OFF; + mHandler.sendMessage(stateChangeMsg); + synchronized(mConnection) { + mBluetooth = null; + } + // Send a Bluetooth Restart message + Message restartMsg = mHandler.obtainMessage( + MESSAGE_RESTART_BLUETOOTH_SERVICE); + mHandler.sendMessageDelayed(restartMsg, + SERVICE_RESTART_TIME_MS); + break; + } + case MESSAGE_RESTART_BLUETOOTH_SERVICE: + { + Log.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE:" + +" Restart IBluetooth service"); + /* Enable without persisting the setting as + it doesnt change when IBluetooth + service restarts */ + handleEnable(false, mQuietEnable); + break; + } + + case MESSAGE_TIMEOUT_UNBIND: + { + Log.e(TAG, "MESSAGE_TIMEOUT_UNBIND"); + synchronized(mConnection) { + mUnbinding = false; + } + break; + } + } + } + }; + + private void handleEnable(boolean persist, boolean quietMode) { + if (persist) { + persistBluetoothSetting(true); + } + + mQuietEnable = quietMode; + + synchronized(mConnection) { + if (mBluetooth == null) { + //Start bind timeout and bind + Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND); + mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS); + mConnection.setGetNameAddressOnly(false); + Intent i = new Intent(IBluetooth.class.getName()); + if (!mContext.bindService(i, mConnection,Context.BIND_AUTO_CREATE)) { + mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); + Log.e(TAG, "Fail to bind to: " + IBluetooth.class.getName()); + } + } else { + //Check if name and address is loaded if not get it first. + if (!isNameAndAddressSet()) { + try { + if (DBG) Log.d(TAG,"Getting and storing Bluetooth name and address prior to enable."); + storeNameAndAddress(mBluetooth.getName(),mBluetooth.getAddress()); + } catch (RemoteException e) {Log.e(TAG, "", e);}; + } + + //Enable bluetooth + try { + if (!mQuietEnable) { + if(!mBluetooth.enable()) { + Log.e(TAG,"IBluetooth.enable() returned false"); + } + } + else { + if(!mBluetooth.enableNoAutoConnect()) { + Log.e(TAG,"IBluetooth.enableNoAutoConnect() returned false"); + } + } + } catch (RemoteException e) { + Log.e(TAG,"Unable to call enable()",e); + } + } + } + } + + private void handleDisable(boolean persist) { + synchronized(mConnection) { + if (mBluetooth != null ) { + if (persist) { + persistBluetoothSetting(false); + } + mConnection.setGetNameAddressOnly(false); + if (DBG) Log.d(TAG,"Sending off request."); + + try { + if(!mBluetooth.disable()) { + Log.e(TAG,"IBluetooth.disable() returned false"); + } + } catch (RemoteException e) { + Log.e(TAG,"Unable to call disable()",e); + } + } + } + } +} diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java index 8a6a550..74ec6e2 100644 --- a/services/java/com/android/server/ClipboardService.java +++ b/services/java/com/android/server/ClipboardService.java @@ -17,6 +17,7 @@ package com.android.server; import android.app.ActivityManagerNative; +import android.app.AppGlobals; import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.ClipData; @@ -26,6 +27,7 @@ import android.content.IOnPrimaryClipChangedListener; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -36,7 +38,7 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.UserId; +import android.os.UserHandle; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -96,7 +98,7 @@ public class ClipboardService extends IClipboard.Stub { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_USER_REMOVED.equals(action)) { - removeClipboard(intent.getIntExtra(Intent.EXTRA_USERID, 0)); + removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); } } }, userFilter); @@ -115,12 +117,11 @@ public class ClipboardService extends IClipboard.Stub { } private PerUserClipboard getClipboard() { - return getClipboard(UserId.getCallingUserId()); + return getClipboard(UserHandle.getCallingUserId()); } private PerUserClipboard getClipboard(int userId) { synchronized (mClipboards) { - Slog.i(TAG, "Got clipboard for user=" + userId); PerUserClipboard puc = mClipboards.get(userId); if (puc == null) { puc = new PerUserClipboard(userId); @@ -255,15 +256,22 @@ public class ClipboardService extends IClipboard.Stub { } private final void addActiveOwnerLocked(int uid, String pkg) { - PackageInfo pi; + final IPackageManager pm = AppGlobals.getPackageManager(); + final int targetUserHandle = UserHandle.getCallingUserId(); + final long oldIdentity = Binder.clearCallingIdentity(); try { - pi = mPm.getPackageInfo(pkg, 0); - if (!UserId.isSameApp(pi.applicationInfo.uid, uid)) { + PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle); + if (pi == null) { + throw new IllegalArgumentException("Unknown package " + pkg); + } + if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) { throw new SecurityException("Calling uid " + uid + " does not own package " + pkg); } - } catch (NameNotFoundException e) { - throw new IllegalArgumentException("Unknown package " + pkg, e); + } catch (RemoteException e) { + // Can't happen; the package manager is in the same process + } finally { + Binder.restoreCallingIdentity(oldIdentity); } PerUserClipboard clipboard = getClipboard(); if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) { diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java index 9a25d2e..c316733 100644 --- a/services/java/com/android/server/CommonTimeManagementService.java +++ b/services/java/com/android/server/CommonTimeManagementService.java @@ -120,6 +120,8 @@ class CommonTimeManagementService extends Binder { reevaluateServiceState(); } public void limitReached(String limitName, String iface) { } + + public void interfaceClassDataActivityChanged(String label, boolean active) {} }; private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() { diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 86ada40..5c7a3ed 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -16,25 +16,38 @@ package com.android.server; +import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.MANAGE_NETWORK_POLICY; +import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; +import static android.net.ConnectivityManager.TYPE_DUMMY; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivityManager.TYPE_WIMAX; +import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import android.bluetooth.BluetoothTetheringDataTracker; +import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; +import android.net.CaptivePortalTracker; import android.net.ConnectivityManager; import android.net.DummyDataStateTracker; import android.net.EthernetDataTracker; import android.net.IConnectivityManager; +import android.net.INetworkManagementEventObserver; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; @@ -64,11 +77,15 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PowerManager; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings; +import android.security.Credentials; +import android.security.KeyStore; import android.text.TextUtils; import android.util.EventLog; import android.util.Slog; @@ -76,21 +93,24 @@ import android.util.SparseIntArray; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; +import com.android.server.net.BaseNetworkObserver; +import com.android.server.net.LockdownVpnTracker; import com.google.android.collect.Lists; import com.google.android.collect.Sets; + import dalvik.system.DexClassLoader; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.InvocationTargetException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -106,13 +126,15 @@ import java.util.List; * @hide */ public class ConnectivityService extends IConnectivityManager.Stub { + private static final String TAG = "ConnectivityService"; private static final boolean DBG = true; private static final boolean VDBG = false; - private static final String TAG = "ConnectivityService"; private static final boolean LOGD_RULES = false; + // TODO: create better separation between radio types and network types + // how long to wait before switching back to a radio's default network private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000; // system property that can override the above value @@ -126,7 +148,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Tethering mTethering; private boolean mTetheringConfigValid = false; + private KeyStore mKeyStore; + private Vpn mVpn; + private VpnCallback mVpnCallback = new VpnCallback(); + + private boolean mLockdownEnabled; + private LockdownVpnTracker mLockdownTracker; /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */ private Object mRulesLock = new Object(); @@ -142,6 +170,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private NetworkStateTracker mNetTrackers[]; + /* Handles captive portal check on a network */ + private CaptivePortalTracker mCaptivePortalTracker; + /** * The link properties that define the current links */ @@ -186,95 +217,83 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final boolean TO_DEFAULT_TABLE = true; private static final boolean TO_SECONDARY_TABLE = false; - // Share the event space with NetworkStateTracker (which can't see this - // internal class but sends us events). If you change these, change - // NetworkStateTracker.java too. - private static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1; - private static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100; - /** * used internally as a delayed event to make us switch back to the * default network */ - private static final int EVENT_RESTORE_DEFAULT_NETWORK = - MAX_NETWORK_STATE_TRACKER_EVENT + 1; + private static final int EVENT_RESTORE_DEFAULT_NETWORK = 1; /** * used internally to change our mobile data enabled flag */ - private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = - MAX_NETWORK_STATE_TRACKER_EVENT + 2; + private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2; /** * used internally to change our network preference setting * arg1 = networkType to prefer */ - private static final int EVENT_SET_NETWORK_PREFERENCE = - MAX_NETWORK_STATE_TRACKER_EVENT + 3; + private static final int EVENT_SET_NETWORK_PREFERENCE = 3; /** * used internally to synchronize inet condition reports * arg1 = networkType * arg2 = condition (0 bad, 100 good) */ - private static final int EVENT_INET_CONDITION_CHANGE = - MAX_NETWORK_STATE_TRACKER_EVENT + 4; + private static final int EVENT_INET_CONDITION_CHANGE = 4; /** * used internally to mark the end of inet condition hold periods * arg1 = networkType */ - private static final int EVENT_INET_CONDITION_HOLD_END = - MAX_NETWORK_STATE_TRACKER_EVENT + 5; + private static final int EVENT_INET_CONDITION_HOLD_END = 5; /** * used internally to set enable/disable cellular data * arg1 = ENBALED or DISABLED */ - private static final int EVENT_SET_MOBILE_DATA = - MAX_NETWORK_STATE_TRACKER_EVENT + 7; + private static final int EVENT_SET_MOBILE_DATA = 7; /** * used internally to clear a wakelock when transitioning * from one net to another */ - private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = - MAX_NETWORK_STATE_TRACKER_EVENT + 8; + private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8; /** * used internally to reload global proxy settings */ - private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = - MAX_NETWORK_STATE_TRACKER_EVENT + 9; + private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = 9; /** * used internally to set external dependency met/unmet * arg1 = ENABLED (met) or DISABLED (unmet) * arg2 = NetworkType */ - private static final int EVENT_SET_DEPENDENCY_MET = - MAX_NETWORK_STATE_TRACKER_EVENT + 10; + private static final int EVENT_SET_DEPENDENCY_MET = 10; /** * used internally to restore DNS properties back to the * default network */ - private static final int EVENT_RESTORE_DNS = - MAX_NETWORK_STATE_TRACKER_EVENT + 11; + private static final int EVENT_RESTORE_DNS = 11; /** * used internally to send a sticky broadcast delayed. */ - private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = - MAX_NETWORK_STATE_TRACKER_EVENT + 12; + private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 12; /** * Used internally to * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}. */ - private static final int EVENT_SET_POLICY_DATA_ENABLE = MAX_NETWORK_STATE_TRACKER_EVENT + 13; + private static final int EVENT_SET_POLICY_DATA_ENABLE = 13; - private Handler mHandler; + private static final int EVENT_VPN_STATE_CHANGED = 14; + + /** Handler used for internal events. */ + private InternalHandler mHandler; + /** Handler used for incoming {@link NetworkStateTracker} events. */ + private NetworkStateTrackerHandler mTrackerHandler; // list of DeathRecipients used to make sure features are turned off when // a process dies @@ -328,11 +347,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { public ConnectivityService(Context context, INetworkManagementService netd, INetworkStatsService statsService, INetworkPolicyManager policyManager) { + // Currently, omitting a NetworkFactory will create one internally + // TODO: create here when we have cleaner WiMAX support + this(context, netd, statsService, policyManager, null); + } + + public ConnectivityService(Context context, INetworkManagementService netManager, + INetworkStatsService statsService, INetworkPolicyManager policyManager, + NetworkFactory netFactory) { if (DBG) log("ConnectivityService starting up"); HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); handlerThread.start(); - mHandler = new MyHandler(handlerThread.getLooper()); + mHandler = new InternalHandler(handlerThread.getLooper()); + mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper()); + + if (netFactory == null) { + netFactory = new DefaultNetworkFactory(context, mTrackerHandler); + } // setup our unique device name if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { @@ -358,8 +390,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } mContext = checkNotNull(context, "missing Context"); - mNetd = checkNotNull(netd, "missing INetworkManagementService"); + mNetd = checkNotNull(netManager, "missing INetworkManagementService"); mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager"); + mKeyStore = KeyStore.getInstance(); try { mPolicyManager.registerListener(mPolicyListener); @@ -472,69 +505,38 @@ public class ConnectivityService extends IConnectivityManager.Stub { mTestMode = SystemProperties.get("cm.test.mode").equals("true") && SystemProperties.get("ro.build.type").equals("eng"); - /* - * Create the network state trackers for Wi-Fi and mobile - * data. Maybe this could be done with a factory class, - * but it's not clear that it's worth it, given that - * the number of different network types is not going - * to change very often. - */ - for (int netType : mPriorityList) { - switch (mNetConfigs[netType].radio) { - case ConnectivityManager.TYPE_WIFI: - mNetTrackers[netType] = new WifiStateTracker(netType, - mNetConfigs[netType].name); - mNetTrackers[netType].startMonitoring(context, mHandler); - break; - case ConnectivityManager.TYPE_MOBILE: - mNetTrackers[netType] = new MobileDataStateTracker(netType, - mNetConfigs[netType].name); - mNetTrackers[netType].startMonitoring(context, mHandler); - break; - case ConnectivityManager.TYPE_DUMMY: - mNetTrackers[netType] = new DummyDataStateTracker(netType, - mNetConfigs[netType].name); - mNetTrackers[netType].startMonitoring(context, mHandler); - break; - case ConnectivityManager.TYPE_BLUETOOTH: - mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance(); - mNetTrackers[netType].startMonitoring(context, mHandler); - break; - case ConnectivityManager.TYPE_WIMAX: - mNetTrackers[netType] = makeWimaxStateTracker(); - if (mNetTrackers[netType]!= null) { - mNetTrackers[netType].startMonitoring(context, mHandler); - } - break; - case ConnectivityManager.TYPE_ETHERNET: - mNetTrackers[netType] = EthernetDataTracker.getInstance(); - mNetTrackers[netType].startMonitoring(context, mHandler); - break; - default: - loge("Trying to create a DataStateTracker for an unknown radio type " + - mNetConfigs[netType].radio); + + // Create and start trackers for hard-coded networks + for (int targetNetworkType : mPriorityList) { + final NetworkConfig config = mNetConfigs[targetNetworkType]; + final NetworkStateTracker tracker; + try { + tracker = netFactory.createTracker(targetNetworkType, config); + mNetTrackers[targetNetworkType] = tracker; + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Problem creating " + getNetworkTypeName(targetNetworkType) + + " tracker: " + e); continue; } - mCurrentLinkProperties[netType] = null; - if (mNetTrackers[netType] != null && mNetConfigs[netType].isDefault()) { - mNetTrackers[netType].reconnect(); + + tracker.startMonitoring(context, mTrackerHandler); + if (config.isDefault()) { + tracker.reconnect(); } } - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b); - - mTethering = new Tethering(mContext, nmService, statsService, this, mHandler.getLooper()); + mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper()); mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 || mTethering.getTetherableWifiRegexs().length != 0 || mTethering.getTetherableBluetoothRegexs().length != 0) && mTethering.getUpstreamIfaceTypes().length != 0); - mVpn = new Vpn(mContext, new VpnCallback()); + mVpn = new Vpn(mContext, mVpnCallback, mNetd); + mVpn.startMonitoring(mContext, mTrackerHandler); try { - nmService.registerObserver(mTethering); - nmService.registerObserver(mVpn); + mNetd.registerObserver(mTethering); + mNetd.registerObserver(mDataActivityObserver); } catch (RemoteException e) { loge("Error registering observer :" + e); } @@ -548,8 +550,55 @@ public class ConnectivityService extends IConnectivityManager.Stub { loadGlobalProxy(); } -private NetworkStateTracker makeWimaxStateTracker() { - //Initialize Wimax + + /** + * Factory that creates {@link NetworkStateTracker} instances using given + * {@link NetworkConfig}. + */ + public interface NetworkFactory { + public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config); + } + + private static class DefaultNetworkFactory implements NetworkFactory { + private final Context mContext; + private final Handler mTrackerHandler; + + public DefaultNetworkFactory(Context context, Handler trackerHandler) { + mContext = context; + mTrackerHandler = trackerHandler; + } + + @Override + public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config) { + switch (config.radio) { + case TYPE_WIFI: + return new WifiStateTracker(targetNetworkType, config.name); + case TYPE_MOBILE: + return new MobileDataStateTracker(targetNetworkType, config.name); + case TYPE_DUMMY: + return new DummyDataStateTracker(targetNetworkType, config.name); + case TYPE_BLUETOOTH: + return BluetoothTetheringDataTracker.getInstance(); + case TYPE_WIMAX: + return makeWimaxStateTracker(mContext, mTrackerHandler); + case TYPE_ETHERNET: + return EthernetDataTracker.getInstance(); + default: + throw new IllegalArgumentException( + "Trying to create a NetworkStateTracker for an unknown radio type: " + + config.radio); + } + } + } + + /** + * Loads external WiMAX library and registers as system service, returning a + * {@link NetworkStateTracker} for WiMAX. Caller is still responsible for + * invoking {@link NetworkStateTracker#startMonitoring(Context, Handler)}. + */ + private static NetworkStateTracker makeWimaxStateTracker( + Context context, Handler trackerHandler) { + // Initialize Wimax DexClassLoader wimaxClassLoader; Class wimaxStateTrackerClass = null; Class wimaxServiceClass = null; @@ -562,25 +611,25 @@ private NetworkStateTracker makeWimaxStateTracker() { NetworkStateTracker wimaxStateTracker = null; - boolean isWimaxEnabled = mContext.getResources().getBoolean( + boolean isWimaxEnabled = context.getResources().getBoolean( com.android.internal.R.bool.config_wimaxEnabled); if (isWimaxEnabled) { try { - wimaxJarLocation = mContext.getResources().getString( + wimaxJarLocation = context.getResources().getString( com.android.internal.R.string.config_wimaxServiceJarLocation); - wimaxLibLocation = mContext.getResources().getString( + wimaxLibLocation = context.getResources().getString( com.android.internal.R.string.config_wimaxNativeLibLocation); - wimaxManagerClassName = mContext.getResources().getString( + wimaxManagerClassName = context.getResources().getString( com.android.internal.R.string.config_wimaxManagerClassname); - wimaxServiceClassName = mContext.getResources().getString( + wimaxServiceClassName = context.getResources().getString( com.android.internal.R.string.config_wimaxServiceClassname); - wimaxStateTrackerClassName = mContext.getResources().getString( + wimaxStateTrackerClassName = context.getResources().getString( com.android.internal.R.string.config_wimaxStateTrackerClassname); log("wimaxJarLocation: " + wimaxJarLocation); wimaxClassLoader = new DexClassLoader(wimaxJarLocation, - new ContextWrapper(mContext).getCacheDir().getAbsolutePath(), + new ContextWrapper(context).getCacheDir().getAbsolutePath(), wimaxLibLocation, ClassLoader.getSystemClassLoader()); try { @@ -601,13 +650,13 @@ private NetworkStateTracker makeWimaxStateTracker() { Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor (new Class[] {Context.class, Handler.class}); - wimaxStateTracker = (NetworkStateTracker)wmxStTrkrConst.newInstance(mContext, - mHandler); + wimaxStateTracker = (NetworkStateTracker) wmxStTrkrConst.newInstance( + context, trackerHandler); Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor (new Class[] {Context.class, wimaxStateTrackerClass}); wmxSrvConst.setAccessible(true); - IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(mContext, wimaxStateTracker); + IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(context, wimaxStateTracker); wmxSrvConst.setAccessible(false); ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker); @@ -623,6 +672,7 @@ private NetworkStateTracker makeWimaxStateTracker() { return wimaxStateTracker; } + /** * Sets the preferred network. * @param preference the new preference @@ -630,7 +680,8 @@ private NetworkStateTracker makeWimaxStateTracker() { public void setNetworkPreference(int preference) { enforceChangePermission(); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0)); + mHandler.sendMessage( + mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0)); } public int getNetworkPreference() { @@ -749,6 +800,9 @@ private NetworkStateTracker makeWimaxStateTracker() { info = new NetworkInfo(info); info.setDetailedState(DetailedState.BLOCKED, null, null); } + if (mLockdownTracker != null) { + info = mLockdownTracker.augmentNetworkInfo(info); + } return info; } @@ -766,6 +820,17 @@ private NetworkStateTracker makeWimaxStateTracker() { return getNetworkInfo(mActiveDefaultNetwork, uid); } + public NetworkInfo getActiveNetworkInfoUnfiltered() { + enforceAccessPermission(); + if (isNetworkTypeValid(mActiveDefaultNetwork)) { + final NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork]; + if (tracker != null) { + return tracker.getNetworkInfo(); + } + } + return null; + } + @Override public NetworkInfo getActiveNetworkInfoForUid(int uid) { enforceConnectivityInternalPermission(); @@ -923,6 +988,14 @@ private NetworkStateTracker makeWimaxStateTracker() { return tracker != null && tracker.setRadio(turnOn); } + private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() { + @Override + public void interfaceClassDataActivityChanged(String label, boolean active) { + int deviceType = Integer.parseInt(label); + sendDataActivityBroadcast(deviceType, active); + } + }; + /** * Used to notice when the calling process dies so we can self-expire * @@ -1017,6 +1090,12 @@ private NetworkStateTracker makeWimaxStateTracker() { // TODO - move this into individual networktrackers int usedNetworkType = convertFeatureToNetworkType(networkType, feature); + if (mLockdownEnabled) { + // Since carrier APNs usually aren't available from VPN + // endpoint, mark them as unavailable. + return PhoneConstants.APN_TYPE_NOT_AVAILABLE; + } + if (mProtectedNetworks.contains(usedNetworkType)) { enforceConnectivityInternalPermission(); } @@ -1291,8 +1370,10 @@ private NetworkStateTracker makeWimaxStateTracker() { return false; } NetworkStateTracker tracker = mNetTrackers[networkType]; + DetailedState netState = tracker.getNetworkInfo().getDetailedState(); - if (tracker == null || !tracker.getNetworkInfo().isConnected() || + if (tracker == null || (netState != DetailedState.CONNECTED && + netState != DetailedState.CAPTIVE_PORTAL_CHECK) || tracker.isTeardownRequested()) { if (VDBG) { log("requestRouteToHostAddress on down network " + @@ -1591,6 +1672,10 @@ private NetworkStateTracker makeWimaxStateTracker() { int prevNetType = info.getType(); mNetTrackers[prevNetType].setTeardownRequested(false); + + // Remove idletimer previously setup in {@code handleConnect} + removeDataActivityTracking(prevNetType); + /* * If the disconnected network is not the active one, then don't report * this as a loss of connectivity. What probably happened is that we're @@ -1610,6 +1695,7 @@ private NetworkStateTracker makeWimaxStateTracker() { Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); info.setFailover(false); @@ -1719,7 +1805,7 @@ private NetworkStateTracker makeWimaxStateTracker() { } } - private void sendConnectedBroadcast(NetworkInfo info) { + public void sendConnectedBroadcast(NetworkInfo info) { sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE); sendGeneralBroadcast(info, CONNECTIVITY_ACTION); } @@ -1734,8 +1820,13 @@ private NetworkStateTracker makeWimaxStateTracker() { } private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { + if (mLockdownTracker != null) { + info = mLockdownTracker.augmentNetworkInfo(info); + } + Intent intent = new Intent(bcastType); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); info.setFailover(false); @@ -1759,6 +1850,19 @@ private NetworkStateTracker makeWimaxStateTracker() { sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs); } + private void sendDataActivityBroadcast(int deviceType, boolean active) { + Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE); + intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType); + intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, + RECEIVE_DATA_ACTIVITY_CHANGE, null, null, 0, null, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * Called when an attempt to fail over to another network has failed. * @param info the {@link NetworkInfo} for the failed network @@ -1779,6 +1883,7 @@ private NetworkStateTracker makeWimaxStateTracker() { Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (getActiveNetworkInfo() == null) { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } @@ -1829,7 +1934,12 @@ private NetworkStateTracker makeWimaxStateTracker() { log("sendStickyBroadcast: action=" + intent.getAction()); } - mContext.sendStickyBroadcast(intent); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } } } @@ -1850,37 +1960,55 @@ private NetworkStateTracker makeWimaxStateTracker() { synchronized(this) { mSystemReady = true; if (mInitialBroadcast != null) { - mContext.sendStickyBroadcast(mInitialBroadcast); + mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL); mInitialBroadcast = null; } } // load the global proxy at startup mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY)); + + // Try bringing up tracker, but if KeyStore isn't ready yet, wait + // for user to unlock device. + if (!updateLockdownVpn()) { + final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT); + mContext.registerReceiver(mUserPresentReceiver, filter); + } + } + + private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Try creating lockdown tracker, since user present usually means + // unlocked keystore. + if (updateLockdownVpn()) { + mContext.unregisterReceiver(this); + } + } + }; + + private boolean isNewNetTypePreferredOverCurrentNetType(int type) { + if ((type != mNetworkPreference && + mNetConfigs[mActiveDefaultNetwork].priority > + mNetConfigs[type].priority) || + mNetworkPreference == mActiveDefaultNetwork) return false; + return true; } private void handleConnect(NetworkInfo info) { - final int type = info.getType(); + final int newNetType = info.getType(); + + setupDataActivityTracking(newNetType); // snapshot isFailover, because sendConnectedBroadcast() resets it boolean isFailover = info.isFailover(); - final NetworkStateTracker thisNet = mNetTrackers[type]; + final NetworkStateTracker thisNet = mNetTrackers[newNetType]; + final String thisIface = thisNet.getLinkProperties().getInterfaceName(); // if this is a default net and other default is running // kill the one not preferred - if (mNetConfigs[type].isDefault()) { - if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) { - if ((type != mNetworkPreference && - mNetConfigs[mActiveDefaultNetwork].priority > - mNetConfigs[type].priority) || - mNetworkPreference == mActiveDefaultNetwork) { - // don't accept this one - if (VDBG) { - log("Not broadcasting CONNECT_ACTION " + - "to torn down network " + info.getTypeName()); - } - teardown(thisNet); - return; - } else { + if (mNetConfigs[newNetType].isDefault()) { + if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) { + if (isNewNetTypePreferredOverCurrentNetType(newNetType)) { // tear down the other NetworkStateTracker otherNet = mNetTrackers[mActiveDefaultNetwork]; @@ -1893,6 +2021,14 @@ private NetworkStateTracker makeWimaxStateTracker() { teardown(thisNet); return; } + } else { + // don't accept this one + if (VDBG) { + log("Not broadcasting CONNECT_ACTION " + + "to torn down network " + info.getTypeName()); + } + teardown(thisNet); + return; } } synchronized (ConnectivityService.this) { @@ -1906,7 +2042,7 @@ private NetworkStateTracker makeWimaxStateTracker() { 1000); } } - mActiveDefaultNetwork = type; + mActiveDefaultNetwork = newNetType; // this will cause us to come up initially as unconnected and switching // to connected after our normal pause unless somebody reports us as reall // disconnected @@ -1918,20 +2054,99 @@ private NetworkStateTracker makeWimaxStateTracker() { } thisNet.setTeardownRequested(false); updateNetworkSettings(thisNet); - handleConnectivityChange(type, false); + handleConnectivityChange(newNetType, false); sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay()); // notify battery stats service about this network - final String iface = thisNet.getLinkProperties().getInterfaceName(); - if (iface != null) { + if (thisIface != null) { try { - BatteryStatsService.getService().noteNetworkInterfaceType(iface, type); + BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType); } catch (RemoteException e) { // ignored; service lives in system_server } } } + private void handleCaptivePortalTrackerCheck(NetworkInfo info) { + if (DBG) log("Captive portal check " + info); + int type = info.getType(); + final NetworkStateTracker thisNet = mNetTrackers[type]; + if (mNetConfigs[type].isDefault()) { + if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) { + if (isNewNetTypePreferredOverCurrentNetType(type)) { + if (DBG) log("Captive check on " + info.getTypeName()); + mCaptivePortalTracker = CaptivePortalTracker.detect(mContext, info, + ConnectivityService.this); + return; + } else { + if (DBG) log("Tear down low priority net " + info.getTypeName()); + teardown(thisNet); + return; + } + } + } + + thisNet.captivePortalCheckComplete(); + } + + /** @hide */ + public void captivePortalCheckComplete(NetworkInfo info) { + mNetTrackers[info.getType()].captivePortalCheckComplete(); + mCaptivePortalTracker = null; + } + + /** + * Setup data activity tracking for the given network interface. + * + * Every {@code setupDataActivityTracking} should be paired with a + * {@link removeDataActivityTracking} for cleanup. + */ + private void setupDataActivityTracking(int type) { + final NetworkStateTracker thisNet = mNetTrackers[type]; + final String iface = thisNet.getLinkProperties().getInterfaceName(); + + final int timeout; + + if (ConnectivityManager.isNetworkTypeMobile(type)) { + timeout = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DATA_ACTIVITY_TIMEOUT_MOBILE, + 0); + // Canonicalize mobile network type + type = ConnectivityManager.TYPE_MOBILE; + } else if (ConnectivityManager.TYPE_WIFI == type) { + timeout = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DATA_ACTIVITY_TIMEOUT_WIFI, + 0); + } else { + // do not track any other networks + timeout = 0; + } + + if (timeout > 0 && iface != null) { + try { + mNetd.addIdleTimer(iface, timeout, Integer.toString(type)); + } catch (RemoteException e) { + } + } + } + + /** + * Remove data activity tracking when network disconnects. + */ + private void removeDataActivityTracking(int type) { + final NetworkStateTracker net = mNetTrackers[type]; + final String iface = net.getLinkProperties().getInterfaceName(); + + if (iface != null && (ConnectivityManager.isNetworkTypeMobile(type) || + ConnectivityManager.TYPE_WIFI == type)) { + try { + // the call fails silently if no idletimer setup for this interface + mNetd.removeIdleTimer(iface); + } catch (RemoteException e) { + } + } + } + /** * After a change in the connectivity state of a network. We're mainly * concerned with making sure that the list of DNS servers is set up @@ -2136,9 +2351,9 @@ private NetworkStateTracker makeWimaxStateTracker() { */ public void updateNetworkSettings(NetworkStateTracker nt) { String key = nt.getTcpBufferSizesPropName(); - String bufferSizes = SystemProperties.get(key); + String bufferSizes = key == null ? null : SystemProperties.get(key); - if (bufferSizes.length() == 0) { + if (TextUtils.isEmpty(bufferSizes)) { if (VDBG) log(key + " not found in system properties. Using defaults"); // Setting to default values so we won't be stuck to previous values @@ -2264,7 +2479,12 @@ private NetworkStateTracker makeWimaxStateTracker() { * Connectivity events can happen before boot has completed ... */ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } } // Caller must grab mDnsLock. @@ -2374,7 +2594,8 @@ private NetworkStateTracker makeWimaxStateTracker() { } @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -2383,20 +2604,28 @@ private NetworkStateTracker makeWimaxStateTracker() { Binder.getCallingUid()); return; } + + // TODO: add locking to get atomic snapshot pw.println(); - for (NetworkStateTracker nst : mNetTrackers) { + for (int i = 0; i < mNetTrackers.length; i++) { + final NetworkStateTracker nst = mNetTrackers[i]; if (nst != null) { + pw.println("NetworkStateTracker for " + getNetworkTypeName(i) + ":"); + pw.increaseIndent(); if (nst.getNetworkInfo().isConnected()) { pw.println("Active network: " + nst.getNetworkInfo(). getTypeName()); } pw.println(nst.getNetworkInfo()); + pw.println(nst.getLinkProperties()); pw.println(nst); pw.println(); + pw.decreaseIndent(); } } pw.println("Network Requester Pids:"); + pw.increaseIndent(); for (int net : mPriorityList) { String pidString = net + ": "; for (Object pid : mNetRequestersPids[net]) { @@ -2405,12 +2634,15 @@ private NetworkStateTracker makeWimaxStateTracker() { pw.println(pidString); } pw.println(); + pw.decreaseIndent(); pw.println("FeatureUsers:"); + pw.increaseIndent(); for (Object requester : mFeatureUsers) { pw.println(requester.toString()); } pw.println(); + pw.decreaseIndent(); synchronized (this) { pw.println("NetworkTranstionWakeLock is currently " + @@ -2424,15 +2656,17 @@ private NetworkStateTracker makeWimaxStateTracker() { if (mInetLog != null) { pw.println(); pw.println("Inet condition reports:"); + pw.increaseIndent(); for(int i = 0; i < mInetLog.size(); i++) { pw.println(mInetLog.get(i)); } + pw.decreaseIndent(); } } // must be stateless - things change under us. - private class MyHandler extends Handler { - public MyHandler(Looper looper) { + private class NetworkStateTrackerHandler extends Handler { + public NetworkStateTrackerHandler(Looper looper) { super(looper); } @@ -2468,6 +2702,9 @@ private NetworkStateTracker makeWimaxStateTracker() { if (info.getDetailedState() == NetworkInfo.DetailedState.FAILED) { handleConnectionFailure(info); + } else if (info.getDetailedState() == + DetailedState.CAPTIVE_PORTAL_CHECK) { + handleCaptivePortalTrackerCheck(info); } else if (state == NetworkInfo.State.DISCONNECTED) { handleDisconnect(info); } else if (state == NetworkInfo.State.SUSPENDED) { @@ -2482,6 +2719,9 @@ private NetworkStateTracker makeWimaxStateTracker() { } else if (state == NetworkInfo.State.CONNECTED) { handleConnect(info); } + if (mLockdownTracker != null) { + mLockdownTracker.onNetworkInfoChanged(info); + } break; case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: info = (NetworkInfo) msg.obj; @@ -2495,6 +2735,19 @@ private NetworkStateTracker makeWimaxStateTracker() { type = info.getType(); updateNetworkSettings(mNetTrackers[type]); break; + } + } + } + + private class InternalHandler extends Handler { + public InternalHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + NetworkInfo info; + switch (msg.what) { case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: String causedBy = null; synchronized (ConnectivityService.this) { @@ -2566,6 +2819,13 @@ private NetworkStateTracker makeWimaxStateTracker() { final int networkType = msg.arg1; final boolean enabled = msg.arg2 == ENABLED; handleSetPolicyDataEnable(networkType, enabled); + break; + } + case EVENT_VPN_STATE_CHANGED: { + if (mLockdownTracker != null) { + mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj); + } + break; } } } @@ -2883,7 +3143,12 @@ private NetworkStateTracker makeWimaxStateTracker() { intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy); - mContext.sendStickyBroadcast(intent); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } } private static class SettingsObserver extends ContentObserver { @@ -2907,11 +3172,11 @@ private NetworkStateTracker makeWimaxStateTracker() { } } - private void log(String s) { + private static void log(String s) { Slog.d(TAG, s); } - private void loge(String s) { + private static void loge(String s) { Slog.e(TAG, s); } @@ -2964,6 +3229,7 @@ private NetworkStateTracker makeWimaxStateTracker() { */ @Override public boolean protectVpn(ParcelFileDescriptor socket) { + throwIfLockdownEnabled(); try { int type = mActiveDefaultNetwork; if (ConnectivityManager.isNetworkTypeValid(type)) { @@ -2990,6 +3256,7 @@ private NetworkStateTracker makeWimaxStateTracker() { */ @Override public boolean prepareVpn(String oldPackage, String newPackage) { + throwIfLockdownEnabled(); return mVpn.prepare(oldPackage, newPackage); } @@ -3002,18 +3269,22 @@ private NetworkStateTracker makeWimaxStateTracker() { */ @Override public ParcelFileDescriptor establishVpn(VpnConfig config) { + throwIfLockdownEnabled(); return mVpn.establish(config); } /** - * Start legacy VPN and return an intent to VpnDialogs. This method is - * used by VpnSettings and not available in ConnectivityManager. - * Permissions are checked in Vpn class. - * @hide + * Start legacy VPN, controlling native daemons as needed. Creates a + * secondary thread to perform connection work, returning quickly. */ @Override - public void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { - mVpn.startLegacyVpn(config, racoon, mtpd); + public void startLegacyVpn(VpnProfile profile) { + throwIfLockdownEnabled(); + final LinkProperties egress = getActiveLinkProperties(); + if (egress == null) { + throw new IllegalStateException("Missing active network connection"); + } + mVpn.startLegacyVpn(profile, mKeyStore, egress); } /** @@ -3024,6 +3295,7 @@ private NetworkStateTracker makeWimaxStateTracker() { */ @Override public LegacyVpnInfo getLegacyVpnInfo() { + throwIfLockdownEnabled(); return mVpn.getLegacyVpnInfo(); } @@ -3038,10 +3310,13 @@ private NetworkStateTracker makeWimaxStateTracker() { * be done whenever a better abstraction is developed. */ public class VpnCallback { - private VpnCallback() { } + public void onStateChanged(NetworkInfo info) { + mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget(); + } + public void override(List<String> dnsServers, List<String> searchDomains) { if (dnsServers == null) { restore(); @@ -3108,4 +3383,65 @@ private NetworkStateTracker makeWimaxStateTracker() { } } } + + @Override + public boolean updateLockdownVpn() { + enforceSystemUid(); + + // Tear down existing lockdown if profile was removed + mLockdownEnabled = LockdownVpnTracker.isEnabled(); + if (mLockdownEnabled) { + if (mKeyStore.state() != KeyStore.State.UNLOCKED) { + Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker"); + return false; + } + + final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN)); + final VpnProfile profile = VpnProfile.decode( + profileName, mKeyStore.get(Credentials.VPN + profileName)); + setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpn, profile)); + } else { + setLockdownTracker(null); + } + + return true; + } + + /** + * Internally set new {@link LockdownVpnTracker}, shutting down any existing + * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. + */ + private void setLockdownTracker(LockdownVpnTracker tracker) { + // Shutdown any existing tracker + final LockdownVpnTracker existing = mLockdownTracker; + mLockdownTracker = null; + if (existing != null) { + existing.shutdown(); + } + + try { + if (tracker != null) { + mNetd.setFirewallEnabled(true); + mLockdownTracker = tracker; + mLockdownTracker.init(); + } else { + mNetd.setFirewallEnabled(false); + } + } catch (RemoteException e) { + // ignored; NMS lives inside system_server + } + } + + private void throwIfLockdownEnabled() { + if (mLockdownEnabled) { + throw new IllegalStateException("Unavailable in lockdown mode"); + } + } + + private static void enforceSystemUid() { + final int uid = Binder.getCallingUid(); + if (uid != Process.SYSTEM_UID) { + throw new SecurityException("Only available to AID_SYSTEM"); + } + } } diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index ea19d6e..28a4310 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -55,6 +55,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -176,6 +177,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { static final long DEF_PASSWORD_EXPIRATION_DATE = 0; long passwordExpirationDate = DEF_PASSWORD_EXPIRATION_DATE; + static final int DEF_KEYGUARD_WIDGET_DISABLED = 0; // none + int disableKeyguardWidgets = DEF_KEYGUARD_WIDGET_DISABLED; + boolean encryptionRequested = false; boolean disableCamera = false; @@ -285,6 +289,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, "value", Boolean.toString(disableCamera)); out.endTag(null, "disable-camera"); } + if (disableKeyguardWidgets != DEF_KEYGUARD_WIDGET_DISABLED) { + out.startTag(null, "disable-keyguard-widgets"); + out.attribute(null, "value", Integer.toString(disableKeyguardWidgets)); + out.endTag(null, "disable-keyguard-widgets"); + } } void readFromXml(XmlPullParser parser) @@ -570,10 +579,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { intent.putExtra("expiration", admin.passwordExpirationDate); } if (result != null) { - mContext.sendOrderedBroadcast(intent, null, result, mHandler, - Activity.RESULT_OK, null, null); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER, + null, result, mHandler, Activity.RESULT_OK, null, null); } else { - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.OWNER); } } @@ -712,7 +721,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void sendChangedNotification() { Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - mContext.sendBroadcast(intent); + long ident = Binder.clearCallingIdentity(); + try { + // TODO: This shouldn't be sent to all users, if DPM is per user. + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(ident); + } } private void loadSettingsLocked() { @@ -1619,14 +1634,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else { // Make sure KEEP_SCREEN_ON is disabled, since that // would allow bypassing of the maximum time to lock. - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); } mLastMaximumTimeToLock = timeMs; try { - getIPowerManager().setMaximumScreenOffTimeount((int)timeMs); + getIPowerManager().setMaximumScreenOffTimeoutFromDeviceAdmin((int)timeMs); } catch (RemoteException e) { Slog.w(TAG, "Failure talking with power manager", e); } @@ -1667,8 +1682,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long ident = Binder.clearCallingIdentity(); try { // Power off the display - mIPowerManager.goToSleepWithReason(SystemClock.uptimeMillis(), - WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN); + getIPowerManager().goToSleep(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN); // Ensure the device is locked getWindowManager().lockNow(); } catch (RemoteException e) { @@ -1734,7 +1749,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } Intent intent = new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLE_REQUESTED); intent.setComponent(admin.info.getComponent()); - mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER, + null, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { try { @@ -2091,6 +2107,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + /** + * Selectively disable keyguard widgets. + */ + public void setKeyguardWidgetsDisabled(ComponentName who, int which) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_WIDGETS); + if ((ap.disableKeyguardWidgets & which) != which) { + ap.disableKeyguardWidgets |= which; + saveSettingsLocked(); + } + syncDeviceCapabilitiesLocked(); + } + } + + /** + * Gets the disabled state for widgets in keyguard for the given admin, + * or the aggregate of all active admins if who is null. + */ + public int getKeyguardWidgetsDisabled(ComponentName who) { + synchronized (this) { + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return (admin != null) ? admin.disableKeyguardWidgets : 0; + } + + // Determine whether or not keyguard widgets are disabled for any active admins. + final int N = mAdminList.size(); + int which = 0; + for (int i = 0; i < N; i++) { + ActiveAdmin admin = mAdminList.get(i); + which |= admin.disableKeyguardWidgets; + } + return which; + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 0ed5189..c919595 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -16,6 +16,9 @@ package com.android.server; +import java.io.FileDescriptor; +import java.io.PrintWriter; + import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -24,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.Environment; import android.os.FileObserver; @@ -35,9 +39,12 @@ import android.os.ServiceManager; import android.os.StatFs; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings; +import android.text.format.Formatter; import android.util.EventLog; import android.util.Slog; +import android.util.TimeUtils; /** * This class implements a service to monitor the amount of disk @@ -71,6 +78,7 @@ public class DeviceStorageMonitorService extends Binder { private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000; private static final int DEFAULT_FULL_THRESHOLD_BYTES = 1024*1024; // 1MB private long mFreeMem; // on /data + private long mFreeMemAfterLastCacheClear; // on /data private long mLastReportedFreeMem; private long mLastReportedFreeMemTime; private boolean mLowMemFlag=false; @@ -95,7 +103,19 @@ public class DeviceStorageMonitorService extends Binder { private final CacheFileDeletedObserver mCacheFileDeletedObserver; private static final int _TRUE = 1; private static final int _FALSE = 0; + // This is the raw threshold that has been set at which we consider + // storage to be low. private long mMemLowThreshold; + // This is the threshold at which we start trying to flush caches + // to get below the low threshold limit. It is less than the low + // threshold; we will allow storage to get a bit beyond the limit + // before flushing and checking if we are actually low. + private long mMemCacheStartTrimThreshold; + // This is the threshold that we try to get to when deleting cache + // files. This is greater than the low threshold so that we will flush + // more files than absolutely needed, to reduce the frequency that + // flushing takes place. + private long mMemCacheTrimToThreshold; private int mMemFullThreshold; /** @@ -190,7 +210,7 @@ public class DeviceStorageMonitorService extends Binder { try { if (localLOGV) Slog.i(TAG, "Clearing cache"); IPackageManager.Stub.asInterface(ServiceManager.getService("package")). - freeStorageAndNotify(mMemLowThreshold, mClearCacheObserver); + freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver); } catch (RemoteException e) { Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e); mClearingCache = false; @@ -216,24 +236,42 @@ public class DeviceStorageMonitorService extends Binder { //post intent to NotificationManager to display icon if necessary if (mFreeMem < mMemLowThreshold) { - if (!mLowMemFlag) { - if (checkCache) { - // See if clearing cache helps - // Note that clearing cache is asynchronous and so we do a - // memory check again once the cache has been cleared. - mThreadStartTime = System.currentTimeMillis(); - mClearSucceeded = false; - clearCache(); - } else { + if (checkCache) { + // We are allowed to clear cache files at this point to + // try to get down below the limit, because this is not + // the initial call after a cache clear has been attempted. + // In this case we will try a cache clear if our free + // space has gone below the cache clear limit. + if (mFreeMem < mMemCacheStartTrimThreshold) { + // We only clear the cache if the free storage has changed + // a significant amount since the last time. + if ((mFreeMemAfterLastCacheClear-mFreeMem) + >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) { + // See if clearing cache helps + // Note that clearing cache is asynchronous and so we do a + // memory check again once the cache has been cleared. + mThreadStartTime = System.currentTimeMillis(); + mClearSucceeded = false; + clearCache(); + } + } + } else { + // This is a call from after clearing the cache. Note + // the amount of free storage at this point. + mFreeMemAfterLastCacheClear = mFreeMem; + if (!mLowMemFlag) { + // We tried to clear the cache, but that didn't get us + // below the low storage limit. Tell the user. Slog.i(TAG, "Running low on memory. Sending notification"); sendNotification(); mLowMemFlag = true; + } else { + if (localLOGV) Slog.v(TAG, "Running low on memory " + + "notification already sent. do nothing"); } - } else { - if (localLOGV) Slog.v(TAG, "Running low on memory " + - "notification already sent. do nothing"); } } else { + mFreeMemAfterLastCacheClear = mFreeMem; if (mLowMemFlag) { Slog.i(TAG, "Memory available. Cancelling notification"); cancelNotification(); @@ -276,7 +314,7 @@ public class DeviceStorageMonitorService extends Binder { Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE); if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value); - value *= mTotalMemory; + value = (value*mTotalMemory)/100; long maxValue = Settings.Secure.getInt( mContentResolver, Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES, @@ -312,8 +350,8 @@ public class DeviceStorageMonitorService extends Binder { mSystemFileStats = new StatFs(SYSTEM_PATH); mCacheFileStats = new StatFs(CACHE_PATH); //initialize total storage on device - mTotalMemory = ((long)mDataFileStats.getBlockCount() * - mDataFileStats.getBlockSize())/100L; + mTotalMemory = (long)mDataFileStats.getBlockCount() * + mDataFileStats.getBlockSize(); mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW); mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK); @@ -325,6 +363,10 @@ public class DeviceStorageMonitorService extends Binder { // cache storage thresholds mMemLowThreshold = getMemThreshold(); mMemFullThreshold = getMemFullThreshold(); + mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4; + mMemCacheTrimToThreshold = mMemLowThreshold + + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2); + mFreeMemAfterLastCacheClear = mTotalMemory; checkMemory(true); mCacheFileDeletedObserver = new CacheFileDeletedObserver(); @@ -375,8 +417,8 @@ public class DeviceStorageMonitorService extends Binder { //cancel notification since memory has been freed mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID); - mContext.removeStickyBroadcast(mStorageLowIntent); - mContext.sendBroadcast(mStorageOkIntent); + mContext.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); + mContext.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL); } /** @@ -392,8 +434,8 @@ public class DeviceStorageMonitorService extends Binder { */ private final void cancelFullNotification() { if(localLOGV) Slog.i(TAG, "Canceling memory full notification"); - mContext.removeStickyBroadcast(mStorageFullIntent); - mContext.sendBroadcast(mStorageNotFullIntent); + mContext.removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL); + mContext.sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL); } public void updateMemory() { @@ -435,4 +477,40 @@ public class DeviceStorageMonitorService extends Binder { EventLogTags.writeCacheFileDeleted(path); } } + + @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 " + SERVICE + " from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("Current DeviceStorageMonitor state:"); + pw.print(" mFreeMem="); pw.print(Formatter.formatFileSize(mContext, mFreeMem)); + pw.print(" mTotalMemory="); + pw.println(Formatter.formatFileSize(mContext, mTotalMemory)); + pw.print(" mFreeMemAfterLastCacheClear="); + pw.println(Formatter.formatFileSize(mContext, mFreeMemAfterLastCacheClear)); + pw.print(" mLastReportedFreeMem="); + pw.print(Formatter.formatFileSize(mContext, mLastReportedFreeMem)); + pw.print(" mLastReportedFreeMemTime="); + TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw); + pw.println(); + pw.print(" mLowMemFlag="); pw.print(mLowMemFlag); + pw.print(" mMemFullFlag="); pw.println(mMemFullFlag); + pw.print(" mClearSucceeded="); pw.print(mClearSucceeded); + pw.print(" mClearingCache="); pw.println(mClearingCache); + pw.print(" mMemLowThreshold="); + pw.print(Formatter.formatFileSize(mContext, mMemLowThreshold)); + pw.print(" mMemFullThreshold="); + pw.println(Formatter.formatFileSize(mContext, mMemFullThreshold)); + pw.print(" mMemCacheStartTrimThreshold="); + pw.print(Formatter.formatFileSize(mContext, mMemCacheStartTrimThreshold)); + pw.print(" mMemCacheTrimToThreshold="); + pw.println(Formatter.formatFileSize(mContext, mMemCacheTrimToThreshold)); + } } diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java index 64789d3..65c39f2 100644 --- a/services/java/com/android/server/DockObserver.java +++ b/services/java/com/android/server/DockObserver.java @@ -16,8 +16,9 @@ package com.android.server; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; +import static android.provider.Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK; +import static android.provider.Settings.Secure.SCREENSAVER_ENABLED; + import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -26,11 +27,16 @@ import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.os.Handler; +import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.PowerManager; import android.os.SystemClock; import android.os.UEventObserver; +import android.os.UserHandle; import android.provider.Settings; -import android.server.BluetoothService; +import android.service.dreams.IDreamManager; import android.util.Log; import android.util.Slog; @@ -40,14 +46,19 @@ import java.io.FileReader; /** * <p>DockObserver monitors for a docking station. */ -class DockObserver extends UEventObserver { +final class DockObserver extends UEventObserver { private static final String TAG = DockObserver.class.getSimpleName(); private static final boolean LOG = false; private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock"; private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state"; - private static final int MSG_DOCK_STATE = 0; + private static final int DEFAULT_SCREENSAVER_ENABLED = 1; + private static final int DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK = 1; + + private static final int MSG_DOCK_STATE_CHANGED = 0; + + private final Object mLock = new Object(); private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; @@ -56,11 +67,8 @@ class DockObserver extends UEventObserver { private final Context mContext; - private PowerManagerService mPowerManager; - - public DockObserver(Context context, PowerManagerService pm) { + public DockObserver(Context context) { mContext = context; - mPowerManager = pm; init(); // set initial status startObserving(DOCK_UEVENT_MATCH); @@ -72,7 +80,7 @@ class DockObserver extends UEventObserver { Slog.v(TAG, "Dock UEVENT: " + event.toString()); } - synchronized (this) { + synchronized (mLock) { try { int newState = Integer.parseInt(event.get("SWITCH_STATE")); if (newState != mDockState) { @@ -86,10 +94,11 @@ class DockObserver extends UEventObserver { && mPreviousDockState != Intent.EXTRA_DOCK_STATE_LE_DESK && mPreviousDockState != Intent.EXTRA_DOCK_STATE_HE_DESK) || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - mPowerManager.userActivityWithForce(SystemClock.uptimeMillis(), - false, true); + PowerManager pm = + (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + pm.wakeUp(SystemClock.uptimeMillis()); } - update(); + updateLocked(); } } } catch (NumberFormatException e) { @@ -98,102 +107,147 @@ class DockObserver extends UEventObserver { } } - private final void init() { - char[] buffer = new char[1024]; - - try { - FileReader file = new FileReader(DOCK_STATE_PATH); - int len = file.read(buffer, 0, 1024); - file.close(); - mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim()); - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have dock station support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); + private void init() { + synchronized (mLock) { + try { + char[] buffer = new char[1024]; + FileReader file = new FileReader(DOCK_STATE_PATH); + try { + int len = file.read(buffer, 0, 1024); + mDockState = Integer.valueOf((new String(buffer, 0, len)).trim()); + mPreviousDockState = mDockState; + } finally { + file.close(); + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "This kernel does not have dock station support"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } } } void systemReady() { - synchronized (this) { + synchronized (mLock) { // don't bother broadcasting undocked here if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - update(); + updateLocked(); } mSystemReady = true; } } - private final void update() { - mHandler.sendEmptyMessage(MSG_DOCK_STATE); + private void updateLocked() { + mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED); } - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_DOCK_STATE: - synchronized (this) { - Slog.i(TAG, "Dock state changed: " + mDockState); + private void handleDockStateChange() { + synchronized (mLock) { + Slog.i(TAG, "Dock state changed: " + mDockState); - final ContentResolver cr = mContext.getContentResolver(); + final ContentResolver cr = mContext.getContentResolver(); - if (Settings.Secure.getInt(cr, - Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { - Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); - return; - } - // Pack up the values and broadcast them to everyone - Intent intent = new Intent(Intent.ACTION_DOCK_EVENT); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); - - // Check if this is Bluetooth Dock - String address = BluetoothService.readDockBluetoothAddress(); - if (address != null) - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address)); - - // User feedback to confirm dock connection. Particularly - // useful for flaky contact pins... - if (Settings.System.getInt(cr, - Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1) - { - String whichSound = null; - if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) || - (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { - whichSound = Settings.System.DESK_UNDOCK_SOUND; - } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) { - whichSound = Settings.System.CAR_UNDOCK_SOUND; - } - } else { - if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) || - (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || - (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { - whichSound = Settings.System.DESK_DOCK_SOUND; - } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) { - whichSound = Settings.System.CAR_DOCK_SOUND; - } - } + if (Settings.Secure.getInt(cr, + Settings.Secure.DEVICE_PROVISIONED, 0) == 0) { + Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); + return; + } + + // Pack up the values and broadcast them to everyone + Intent intent = new Intent(Intent.ACTION_DOCK_EVENT); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState); + + // Check if this is Bluetooth Dock + // TODO(BT): Get Dock address. + // String address = null; + // if (address != null) { + // intent.putExtra(BluetoothDevice.EXTRA_DEVICE, + // BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address)); + // } + + // User feedback to confirm dock connection. Particularly + // useful for flaky contact pins... + if (Settings.System.getInt(cr, + Settings.System.DOCK_SOUNDS_ENABLED, 1) == 1) { + String whichSound = null; + if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { + whichSound = Settings.System.DESK_UNDOCK_SOUND; + } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) { + whichSound = Settings.System.CAR_UNDOCK_SOUND; + } + } else { + if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) || + (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) || + (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) { + whichSound = Settings.System.DESK_DOCK_SOUND; + } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) { + whichSound = Settings.System.CAR_DOCK_SOUND; + } + } - if (whichSound != null) { - final String soundPath = Settings.System.getString(cr, whichSound); - if (soundPath != null) { - final Uri soundUri = Uri.parse("file://" + soundPath); - if (soundUri != null) { - final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); - if (sfx != null) { - sfx.setStreamType(AudioManager.STREAM_SYSTEM); - sfx.play(); - } - } - } + if (whichSound != null) { + final String soundPath = Settings.System.getString(cr, whichSound); + if (soundPath != null) { + final Uri soundUri = Uri.parse("file://" + soundPath); + if (soundUri != null) { + final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); + if (sfx != null) { + sfx.setStreamType(AudioManager.STREAM_SYSTEM); + sfx.play(); } } + } + } + } - mContext.sendStickyBroadcast(intent); + IDreamManager mgr = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams")); + if (mgr != null) { + // dreams feature enabled + boolean undocked = mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED; + if (undocked) { + try { + if (mgr.isDreaming()) { + mgr.awaken(); + } + } catch (RemoteException e) { + Slog.w(TAG, "Unable to awaken!", e); } + } else { + if (isScreenSaverEnabled(mContext) && isScreenSaverActivatedOnDock(mContext)) { + try { + mgr.dream(); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to dream!", e); + } + } + } + } else { + // dreams feature not enabled, send legacy intent + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + } + } + + private static boolean isScreenSaverEnabled(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + SCREENSAVER_ENABLED, DEFAULT_SCREENSAVER_ENABLED) != 0; + } + + private static boolean isScreenSaverActivatedOnDock(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + SCREENSAVER_ACTIVATE_ON_DOCK, DEFAULT_SCREENSAVER_ACTIVATED_ON_DOCK) != 0; + } + + private final Handler mHandler = new Handler(true /*async*/) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DOCK_STATE_CHANGED: + handleDockStateChange(); break; } } diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index 932cba1..0b12410 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -31,6 +31,7 @@ import android.os.Handler; import android.os.Message; import android.os.StatFs; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.text.format.Time; import android.util.Slog; @@ -157,7 +158,8 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { @Override public void handleMessage(Message msg) { if (msg.what == MSG_SEND_BROADCAST) { - mContext.sendBroadcast((Intent)msg.obj, android.Manifest.permission.READ_LOGS); + mContext.sendBroadcastAsUser((Intent)msg.obj, UserHandle.OWNER, + android.Manifest.permission.READ_LOGS); } } }; diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags index 41f7335..840e006 100644 --- a/services/java/com/android/server/EventLogTags.logtags +++ b/services/java/com/android/server/EventLogTags.logtags @@ -114,6 +114,8 @@ option java_package com.android.server # Package Manager ready: 3100 boot_progress_pms_ready (time|2|3) # + check activity_launch_time for Home app +# Value of "unknown sources" setting at app install time +3110 unknown_sources_enabled (value|1) # --------------------------- @@ -146,3 +148,16 @@ option java_package com.android.server # --------------------------- 51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3) 51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3) + + +# --------------------------- +# LockdownVpnTracker.java +# --------------------------- +51200 lockdown_vpn_connecting (egress_net|1) +51201 lockdown_vpn_connected (egress_net|1) +51202 lockdown_vpn_error (egress_net|1) + +# --------------------------- +# ConfigUpdateInstallReceiver.java +# --------------------------- +51300 config_install_failed (dir|3) diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index fdb278d..c685473 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -16,8 +16,8 @@ package com.android.server; import com.android.internal.content.PackageMonitor; -import com.android.internal.os.AtomicFile; import com.android.internal.os.HandlerCaller; +import com.android.internal.os.SomeArgs; import com.android.internal.util.FastXmlSerializer; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethod; @@ -74,6 +74,7 @@ import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.text.style.SuggestionSpan; +import android.util.AtomicFile; import android.util.EventLog; import android.util.LruCache; import android.util.Pair; @@ -2024,7 +2025,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean handleMessage(Message msg) { - HandlerCaller.SomeArgs args; + SomeArgs args; switch (msg.what) { case MSG_SHOW_IM_PICKER: showInputMethodMenu(); @@ -2035,8 +2036,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return true; case MSG_SHOW_IM_SUBTYPE_ENABLER: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; showInputMethodAndSubtypeEnabler((String)args.arg1); + args.recycle(); return true; case MSG_SHOW_IM_CONFIG: @@ -2053,48 +2055,53 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; case MSG_BIND_INPUT: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; try { ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); } catch (RemoteException e) { } + args.recycle(); return true; case MSG_SHOW_SOFT_INPUT: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; try { ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2); } catch (RemoteException e) { } + args.recycle(); return true; case MSG_HIDE_SOFT_INPUT: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; try { ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2); } catch (RemoteException e) { } + args.recycle(); return true; case MSG_ATTACH_TOKEN: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; try { if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); } catch (RemoteException e) { } + args.recycle(); return true; case MSG_CREATE_SESSION: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; try { ((IInputMethod)args.arg1).createSession( (IInputMethodCallback)args.arg2); } catch (RemoteException e) { } + args.recycle(); return true; // --------------------------------------------------------- case MSG_START_INPUT: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; try { SessionState session = (SessionState)args.arg1; setEnabledSessionInMainThread(session); @@ -2102,9 +2109,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub (EditorInfo)args.arg3); } catch (RemoteException e) { } + args.recycle(); return true; case MSG_RESTART_INPUT: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; try { SessionState session = (SessionState)args.arg1; setEnabledSessionInMainThread(session); @@ -2112,6 +2120,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub (EditorInfo)args.arg3); } catch (RemoteException e) { } + args.recycle(); return true; // --------------------------------------------------------- @@ -2124,13 +2133,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; case MSG_BIND_METHOD: - args = (HandlerCaller.SomeArgs)msg.obj; + args = (SomeArgs)msg.obj; try { ((IInputMethodClient)args.arg1).onBindMethod( (InputBindResult)args.arg2); } catch (RemoteException e) { Slog.w(TAG, "Client died receiving input method " + args.arg2); } + args.recycle(); return true; case MSG_SET_ACTIVE: try { @@ -3569,7 +3579,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final String ATTR_IS_AUXILIARY = "isAuxiliary"; private final AtomicFile mAdditionalInputMethodSubtypeFile; private final HashMap<String, InputMethodInfo> mMethodMap; - private final HashMap<String, List<InputMethodSubtype>> mSubtypesMap = + private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap = new HashMap<String, List<InputMethodSubtype>>(); public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap) { if (methodMap == null) { @@ -3585,18 +3595,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile); if (!subtypeFile.exists()) { // If "subtypes.xml" doesn't exist, create a blank file. - writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile, - methodMap); + writeAdditionalInputMethodSubtypes( + mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap); } else { - readAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile); + readAdditionalInputMethodSubtypes( + mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile); } } private void deleteAllInputMethodSubtypes(String imiId) { synchronized (mMethodMap) { - mSubtypesMap.remove(imiId); - writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile, - mMethodMap); + mAdditionalSubtypesMap.remove(imiId); + writeAdditionalInputMethodSubtypes( + mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap); } } @@ -3609,17 +3620,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final InputMethodSubtype subtype = additionalSubtypes[i]; if (!subtypes.contains(subtype)) { subtypes.add(subtype); + } else { + Slog.w(TAG, "Duplicated subtype definition found: " + + subtype.getLocale() + ", " + subtype.getMode()); } } - mSubtypesMap.put(imi.getId(), subtypes); - writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile, - mMethodMap); + mAdditionalSubtypesMap.put(imi.getId(), subtypes); + writeAdditionalInputMethodSubtypes( + mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap); } } public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() { synchronized (mMethodMap) { - return mSubtypesMap; + return mAdditionalSubtypesMap; } } diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index f7e841e..d4769e8 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -34,6 +34,7 @@ import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.LogPrinter; import android.util.Printer; +import android.util.StringBuilderPrinter; import android.content.Intent; import android.content.IntentFilter; @@ -65,11 +66,17 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { register_intent_filter(f, f.actionsIterator(), mTypedActionToFilter, " TypedAction: "); } + + mOldResolver.addFilter(f); + verifyDataStructures(f); } public void removeFilter(F f) { removeFilterInternal(f); mFilters.remove(f); + + mOldResolver.removeFilter(f); + verifyDataStructures(f); } void removeFilterInternal(F f) { @@ -93,18 +100,18 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } boolean dumpMap(PrintWriter out, String titlePrefix, String title, - String prefix, Map<String, ArrayList<F>> map, String packageName, + String prefix, Map<String, F[]> map, String packageName, boolean printFilter) { String eprefix = prefix + " "; String fprefix = prefix + " "; boolean printedSomething = false; Printer printer = null; - for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) { - ArrayList<F> a = e.getValue(); - final int N = a.size(); + for (Map.Entry<String, F[]> e : map.entrySet()) { + F[] a = e.getValue(); + final int N = a.length; boolean printedHeader = false; - for (int i=0; i<N; i++) { - F filter = a.get(i); + F filter; + for (int i=0; i<N && (filter=a[i]) != null; i++) { if (packageName != null && !packageName.equals(packageForFilter(filter))) { continue; } @@ -201,7 +208,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } public List<R> queryIntentFromList(Intent intent, String resolvedType, - boolean defaultOnly, ArrayList<ArrayList<F>> listCut, int userId) { + boolean defaultOnly, ArrayList<F[]> listCut, int userId) { ArrayList<R> resultList = new ArrayList<R>(); final boolean debug = localLOGV || @@ -231,10 +238,10 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { TAG, "Resolving type " + resolvedType + " scheme " + scheme + " of intent " + intent); - ArrayList<F> firstTypeCut = null; - ArrayList<F> secondTypeCut = null; - ArrayList<F> thirdTypeCut = null; - ArrayList<F> schemeCut = null; + F[] firstTypeCut = null; + F[] secondTypeCut = null; + F[] thirdTypeCut = null; + F[] schemeCut = null; // If the intent includes a MIME type, then we want to collect all of // the filters that match that MIME type. @@ -307,6 +314,14 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } sortResults(finalList); + List<R> oldList = mOldResolver.queryIntent(intent, resolvedType, defaultOnly, userId); + if (oldList.size() != finalList.size()) { + ValidationFailure here = new ValidationFailure(); + here.fillInStackTrace(); + Log.wtf(TAG, "Query result " + intent + " size is " + finalList.size() + + "; old implementation is " + oldList.size(), here); + } + if (debug) { Slog.v(TAG, "Final result list:"); for (R r : finalList) { @@ -340,7 +355,9 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { * they are to be delivered to. */ protected abstract String packageForFilter(F filter); - + + protected abstract F[] newArray(int size); + @SuppressWarnings("unchecked") protected R newResult(F filter, int match, int userId) { return (R)filter; @@ -355,6 +372,29 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { out.print(prefix); out.println(filter); } + private final void addFilter(HashMap<String, F[]> map, String name, F filter) { + F[] array = map.get(name); + if (array == null) { + array = newArray(2); + map.put(name, array); + array[0] = filter; + } else { + final int N = array.length; + int i = N; + while (i > 0 && array[i-1] == null) { + i--; + } + if (i < N) { + array[i] = filter; + } else { + F[] newa = newArray((N*3)/2); + System.arraycopy(array, 0, newa, 0, N); + newa[N] = filter; + map.put(name, newa); + } + } + } + private final int register_mime_types(F filter, String prefix) { final Iterator<String> i = filter.typesIterator(); if (i == null) { @@ -374,30 +414,12 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { name = name + "/*"; } - ArrayList<F> array = mTypeToFilter.get(name); - if (array == null) { - //Slog.v(TAG, "Creating new array for " + name); - array = new ArrayList<F>(); - mTypeToFilter.put(name, array); - } - array.add(filter); + addFilter(mTypeToFilter, name, filter); if (slashpos > 0) { - array = mBaseTypeToFilter.get(baseName); - if (array == null) { - //Slog.v(TAG, "Creating new array for " + name); - array = new ArrayList<F>(); - mBaseTypeToFilter.put(baseName, array); - } - array.add(filter); + addFilter(mBaseTypeToFilter, baseName, filter); } else { - array = mWildTypeToFilter.get(baseName); - if (array == null) { - //Slog.v(TAG, "Creating new array for " + name); - array = new ArrayList<F>(); - mWildTypeToFilter.put(baseName, array); - } - array.add(filter); + addFilter(mWildTypeToFilter, baseName, filter); } } @@ -423,25 +445,19 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { name = name + "/*"; } - if (!remove_all_objects(mTypeToFilter.get(name), filter)) { - mTypeToFilter.remove(name); - } + remove_all_objects(mTypeToFilter, name, filter); if (slashpos > 0) { - if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) { - mBaseTypeToFilter.remove(baseName); - } + remove_all_objects(mBaseTypeToFilter, baseName, filter); } else { - if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) { - mWildTypeToFilter.remove(baseName); - } + remove_all_objects(mWildTypeToFilter, baseName, filter); } } return num; } private final int register_intent_filter(F filter, Iterator<String> i, - HashMap<String, ArrayList<F>> dest, String prefix) { + HashMap<String, F[]> dest, String prefix) { if (i == null) { return 0; } @@ -451,19 +467,13 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { String name = i.next(); num++; if (localLOGV) Slog.v(TAG, prefix + name); - ArrayList<F> array = dest.get(name); - if (array == null) { - //Slog.v(TAG, "Creating new array for " + name); - array = new ArrayList<F>(); - dest.put(name, array); - } - array.add(filter); + addFilter(dest, name, filter); } return num; } private final int unregister_intent_filter(F filter, Iterator<String> i, - HashMap<String, ArrayList<F>> dest, String prefix) { + HashMap<String, F[]> dest, String prefix) { if (i == null) { return 0; } @@ -473,26 +483,37 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { String name = i.next(); num++; if (localLOGV) Slog.v(TAG, prefix + name); - if (!remove_all_objects(dest.get(name), filter)) { - dest.remove(name); - } + remove_all_objects(dest, name, filter); } return num; } - private final boolean remove_all_objects(List<F> list, Object object) { - if (list != null) { - int N = list.size(); - for (int idx=0; idx<N; idx++) { - if (list.get(idx) == object) { - list.remove(idx); - idx--; - N--; + private final void remove_all_objects(HashMap<String, F[]> map, String name, + Object object) { + F[] array = map.get(name); + if (array != null) { + int LAST = array.length-1; + while (LAST >= 0 && array[LAST] == null) { + LAST--; + } + for (int idx=LAST; idx>=0; idx--) { + if (array[idx] == object) { + final int remain = LAST - idx; + if (remain > 0) { + System.arraycopy(array, idx+1, array, idx, remain); + } + array[LAST] = null; + LAST--; } } - return N > 0; + if (LAST < 0) { + map.remove(name); + } else if (LAST < (array.length/2)) { + F[] newa = newArray(LAST+2); + System.arraycopy(array, 0, newa, 0, LAST+1); + map.put(name, newa); + } } - return false; } private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) { @@ -505,18 +526,18 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories, boolean debug, boolean defaultOnly, - String resolvedType, String scheme, List<F> src, List<R> dest, int userId) { + String resolvedType, String scheme, F[] src, List<R> dest, int userId) { final String action = intent.getAction(); final Uri data = intent.getData(); final String packageName = intent.getPackage(); final boolean excludingStopped = intent.isExcludingStopped(); - final int N = src != null ? src.size() : 0; + final int N = src != null ? src.length : 0; boolean hasNonDefaults = false; int i; - for (i=0; i<N; i++) { - F filter = src.get(i); + F filter; + for (i=0; i<N && (filter=src[i]) != null; i++) { int match; if (debug) Slog.v(TAG, "Matching against filter " + filter); @@ -585,6 +606,120 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } }; + static class ValidationFailure extends RuntimeException { + } + + private void verifyDataStructures(IntentFilter src) { + compareMaps(src, "mTypeToFilter", mTypeToFilter, mOldResolver.mTypeToFilter); + compareMaps(src, "mBaseTypeToFilter", mBaseTypeToFilter, mOldResolver.mBaseTypeToFilter); + compareMaps(src, "mWildTypeToFilter", mWildTypeToFilter, mOldResolver.mWildTypeToFilter); + compareMaps(src, "mSchemeToFilter", mSchemeToFilter, mOldResolver.mSchemeToFilter); + compareMaps(src, "mActionToFilter", mActionToFilter, mOldResolver.mActionToFilter); + compareMaps(src, "mTypedActionToFilter", mTypedActionToFilter, mOldResolver.mTypedActionToFilter); + } + + private void compareMaps(IntentFilter src, String name, HashMap<String, F[]> cur, + HashMap<String, ArrayList<F>> old) { + if (cur.size() != old.size()) { + StringBuilder missing = new StringBuilder(128); + for (Map.Entry<String, ArrayList<F>> e : old.entrySet()) { + final F[] curArray = cur.get(e.getKey()); + if (curArray == null) { + if (missing.length() > 0) { + missing.append(' '); + } + missing.append(e.getKey()); + } + } + StringBuilder extra = new StringBuilder(128); + for (Map.Entry<String, F[]> e : cur.entrySet()) { + if (old.get(e.getKey()) == null) { + if (extra.length() > 0) { + extra.append(' '); + } + extra.append(e.getKey()); + } + } + StringBuilder srcStr = new StringBuilder(1024); + StringBuilderPrinter printer = new StringBuilderPrinter(srcStr); + src.dump(printer, ""); + ValidationFailure here = new ValidationFailure(); + here.fillInStackTrace(); + Log.wtf(TAG, "New map " + name + " size is " + cur.size() + + "; old implementation is " + old.size() + + "; missing: " + missing.toString() + + "; extra: " + extra.toString() + + "; src: " + srcStr.toString(), here); + return; + } + for (Map.Entry<String, ArrayList<F>> e : old.entrySet()) { + final F[] curArray = cur.get(e.getKey()); + int curLen = curArray != null ? curArray.length : 0; + if (curLen == 0) { + ValidationFailure here = new ValidationFailure(); + here.fillInStackTrace(); + Log.wtf(TAG, "New map " + name + " doesn't contain expected key " + + e.getKey() + " (array=" + curArray + ")"); + return; + } + while (curLen > 0 && curArray[curLen-1] == null) { + curLen--; + } + final ArrayList<F> oldArray = e.getValue(); + final int oldLen = oldArray.size(); + if (curLen != oldLen) { + ValidationFailure here = new ValidationFailure(); + here.fillInStackTrace(); + Log.wtf(TAG, "New map " + name + " entry " + e.getKey() + " size is " + + curLen + "; old implementation is " + oldLen, here); + return; + } + for (int i=0; i<oldLen; i++) { + F f = oldArray.get(i); + boolean found = false; + for (int j=0; j<curLen; j++) { + if (curArray[j] == f) { + found = true; + break; + } + } + if (!found) { + ValidationFailure here = new ValidationFailure(); + here.fillInStackTrace(); + Log.wtf(TAG, "New map " + name + " entry + " + e.getKey() + + " doesn't contain expected filter " + f, here); + } + } + for (int i=0; i<curLen; i++) { + if (curArray[i] == null) { + ValidationFailure here = new ValidationFailure(); + here.fillInStackTrace(); + Log.wtf(TAG, "New map " + name + " entry + " + e.getKey() + + " has unexpected null at " + i + "; array: " + curArray, here); + break; + } + } + } + } + + private final IntentResolverOld<F, R> mOldResolver = new IntentResolverOld<F, R>() { + @Override protected String packageForFilter(F filter) { + return IntentResolver.this.packageForFilter(filter); + } + @Override protected boolean allowFilterResult(F filter, List<R> dest) { + return IntentResolver.this.allowFilterResult(filter, dest); + } + @Override protected boolean isFilterStopped(F filter, int userId) { + return IntentResolver.this.isFilterStopped(filter, userId); + } + @Override protected R newResult(F filter, int match, int userId) { + return IntentResolver.this.newResult(filter, match, userId); + } + @Override protected void sortResults(List<R> results) { + IntentResolver.this.sortResults(results); + } + }; + /** * All filters that have been registered. */ @@ -594,16 +729,14 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { * All of the MIME types that have been registered, such as "image/jpeg", * "image/*", or "{@literal *}/*". */ - private final HashMap<String, ArrayList<F>> mTypeToFilter - = new HashMap<String, ArrayList<F>>(); + private final HashMap<String, F[]> mTypeToFilter = new HashMap<String, F[]>(); /** * The base names of all of all fully qualified MIME types that have been * registered, such as "image" or "*". Wild card MIME types such as * "image/*" will not be here. */ - private final HashMap<String, ArrayList<F>> mBaseTypeToFilter - = new HashMap<String, ArrayList<F>>(); + private final HashMap<String, F[]> mBaseTypeToFilter = new HashMap<String, F[]>(); /** * The base names of all of the MIME types with a sub-type wildcard that @@ -612,26 +745,21 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { * included here. This also includes the "*" for the "{@literal *}/*" * MIME type. */ - private final HashMap<String, ArrayList<F>> mWildTypeToFilter - = new HashMap<String, ArrayList<F>>(); + private final HashMap<String, F[]> mWildTypeToFilter = new HashMap<String, F[]>(); /** * All of the URI schemes (such as http) that have been registered. */ - private final HashMap<String, ArrayList<F>> mSchemeToFilter - = new HashMap<String, ArrayList<F>>(); + private final HashMap<String, F[]> mSchemeToFilter = new HashMap<String, F[]>(); /** * All of the actions that have been registered, but only those that did * not specify data. */ - private final HashMap<String, ArrayList<F>> mActionToFilter - = new HashMap<String, ArrayList<F>>(); + private final HashMap<String, F[]> mActionToFilter = new HashMap<String, F[]>(); /** * All of the actions that have been registered and specified a MIME type. */ - private final HashMap<String, ArrayList<F>> mTypedActionToFilter - = new HashMap<String, ArrayList<F>>(); + private final HashMap<String, F[]> mTypedActionToFilter = new HashMap<String, F[]>(); } - diff --git a/services/java/com/android/server/IntentResolverOld.java b/services/java/com/android/server/IntentResolverOld.java new file mode 100644 index 0000000..4dd77ce --- /dev/null +++ b/services/java/com/android/server/IntentResolverOld.java @@ -0,0 +1,638 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import android.net.Uri; +import android.util.FastImmutableArraySet; +import android.util.Log; +import android.util.PrintWriterPrinter; +import android.util.Slog; +import android.util.LogPrinter; +import android.util.Printer; + +import android.content.Intent; +import android.content.IntentFilter; + +/** + * Temporary for verification of new implementation. + * {@hide} + */ +public abstract class IntentResolverOld<F extends IntentFilter, R extends Object> { + final private static String TAG = "IntentResolver"; + final private static boolean DEBUG = false; + final private static boolean localLOGV = DEBUG || false; + + public void addFilter(F f) { + if (localLOGV) { + Slog.v(TAG, "Adding filter: " + f); + f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " "); + Slog.v(TAG, " Building Lookup Maps:"); + } + + mFilters.add(f); + int numS = register_intent_filter(f, f.schemesIterator(), + mSchemeToFilter, " Scheme: "); + int numT = register_mime_types(f, " Type: "); + if (numS == 0 && numT == 0) { + register_intent_filter(f, f.actionsIterator(), + mActionToFilter, " Action: "); + } + if (numT != 0) { + register_intent_filter(f, f.actionsIterator(), + mTypedActionToFilter, " TypedAction: "); + } + } + + public void removeFilter(F f) { + removeFilterInternal(f); + mFilters.remove(f); + } + + void removeFilterInternal(F f) { + if (localLOGV) { + Slog.v(TAG, "Removing filter: " + f); + f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " "); + Slog.v(TAG, " Cleaning Lookup Maps:"); + } + + int numS = unregister_intent_filter(f, f.schemesIterator(), + mSchemeToFilter, " Scheme: "); + int numT = unregister_mime_types(f, " Type: "); + if (numS == 0 && numT == 0) { + unregister_intent_filter(f, f.actionsIterator(), + mActionToFilter, " Action: "); + } + if (numT != 0) { + unregister_intent_filter(f, f.actionsIterator(), + mTypedActionToFilter, " TypedAction: "); + } + } + + boolean dumpMap(PrintWriter out, String titlePrefix, String title, + String prefix, Map<String, ArrayList<F>> map, String packageName, + boolean printFilter) { + String eprefix = prefix + " "; + String fprefix = prefix + " "; + boolean printedSomething = false; + Printer printer = null; + for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) { + ArrayList<F> a = e.getValue(); + final int N = a.size(); + boolean printedHeader = false; + for (int i=0; i<N; i++) { + F filter = a.get(i); + if (packageName != null && !packageName.equals(packageForFilter(filter))) { + continue; + } + if (title != null) { + out.print(titlePrefix); out.println(title); + title = null; + } + if (!printedHeader) { + out.print(eprefix); out.print(e.getKey()); out.println(":"); + printedHeader = true; + } + printedSomething = true; + dumpFilter(out, fprefix, filter); + if (printFilter) { + if (printer == null) { + printer = new PrintWriterPrinter(out); + } + filter.dump(printer, fprefix + " "); + } + } + } + return printedSomething; + } + + public boolean dump(PrintWriter out, String title, String prefix, String packageName, + boolean printFilter) { + String innerPrefix = prefix + " "; + String sepPrefix = "\n" + prefix; + String curPrefix = title + "\n" + prefix; + if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix, + mTypeToFilter, packageName, printFilter)) { + curPrefix = sepPrefix; + } + if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix, + mBaseTypeToFilter, packageName, printFilter)) { + curPrefix = sepPrefix; + } + if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix, + mWildTypeToFilter, packageName, printFilter)) { + curPrefix = sepPrefix; + } + if (dumpMap(out, curPrefix, "Schemes:", innerPrefix, + mSchemeToFilter, packageName, printFilter)) { + curPrefix = sepPrefix; + } + if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix, + mActionToFilter, packageName, printFilter)) { + curPrefix = sepPrefix; + } + if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix, + mTypedActionToFilter, packageName, printFilter)) { + curPrefix = sepPrefix; + } + return curPrefix == sepPrefix; + } + + private class IteratorWrapper implements Iterator<F> { + private final Iterator<F> mI; + private F mCur; + + IteratorWrapper(Iterator<F> it) { + mI = it; + } + + public boolean hasNext() { + return mI.hasNext(); + } + + public F next() { + return (mCur = mI.next()); + } + + public void remove() { + if (mCur != null) { + removeFilterInternal(mCur); + } + mI.remove(); + } + + } + + /** + * Returns an iterator allowing filters to be removed. + */ + public Iterator<F> filterIterator() { + return new IteratorWrapper(mFilters.iterator()); + } + + /** + * Returns a read-only set of the filters. + */ + public Set<F> filterSet() { + return Collections.unmodifiableSet(mFilters); + } + + public List<R> queryIntentFromList(Intent intent, String resolvedType, + boolean defaultOnly, ArrayList<ArrayList<F>> listCut, int userId) { + ArrayList<R> resultList = new ArrayList<R>(); + + final boolean debug = localLOGV || + ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); + + FastImmutableArraySet<String> categories = getFastIntentCategories(intent); + final String scheme = intent.getScheme(); + int N = listCut.size(); + for (int i = 0; i < N; ++i) { + buildResolveList(intent, categories, debug, defaultOnly, + resolvedType, scheme, listCut.get(i), resultList, userId); + } + sortResults(resultList); + return resultList; + } + + public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly, + int userId) { + String scheme = intent.getScheme(); + + ArrayList<R> finalList = new ArrayList<R>(); + + final boolean debug = localLOGV || + ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); + + if (debug) Slog.v( + TAG, "Resolving type " + resolvedType + " scheme " + scheme + + " of intent " + intent); + + ArrayList<F> firstTypeCut = null; + ArrayList<F> secondTypeCut = null; + ArrayList<F> thirdTypeCut = null; + ArrayList<F> schemeCut = null; + + // If the intent includes a MIME type, then we want to collect all of + // the filters that match that MIME type. + if (resolvedType != null) { + int slashpos = resolvedType.indexOf('/'); + if (slashpos > 0) { + final String baseType = resolvedType.substring(0, slashpos); + if (!baseType.equals("*")) { + if (resolvedType.length() != slashpos+2 + || resolvedType.charAt(slashpos+1) != '*') { + // Not a wild card, so we can just look for all filters that + // completely match or wildcards whose base type matches. + firstTypeCut = mTypeToFilter.get(resolvedType); + if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut); + secondTypeCut = mWildTypeToFilter.get(baseType); + if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut); + } else { + // We can match anything with our base type. + firstTypeCut = mBaseTypeToFilter.get(baseType); + if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut); + secondTypeCut = mWildTypeToFilter.get(baseType); + if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut); + } + // Any */* types always apply, but we only need to do this + // if the intent type was not already */*. + thirdTypeCut = mWildTypeToFilter.get("*"); + if (debug) Slog.v(TAG, "Third type cut: " + thirdTypeCut); + } else if (intent.getAction() != null) { + // The intent specified any type ({@literal *}/*). This + // can be a whole heck of a lot of things, so as a first + // cut let's use the action instead. + firstTypeCut = mTypedActionToFilter.get(intent.getAction()); + if (debug) Slog.v(TAG, "Typed Action list: " + firstTypeCut); + } + } + } + + // If the intent includes a data URI, then we want to collect all of + // the filters that match its scheme (we will further refine matches + // on the authority and path by directly matching each resulting filter). + if (scheme != null) { + schemeCut = mSchemeToFilter.get(scheme); + if (debug) Slog.v(TAG, "Scheme list: " + schemeCut); + } + + // If the intent does not specify any data -- either a MIME type or + // a URI -- then we will only be looking for matches against empty + // data. + if (resolvedType == null && scheme == null && intent.getAction() != null) { + firstTypeCut = mActionToFilter.get(intent.getAction()); + if (debug) Slog.v(TAG, "Action list: " + firstTypeCut); + } + + FastImmutableArraySet<String> categories = getFastIntentCategories(intent); + if (firstTypeCut != null) { + buildResolveList(intent, categories, debug, defaultOnly, + resolvedType, scheme, firstTypeCut, finalList, userId); + } + if (secondTypeCut != null) { + buildResolveList(intent, categories, debug, defaultOnly, + resolvedType, scheme, secondTypeCut, finalList, userId); + } + if (thirdTypeCut != null) { + buildResolveList(intent, categories, debug, defaultOnly, + resolvedType, scheme, thirdTypeCut, finalList, userId); + } + if (schemeCut != null) { + buildResolveList(intent, categories, debug, defaultOnly, + resolvedType, scheme, schemeCut, finalList, userId); + } + sortResults(finalList); + + if (debug) { + Slog.v(TAG, "Final result list:"); + for (R r : finalList) { + Slog.v(TAG, " " + r); + } + } + return finalList; + } + + /** + * Control whether the given filter is allowed to go into the result + * list. Mainly intended to prevent adding multiple filters for the + * same target object. + */ + protected boolean allowFilterResult(F filter, List<R> dest) { + return true; + } + + /** + * Returns whether the object associated with the given filter is + * "stopped," that is whether it should not be included in the result + * if the intent requests to excluded stopped objects. + */ + protected boolean isFilterStopped(F filter, int userId) { + return false; + } + + /** + * Return the package that owns this filter. This must be implemented to + * provide correct filtering of Intents that have specified a package name + * they are to be delivered to. + */ + protected abstract String packageForFilter(F filter); + + @SuppressWarnings("unchecked") + protected R newResult(F filter, int match, int userId) { + return (R)filter; + } + + @SuppressWarnings("unchecked") + protected void sortResults(List<R> results) { + Collections.sort(results, mResolvePrioritySorter); + } + + protected void dumpFilter(PrintWriter out, String prefix, F filter) { + out.print(prefix); out.println(filter); + } + + private final int register_mime_types(F filter, String prefix) { + final Iterator<String> i = filter.typesIterator(); + if (i == null) { + return 0; + } + + int num = 0; + while (i.hasNext()) { + String name = i.next(); + num++; + if (localLOGV) Slog.v(TAG, prefix + name); + String baseName = name; + final int slashpos = name.indexOf('/'); + if (slashpos > 0) { + baseName = name.substring(0, slashpos).intern(); + } else { + name = name + "/*"; + } + + ArrayList<F> array = mTypeToFilter.get(name); + if (array == null) { + //Slog.v(TAG, "Creating new array for " + name); + array = new ArrayList<F>(); + mTypeToFilter.put(name, array); + } + array.add(filter); + + if (slashpos > 0) { + array = mBaseTypeToFilter.get(baseName); + if (array == null) { + //Slog.v(TAG, "Creating new array for " + name); + array = new ArrayList<F>(); + mBaseTypeToFilter.put(baseName, array); + } + array.add(filter); + } else { + array = mWildTypeToFilter.get(baseName); + if (array == null) { + //Slog.v(TAG, "Creating new array for " + name); + array = new ArrayList<F>(); + mWildTypeToFilter.put(baseName, array); + } + array.add(filter); + } + } + + return num; + } + + private final int unregister_mime_types(F filter, String prefix) { + final Iterator<String> i = filter.typesIterator(); + if (i == null) { + return 0; + } + + int num = 0; + while (i.hasNext()) { + String name = i.next(); + num++; + if (localLOGV) Slog.v(TAG, prefix + name); + String baseName = name; + final int slashpos = name.indexOf('/'); + if (slashpos > 0) { + baseName = name.substring(0, slashpos).intern(); + } else { + name = name + "/*"; + } + + if (!remove_all_objects(mTypeToFilter.get(name), filter)) { + mTypeToFilter.remove(name); + } + + if (slashpos > 0) { + if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) { + mBaseTypeToFilter.remove(baseName); + } + } else { + if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) { + mWildTypeToFilter.remove(baseName); + } + } + } + return num; + } + + private final int register_intent_filter(F filter, Iterator<String> i, + HashMap<String, ArrayList<F>> dest, String prefix) { + if (i == null) { + return 0; + } + + int num = 0; + while (i.hasNext()) { + String name = i.next(); + num++; + if (localLOGV) Slog.v(TAG, prefix + name); + ArrayList<F> array = dest.get(name); + if (array == null) { + //Slog.v(TAG, "Creating new array for " + name); + array = new ArrayList<F>(); + dest.put(name, array); + } + array.add(filter); + } + return num; + } + + private final int unregister_intent_filter(F filter, Iterator<String> i, + HashMap<String, ArrayList<F>> dest, String prefix) { + if (i == null) { + return 0; + } + + int num = 0; + while (i.hasNext()) { + String name = i.next(); + num++; + if (localLOGV) Slog.v(TAG, prefix + name); + if (!remove_all_objects(dest.get(name), filter)) { + dest.remove(name); + } + } + return num; + } + + private final boolean remove_all_objects(List<F> list, Object object) { + if (list != null) { + int N = list.size(); + for (int idx=0; idx<N; idx++) { + if (list.get(idx) == object) { + list.remove(idx); + idx--; + N--; + } + } + return N > 0; + } + return false; + } + + private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) { + final Set<String> categories = intent.getCategories(); + if (categories == null) { + return null; + } + return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()])); + } + + private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories, + boolean debug, boolean defaultOnly, + String resolvedType, String scheme, List<F> src, List<R> dest, int userId) { + final String action = intent.getAction(); + final Uri data = intent.getData(); + final String packageName = intent.getPackage(); + + final boolean excludingStopped = intent.isExcludingStopped(); + + final int N = src != null ? src.size() : 0; + boolean hasNonDefaults = false; + int i; + for (i=0; i<N; i++) { + F filter = src.get(i); + int match; + if (debug) Slog.v(TAG, "Matching against filter " + filter); + + if (excludingStopped && isFilterStopped(filter, userId)) { + if (debug) { + Slog.v(TAG, " Filter's target is stopped; skipping"); + } + continue; + } + + // Is delivery being limited to filters owned by a particular package? + if (packageName != null && !packageName.equals(packageForFilter(filter))) { + if (debug) { + Slog.v(TAG, " Filter is not from package " + packageName + "; skipping"); + } + continue; + } + + // Do we already have this one? + if (!allowFilterResult(filter, dest)) { + if (debug) { + Slog.v(TAG, " Filter's target already added"); + } + continue; + } + + match = filter.match(action, resolvedType, scheme, data, categories, TAG); + if (match >= 0) { + if (debug) Slog.v(TAG, " Filter matched! match=0x" + + Integer.toHexString(match)); + if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) { + final R oneResult = newResult(filter, match, userId); + if (oneResult != null) { + dest.add(oneResult); + } + } else { + hasNonDefaults = true; + } + } else { + if (debug) { + String reason; + switch (match) { + case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; + case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; + case IntentFilter.NO_MATCH_DATA: reason = "data"; break; + case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; + default: reason = "unknown reason"; break; + } + Slog.v(TAG, " Filter did not match: " + reason); + } + } + } + + if (dest.size() == 0 && hasNonDefaults) { + Slog.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT"); + } + } + + // Sorts a List of IntentFilter objects into descending priority order. + @SuppressWarnings("rawtypes") + private static final Comparator mResolvePrioritySorter = new Comparator() { + public int compare(Object o1, Object o2) { + final int q1 = ((IntentFilter) o1).getPriority(); + final int q2 = ((IntentFilter) o2).getPriority(); + return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0); + } + }; + + /** + * All filters that have been registered. + */ + final HashSet<F> mFilters = new HashSet<F>(); + + /** + * All of the MIME types that have been registered, such as "image/jpeg", + * "image/*", or "{@literal *}/*". + */ + final HashMap<String, ArrayList<F>> mTypeToFilter + = new HashMap<String, ArrayList<F>>(); + + /** + * The base names of all of all fully qualified MIME types that have been + * registered, such as "image" or "*". Wild card MIME types such as + * "image/*" will not be here. + */ + final HashMap<String, ArrayList<F>> mBaseTypeToFilter + = new HashMap<String, ArrayList<F>>(); + + /** + * The base names of all of the MIME types with a sub-type wildcard that + * have been registered. For example, a filter with "image/*" will be + * included here as "image" but one with "image/jpeg" will not be + * included here. This also includes the "*" for the "{@literal *}/*" + * MIME type. + */ + final HashMap<String, ArrayList<F>> mWildTypeToFilter + = new HashMap<String, ArrayList<F>>(); + + /** + * All of the URI schemes (such as http) that have been registered. + */ + final HashMap<String, ArrayList<F>> mSchemeToFilter + = new HashMap<String, ArrayList<F>>(); + + /** + * All of the actions that have been registered, but only those that did + * not specify data. + */ + final HashMap<String, ArrayList<F>> mActionToFilter + = new HashMap<String, ArrayList<F>>(); + + /** + * All of the actions that have been registered and specified a MIME type. + */ + final HashMap<String, ArrayList<F>> mTypedActionToFilter + = new HashMap<String, ArrayList<F>>(); +} + diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java index 1e95f3e..89bfcac 100644 --- a/services/java/com/android/server/LightsService.java +++ b/services/java/com/android/server/LightsService.java @@ -31,29 +31,29 @@ public class LightsService { private static final String TAG = "LightsService"; private static final boolean DEBUG = false; - static final int LIGHT_ID_BACKLIGHT = 0; - static final int LIGHT_ID_KEYBOARD = 1; - static final int LIGHT_ID_BUTTONS = 2; - static final int LIGHT_ID_BATTERY = 3; - static final int LIGHT_ID_NOTIFICATIONS = 4; - static final int LIGHT_ID_ATTENTION = 5; - static final int LIGHT_ID_BLUETOOTH = 6; - static final int LIGHT_ID_WIFI = 7; - static final int LIGHT_ID_COUNT = 8; - - static final int LIGHT_FLASH_NONE = 0; - static final int LIGHT_FLASH_TIMED = 1; - static final int LIGHT_FLASH_HARDWARE = 2; + public static final int LIGHT_ID_BACKLIGHT = 0; + public static final int LIGHT_ID_KEYBOARD = 1; + public static final int LIGHT_ID_BUTTONS = 2; + public static final int LIGHT_ID_BATTERY = 3; + public static final int LIGHT_ID_NOTIFICATIONS = 4; + public static final int LIGHT_ID_ATTENTION = 5; + public static final int LIGHT_ID_BLUETOOTH = 6; + public static final int LIGHT_ID_WIFI = 7; + public static final int LIGHT_ID_COUNT = 8; + + public static final int LIGHT_FLASH_NONE = 0; + public static final int LIGHT_FLASH_TIMED = 1; + public static final int LIGHT_FLASH_HARDWARE = 2; /** * Light brightness is managed by a user setting. */ - static final int BRIGHTNESS_MODE_USER = 0; + public static final int BRIGHTNESS_MODE_USER = 0; /** * Light brightness is managed by a light sensor. */ - static final int BRIGHTNESS_MODE_SENSOR = 1; + public static final int BRIGHTNESS_MODE_SENSOR = 1; private final Light mLights[] = new Light[LIGHT_ID_COUNT]; diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 8c1581c..197f6ab 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -16,27 +16,22 @@ package com.android.server; -import android.app.Activity; import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.ContentQueryMap; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.PackageInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.Signature; import android.content.res.Resources; import android.database.ContentObserver; import android.database.Cursor; import android.location.Address; import android.location.Criteria; import android.location.GeocoderParams; +import android.location.Geofence; import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; import android.location.ILocationListener; @@ -45,9 +40,8 @@ import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; +import android.location.LocationRequest; import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -57,17 +51,22 @@ import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; +import android.provider.Settings.NameValueTable; import android.util.Log; import android.util.Slog; -import android.util.PrintWriterPrinter; import com.android.internal.content.PackageMonitor; -import com.android.internal.location.GpsNetInitiatedHandler; - +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; import com.android.server.location.GeocoderProxy; +import com.android.server.location.GeofenceManager; import com.android.server.location.GpsLocationProvider; +import com.android.server.location.LocationBlacklist; +import com.android.server.location.LocationFudger; import com.android.server.location.LocationProviderInterface; import com.android.server.location.LocationProviderProxy; import com.android.server.location.MockProvider; @@ -76,12 +75,9 @@ import com.android.server.location.PassiveProvider; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Observable; @@ -91,154 +87,264 @@ import java.util.Set; /** * The service class that manages LocationProviders and issues location * updates and alerts. - * - * {@hide} */ public class LocationManagerService extends ILocationManager.Stub implements Runnable { private static final String TAG = "LocationManagerService"; - private static final boolean LOCAL_LOGV = false; + public static final boolean D = false; - // The last time a location was written, by provider name. - private HashMap<String,Long> mLastWriteTime = new HashMap<String,Long>(); + private static final String WAKELOCK_KEY = TAG; + private static final String THREAD_NAME = TAG; private static final String ACCESS_FINE_LOCATION = - android.Manifest.permission.ACCESS_FINE_LOCATION; + android.Manifest.permission.ACCESS_FINE_LOCATION; private static final String ACCESS_COARSE_LOCATION = - android.Manifest.permission.ACCESS_COARSE_LOCATION; + android.Manifest.permission.ACCESS_COARSE_LOCATION; private static final String ACCESS_MOCK_LOCATION = - android.Manifest.permission.ACCESS_MOCK_LOCATION; + android.Manifest.permission.ACCESS_MOCK_LOCATION; private static final String ACCESS_LOCATION_EXTRA_COMMANDS = - android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; + android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS; private static final String INSTALL_LOCATION_PROVIDER = - android.Manifest.permission.INSTALL_LOCATION_PROVIDER; + android.Manifest.permission.INSTALL_LOCATION_PROVIDER; + + private static final String NETWORK_LOCATION_SERVICE_ACTION = + "com.android.location.service.v2.NetworkLocationProvider"; + private static final String FUSED_LOCATION_SERVICE_ACTION = + "com.android.location.service.FusedLocationProvider"; - private static final String BLACKLIST_CONFIG_NAME = "locationPackagePrefixBlacklist"; - private static final String WHITELIST_CONFIG_NAME = "locationPackagePrefixWhitelist"; + private static final int MSG_LOCATION_CHANGED = 1; // Location Providers may sometimes deliver location updates // slightly faster that requested - provide grace period so // we don't unnecessarily filter events that are otherwise on // time - private static final int MAX_PROVIDER_SCHEDULING_JITTER = 100; + private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100; - // Set of providers that are explicitly enabled - private final Set<String> mEnabledProviders = new HashSet<String>(); + private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest(); - // Set of providers that are explicitly disabled - private final Set<String> mDisabledProviders = new HashSet<String>(); - - // Locations, status values, and extras for mock providers - private final HashMap<String,MockProvider> mMockProviders = new HashMap<String,MockProvider>(); + private final Context mContext; - private static boolean sProvidersLoaded = false; + // used internally for synchronization + private final Object mLock = new Object(); - private final Context mContext; - private PackageManager mPackageManager; // final after initialize() - private String mNetworkLocationProviderPackageName; // only used on handler thread - private String mGeocodeProviderPackageName; // only used on handler thread + // --- fields below are final after init() --- + private LocationFudger mLocationFudger; + private GeofenceManager mGeofenceManager; + private PowerManager.WakeLock mWakeLock; + private PackageManager mPackageManager; private GeocoderProxy mGeocodeProvider; private IGpsStatusProvider mGpsStatusProvider; private INetInitiatedListener mNetInitiatedListener; private LocationWorkerHandler mLocationHandler; + private PassiveProvider mPassiveProvider; // track passive provider for special cases + private LocationBlacklist mBlacklist; - // Cache the real providers for use in addTestProvider() and removeTestProvider() - LocationProviderProxy mNetworkLocationProvider; - LocationProviderInterface mGpsLocationProvider; + // --- fields below are protected by mWakeLock --- + private int mPendingBroadcasts; - // Handler messages - private static final int MESSAGE_LOCATION_CHANGED = 1; - private static final int MESSAGE_PACKAGE_UPDATED = 2; + // --- fields below are protected by mLock --- + // Set of providers that are explicitly enabled + private final Set<String> mEnabledProviders = new HashSet<String>(); - // wakelock variables - private final static String WAKELOCK_KEY = "LocationManagerService"; - private PowerManager.WakeLock mWakeLock = null; - private int mPendingBroadcasts; - - /** - * List of all receivers. - */ - private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>(); + // Set of providers that are explicitly disabled + private final Set<String> mDisabledProviders = new HashSet<String>(); + // Mock (test) providers + private final HashMap<String, MockProvider> mMockProviders = + new HashMap<String, MockProvider>(); - /** - * List of location providers. - */ + // all receivers + private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>(); + + // currently installed providers (with mocks replacing real providers) private final ArrayList<LocationProviderInterface> mProviders = - new ArrayList<LocationProviderInterface>(); - private final HashMap<String, LocationProviderInterface> mProvidersByName - = new HashMap<String, LocationProviderInterface>(); + new ArrayList<LocationProviderInterface>(); - /** - * Object used internally for synchronization - */ - private final Object mLock = new Object(); + // real providers, saved here when mocked out + private final HashMap<String, LocationProviderInterface> mRealProviders = + new HashMap<String, LocationProviderInterface>(); - /** - * Mapping from provider name to all its UpdateRecords - */ - private final HashMap<String,ArrayList<UpdateRecord>> mRecordsByProvider = - new HashMap<String,ArrayList<UpdateRecord>>(); + // mapping from provider name to provider + private final HashMap<String, LocationProviderInterface> mProvidersByName = + new HashMap<String, LocationProviderInterface>(); - /** - * Temporary filled in when computing min time for a provider. Access is - * protected by global lock mLock. - */ - private final WorkSource mTmpWorkSource = new WorkSource(); + // mapping from provider name to all its UpdateRecords + private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider = + new HashMap<String, ArrayList<UpdateRecord>>(); + + // mapping from provider name to last known location + private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>(); + + // all providers that operate over proxy, for authorizing incoming location + private final ArrayList<LocationProviderProxy> mProxyProviders = + new ArrayList<LocationProviderProxy>(); + + public LocationManagerService(Context context) { + super(); + mContext = context; + + if (D) Log.d(TAG, "Constructed"); + + // most startup is deferred until systemReady() + } + + public void systemReady() { + Thread thread = new Thread(null, this, THREAD_NAME); + thread.start(); + } - // Proximity listeners - private Receiver mProximityReceiver = null; - private ILocationListener mProximityListener = null; - private HashMap<PendingIntent,ProximityAlert> mProximityAlerts = - new HashMap<PendingIntent,ProximityAlert>(); - private HashSet<ProximityAlert> mProximitiesEntered = - new HashSet<ProximityAlert>(); + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + Looper.prepare(); + mLocationHandler = new LocationWorkerHandler(); + init(); + Looper.loop(); + } + + private void init() { + if (D) Log.d(TAG, "init()"); + + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); + mPackageManager = mContext.getPackageManager(); - // Last known location for each provider - private HashMap<String,Location> mLastKnownLocation = - new HashMap<String,Location>(); + mBlacklist = new LocationBlacklist(mContext, mLocationHandler); + mBlacklist.init(); + mLocationFudger = new LocationFudger(mContext, mLocationHandler); + + synchronized (mLock) { + loadProvidersLocked(); + } + + mGeofenceManager = new GeofenceManager(mContext, mBlacklist); + + // listen for settings changes + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true, + new ContentObserver(mLocationHandler) { + @Override + public void onChange(boolean selfChange) { + synchronized (mLock) { + updateProvidersLocked(); + } + } + }); + mPackageMonitor.register(mContext, Looper.myLooper(), true); + + updateProvidersLocked(); + } - private int mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; + private void loadProvidersLocked() { + if (GpsLocationProvider.isSupported()) { + // Create a gps location provider + GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); + mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); + mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); + addProviderLocked(gpsProvider); + mRealProviders.put(LocationManager.GPS_PROVIDER, gpsProvider); + } - // for prefix blacklist - private String[] mWhitelist = new String[0]; - private String[] mBlacklist = new String[0]; + // create a passive location provider, which is always enabled + PassiveProvider passiveProvider = new PassiveProvider(this); + addProviderLocked(passiveProvider); + mEnabledProviders.add(passiveProvider.getName()); + mPassiveProvider = passiveProvider; + + /* + Load package name(s) containing location provider support. + These packages can contain services implementing location providers: + Geocoder Provider, Network Location Provider, and + Fused Location Provider. They will each be searched for + service components implementing these providers. + The location framework also has support for installation + of new location providers at run-time. The new package does not + have to be explicitly listed here, however it must have a signature + that matches the signature of at least one package on this list. + */ + Resources resources = mContext.getResources(); + ArrayList<String> providerPackageNames = new ArrayList<String>(); + String[] pkgs1 = resources.getStringArray( + com.android.internal.R.array.config_locationProviderPackageNames); + String[] pkgs2 = resources.getStringArray( + com.android.internal.R.array.config_overlay_locationProviderPackageNames); + if (D) Log.d(TAG, "built-in location providers: " + Arrays.toString(pkgs1)); + if (D) Log.d(TAG, "overlay location providers: " + Arrays.toString(pkgs2)); + if (pkgs1 != null) providerPackageNames.addAll(Arrays.asList(pkgs1)); + if (pkgs2 != null) providerPackageNames.addAll(Arrays.asList(pkgs2)); + + // bind to network provider + LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( + mContext, + LocationManager.NETWORK_PROVIDER, + NETWORK_LOCATION_SERVICE_ACTION, + providerPackageNames, mLocationHandler); + if (networkProvider != null) { + mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider); + mProxyProviders.add(networkProvider); + addProviderLocked(networkProvider); + } else { + Slog.w(TAG, "no network location provider found"); + } + + // bind to fused provider + LocationProviderProxy fusedLocationProvider = LocationProviderProxy.createAndBind( + mContext, + LocationManager.FUSED_PROVIDER, + FUSED_LOCATION_SERVICE_ACTION, + providerPackageNames, mLocationHandler); + if (fusedLocationProvider != null) { + addProviderLocked(fusedLocationProvider); + mProxyProviders.add(fusedLocationProvider); + mEnabledProviders.add(fusedLocationProvider.getName()); + } else { + Slog.e(TAG, "no fused location provider found", + new IllegalStateException("Location service needs a fused location provider")); + } - // for Settings change notification - private ContentQueryMap mSettings; + // bind to geocoder provider + mGeocodeProvider = GeocoderProxy.createAndBind(mContext, providerPackageNames); + if (mGeocodeProvider == null) { + Slog.e(TAG, "no geocoder provider found"); + } + } /** * A wrapper class holding either an ILocationListener or a PendingIntent to receive * location updates. */ private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished { + final int mUid; // uid of receiver + final int mPid; // pid of receiver + final String mPackageName; // package name of receiver + final String mPermission; // best permission that receiver has + final ILocationListener mListener; final PendingIntent mPendingIntent; final Object mKey; + final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>(); - final String mPackageName; int mPendingBroadcasts; - String mRequiredPermissions; - Receiver(ILocationListener listener, String packageName) { + Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid, + String packageName) { mListener = listener; - mPendingIntent = null; - mKey = listener.asBinder(); - mPackageName = packageName; - } - - Receiver(PendingIntent intent, String packageName) { mPendingIntent = intent; - mListener = null; - mKey = intent; + if (listener != null) { + mKey = listener.asBinder(); + } else { + mKey = intent; + } + mPermission = checkPermission(); + mUid = uid; + mPid = pid; mPackageName = packageName; } @Override public boolean equals(Object otherObj) { if (otherObj instanceof Receiver) { - return mKey.equals( - ((Receiver)otherObj).mKey); + return mKey.equals(((Receiver)otherObj).mKey); } return false; } @@ -250,18 +356,19 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public String toString() { - String result; + StringBuilder s = new StringBuilder(); + s.append("Reciever["); + s.append(Integer.toHexString(System.identityHashCode(this))); if (mListener != null) { - result = "Receiver{" - + Integer.toHexString(System.identityHashCode(this)) - + " Listener " + mKey + "}"; + s.append(" listener"); } else { - result = "Receiver{" - + Integer.toHexString(System.identityHashCode(this)) - + " Intent " + mKey + "}"; + s.append(" intent"); } - result += "mUpdateRecords: " + mUpdateRecords; - return result; + for (String p : mUpdateRecords.keySet()) { + s.append(" ").append(mUpdateRecords.get(p).toString()); + } + s.append("]"); + return s.toString(); } public boolean isListener() { @@ -279,13 +386,6 @@ public class LocationManagerService extends ILocationManager.Stub implements Run throw new IllegalStateException("Request for non-existent listener"); } - public PendingIntent getPendingIntent() { - if (mPendingIntent != null) { - return mPendingIntent; - } - throw new IllegalStateException("Request for non-existent intent"); - } - public boolean callStatusChangedLocked(String provider, int status, Bundle extras) { if (mListener != null) { try { @@ -293,11 +393,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mListener.onStatusChanged(provider, status, extras); - if (mListener != mProximityListener) { - // call this after broadcasting so we do not increment - // if we throw an exeption. - incrementPendingBroadcastsLocked(); - } + // call this after broadcasting so we do not increment + // if we throw an exeption. + incrementPendingBroadcastsLocked(); } } catch (RemoteException e) { return false; @@ -311,7 +409,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -330,11 +428,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mListener.onLocationChanged(location); - if (mListener != mProximityListener) { - // call this after broadcasting so we do not increment - // if we throw an exeption. - incrementPendingBroadcastsLocked(); - } + // call this after broadcasting so we do not increment + // if we throw an exeption. + incrementPendingBroadcastsLocked(); } } catch (RemoteException e) { return false; @@ -347,7 +443,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -370,11 +466,9 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } else { mListener.onProviderDisabled(provider); } - if (mListener != mProximityListener) { - // call this after broadcasting so we do not increment - // if we throw an exeption. - incrementPendingBroadcastsLocked(); - } + // call this after broadcasting so we do not increment + // if we throw an exeption. + incrementPendingBroadcastsLocked(); } } catch (RemoteException e) { return false; @@ -387,7 +481,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // synchronize to ensure incrementPendingBroadcastsLocked() // is called before decrementPendingBroadcasts() mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler, - mRequiredPermissions); + mPermission); // call this after broadcasting so we do not increment // if we throw an exeption. incrementPendingBroadcastsLocked(); @@ -401,9 +495,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run @Override public void binderDied() { - if (LOCAL_LOGV) { - Slog.v(TAG, "Location listener died"); - } + if (D) Log.d(TAG, "Location listener died"); + synchronized (mLock) { removeUpdatesLocked(this); } @@ -415,6 +508,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + @Override public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, String resultData, Bundle resultExtras) { synchronized (this) { @@ -437,6 +531,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + @Override public void locationCallbackFinished(ILocationListener listener) { //Do not use getReceiver here as that will add the ILocationListener to //the receiver list if it is not found. If it is not found then the @@ -454,198 +549,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - private final class SettingsObserver implements Observer { - public void update(Observable o, Object arg) { - synchronized (mLock) { - updateProvidersLocked(); - } - } - } - - private void addProvider(LocationProviderInterface provider) { + private void addProviderLocked(LocationProviderInterface provider) { mProviders.add(provider); mProvidersByName.put(provider.getName(), provider); } - private void removeProvider(LocationProviderInterface provider) { + private void removeProviderLocked(LocationProviderInterface provider) { + provider.disable(); mProviders.remove(provider); mProvidersByName.remove(provider.getName()); } - private void loadProviders() { - synchronized (mLock) { - if (sProvidersLoaded) { - return; - } - - // Load providers - loadProvidersLocked(); - sProvidersLoaded = true; - } - } - - private void loadProvidersLocked() { - try { - _loadProvidersLocked(); - } catch (Exception e) { - Slog.e(TAG, "Exception loading providers:", e); - } - } - - private void _loadProvidersLocked() { - // Attempt to load "real" providers first - if (GpsLocationProvider.isSupported()) { - // Create a gps location provider - GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this); - mGpsStatusProvider = gpsProvider.getGpsStatusProvider(); - mNetInitiatedListener = gpsProvider.getNetInitiatedListener(); - addProvider(gpsProvider); - mGpsLocationProvider = gpsProvider; - } - - // create a passive location provider, which is always enabled - PassiveProvider passiveProvider = new PassiveProvider(this); - addProvider(passiveProvider); - mEnabledProviders.add(passiveProvider.getName()); - - // initialize external network location and geocoder services. - // The initial value of mNetworkLocationProviderPackageName and - // mGeocodeProviderPackageName is just used to determine what - // signatures future mNetworkLocationProviderPackageName and - // mGeocodeProviderPackageName packages must have. So alternate - // providers can be installed under a different package name - // so long as they have the same signature as the original - // provider packages. - if (mNetworkLocationProviderPackageName != null) { - String packageName = findBestPackage(LocationProviderProxy.SERVICE_ACTION, - mNetworkLocationProviderPackageName); - if (packageName != null) { - mNetworkLocationProvider = new LocationProviderProxy(mContext, - LocationManager.NETWORK_PROVIDER, - packageName, mLocationHandler); - mNetworkLocationProviderPackageName = packageName; - addProvider(mNetworkLocationProvider); - } - } - if (mGeocodeProviderPackageName != null) { - String packageName = findBestPackage(GeocoderProxy.SERVICE_ACTION, - mGeocodeProviderPackageName); - if (packageName != null) { - mGeocodeProvider = new GeocoderProxy(mContext, packageName); - mGeocodeProviderPackageName = packageName; - } - } - - updateProvidersLocked(); - } - - /** - * Pick the best (network location provider or geocode provider) package. - * The best package: - * - implements serviceIntentName - * - has signatures that match that of sigPackageName - * - has the highest version value in a meta-data field in the service component - */ - String findBestPackage(String serviceIntentName, String sigPackageName) { - Intent intent = new Intent(serviceIntentName); - List<ResolveInfo> infos = mPackageManager.queryIntentServices(intent, - PackageManager.GET_META_DATA); - if (infos == null) return null; - - int bestVersion = Integer.MIN_VALUE; - String bestPackage = null; - for (ResolveInfo info : infos) { - String packageName = info.serviceInfo.packageName; - // check signature - if (mPackageManager.checkSignatures(packageName, sigPackageName) != - PackageManager.SIGNATURE_MATCH) { - Slog.w(TAG, packageName + " implements " + serviceIntentName + - " but its signatures don't match those in " + sigPackageName + - ", ignoring"); - continue; - } - // read version - int version = 0; - if (info.serviceInfo.metaData != null) { - version = info.serviceInfo.metaData.getInt("version", 0); - } - if (LOCAL_LOGV) Slog.v(TAG, packageName + " implements " + serviceIntentName + - " with version " + version); - if (version > bestVersion) { - bestVersion = version; - bestPackage = packageName; - } - } - - return bestPackage; - } - - /** - * @param context the context that the LocationManagerService runs in - */ - public LocationManagerService(Context context) { - super(); - mContext = context; - Resources resources = context.getResources(); - - mNetworkLocationProviderPackageName = resources.getString( - com.android.internal.R.string.config_networkLocationProviderPackageName); - mGeocodeProviderPackageName = resources.getString( - com.android.internal.R.string.config_geocodeProviderPackageName); - - mPackageMonitor.register(context, null, true); - - if (LOCAL_LOGV) { - Slog.v(TAG, "Constructed LocationManager Service"); - } - } - - void systemReady() { - // we defer starting up the service until the system is ready - Thread thread = new Thread(null, this, "LocationManagerService"); - thread.start(); - } - - private void initialize() { - // Create a wake lock, needs to be done before calling loadProviders() below - PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); - mPackageManager = mContext.getPackageManager(); - - // Load providers - loadProviders(); - loadBlacklist(); - - // Register for Network (Wifi or Mobile) updates - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - // Register for Package Manager updates - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); - intentFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); - mContext.registerReceiver(mBroadcastReceiver, intentFilter); - IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mBroadcastReceiver, sdFilter); - - // listen for settings changes - ContentResolver resolver = mContext.getContentResolver(); - Cursor settingsCursor = resolver.query(Settings.Secure.CONTENT_URI, null, - "(" + Settings.System.NAME + "=?)", - new String[]{Settings.Secure.LOCATION_PROVIDERS_ALLOWED}, - null); - mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mLocationHandler); - SettingsObserver settingsObserver = new SettingsObserver(); - mSettings.addObserver(settingsObserver); - } - - public void run() - { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - Looper.prepare(); - mLocationHandler = new LocationWorkerHandler(); - initialize(); - Looper.loop(); - } private boolean isAllowedBySettingsLocked(String provider) { if (mEnabledProviders.contains(provider)) { @@ -660,317 +574,141 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return Settings.Secure.isLocationProviderEnabled(resolver, provider); } - private String checkPermissionsSafe(String provider, String lastPermission) { - if (LocationManager.GPS_PROVIDER.equals(provider) - || LocationManager.PASSIVE_PROVIDER.equals(provider)) { - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Provider " + provider - + " requires ACCESS_FINE_LOCATION permission"); - } - return ACCESS_FINE_LOCATION; - } - - // Assume any other provider requires the coarse or fine permission. - if (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { - return ACCESS_FINE_LOCATION.equals(lastPermission) - ? lastPermission : ACCESS_COARSE_LOCATION; - } - if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { + /** + * Throw SecurityException if caller has neither COARSE or FINE. + * Otherwise, return the best permission. + */ + private String checkPermission() { + if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) == + PackageManager.PERMISSION_GRANTED) { return ACCESS_FINE_LOCATION; + } else if (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) == + PackageManager.PERMISSION_GRANTED) { + return ACCESS_COARSE_LOCATION; } - throw new SecurityException("Provider " + provider - + " requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission"); + throw new SecurityException("Location requires either ACCESS_COARSE_LOCATION or" + + " ACCESS_FINE_LOCATION permission"); } - private boolean isAllowedProviderSafe(String provider) { - if ((LocationManager.GPS_PROVIDER.equals(provider) - || LocationManager.PASSIVE_PROVIDER.equals(provider)) - && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED)) { - return false; - } - if (LocationManager.NETWORK_PROVIDER.equals(provider) - && (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) - && (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) - != PackageManager.PERMISSION_GRANTED)) { - return false; + /** + * Throw SecurityException if caller lacks permission to use Geofences. + */ + private void checkGeofencePermission() { + if (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) != + PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission"); } - - return true; } + /** + * Returns all providers by name, including passive, but excluding + * fused. + */ + @Override public List<String> getAllProviders() { - try { - synchronized (mLock) { - return _getAllProvidersLocked(); + checkPermission(); + + ArrayList<String> out; + synchronized (mLock) { + out = new ArrayList<String>(mProviders.size()); + for (LocationProviderInterface provider : mProviders) { + String name = provider.getName(); + if (LocationManager.FUSED_PROVIDER.equals(name)) { + continue; + } + out.add(name); } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getAllProviders got exception:", e); - return null; } - } - private List<String> _getAllProvidersLocked() { - if (LOCAL_LOGV) { - Slog.v(TAG, "getAllProviders"); - } - ArrayList<String> out = new ArrayList<String>(mProviders.size()); - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - out.add(p.getName()); - } + if (D) Log.d(TAG, "getAllProviders()=" + out); return out; } + /** + * Return all providers by name, that match criteria and are optionally + * enabled. + * Can return passive provider, but never returns fused provider. + */ + @Override public List<String> getProviders(Criteria criteria, boolean enabledOnly) { - try { - synchronized (mLock) { - return _getProvidersLocked(criteria, enabledOnly); - } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getProviders got exception:", e); - return null; - } - } + checkPermission(); - private List<String> _getProvidersLocked(Criteria criteria, boolean enabledOnly) { - if (LOCAL_LOGV) { - Slog.v(TAG, "getProviders"); - } - ArrayList<String> out = new ArrayList<String>(mProviders.size()); - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - String name = p.getName(); - if (isAllowedProviderSafe(name)) { + ArrayList<String> out; + synchronized (mLock) { + out = new ArrayList<String>(mProviders.size()); + for (LocationProviderInterface provider : mProviders) { + String name = provider.getName(); + if (LocationManager.FUSED_PROVIDER.equals(name)) { + continue; + } if (enabledOnly && !isAllowedBySettingsLocked(name)) { continue; } - if (criteria != null && !p.meetsCriteria(criteria)) { + if (criteria != null && !LocationProvider.propertiesMeetCriteria( + name, provider.getProperties(), criteria)) { continue; } out.add(name); } } + + if (D) Log.d(TAG, "getProviders()=" + out); return out; } /** - * Returns the next looser power requirement, in the sequence: - * - * POWER_LOW -> POWER_MEDIUM -> POWER_HIGH -> NO_REQUIREMENT + * Return the name of the best provider given a Criteria object. + * This method has been deprecated from the public API, + * and the whole LoactionProvider (including #meetsCriteria) + * has been deprecated as well. So this method now uses + * some simplified logic. */ - private int nextPower(int power) { - switch (power) { - case Criteria.POWER_LOW: - return Criteria.POWER_MEDIUM; - case Criteria.POWER_MEDIUM: - return Criteria.POWER_HIGH; - case Criteria.POWER_HIGH: - return Criteria.NO_REQUIREMENT; - case Criteria.NO_REQUIREMENT: - default: - return Criteria.NO_REQUIREMENT; - } - } + @Override + public String getBestProvider(Criteria criteria, boolean enabledOnly) { + String result = null; + checkPermission(); - /** - * Returns the next looser accuracy requirement, in the sequence: - * - * ACCURACY_FINE -> ACCURACY_APPROXIMATE-> NO_REQUIREMENT - */ - private int nextAccuracy(int accuracy) { - if (accuracy == Criteria.ACCURACY_FINE) { - return Criteria.ACCURACY_COARSE; - } else { - return Criteria.NO_REQUIREMENT; + List<String> providers = getProviders(criteria, enabledOnly); + if (providers.size() < 1) { + result = pickBest(providers); + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return result; } - } - - private class LpPowerComparator implements Comparator<LocationProviderInterface> { - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - // Smaller is better - return (l1.getPowerRequirement() - l2.getPowerRequirement()); - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (l1.getPowerRequirement() == l2.getPowerRequirement()); - } - } - - private class LpAccuracyComparator implements Comparator<LocationProviderInterface> { - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - // Smaller is better - return (l1.getAccuracy() - l2.getAccuracy()); - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (l1.getAccuracy() == l2.getAccuracy()); - } - } - - private class LpCapabilityComparator implements Comparator<LocationProviderInterface> { - - private static final int ALTITUDE_SCORE = 4; - private static final int BEARING_SCORE = 4; - private static final int SPEED_SCORE = 4; - - private int score(LocationProviderInterface p) { - return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) + - (p.supportsBearing() ? BEARING_SCORE : 0) + - (p.supportsSpeed() ? SPEED_SCORE : 0); + providers = getProviders(null, enabledOnly); + if (providers.size() >= 1) { + result = pickBest(providers); + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return result; } - public int compare(LocationProviderInterface l1, LocationProviderInterface l2) { - return (score(l2) - score(l1)); // Bigger is better - } - - public boolean equals(LocationProviderInterface l1, LocationProviderInterface l2) { - return (score(l1) == score(l2)); - } + if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result); + return null; } - private LocationProviderInterface best(List<String> providerNames) { - ArrayList<LocationProviderInterface> providers; - synchronized (mLock) { - providers = new ArrayList<LocationProviderInterface>(providerNames.size()); - for (String name : providerNames) { - providers.add(mProvidersByName.get(name)); - } - } - - if (providers.size() < 2) { - return providers.get(0); - } - - // First, sort by power requirement - Collections.sort(providers, new LpPowerComparator()); - int power = providers.get(0).getPowerRequirement(); - if (power < providers.get(1).getPowerRequirement()) { + private String pickBest(List<String> providers) { + if (providers.contains(LocationManager.NETWORK_PROVIDER)) { + return LocationManager.NETWORK_PROVIDER; + } else if (providers.contains(LocationManager.GPS_PROVIDER)) { + return LocationManager.GPS_PROVIDER; + } else { return providers.get(0); } - - int idx, size; - - ArrayList<LocationProviderInterface> tmp = new ArrayList<LocationProviderInterface>(); - idx = 0; - size = providers.size(); - while ((idx < size) && (providers.get(idx).getPowerRequirement() == power)) { - tmp.add(providers.get(idx)); - idx++; - } - - // Next, sort by accuracy - Collections.sort(tmp, new LpAccuracyComparator()); - int acc = tmp.get(0).getAccuracy(); - if (acc < tmp.get(1).getAccuracy()) { - return tmp.get(0); - } - - ArrayList<LocationProviderInterface> tmp2 = new ArrayList<LocationProviderInterface>(); - idx = 0; - size = tmp.size(); - while ((idx < size) && (tmp.get(idx).getAccuracy() == acc)) { - tmp2.add(tmp.get(idx)); - idx++; - } - - // Finally, sort by capability "score" - Collections.sort(tmp2, new LpCapabilityComparator()); - return tmp2.get(0); - } - - /** - * Returns the name of the provider that best meets the given criteria. Only providers - * that are permitted to be accessed by the calling activity will be - * returned. If several providers meet the criteria, the one with the best - * accuracy is returned. If no provider meets the criteria, - * the criteria are loosened in the following sequence: - * - * <ul> - * <li> power requirement - * <li> accuracy - * <li> bearing - * <li> speed - * <li> altitude - * </ul> - * - * <p> Note that the requirement on monetary cost is not removed - * in this process. - * - * @param criteria the criteria that need to be matched - * @param enabledOnly if true then only a provider that is currently enabled is returned - * @return name of the provider that best matches the requirements - */ - public String getBestProvider(Criteria criteria, boolean enabledOnly) { - List<String> goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Make a copy of the criteria that we can modify - criteria = new Criteria(criteria); - - // Loosen power requirement - int power = criteria.getPowerRequirement(); - while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) { - power = nextPower(power); - criteria.setPowerRequirement(power); - goodProviders = getProviders(criteria, enabledOnly); - } - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Loosen accuracy requirement - int accuracy = criteria.getAccuracy(); - while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) { - accuracy = nextAccuracy(accuracy); - criteria.setAccuracy(accuracy); - goodProviders = getProviders(criteria, enabledOnly); - } - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Remove bearing requirement - criteria.setBearingRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Remove speed requirement - criteria.setSpeedRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - // Remove altitude requirement - criteria.setAltitudeRequired(false); - goodProviders = getProviders(criteria, enabledOnly); - if (!goodProviders.isEmpty()) { - return best(goodProviders).getName(); - } - - return null; } + @Override public boolean providerMeetsCriteria(String provider, Criteria criteria) { + checkPermission(); + LocationProviderInterface p = mProvidersByName.get(provider); if (p == null) { throw new IllegalArgumentException("provider=" + provider); } - return p.meetsCriteria(criteria); + + boolean result = LocationProvider.propertiesMeetCriteria( + p.getName(), p.getProperties(), criteria); + if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result); + return result; } private void updateProvidersLocked() { @@ -989,7 +727,8 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } if (changesMade) { - mContext.sendBroadcast(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION)); + mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION), + UserHandle.ALL); } } @@ -997,16 +736,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run int listeners = 0; LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return; - } + if (p == null) return; ArrayList<Receiver> deadReceivers = null; - + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); if (records != null) { final int N = records.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { UpdateRecord record = records.get(i); // Sends a notification message to the receiver if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) { @@ -1020,67 +757,74 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } if (deadReceivers != null) { - for (int i=deadReceivers.size()-1; i>=0; i--) { + for (int i = deadReceivers.size() - 1; i >= 0; i--) { removeUpdatesLocked(deadReceivers.get(i)); } } - + if (enabled) { p.enable(); if (listeners > 0) { - p.setMinTime(getMinTimeLocked(provider), mTmpWorkSource); - p.enableLocationTracking(true); + applyRequirementsLocked(provider); } } else { - p.enableLocationTracking(false); p.disable(); } } - private long getMinTimeLocked(String provider) { - long minTime = Long.MAX_VALUE; + private void applyRequirementsLocked(String provider) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) return; + ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - mTmpWorkSource.clear(); + WorkSource worksource = new WorkSource(); + ProviderRequest providerRequest = new ProviderRequest(); + if (records != null) { - for (int i=records.size()-1; i>=0; i--) { - UpdateRecord ur = records.get(i); - long curTime = ur.mMinTime; - if (curTime < minTime) { - minTime = curTime; + for (UpdateRecord record : records) { + LocationRequest locationRequest = record.mRequest; + + providerRequest.locationRequests.add(locationRequest); + if (locationRequest.getInterval() < providerRequest.interval) { + providerRequest.reportLocation = true; + providerRequest.interval = locationRequest.getInterval(); } } - long inclTime = (minTime*3)/2; - for (int i=records.size()-1; i>=0; i--) { - UpdateRecord ur = records.get(i); - if (ur.mMinTime <= inclTime) { - mTmpWorkSource.add(ur.mUid); + + if (providerRequest.reportLocation) { + // calculate who to blame for power + // This is somewhat arbitrary. We pick a threshold interval + // that is slightly higher that the minimum interval, and + // spread the blame across all applications with a request + // under that threshold. + long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; + for (UpdateRecord record : records) { + LocationRequest locationRequest = record.mRequest; + if (locationRequest.getInterval() <= thresholdInterval) { + worksource.add(record.mReceiver.mUid); + } } } } - return minTime; + + if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest); + p.setRequest(providerRequest, worksource); } private class UpdateRecord { final String mProvider; + final LocationRequest mRequest; final Receiver mReceiver; - final long mMinTime; - final float mMinDistance; - final boolean mSingleShot; - final int mUid; Location mLastFixBroadcast; long mLastStatusBroadcast; /** * Note: must be constructed with lock held. */ - UpdateRecord(String provider, long minTime, float minDistance, boolean singleShot, - Receiver receiver, int uid) { + UpdateRecord(String provider, LocationRequest request, Receiver receiver) { mProvider = provider; + mRequest = request; mReceiver = receiver; - mMinTime = minTime; - mMinDistance = minDistance; - mSingleShot = singleShot; - mUid = uid; ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); if (records == null) { @@ -1096,45 +840,49 @@ public class LocationManagerService extends ILocationManager.Stub implements Run * Method to be called when a record will no longer be used. Calling this multiple times * must have the same effect as calling it once. */ - void disposeLocked() { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(this.mProvider); - if (records != null) { - records.remove(this); + void disposeLocked(boolean removeReceiver) { + // remove from mRecordsByProvider + ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider); + if (globalRecords != null) { + globalRecords.remove(this); + } + + if (!removeReceiver) return; // the caller will handle the rest + + // remove from Receiver#mUpdateRecords + HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords; + if (receiverRecords != null) { + receiverRecords.remove(this.mProvider); + + // and also remove the Receiver if it has no more update records + if (removeReceiver && receiverRecords.size() == 0) { + removeUpdatesLocked(mReceiver); + } } } @Override public String toString() { - return "UpdateRecord{" - + Integer.toHexString(System.identityHashCode(this)) - + " mProvider: " + mProvider + " mUid: " + mUid + "}"; - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + this); - pw.println(prefix + "mProvider=" + mProvider + " mReceiver=" + mReceiver); - pw.println(prefix + "mMinTime=" + mMinTime + " mMinDistance=" + mMinDistance); - pw.println(prefix + "mSingleShot=" + mSingleShot); - pw.println(prefix + "mUid=" + mUid); - pw.println(prefix + "mLastFixBroadcast:"); - if (mLastFixBroadcast != null) { - mLastFixBroadcast.dump(new PrintWriterPrinter(pw), prefix + " "); - } - pw.println(prefix + "mLastStatusBroadcast=" + mLastStatusBroadcast); + StringBuilder s = new StringBuilder(); + s.append("UpdateRecord["); + s.append(mProvider); + s.append(' ').append(mReceiver.mPackageName).append('('); + s.append(mReceiver.mUid).append(')'); + s.append(' ').append(mRequest); + s.append(']'); + return s.toString(); } } - private Receiver getReceiver(ILocationListener listener, String packageName) { + private Receiver getReceiver(ILocationListener listener, int pid, int uid, String packageName) { IBinder binder = listener.asBinder(); Receiver receiver = mReceivers.get(binder); if (receiver == null) { - receiver = new Receiver(listener, packageName); + receiver = new Receiver(listener, null, pid, uid, packageName); mReceivers.put(binder, receiver); try { - if (receiver.isListener()) { - receiver.getListener().asBinder().linkToDeath(receiver, 0); - } + receiver.getListener().asBinder().linkToDeath(receiver, 0); } catch (RemoteException e) { Slog.e(TAG, "linkToDeath failed:", e); return null; @@ -1143,251 +891,257 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return receiver; } - private Receiver getReceiver(PendingIntent intent, String packageName) { + private Receiver getReceiver(PendingIntent intent, int pid, int uid, String packageName) { Receiver receiver = mReceivers.get(intent); if (receiver == null) { - receiver = new Receiver(intent, packageName); + receiver = new Receiver(null, intent, pid, uid, packageName); mReceivers.put(intent, receiver); } return receiver; } - private boolean providerHasListener(String provider, int uid, Receiver excludedReceiver) { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - if (records != null) { - for (int i = records.size() - 1; i >= 0; i--) { - UpdateRecord record = records.get(i); - if (record.mUid == uid && record.mReceiver != excludedReceiver) { - return true; - } - } + private String checkPermissionAndRequest(LocationRequest request) { + String perm = checkPermission(); + + if (ACCESS_COARSE_LOCATION.equals(perm)) { + switch (request.getQuality()) { + case LocationRequest.ACCURACY_FINE: + request.setQuality(LocationRequest.ACCURACY_BLOCK); + break; + case LocationRequest.POWER_HIGH: + request.setQuality(LocationRequest.POWER_LOW); + break; + } + // throttle + if (request.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) { + request.setInterval(LocationFudger.FASTEST_INTERVAL_MS); + } + if (request.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) { + request.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS); + } } - for (ProximityAlert alert : mProximityAlerts.values()) { - if (alert.mUid == uid) { - return true; - } + // make getFastestInterval() the minimum of interval and fastest interval + if (request.getFastestInterval() > request.getInterval()) { + request.setFastestInterval(request.getInterval()); } - return false; + return perm; } - public void requestLocationUpdates(String provider, Criteria criteria, - long minTime, float minDistance, boolean singleShot, ILocationListener listener, - String packageName) { - checkPackageName(Binder.getCallingUid(), packageName); - if (criteria != null) { - // FIXME - should we consider using multiple providers simultaneously - // rather than only the best one? - // Should we do anything different for single shot fixes? - provider = getBestProvider(criteria, true); - if (provider == null) { - throw new IllegalArgumentException("no providers found for criteria"); - } + private void checkPackageName(String packageName) { + if (packageName == null) { + throw new SecurityException("invalid package name: " + packageName); } - try { - synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, - getReceiver(listener, packageName)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "requestUpdates got exception:", e); + int uid = Binder.getCallingUid(); + String[] packages = mPackageManager.getPackagesForUid(uid); + if (packages == null) { + throw new SecurityException("invalid UID " + uid); + } + for (String pkg : packages) { + if (packageName.equals(pkg)) return; } + throw new SecurityException("invalid package name: " + packageName); } - void validatePendingIntent(PendingIntent intent) { - if (intent.isTargetedToPackage()) { - return; + private void checkPendingIntent(PendingIntent intent) { + if (intent == null) { + throw new IllegalArgumentException("invalid pending intent: " + intent); } - Slog.i(TAG, "Given Intent does not require a specific package: " - + intent); - // XXX we should really throw a security exception, if the caller's - // targetSdkVersion is high enough. - //throw new SecurityException("Given Intent does not require a specific package: " - // + intent); } - public void requestLocationUpdatesPI(String provider, Criteria criteria, - long minTime, float minDistance, boolean singleShot, PendingIntent intent, - String packageName) { - checkPackageName(Binder.getCallingUid(), packageName); - validatePendingIntent(intent); - if (criteria != null) { - // FIXME - should we consider using multiple providers simultaneously - // rather than only the best one? - // Should we do anything different for single shot fixes? - provider = getBestProvider(criteria, true); - if (provider == null) { - throw new IllegalArgumentException("no providers found for criteria"); - } - } - try { - synchronized (mLock) { - requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot, - getReceiver(intent, packageName)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "requestUpdates got exception:", e); + private Receiver checkListenerOrIntent(ILocationListener listener, PendingIntent intent, + int pid, int uid, String packageName) { + if (intent == null && listener == null) { + throw new IllegalArgumentException("need eiter listener or intent"); + } else if (intent != null && listener != null) { + throw new IllegalArgumentException("cannot register both listener and intent"); + } else if (intent != null) { + checkPendingIntent(intent); + return getReceiver(intent, pid, uid, packageName); + } else { + return getReceiver(listener, pid, uid, packageName); } } - private void requestLocationUpdatesLocked(String provider, long minTime, float minDistance, - boolean singleShot, Receiver receiver) { + @Override + public void requestLocationUpdates(LocationRequest request, ILocationListener listener, + PendingIntent intent, String packageName) { + if (request == null) request = DEFAULT_LOCATION_REQUEST; + checkPackageName(packageName); + checkPermissionAndRequest(request); - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - throw new IllegalArgumentException("requested provider " + provider + - " doesn't exisit"); - } - receiver.mRequiredPermissions = checkPermissionsSafe(provider, - receiver.mRequiredPermissions); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + Receiver recevier = checkListenerOrIntent(listener, intent, pid, uid, packageName); - // so wakelock calls will succeed - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); - boolean newUid = !providerHasListener(provider, callingUid, null); + // providers may use public location API's, need to clear identity long identity = Binder.clearCallingIdentity(); try { - UpdateRecord r = new UpdateRecord(provider, minTime, minDistance, singleShot, - receiver, callingUid); - UpdateRecord oldRecord = receiver.mUpdateRecords.put(provider, r); - if (oldRecord != null) { - oldRecord.disposeLocked(); - } - - if (newUid) { - p.addListener(callingUid); - } - - boolean isProviderEnabled = isAllowedBySettingsLocked(provider); - if (isProviderEnabled) { - long minTimeForProvider = getMinTimeLocked(provider); - Slog.i(TAG, "request " + provider + " (pid " + callingPid + ") " + minTime + - " " + minTimeForProvider + (singleShot ? " (singleshot)" : "")); - p.setMinTime(minTimeForProvider, mTmpWorkSource); - // try requesting single shot if singleShot is true, and fall back to - // regular location tracking if requestSingleShotFix() is not supported - if (!singleShot || !p.requestSingleShotFix()) { - p.enableLocationTracking(true); - } - } else { - // Notify the listener that updates are currently disabled - receiver.callProviderEnabledLocked(provider, false); - } - if (LOCAL_LOGV) { - Slog.v(TAG, "_requestLocationUpdates: provider = " + provider + " listener = " + receiver); + synchronized (mLock) { + requestLocationUpdatesLocked(request, recevier, pid, uid, packageName); } } finally { Binder.restoreCallingIdentity(identity); } } - public void removeUpdates(ILocationListener listener, String packageName) { - try { - synchronized (mLock) { - removeUpdatesLocked(getReceiver(listener, packageName)); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "removeUpdates got exception:", e); + private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver, + int pid, int uid, String packageName) { + // Figure out the provider. Either its explicitly request (legacy use cases), or + // use the fused provider + if (request == null) request = DEFAULT_LOCATION_REQUEST; + String name = request.getProvider(); + if (name == null) name = LocationManager.FUSED_PROVIDER; + LocationProviderInterface provider = mProvidersByName.get(name); + if (provider == null) { + throw new IllegalArgumentException("provider doesn't exisit: " + provider); + } + + Log.i(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver)) + " " + + name + " " + request + " from " + packageName + "(" + uid + ")"); + + UpdateRecord record = new UpdateRecord(name, request, receiver); + UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record); + if (oldRecord != null) { + oldRecord.disposeLocked(false); + } + + boolean isProviderEnabled = isAllowedBySettingsLocked(name); + if (isProviderEnabled) { + applyRequirementsLocked(name); + } else { + // Notify the listener that updates are currently disabled + receiver.callProviderEnabledLocked(name, false); } } - public void removeUpdatesPI(PendingIntent intent, String packageName) { + @Override + public void removeUpdates(ILocationListener listener, PendingIntent intent, + String packageName) { + checkPackageName(packageName); + checkPermission(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + Receiver receiver = checkListenerOrIntent(listener, intent, pid, uid, packageName); + + // providers may use public location API's, need to clear identity + long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - removeUpdatesLocked(getReceiver(intent, packageName)); + removeUpdatesLocked(receiver); } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "removeUpdates got exception:", e); + } finally { + Binder.restoreCallingIdentity(identity); } } private void removeUpdatesLocked(Receiver receiver) { - if (LOCAL_LOGV) { - Slog.v(TAG, "_removeUpdates: listener = " + receiver); - } + Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver))); - // so wakelock calls will succeed - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); - long identity = Binder.clearCallingIdentity(); - try { - if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) { - receiver.getListener().asBinder().unlinkToDeath(receiver, 0); - synchronized(receiver) { - if(receiver.mPendingBroadcasts > 0) { - decrementPendingBroadcasts(); - receiver.mPendingBroadcasts = 0; - } + if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) { + receiver.getListener().asBinder().unlinkToDeath(receiver, 0); + synchronized (receiver) { + if (receiver.mPendingBroadcasts > 0) { + decrementPendingBroadcasts(); + receiver.mPendingBroadcasts = 0; } } + } - // Record which providers were associated with this listener - HashSet<String> providers = new HashSet<String>(); - HashMap<String,UpdateRecord> oldRecords = receiver.mUpdateRecords; - if (oldRecords != null) { - // Call dispose() on the obsolete update records. - for (UpdateRecord record : oldRecords.values()) { - if (!providerHasListener(record.mProvider, callingUid, receiver)) { - LocationProviderInterface p = mProvidersByName.get(record.mProvider); - if (p != null) { - p.removeListener(callingUid); - } - } - record.disposeLocked(); - } - // Accumulate providers - providers.addAll(oldRecords.keySet()); + // Record which providers were associated with this listener + HashSet<String> providers = new HashSet<String>(); + HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords; + if (oldRecords != null) { + // Call dispose() on the obsolete update records. + for (UpdateRecord record : oldRecords.values()) { + record.disposeLocked(false); } + // Accumulate providers + providers.addAll(oldRecords.keySet()); + } - // See if the providers associated with this listener have any - // other listeners; if one does, inform it of the new smallest minTime - // value; if one does not, disable location tracking for it - for (String provider : providers) { - // If provider is already disabled, don't need to do anything - if (!isAllowedBySettingsLocked(provider)) { - continue; - } + // update provider + for (String provider : providers) { + // If provider is already disabled, don't need to do anything + if (!isAllowedBySettingsLocked(provider)) { + continue; + } - boolean hasOtherListener = false; - ArrayList<UpdateRecord> recordsForProvider = mRecordsByProvider.get(provider); - if (recordsForProvider != null && recordsForProvider.size() > 0) { - hasOtherListener = true; - } + applyRequirementsLocked(provider); + } + } - LocationProviderInterface p = mProvidersByName.get(provider); - if (p != null) { - if (hasOtherListener) { - long minTime = getMinTimeLocked(provider); - Slog.i(TAG, "remove " + provider + " (pid " + callingPid + - "), next minTime = " + minTime); - p.setMinTime(minTime, mTmpWorkSource); - } else { - Slog.i(TAG, "remove " + provider + " (pid " + callingPid + - "), disabled"); - p.enableLocationTracking(false); - } - } + @Override + public Location getLastLocation(LocationRequest request, String packageName) { + if (D) Log.d(TAG, "getLastLocation: " + request); + if (request == null) request = DEFAULT_LOCATION_REQUEST; + String perm = checkPermissionAndRequest(request); + checkPackageName(packageName); + + if (mBlacklist.isBlacklisted(packageName)) { + if (D) Log.d(TAG, "not returning last loc for blacklisted app: " + + packageName); + return null; + } + + synchronized (mLock) { + // Figure out the provider. Either its explicitly request (deprecated API's), + // or use the fused provider + String name = request.getProvider(); + if (name == null) name = LocationManager.FUSED_PROVIDER; + LocationProviderInterface provider = mProvidersByName.get(name); + if (provider == null) return null; + + if (!isAllowedBySettingsLocked(name)) return null; + + Location location = mLastLocation.get(name); + if (ACCESS_FINE_LOCATION.equals(perm)) { + return location; + } else { + return mLocationFudger.getOrCreate(location); } + } + } + + @Override + public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent, + String packageName) { + if (request == null) request = DEFAULT_LOCATION_REQUEST; + checkGeofencePermission(); + checkPermissionAndRequest(request); + checkPendingIntent(intent); + checkPackageName(packageName); + + if (D) Log.d(TAG, "requestGeofence: " + request + " " + geofence + " " + intent); + + // geo-fence manager uses the public location API, need to clear identity + int uid = Binder.getCallingUid(); + long identity = Binder.clearCallingIdentity(); + try { + mGeofenceManager.addFence(request, geofence, intent, uid, packageName); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) { + checkGeofencePermission(); + checkPendingIntent(intent); + checkPackageName(packageName); + + if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent); + + // geo-fence manager uses the public location API, need to clear identity + long identity = Binder.clearCallingIdentity(); + try { + mGeofenceManager.removeFence(geofence, intent); } finally { Binder.restoreCallingIdentity(identity); } } + + @Override public boolean addGpsStatusListener(IGpsStatusListener listener) { if (mGpsStatusProvider == null) { return false; @@ -1406,6 +1160,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run return true; } + @Override public void removeGpsStatusListener(IGpsStatusListener listener) { synchronized (mLock) { try { @@ -1416,14 +1171,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + @Override public boolean sendExtraCommand(String provider, String command, Bundle extras) { if (provider == null) { // throw NullPointerException to remain compatible with previous implementation throw new NullPointerException(); } - // first check for permission to the provider - checkPermissionsSafe(provider, null); + checkPermission(); // and check for ACCESS_LOCATION_EXTRA_COMMANDS if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS) != PackageManager.PERMISSION_GRANTED)) { @@ -1432,424 +1187,113 @@ public class LocationManagerService extends ILocationManager.Stub implements Run synchronized (mLock) { LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return false; - } - + if (p == null) return false; + return p.sendExtraCommand(command, extras); } } - public boolean sendNiResponse(int notifId, int userResponse) - { + @Override + public boolean sendNiResponse(int notifId, int userResponse) { if (Binder.getCallingUid() != Process.myUid()) { throw new SecurityException( "calling sendNiResponse from outside of the system is not allowed"); } try { return mNetInitiatedListener.sendNiResponse(notifId, userResponse); - } - catch (RemoteException e) - { + } catch (RemoteException e) { Slog.e(TAG, "RemoteException in LocationManagerService.sendNiResponse"); return false; } } - class ProximityAlert { - final int mUid; - final double mLatitude; - final double mLongitude; - final float mRadius; - final long mExpiration; - final PendingIntent mIntent; - final Location mLocation; - final String mPackageName; - - public ProximityAlert(int uid, double latitude, double longitude, - float radius, long expiration, PendingIntent intent, String packageName) { - mUid = uid; - mLatitude = latitude; - mLongitude = longitude; - mRadius = radius; - mExpiration = expiration; - mIntent = intent; - mPackageName = packageName; - - mLocation = new Location(""); - mLocation.setLatitude(latitude); - mLocation.setLongitude(longitude); - } - - long getExpiration() { - return mExpiration; - } - - PendingIntent getIntent() { - return mIntent; - } - - boolean isInProximity(double latitude, double longitude, float accuracy) { - Location loc = new Location(""); - loc.setLatitude(latitude); - loc.setLongitude(longitude); - - double radius = loc.distanceTo(mLocation); - return radius <= Math.max(mRadius,accuracy); - } - - @Override - public String toString() { - return "ProximityAlert{" - + Integer.toHexString(System.identityHashCode(this)) - + " uid " + mUid + mIntent + "}"; - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + this); - pw.println(prefix + "mLatitude=" + mLatitude + " mLongitude=" + mLongitude); - pw.println(prefix + "mRadius=" + mRadius + " mExpiration=" + mExpiration); - pw.println(prefix + "mIntent=" + mIntent); - pw.println(prefix + "mLocation:"); - mLocation.dump(new PrintWriterPrinter(pw), prefix + " "); - } - } - - // Listener for receiving locations to trigger proximity alerts - class ProximityListener extends ILocationListener.Stub implements PendingIntent.OnFinished { - - boolean isGpsAvailable = false; - - // Note: this is called with the lock held. - public void onLocationChanged(Location loc) { - - // If Gps is available, then ignore updates from NetworkLocationProvider - if (loc.getProvider().equals(LocationManager.GPS_PROVIDER)) { - isGpsAvailable = true; - } - if (isGpsAvailable && loc.getProvider().equals(LocationManager.NETWORK_PROVIDER)) { - return; - } - - // Process proximity alerts - long now = System.currentTimeMillis(); - double latitude = loc.getLatitude(); - double longitude = loc.getLongitude(); - float accuracy = loc.getAccuracy(); - ArrayList<PendingIntent> intentsToRemove = null; - - for (ProximityAlert alert : mProximityAlerts.values()) { - PendingIntent intent = alert.getIntent(); - long expiration = alert.getExpiration(); - - if (inBlacklist(alert.mPackageName)) { - continue; - } - - if ((expiration == -1) || (now <= expiration)) { - boolean entered = mProximitiesEntered.contains(alert); - boolean inProximity = - alert.isInProximity(latitude, longitude, accuracy); - if (!entered && inProximity) { - if (LOCAL_LOGV) { - Slog.v(TAG, "Entered alert"); - } - mProximitiesEntered.add(alert); - Intent enteredIntent = new Intent(); - enteredIntent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); - try { - synchronized (this) { - // synchronize to ensure incrementPendingBroadcasts() - // is called before decrementPendingBroadcasts() - intent.send(mContext, 0, enteredIntent, this, mLocationHandler, - ACCESS_FINE_LOCATION); - // call this after broadcasting so we do not increment - // if we throw an exeption. - incrementPendingBroadcasts(); - } - } catch (PendingIntent.CanceledException e) { - if (LOCAL_LOGV) { - Slog.v(TAG, "Canceled proximity alert: " + alert, e); - } - if (intentsToRemove == null) { - intentsToRemove = new ArrayList<PendingIntent>(); - } - intentsToRemove.add(intent); - } - } else if (entered && !inProximity) { - if (LOCAL_LOGV) { - Slog.v(TAG, "Exited alert"); - } - mProximitiesEntered.remove(alert); - Intent exitedIntent = new Intent(); - exitedIntent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); - try { - synchronized (this) { - // synchronize to ensure incrementPendingBroadcasts() - // is called before decrementPendingBroadcasts() - intent.send(mContext, 0, exitedIntent, this, mLocationHandler, - ACCESS_FINE_LOCATION); - // call this after broadcasting so we do not increment - // if we throw an exeption. - incrementPendingBroadcasts(); - } - } catch (PendingIntent.CanceledException e) { - if (LOCAL_LOGV) { - Slog.v(TAG, "Canceled proximity alert: " + alert, e); - } - if (intentsToRemove == null) { - intentsToRemove = new ArrayList<PendingIntent>(); - } - intentsToRemove.add(intent); - } - } - } else { - // Mark alert for expiration - if (LOCAL_LOGV) { - Slog.v(TAG, "Expiring proximity alert: " + alert); - } - if (intentsToRemove == null) { - intentsToRemove = new ArrayList<PendingIntent>(); - } - intentsToRemove.add(alert.getIntent()); - } - } - - // Remove expired alerts - if (intentsToRemove != null) { - for (PendingIntent i : intentsToRemove) { - ProximityAlert alert = mProximityAlerts.get(i); - mProximitiesEntered.remove(alert); - removeProximityAlertLocked(i); - } - } - } - - // Note: this is called with the lock held. - public void onProviderDisabled(String provider) { - if (provider.equals(LocationManager.GPS_PROVIDER)) { - isGpsAvailable = false; - } - } - - // Note: this is called with the lock held. - public void onProviderEnabled(String provider) { - // ignore - } - - // Note: this is called with the lock held. - public void onStatusChanged(String provider, int status, Bundle extras) { - if ((provider.equals(LocationManager.GPS_PROVIDER)) && - (status != LocationProvider.AVAILABLE)) { - isGpsAvailable = false; - } - } - - public void onSendFinished(PendingIntent pendingIntent, Intent intent, - int resultCode, String resultData, Bundle resultExtras) { - // synchronize to ensure incrementPendingBroadcasts() - // is called before decrementPendingBroadcasts() - synchronized (this) { - decrementPendingBroadcasts(); - } - } - } - - public void addProximityAlert(double latitude, double longitude, - float radius, long expiration, PendingIntent intent, String packageName) { - validatePendingIntent(intent); - try { - synchronized (mLock) { - addProximityAlertLocked(latitude, longitude, radius, expiration, intent, - packageName); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "addProximityAlert got exception:", e); - } - } - - private void addProximityAlertLocked(double latitude, double longitude, - float radius, long expiration, PendingIntent intent, String packageName) { - if (LOCAL_LOGV) { - Slog.v(TAG, "addProximityAlert: latitude = " + latitude + - ", longitude = " + longitude + - ", expiration = " + expiration + - ", intent = " + intent); - } - - checkPackageName(Binder.getCallingUid(), packageName); - - // Require ability to access all providers for now - if (!isAllowedProviderSafe(LocationManager.GPS_PROVIDER) || - !isAllowedProviderSafe(LocationManager.NETWORK_PROVIDER)) { - throw new SecurityException("Requires ACCESS_FINE_LOCATION permission"); - } - - if (expiration != -1) { - expiration += System.currentTimeMillis(); - } - ProximityAlert alert = new ProximityAlert(Binder.getCallingUid(), - latitude, longitude, radius, expiration, intent, packageName); - mProximityAlerts.put(intent, alert); - - if (mProximityReceiver == null) { - mProximityListener = new ProximityListener(); - mProximityReceiver = new Receiver(mProximityListener, packageName); - - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface provider = mProviders.get(i); - requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, - false, mProximityReceiver); - } - } - } - - public void removeProximityAlert(PendingIntent intent) { - try { - synchronized (mLock) { - removeProximityAlertLocked(intent); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "removeProximityAlert got exception:", e); - } - } - - private void removeProximityAlertLocked(PendingIntent intent) { - if (LOCAL_LOGV) { - Slog.v(TAG, "removeProximityAlert: intent = " + intent); - } - - mProximityAlerts.remove(intent); - if (mProximityAlerts.size() == 0) { - if (mProximityReceiver != null) { - removeUpdatesLocked(mProximityReceiver); - } - mProximityReceiver = null; - mProximityListener = null; - } - } - /** * @return null if the provider does not exist * @throws SecurityException if the provider is not allowed to be * accessed by the caller */ - public Bundle getProviderInfo(String provider) { - try { - synchronized (mLock) { - return _getProviderInfoLocked(provider); - } - } catch (SecurityException se) { - throw se; - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - Slog.e(TAG, "_getProviderInfo got exception:", e); - return null; - } - } + @Override + public ProviderProperties getProviderProperties(String provider) { + checkPermission(); - private Bundle _getProviderInfoLocked(String provider) { - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return null; + LocationProviderInterface p; + synchronized (mLock) { + p = mProvidersByName.get(provider); } - checkPermissionsSafe(provider, null); - - Bundle b = new Bundle(); - b.putBoolean("network", p.requiresNetwork()); - b.putBoolean("satellite", p.requiresSatellite()); - b.putBoolean("cell", p.requiresCell()); - b.putBoolean("cost", p.hasMonetaryCost()); - b.putBoolean("altitude", p.supportsAltitude()); - b.putBoolean("speed", p.supportsSpeed()); - b.putBoolean("bearing", p.supportsBearing()); - b.putInt("power", p.getPowerRequirement()); - b.putInt("accuracy", p.getAccuracy()); - - return b; + if (p == null) return null; + return p.getProperties(); } + @Override public boolean isProviderEnabled(String provider) { - try { - synchronized (mLock) { - return _isProviderEnabledLocked(provider); - } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "isProviderEnabled got exception:", e); - return false; + checkPermission(); + if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; + + synchronized (mLock) { + LocationProviderInterface p = mProvidersByName.get(provider); + if (p == null) return false; + + return isAllowedBySettingsLocked(provider); } } - public void reportLocation(Location location, boolean passive) { + private void checkCallerIsProvider() { if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires INSTALL_LOCATION_PROVIDER permission"); + == PackageManager.PERMISSION_GRANTED) { + return; } - mLocationHandler.removeMessages(MESSAGE_LOCATION_CHANGED, location); - Message m = Message.obtain(mLocationHandler, MESSAGE_LOCATION_CHANGED, location); - m.arg1 = (passive ? 1 : 0); - mLocationHandler.sendMessageAtFrontOfQueue(m); - } + // Previously we only used the INSTALL_LOCATION_PROVIDER + // check. But that is system or signature + // protection level which is not flexible enough for + // providers installed oustide the system image. So + // also allow providers with a UID matching the + // currently bound package name - private boolean _isProviderEnabledLocked(String provider) { - checkPermissionsSafe(provider, null); + int uid = Binder.getCallingUid(); - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return false; + if (mGeocodeProvider != null) { + if (doesPackageHaveUid(uid, mGeocodeProvider.getConnectedPackageName())) return; } - return isAllowedBySettingsLocked(provider); + for (LocationProviderProxy proxy : mProxyProviders) { + if (doesPackageHaveUid(uid, proxy.getConnectedPackageName())) return; + } + throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " + + "or UID of a currently bound location provider"); } - public Location getLastKnownLocation(String provider, String packageName) { - if (LOCAL_LOGV) { - Slog.v(TAG, "getLastKnownLocation: " + provider); + private boolean doesPackageHaveUid(int uid, String packageName) { + if (packageName == null) { + return false; } try { - synchronized (mLock) { - return _getLastKnownLocationLocked(provider, packageName); + ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0); + if (appInfo.uid != uid) { + return false; } - } catch (SecurityException se) { - throw se; - } catch (Exception e) { - Slog.e(TAG, "getLastKnownLocation got exception:", e); - return null; + } catch (NameNotFoundException e) { + return false; } + return true; } - private Location _getLastKnownLocationLocked(String provider, String packageName) { - checkPermissionsSafe(provider, null); - checkPackageName(Binder.getCallingUid(), packageName); - - LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return null; - } - - if (!isAllowedBySettingsLocked(provider)) { - return null; - } + @Override + public void reportLocation(Location location, boolean passive) { + checkCallerIsProvider(); - if (inBlacklist(packageName)) { - return null; + if (!location.isComplete()) { + Log.w(TAG, "Dropping incomplete location: " + location); + return; } - return mLastKnownLocation.get(provider); + mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location); + Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location); + m.arg1 = (passive ? 1 : 0); + mLocationHandler.sendMessageAtFrontOfQueue(m); } + private static boolean shouldBroadcastSafe(Location loc, Location lastLoc, UpdateRecord record) { // Always broadcast the first update if (lastLoc == null) { @@ -1857,13 +1301,14 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } // Check whether sufficient time has passed - long minTime = record.mMinTime; - if (loc.getTime() - lastLoc.getTime() < minTime - MAX_PROVIDER_SCHEDULING_JITTER) { + long minTime = record.mRequest.getFastestInterval(); + long delta = (loc.getElapsedRealtimeNano() - lastLoc.getElapsedRealtimeNano()) / 1000000L; + if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) { return false; } // Check whether sufficient distance has been traveled - double minDistance = record.mMinDistance; + double minDistance = record.mRequest.getSmallestDisplacement(); if (minDistance > 0.0) { if (loc.distanceTo(lastLoc) <= minDistance) { return false; @@ -1874,24 +1319,26 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } private void handleLocationChangedLocked(Location location, boolean passive) { + if (D) Log.d(TAG, "incoming location: " + location); + + long now = SystemClock.elapsedRealtime(); String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider()); ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider); - if (records == null || records.size() == 0) { - return; - } + if (records == null || records.size() == 0) return; LocationProviderInterface p = mProvidersByName.get(provider); - if (p == null) { - return; - } + if (p == null) return; + + // Add the coarse location as an extra + Location coarse = mLocationFudger.getOrCreate(location); - // Update last known location for provider - Location lastLocation = mLastKnownLocation.get(provider); + // Update last known locations + Location lastLocation = mLastLocation.get(provider); if (lastLocation == null) { - mLastKnownLocation.put(provider, new Location(location)); - } else { - lastLocation.set(location); + lastLocation = new Location(provider); + mLastLocation.put(provider, lastLocation); } + lastLocation.set(location); // Fetch latest status update time long newStatusUpdateTime = p.getStatusUpdateTime(); @@ -1901,18 +1348,25 @@ public class LocationManagerService extends ILocationManager.Stub implements Run int status = p.getStatus(extras); ArrayList<Receiver> deadReceivers = null; - + ArrayList<UpdateRecord> deadUpdateRecords = null; + // Broadcast location or status to all listeners - final int N = records.size(); - for (int i=0; i<N; i++) { - UpdateRecord r = records.get(i); + for (UpdateRecord r : records) { Receiver receiver = r.mReceiver; boolean receiverDead = false; - if (inBlacklist(receiver.mPackageName)) { + if (mBlacklist.isBlacklisted(receiver.mPackageName)) { + if (D) Log.d(TAG, "skipping loc update for blacklisted app: " + + receiver.mPackageName); continue; } + if (ACCESS_FINE_LOCATION.equals(receiver.mPermission)) { + location = lastLocation; // use fine location + } else { + location = coarse; // use coarse location + } + Location lastLoc = r.mLastFixBroadcast; if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) { if (lastLoc == null) { @@ -1938,8 +1392,15 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - // remove receiver if it is dead or we just processed a single shot request - if (receiverDead || r.mSingleShot) { + // track expired records + if (r.mRequest.getNumUpdates() == 0 || r.mRequest.getExpireAt() < now) { + if (deadUpdateRecords == null) { + deadUpdateRecords = new ArrayList<UpdateRecord>(); + } + deadUpdateRecords.add(r); + } + // track dead receivers + if (receiverDead) { if (deadReceivers == null) { deadReceivers = new ArrayList<Receiver>(); } @@ -1948,185 +1409,72 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } } - + + // remove dead records and receivers outside the loop if (deadReceivers != null) { - for (int i=deadReceivers.size()-1; i>=0; i--) { - removeUpdatesLocked(deadReceivers.get(i)); + for (Receiver receiver : deadReceivers) { + removeUpdatesLocked(receiver); + } + } + if (deadUpdateRecords != null) { + for (UpdateRecord r : deadUpdateRecords) { + r.disposeLocked(true); } } } private class LocationWorkerHandler extends Handler { - @Override public void handleMessage(Message msg) { - try { - if (msg.what == MESSAGE_LOCATION_CHANGED) { - // log("LocationWorkerHandler: MESSAGE_LOCATION_CHANGED!"); - - synchronized (mLock) { - Location location = (Location) msg.obj; - String provider = location.getProvider(); - boolean passive = (msg.arg1 == 1); - - if (!passive) { - // notify other providers of the new location - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface p = mProviders.get(i); - if (!provider.equals(p.getName())) { - p.updateLocation(location); - } - } - } + switch (msg.what) { + case MSG_LOCATION_CHANGED: + handleLocationChanged((Location) msg.obj, msg.arg1 == 1); + break; + } + } + } - if (isAllowedBySettingsLocked(provider)) { - handleLocationChangedLocked(location, passive); - } - } - } else if (msg.what == MESSAGE_PACKAGE_UPDATED) { - String packageName = (String) msg.obj; - - // reconnect to external providers if there is a better package - if (mNetworkLocationProviderPackageName != null && - mPackageManager.resolveService( - new Intent(LocationProviderProxy.SERVICE_ACTION) - .setPackage(packageName), 0) != null) { - // package implements service, perform full check - String bestPackage = findBestPackage( - LocationProviderProxy.SERVICE_ACTION, - mNetworkLocationProviderPackageName); - if (packageName.equals(bestPackage)) { - mNetworkLocationProvider.reconnect(bestPackage); - mNetworkLocationProviderPackageName = packageName; - } - } - if (mGeocodeProviderPackageName != null && - mPackageManager.resolveService( - new Intent(GeocoderProxy.SERVICE_ACTION) - .setPackage(packageName), 0) != null) { - // package implements service, perform full check - String bestPackage = findBestPackage( - GeocoderProxy.SERVICE_ACTION, - mGeocodeProviderPackageName); - if (packageName.equals(bestPackage)) { - mGeocodeProvider.reconnect(bestPackage); - mGeocodeProviderPackageName = packageName; - } - } - } - } catch (Exception e) { - // Log, don't crash! - Slog.e(TAG, "Exception in LocationWorkerHandler.handleMessage:", e); + private void handleLocationChanged(Location location, boolean passive) { + String provider = location.getProvider(); + + if (!passive) { + // notify passive provider of the new location + mPassiveProvider.updateLocation(location); + } + + synchronized (mLock) { + if (isAllowedBySettingsLocked(provider)) { + handleLocationChangedLocked(location, passive); } } } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + private final PackageMonitor mPackageMonitor = new PackageMonitor() { @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - boolean queryRestart = action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART); - if (queryRestart - || action.equals(Intent.ACTION_PACKAGE_REMOVED) - || action.equals(Intent.ACTION_PACKAGE_RESTARTED) - || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - synchronized (mLock) { - int uidList[] = null; - if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { - uidList = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); - } else { - uidList = new int[]{intent.getIntExtra(Intent.EXTRA_UID, -1)}; - } - if (uidList == null || uidList.length == 0) { - return; - } - for (int uid : uidList) { - if (uid >= 0) { - ArrayList<Receiver> removedRecs = null; - for (ArrayList<UpdateRecord> i : mRecordsByProvider.values()) { - for (int j=i.size()-1; j>=0; j--) { - UpdateRecord ur = i.get(j); - if (ur.mReceiver.isPendingIntent() && ur.mUid == uid) { - if (queryRestart) { - setResultCode(Activity.RESULT_OK); - return; - } - if (removedRecs == null) { - removedRecs = new ArrayList<Receiver>(); - } - if (!removedRecs.contains(ur.mReceiver)) { - removedRecs.add(ur.mReceiver); - } - } - } - } - ArrayList<ProximityAlert> removedAlerts = null; - for (ProximityAlert i : mProximityAlerts.values()) { - if (i.mUid == uid) { - if (queryRestart) { - setResultCode(Activity.RESULT_OK); - return; - } - if (removedAlerts == null) { - removedAlerts = new ArrayList<ProximityAlert>(); - } - if (!removedAlerts.contains(i)) { - removedAlerts.add(i); - } - } - } - if (removedRecs != null) { - for (int i=removedRecs.size()-1; i>=0; i--) { - removeUpdatesLocked(removedRecs.get(i)); - } - } - if (removedAlerts != null) { - for (int i=removedAlerts.size()-1; i>=0; i--) { - removeProximityAlertLocked(removedAlerts.get(i).mIntent); - } - } + public void onPackageDisappeared(String packageName, int reason) { + // remove all receivers associated with this package name + synchronized (mLock) { + ArrayList<Receiver> deadReceivers = null; + + for (Receiver receiver : mReceivers.values()) { + if (receiver.mPackageName.equals(packageName)) { + if (deadReceivers == null) { + deadReceivers = new ArrayList<Receiver>(); } + deadReceivers.add(receiver); } } - } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - boolean noConnectivity = - intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); - if (!noConnectivity) { - mNetworkState = LocationProvider.AVAILABLE; - } else { - mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; - } - final ConnectivityManager connManager = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - final NetworkInfo info = connManager.getActiveNetworkInfo(); - - // Notify location providers of current network state - synchronized (mLock) { - for (int i = mProviders.size() - 1; i >= 0; i--) { - LocationProviderInterface provider = mProviders.get(i); - if (provider.requiresNetwork()) { - provider.updateNetworkState(mNetworkState, info); - } + // perform removal outside of mReceivers loop + if (deadReceivers != null) { + for (Receiver receiver : deadReceivers) { + removeUpdatesLocked(receiver); } } } } }; - private final PackageMonitor mPackageMonitor = new PackageMonitor() { - @Override - public void onPackageUpdateFinished(String packageName, int uid) { - // Called by main thread; divert work to LocationWorker. - Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget(); - } - @Override - public void onPackageAdded(String packageName, int uid) { - // Called by main thread; divert work to LocationWorker. - Message.obtain(mLocationHandler, MESSAGE_PACKAGE_UPDATED, packageName).sendToTarget(); - } - }; - // Wake locks private void incrementPendingBroadcasts() { @@ -2166,10 +1514,12 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // Geocoder + @Override public boolean geocoderIsPresent() { return mGeocodeProvider != null; } + @Override public String getFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) { if (mGeocodeProvider != null) { @@ -2180,6 +1530,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } + @Override public String getFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, int maxResults, @@ -2205,12 +1556,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission"); - } + } } - public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite, - boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, - boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + @Override + public void addTestProvider(String name, ProviderProperties properties) { checkMockPermissionsSafe(); if (LocationManager.PASSIVE_PROVIDER.equals(name)) { @@ -2219,30 +1569,28 @@ public class LocationManagerService extends ILocationManager.Stub implements Run long identity = Binder.clearCallingIdentity(); synchronized (mLock) { - MockProvider provider = new MockProvider(name, this, - requiresNetwork, requiresSatellite, - requiresCell, hasMonetaryCost, supportsAltitude, - supportsSpeed, supportsBearing, powerRequirement, accuracy); + MockProvider provider = new MockProvider(name, this, properties); // remove the real provider if we are replacing GPS or network provider if (LocationManager.GPS_PROVIDER.equals(name) - || LocationManager.NETWORK_PROVIDER.equals(name)) { + || LocationManager.NETWORK_PROVIDER.equals(name) + || LocationManager.FUSED_PROVIDER.equals(name)) { LocationProviderInterface p = mProvidersByName.get(name); if (p != null) { - p.enableLocationTracking(false); - removeProvider(p); + removeProviderLocked(p); } } if (mProvidersByName.get(name) != null) { throw new IllegalArgumentException("Provider \"" + name + "\" already exists"); } - addProvider(provider); + addProviderLocked(provider); mMockProviders.put(name, provider); - mLastKnownLocation.put(name, null); + mLastLocation.put(name, null); updateProvidersLocked(); } Binder.restoreCallingIdentity(identity); } + @Override public void removeTestProvider(String provider) { checkMockPermissionsSafe(); synchronized (mLock) { @@ -2251,22 +1599,21 @@ public class LocationManagerService extends ILocationManager.Stub implements Run throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } long identity = Binder.clearCallingIdentity(); - removeProvider(mProvidersByName.get(provider)); + removeProviderLocked(mProvidersByName.get(provider)); mMockProviders.remove(mockProvider); - // reinstall real provider if we were mocking GPS or network provider - if (LocationManager.GPS_PROVIDER.equals(provider) && - mGpsLocationProvider != null) { - addProvider(mGpsLocationProvider); - } else if (LocationManager.NETWORK_PROVIDER.equals(provider) && - mNetworkLocationProvider != null) { - addProvider(mNetworkLocationProvider); - } - mLastKnownLocation.put(provider, null); + + // reinstate real provider if available + LocationProviderInterface realProvider = mRealProviders.get(provider); + if (realProvider != null) { + addProviderLocked(realProvider); + } + mLastLocation.put(provider, null); updateProvidersLocked(); Binder.restoreCallingIdentity(identity); } } + @Override public void setTestProviderLocation(String provider, Location loc) { checkMockPermissionsSafe(); synchronized (mLock) { @@ -2281,6 +1628,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + @Override public void clearTestProviderLocation(String provider) { checkMockPermissionsSafe(); synchronized (mLock) { @@ -2292,6 +1640,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + @Override public void setTestProviderEnabled(String provider, boolean enabled) { checkMockPermissionsSafe(); synchronized (mLock) { @@ -2314,6 +1663,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + @Override public void clearTestProviderEnabled(String provider) { checkMockPermissionsSafe(); synchronized (mLock) { @@ -2329,6 +1679,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + @Override public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) { checkMockPermissionsSafe(); synchronized (mLock) { @@ -2340,6 +1691,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } + @Override public void clearTestProviderStatus(String provider) { checkMockPermissionsSafe(); synchronized (mLock) { @@ -2351,119 +1703,13 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } } - public class BlacklistObserver extends ContentObserver { - public BlacklistObserver(Handler handler) { - super(handler); - } - @Override - public void onChange(boolean selfChange) { - reloadBlacklist(); - } - } - - private void loadBlacklist() { - // Register for changes - BlacklistObserver observer = new BlacklistObserver(mLocationHandler); - mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( - BLACKLIST_CONFIG_NAME), false, observer); - mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( - WHITELIST_CONFIG_NAME), false, observer); - reloadBlacklist(); - } - - private void reloadBlacklist() { - String blacklist[] = getStringArray(BLACKLIST_CONFIG_NAME); - String whitelist[] = getStringArray(WHITELIST_CONFIG_NAME); - synchronized (mLock) { - mWhitelist = whitelist; - Slog.i(TAG, "whitelist: " + arrayToString(mWhitelist)); - mBlacklist = blacklist; - Slog.i(TAG, "blacklist: " + arrayToString(mBlacklist)); - } - } - - private static String arrayToString(String[] array) { - StringBuilder s = new StringBuilder(); - s.append('['); - boolean first = true; - for (String a : array) { - if (!first) s.append(','); - first = false; - s.append(a); - } - s.append(']'); - return s.toString(); - } - - private String[] getStringArray(String key) { - String flatString = Settings.Secure.getString(mContext.getContentResolver(), key); - if (flatString == null) { - return new String[0]; - } - String[] splitStrings = flatString.split(","); - ArrayList<String> result = new ArrayList<String>(); - for (String pkg : splitStrings) { - pkg = pkg.trim(); - if (pkg.isEmpty()) { - continue; - } - result.add(pkg); - } - return result.toArray(new String[result.size()]); - } - - /** - * Return true if in blacklist, and not in whitelist. - */ - private boolean inBlacklist(String packageName) { - synchronized (mLock) { - for (String black : mBlacklist) { - if (packageName.startsWith(black)) { - if (inWhitelist(packageName)) { - continue; - } else { - if (LOCAL_LOGV) Log.d(TAG, "dropping location (blacklisted): " - + packageName + " matches " + black); - return true; - } - } - } - } - return false; - } - - /** - * Return true if any of packages are in whitelist - */ - private boolean inWhitelist(String pkg) { - synchronized (mLock) { - for (String white : mWhitelist) { - if (pkg.startsWith(white)) return true; - } - } - return false; - } - - private void checkPackageName(int uid, String packageName) { - if (packageName == null) { - throw new SecurityException("packageName cannot be null"); - } - String[] packages = mPackageManager.getPackagesForUid(uid); - if (packages == null) { - throw new SecurityException("invalid UID " + uid); - } - for (String pkg : packages) { - if (packageName.equals(pkg)) return; - } - throw new SecurityException("invalid package name"); - } - private void log(String log) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Slog.d(TAG, log); } } - + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -2472,83 +1718,65 @@ public class LocationManagerService extends ILocationManager.Stub implements Run + ", uid=" + Binder.getCallingUid()); return; } - + synchronized (mLock) { pw.println("Current Location Manager state:"); - pw.println(" sProvidersLoaded=" + sProvidersLoaded); - pw.println(" Listeners:"); - int N = mReceivers.size(); - for (int i=0; i<N; i++) { - pw.println(" " + mReceivers.get(i)); - } pw.println(" Location Listeners:"); - for (Receiver i : mReceivers.values()) { - pw.println(" " + i + ":"); - for (Map.Entry<String,UpdateRecord> j : i.mUpdateRecords.entrySet()) { - pw.println(" " + j.getKey() + ":"); - j.getValue().dump(pw, " "); - } + for (Receiver receiver : mReceivers.values()) { + pw.println(" " + receiver); } - pw.println(" Package blacklist:" + arrayToString(mBlacklist)); - pw.println(" Package whitelist:" + arrayToString(mWhitelist)); pw.println(" Records by Provider:"); - for (Map.Entry<String, ArrayList<UpdateRecord>> i - : mRecordsByProvider.entrySet()) { - pw.println(" " + i.getKey() + ":"); - for (UpdateRecord j : i.getValue()) { - pw.println(" " + j + ":"); - j.dump(pw, " "); + for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) { + pw.println(" " + entry.getKey() + ":"); + for (UpdateRecord record : entry.getValue()) { + pw.println(" " + record); } } pw.println(" Last Known Locations:"); - for (Map.Entry<String, Location> i - : mLastKnownLocation.entrySet()) { - pw.println(" " + i.getKey() + ":"); - i.getValue().dump(new PrintWriterPrinter(pw), " "); - } - if (mProximityAlerts.size() > 0) { - pw.println(" Proximity Alerts:"); - for (Map.Entry<PendingIntent, ProximityAlert> i - : mProximityAlerts.entrySet()) { - pw.println(" " + i.getKey() + ":"); - i.getValue().dump(pw, " "); - } - } - if (mProximitiesEntered.size() > 0) { - pw.println(" Proximities Entered:"); - for (ProximityAlert i : mProximitiesEntered) { - pw.println(" " + i + ":"); - i.dump(pw, " "); - } + for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) { + String provider = entry.getKey(); + Location location = entry.getValue(); + pw.println(" " + provider + ": " + location); } - pw.println(" mProximityReceiver=" + mProximityReceiver); - pw.println(" mProximityListener=" + mProximityListener); + + mGeofenceManager.dump(pw); + if (mEnabledProviders.size() > 0) { pw.println(" Enabled Providers:"); for (String i : mEnabledProviders) { pw.println(" " + i); } - + } if (mDisabledProviders.size() > 0) { pw.println(" Disabled Providers:"); for (String i : mDisabledProviders) { pw.println(" " + i); } - } + pw.append(" "); + mBlacklist.dump(pw); if (mMockProviders.size() > 0) { pw.println(" Mock Providers:"); for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) { i.getValue().dump(pw, " "); } } + + pw.append(" fudger: "); + mLocationFudger.dump(fd, pw, args); + + if (args.length > 0 && "short".equals(args[0])) { + return; + } for (LocationProviderInterface provider: mProviders) { - String state = provider.getInternalState(); - if (state != null) { - pw.println(provider.getName() + " Internal State:"); - pw.write(state); + pw.print(provider.getName() + " Internal State"); + if (provider instanceof LocationProviderProxy) { + LocationProviderProxy proxy = (LocationProviderProxy) provider; + pw.print(" (" + proxy.getConnectedPackageName() + ")"); } + pw.println(":"); + provider.dump(fd, pw, args); } } } diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 04267a3..32ab154 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -16,11 +16,7 @@ package com.android.server; -import com.android.internal.app.IMediaContainerService; -import com.android.internal.util.XmlUtils; -import com.android.server.am.ActivityManagerService; -import com.android.server.pm.PackageManagerService; -import com.android.server.NativeDaemonConnector.Command; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.content.BroadcastReceiver; @@ -30,6 +26,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.ObbInfo; import android.content.res.Resources; import android.content.res.TypedArray; @@ -38,17 +35,16 @@ import android.hardware.usb.UsbManager; import android.net.Uri; import android.os.Binder; import android.os.Environment; +import android.os.Environment.UserEnvironment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; import android.os.SystemProperties; -import android.os.UserId; +import android.os.UserHandle; import android.os.storage.IMountService; import android.os.storage.IMountServiceListener; import android.os.storage.IMountShutdownObserver; @@ -61,9 +57,18 @@ import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; -import org.xmlpull.v1.XmlPullParser; +import com.android.internal.app.IMediaContainerService; +import com.android.internal.util.XmlUtils; +import com.android.server.NativeDaemonConnector.Command; +import com.android.server.am.ActivityManagerService; +import com.android.server.pm.PackageManagerService; +import com.android.server.pm.UserManagerService; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + import org.xmlpull.v1.XmlPullParserException; +import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -81,7 +86,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.Set; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; @@ -96,9 +100,11 @@ import javax.crypto.spec.PBEKeySpec; class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks, Watchdog.Monitor { - private static final boolean LOCAL_LOGD = false; - private static final boolean DEBUG_UNMOUNT = false; - private static final boolean DEBUG_EVENTS = false; + // TODO: listen for user creation/deletion + + private static final boolean LOCAL_LOGD = true; + private static final boolean DEBUG_UNMOUNT = true; + private static final boolean DEBUG_EVENTS = true; private static final boolean DEBUG_OBB = false; // Disable this since it messes up long-running cryptfs operations. @@ -166,25 +172,34 @@ class MountService extends IMountService.Stub public static final int VolumeBadRemoval = 632; } - private Context mContext; - private NativeDaemonConnector mConnector; - private final ArrayList<StorageVolume> mVolumes = new ArrayList<StorageVolume>(); - private StorageVolume mPrimaryVolume; - private final HashMap<String, String> mVolumeStates = new HashMap<String, String>(); - private final HashMap<String, StorageVolume> mVolumeMap = new HashMap<String, StorageVolume>(); - private String mExternalStoragePath; + private Context mContext; + private NativeDaemonConnector mConnector; + + private final Object mVolumesLock = new Object(); + + /** When defined, base template for user-specific {@link StorageVolume}. */ + private StorageVolume mEmulatedTemplate; + + // @GuardedBy("mVolumesLock") + private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList(); + /** Map from path to {@link StorageVolume} */ + // @GuardedBy("mVolumesLock") + private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap(); + /** Map from path to state */ + // @GuardedBy("mVolumesLock") + private final HashMap<String, String> mVolumeStates = Maps.newHashMap(); + + private volatile boolean mSystemReady = false; + private PackageManagerService mPms; private boolean mUmsEnabling; private boolean mUmsAvailable = false; // Used as a lock for methods that register/unregister listeners. final private ArrayList<MountServiceBinderListener> mListeners = new ArrayList<MountServiceBinderListener>(); - private boolean mBooted = false; private CountDownLatch mConnectedSignal = new CountDownLatch(1); private CountDownLatch mAsecsScanned = new CountDownLatch(1); private boolean mSendUmsConnectedOnBoot = false; - // true if we should fake MEDIA_MOUNTED state for external storage - private boolean mEmulateExternalStorage = false; /** * Private hash of currently mounted secure containers. @@ -303,6 +318,8 @@ class MountService extends IMountService.Stub private static final int H_UNMOUNT_PM_UPDATE = 1; private static final int H_UNMOUNT_PM_DONE = 2; private static final int H_UNMOUNT_MS = 3; + private static final int H_SYSTEM_READY = 4; + private static final int RETRY_UNMOUNT_DELAY = 30; // in ms private static final int MAX_UNMOUNT_RETRIES = 4; @@ -437,17 +454,26 @@ class MountService extends IMountService.Stub } break; } - case H_UNMOUNT_MS : { + case H_UNMOUNT_MS: { if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS"); UnmountCallBack ucb = (UnmountCallBack) msg.obj; ucb.handleFinished(); break; } + case H_SYSTEM_READY: { + try { + handleSystemReady(); + } catch (Exception ex) { + Slog.e(TAG, "Boot-time mount exception", ex); + } + break; + } } } }; - final private HandlerThread mHandlerThread; - final private Handler mHandler; + + private final HandlerThread mHandlerThread; + private final Handler mHandler; void waitForAsecScan() { waitForLatch(mAsecsScanned); @@ -476,90 +502,119 @@ class MountService extends IMountService.Stub } } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); + private void handleSystemReady() { + // Snapshot current volume states since it's not safe to call into vold + // while holding locks. + final HashMap<String, String> snapshot; + synchronized (mVolumesLock) { + snapshot = new HashMap<String, String>(mVolumeStates); + } - if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { - mBooted = true; + for (Map.Entry<String, String> entry : snapshot.entrySet()) { + final String path = entry.getKey(); + final String state = entry.getValue(); + if (state.equals(Environment.MEDIA_UNMOUNTED)) { + int rc = doMountVolume(path); + if (rc != StorageResultCode.OperationSucceeded) { + Slog.e(TAG, String.format("Boot-time mount failed (%d)", + rc)); + } + } else if (state.equals(Environment.MEDIA_SHARED)) { /* - * In the simulator, we need to broadcast a volume mounted event - * to make the media scanner run. + * Bootstrap UMS enabled state since vold indicates + * the volume is shared (runtime restart while ums enabled) */ - if ("simulator".equals(SystemProperties.get("ro.product.device"))) { - notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia, - VolumeState.Mounted); - return; + notifyVolumeStateChange(null, path, VolumeState.NoMedia, + VolumeState.Shared); + } + } + + // Push mounted state for all emulated storage + synchronized (mVolumesLock) { + for (StorageVolume volume : mVolumes) { + if (volume.isEmulated()) { + updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); } - new Thread() { - @Override - public void run() { - try { - // it is not safe to call vold with mVolumeStates locked - // so we make a copy of the paths and states and process them - // outside the lock - String[] paths; - String[] states; - int count; - synchronized (mVolumeStates) { - Set<String> keys = mVolumeStates.keySet(); - count = keys.size(); - paths = keys.toArray(new String[count]); - states = new String[count]; - for (int i = 0; i < count; i++) { - states[i] = mVolumeStates.get(paths[i]); - } - } + } + } - for (int i = 0; i < count; i++) { - String path = paths[i]; - String state = states[i]; - - if (state.equals(Environment.MEDIA_UNMOUNTED)) { - int rc = doMountVolume(path); - if (rc != StorageResultCode.OperationSucceeded) { - Slog.e(TAG, String.format("Boot-time mount failed (%d)", - rc)); - } - } else if (state.equals(Environment.MEDIA_SHARED)) { - /* - * Bootstrap UMS enabled state since vold indicates - * the volume is shared (runtime restart while ums enabled) - */ - notifyVolumeStateChange(null, path, VolumeState.NoMedia, - VolumeState.Shared); - } - } + /* + * If UMS was connected on boot, send the connected event + * now that we're up. + */ + if (mSendUmsConnectedOnBoot) { + sendUmsIntent(true); + mSendUmsConnectedOnBoot = false; + } + } - /* notify external storage has mounted to trigger media scanner */ - if (mEmulateExternalStorage) { - notifyVolumeStateChange(null, - Environment.getExternalStorageDirectory().getPath(), - VolumeState.NoMedia, VolumeState.Mounted); - } + private final BroadcastReceiver mBootReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId == -1) return; + final UserHandle user = new UserHandle(userId); - /* - * If UMS was connected on boot, send the connected event - * now that we're up. - */ - if (mSendUmsConnectedOnBoot) { - sendUmsIntent(true); - mSendUmsConnectedOnBoot = false; - } - } catch (Exception ex) { - Slog.e(TAG, "Boot-time mount exception", ex); + Slog.d(TAG, "BOOT_COMPLETED for " + user); + + // Broadcast mounted volumes to newly booted user. This kicks off + // media scanner when a user becomes active. + synchronized (mVolumesLock) { + for (StorageVolume volume : mVolumes) { + final UserHandle owner = volume.getOwner(); + final boolean ownerMatch = owner == null + || owner.getIdentifier() == user.getIdentifier(); + + final String state = mVolumeStates.get(volume.getPath()); + + if (ownerMatch && (Environment.MEDIA_MOUNTED.equals(state) + || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state))) { + sendStorageIntent(Intent.ACTION_MEDIA_MOUNTED, volume, user); + } + } + } + } + }; + + private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId == -1) return; + final UserHandle user = new UserHandle(userId); + + final String action = intent.getAction(); + if (Intent.ACTION_USER_ADDED.equals(action)) { + synchronized (mVolumesLock) { + createEmulatedVolumeForUserLocked(user); + } + + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + synchronized (mVolumesLock) { + final List<StorageVolume> toRemove = Lists.newArrayList(); + for (StorageVolume volume : mVolumes) { + if (user.equals(volume.getOwner())) { + toRemove.add(volume); } } - }.start(); - } else if (action.equals(UsbManager.ACTION_USB_STATE)) { - boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) && - intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false)); - notifyShareAvailabilityChange(available); + for (StorageVolume volume : toRemove) { + removeVolumeLocked(volume); + } + } } } }; + + private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) && + intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false)); + notifyShareAvailabilityChange(available); + } + }; + private final class MountServiceBinderListener implements IBinder.DeathRecipient { final IMountServiceListener mListener; @@ -590,11 +645,13 @@ class MountService extends IMountService.Stub } } - private void updatePublicVolumeState(String path, String state) { - String oldState; - synchronized(mVolumeStates) { + private void updatePublicVolumeState(StorageVolume volume, String state) { + final String path = volume.getPath(); + final String oldState; + synchronized (mVolumesLock) { oldState = mVolumeStates.put(path, state); } + if (state.equals(oldState)) { Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s", state, state, path)); @@ -603,24 +660,24 @@ class MountService extends IMountService.Stub Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")"); - if (path.equals(mExternalStoragePath)) { - // Update state on PackageManager, but only of real events - if (!mEmulateExternalStorage) { - if (Environment.MEDIA_UNMOUNTED.equals(state)) { - mPms.updateExternalMediaStatus(false, false); + // Tell PackageManager about changes to primary volume state, but only + // when not emulated. + if (volume.isPrimary() && !volume.isEmulated()) { + if (Environment.MEDIA_UNMOUNTED.equals(state)) { + mPms.updateExternalMediaStatus(false, false); - /* - * Some OBBs might have been unmounted when this volume was - * unmounted, so send a message to the handler to let it know to - * remove those from the list of mounted OBBS. - */ - mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( - OBB_FLUSH_MOUNT_STATE, path)); - } else if (Environment.MEDIA_MOUNTED.equals(state)) { - mPms.updateExternalMediaStatus(true, false); - } + /* + * Some OBBs might have been unmounted when this volume was + * unmounted, so send a message to the handler to let it know to + * remove those from the list of mounted OBBS. + */ + mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( + OBB_FLUSH_MOUNT_STATE, path)); + } else if (Environment.MEDIA_MOUNTED.equals(state)) { + mPms.updateExternalMediaStatus(true, false); } } + synchronized (mListeners) { for (int i = mListeners.size() -1; i >= 0; i--) { MountServiceBinderListener bl = mListeners.get(i); @@ -637,7 +694,6 @@ class MountService extends IMountService.Stub } /** - * * Callback from NativeDaemonConnector */ public void onDaemonConnected() { @@ -661,6 +717,11 @@ class MountService extends IMountService.Stub String path = tok[1]; String state = Environment.MEDIA_REMOVED; + final StorageVolume volume; + synchronized (mVolumesLock) { + volume = mVolumesByPath.get(path); + } + int st = Integer.parseInt(tok[2]); if (st == VolumeState.NoMedia) { state = Environment.MEDIA_REMOVED; @@ -678,12 +739,15 @@ class MountService extends IMountService.Stub if (state != null) { if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state); - updatePublicVolumeState(path, state); + updatePublicVolumeState(volume, state); } } } catch (Exception e) { Slog.e(TAG, "Error processing initial volume state", e); - updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED); + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary != null) { + updatePublicVolumeState(primary, Environment.MEDIA_REMOVED); + } } /* @@ -749,6 +813,13 @@ class MountService extends IMountService.Stub Slog.e(TAG, "Failed to parse major/minor", ex); } + final StorageVolume volume; + final String state; + synchronized (mVolumesLock) { + volume = mVolumesByPath.get(path); + state = mVolumeStates.get(path); + } + if (code == VoldResponseCode.VolumeDiskInserted) { new Thread() { @Override @@ -772,27 +843,27 @@ class MountService extends IMountService.Stub } /* Send the media unmounted event first */ if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); - sendStorageIntent(Environment.MEDIA_UNMOUNTED, path); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); + sendStorageIntent(Environment.MEDIA_UNMOUNTED, volume, UserHandle.ALL); if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed"); - updatePublicVolumeState(path, Environment.MEDIA_REMOVED); + updatePublicVolumeState(volume, Environment.MEDIA_REMOVED); action = Intent.ACTION_MEDIA_REMOVED; } else if (code == VoldResponseCode.VolumeBadRemoval) { if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first"); /* Send the media unmounted event first */ - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); action = Intent.ACTION_MEDIA_UNMOUNTED; if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal"); - updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL); + updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL); action = Intent.ACTION_MEDIA_BAD_REMOVAL; } else { Slog.e(TAG, String.format("Unknown code {%d}", code)); } if (action != null) { - sendStorageIntent(action, path); + sendStorageIntent(action, volume, UserHandle.ALL); } } else { return false; @@ -802,14 +873,20 @@ class MountService extends IMountService.Stub } private void notifyVolumeStateChange(String label, String path, int oldState, int newState) { - String vs = getVolumeState(path); - if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs); + final StorageVolume volume; + final String state; + synchronized (mVolumesLock) { + volume = mVolumesByPath.get(path); + state = getVolumeState(path); + } + + if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state); String action = null; if (oldState == VolumeState.Shared && newState != oldState) { if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent"); - sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, path); + sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL); } if (newState == VolumeState.Init) { @@ -820,22 +897,22 @@ class MountService extends IMountService.Stub * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or * if we're in the process of enabling UMS */ - if (!vs.equals( - Environment.MEDIA_BAD_REMOVAL) && !vs.equals( - Environment.MEDIA_NOFS) && !vs.equals( + if (!state.equals( + Environment.MEDIA_BAD_REMOVAL) && !state.equals( + Environment.MEDIA_NOFS) && !state.equals( Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) { if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable"); - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); action = Intent.ACTION_MEDIA_UNMOUNTED; } } else if (newState == VolumeState.Pending) { } else if (newState == VolumeState.Checking) { if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking"); - updatePublicVolumeState(path, Environment.MEDIA_CHECKING); + updatePublicVolumeState(volume, Environment.MEDIA_CHECKING); action = Intent.ACTION_MEDIA_CHECKING; } else if (newState == VolumeState.Mounted) { if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted"); - updatePublicVolumeState(path, Environment.MEDIA_MOUNTED); + updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); action = Intent.ACTION_MEDIA_MOUNTED; } else if (newState == VolumeState.Unmounting) { action = Intent.ACTION_MEDIA_EJECT; @@ -843,11 +920,11 @@ class MountService extends IMountService.Stub } else if (newState == VolumeState.Shared) { if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted"); /* Send the media unmounted event first */ - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED); - sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, path); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED); + sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL); if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared"); - updatePublicVolumeState(path, Environment.MEDIA_SHARED); + updatePublicVolumeState(volume, Environment.MEDIA_SHARED); action = Intent.ACTION_MEDIA_SHARED; if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent"); } else if (newState == VolumeState.SharedMnt) { @@ -858,13 +935,18 @@ class MountService extends IMountService.Stub } if (action != null) { - sendStorageIntent(action, path); + sendStorageIntent(action, volume, UserHandle.ALL); } } private int doMountVolume(String path) { int rc = StorageResultCode.OperationSucceeded; + final StorageVolume volume; + synchronized (mVolumesLock) { + volume = mVolumesByPath.get(path); + } + if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); try { mConnector.execute("volume", "mount", path); @@ -884,7 +966,7 @@ class MountService extends IMountService.Stub /* * Media is blank or does not contain a supported filesystem */ - updatePublicVolumeState(path, Environment.MEDIA_NOFS); + updatePublicVolumeState(volume, Environment.MEDIA_NOFS); action = Intent.ACTION_MEDIA_NOFS; rc = StorageResultCode.OperationFailedMediaBlank; } else if (code == VoldResponseCode.OpFailedMediaCorrupt) { @@ -892,7 +974,7 @@ class MountService extends IMountService.Stub /* * Volume consistency check failed */ - updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE); + updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE); action = Intent.ACTION_MEDIA_UNMOUNTABLE; rc = StorageResultCode.OperationFailedMediaCorrupt; } else { @@ -903,7 +985,7 @@ class MountService extends IMountService.Stub * Send broadcast intent (if required for the failure) */ if (action != null) { - sendStorageIntent(action, path); + sendStorageIntent(action, volume, UserHandle.ALL); } } @@ -1011,14 +1093,16 @@ class MountService extends IMountService.Stub } } - if (mBooted == true) { + if (mSystemReady == true) { sendUmsIntent(avail); } else { mSendUmsConnectedOnBoot = avail; } - final String path = Environment.getExternalStorageDirectory().getPath(); - if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) { + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (avail == false && primary != null + && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) { + final String path = primary.getPath(); /* * USB mass storage disconnected while enabled */ @@ -1042,17 +1126,17 @@ class MountService extends IMountService.Stub } } - private void sendStorageIntent(String action, String path) { - Intent intent = new Intent(action, Uri.parse("file://" + path)); - // add StorageVolume extra - intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.get(path)); - Slog.d(TAG, "sendStorageIntent " + intent); - mContext.sendBroadcast(intent); + private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) { + final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath())); + intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume); + Slog.d(TAG, "sendStorageIntent " + intent + " to " + user); + mContext.sendBroadcastAsUser(intent, user); } private void sendUmsIntent(boolean c) { - mContext.sendBroadcast( - new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED))); + mContext.sendBroadcastAsUser( + new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)), + UserHandle.ALL); } private void validatePermission(String perm) { @@ -1065,7 +1149,10 @@ class MountService extends IMountService.Stub private static final String TAG_STORAGE_LIST = "StorageList"; private static final String TAG_STORAGE = "storage"; - private void readStorageList() { + private void readStorageListLocked() { + mVolumes.clear(); + mVolumeStates.clear(); + Resources resources = mContext.getResources(); int id = com.android.internal.R.xml.storage_list; @@ -1084,7 +1171,7 @@ class MountService extends IMountService.Stub TypedArray a = resources.obtainAttributes(attrs, com.android.internal.R.styleable.Storage); - CharSequence path = a.getText( + String path = a.getString( com.android.internal.R.styleable.Storage_mountPoint); int descriptionId = a.getResourceId( com.android.internal.R.styleable.Storage_storageDescription, -1); @@ -1109,28 +1196,29 @@ class MountService extends IMountService.Stub " emulated: " + emulated + " mtpReserve: " + mtpReserve + " allowMassStorage: " + allowMassStorage + " maxFileSize: " + maxFileSize); - if (path == null || description == null) { - Slog.e(TAG, "path or description is null in readStorageList"); - } else { - String pathString = path.toString(); - StorageVolume volume = new StorageVolume(pathString, - descriptionId, removable, emulated, - mtpReserve, allowMassStorage, maxFileSize); - if (primary) { - if (mPrimaryVolume == null) { - mPrimaryVolume = volume; - } else { - Slog.e(TAG, "multiple primary volumes in storage list"); - } + + if (emulated) { + // For devices with emulated storage, we create separate + // volumes for each known user. + mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false, + true, mtpReserve, false, maxFileSize, null); + + final UserManagerService userManager = UserManagerService.getInstance(); + for (UserInfo user : userManager.getUsers()) { + createEmulatedVolumeForUserLocked(user.getUserHandle()); } - if (mPrimaryVolume == volume) { - // primay volume must be first - mVolumes.add(0, volume); + + } else { + if (path == null || description == null) { + Slog.e(TAG, "Missing storage path or description in readStorageList"); } else { - mVolumes.add(volume); + final StorageVolume volume = new StorageVolume(new File(path), + descriptionId, primary, removable, emulated, mtpReserve, + allowMassStorage, maxFileSize, null); + addVolumeLocked(volume); } - mVolumeMap.put(pathString, volume); } + a.recycle(); } } @@ -1139,48 +1227,105 @@ class MountService extends IMountService.Stub } catch (IOException e) { throw new RuntimeException(e); } finally { - // compute storage ID for each volume - int length = mVolumes.size(); - for (int i = 0; i < length; i++) { - mVolumes.get(i).setStorageId(i); + // Compute storage ID for each physical volume; emulated storage is + // always 0 when defined. + int index = isExternalStorageEmulated() ? 1 : 0; + for (StorageVolume volume : mVolumes) { + if (!volume.isEmulated()) { + volume.setStorageId(index++); + } } parser.close(); } } /** + * Create and add new {@link StorageVolume} for given {@link UserHandle} + * using {@link #mEmulatedTemplate} as template. + */ + private void createEmulatedVolumeForUserLocked(UserHandle user) { + if (mEmulatedTemplate == null) { + throw new IllegalStateException("Missing emulated volume multi-user template"); + } + + final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier()); + final File path = userEnv.getExternalStorageDirectory(); + final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user); + volume.setStorageId(0); + addVolumeLocked(volume); + + if (mSystemReady) { + updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED); + } else { + // Place stub status for early callers to find + mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED); + } + } + + private void addVolumeLocked(StorageVolume volume) { + Slog.d(TAG, "addVolumeLocked() " + volume); + mVolumes.add(volume); + final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume); + if (existing != null) { + throw new IllegalStateException( + "Volume at " + volume.getPath() + " already exists: " + existing); + } + } + + private void removeVolumeLocked(StorageVolume volume) { + Slog.d(TAG, "removeVolumeLocked() " + volume); + mVolumes.remove(volume); + mVolumesByPath.remove(volume.getPath()); + mVolumeStates.remove(volume.getPath()); + } + + private StorageVolume getPrimaryPhysicalVolume() { + synchronized (mVolumesLock) { + for (StorageVolume volume : mVolumes) { + if (volume.isPrimary() && !volume.isEmulated()) { + return volume; + } + } + } + return null; + } + + /** * Constructs a new MountService instance * * @param context Binder context for this service */ public MountService(Context context) { mContext = context; - readStorageList(); - if (mPrimaryVolume != null) { - mExternalStoragePath = mPrimaryVolume.getPath(); - mEmulateExternalStorage = mPrimaryVolume.isEmulated(); - if (mEmulateExternalStorage) { - Slog.d(TAG, "using emulated external storage"); - mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED); - } + synchronized (mVolumesLock) { + readStorageListLocked(); } // XXX: This will go away soon in favor of IMountServiceObserver mPms = (PackageManagerService) ServiceManager.getService("package"); - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BOOT_COMPLETED); - // don't bother monitoring USB if mass storage is not supported on our primary volume - if (mPrimaryVolume != null && mPrimaryVolume.allowMassStorage()) { - filter.addAction(UsbManager.ACTION_USB_STATE); - } - mContext.registerReceiver(mBroadcastReceiver, filter, null, null); - mHandlerThread = new HandlerThread("MountService"); mHandlerThread.start(); mHandler = new MountServiceHandler(mHandlerThread.getLooper()); + // Watch for user boot completion + mContext.registerReceiverAsUser(mBootReceiver, UserHandle.ALL, + new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, mHandler); + + // Watch for user changes + final IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_ADDED); + userFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); + + // Watch for USB changes on primary volume + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary != null && primary.allowMassStorage()) { + mContext.registerReceiver( + mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler); + } + // Add OBB Action Handler to MountService thread. mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); @@ -1200,6 +1345,11 @@ class MountService extends IMountService.Stub } } + public void systemReady() { + mSystemReady = true; + mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); + } + /** * Exposed API calls below here */ @@ -1232,7 +1382,7 @@ class MountService extends IMountService.Stub validatePermission(android.Manifest.permission.SHUTDOWN); Slog.i(TAG, "Shutting down"); - synchronized (mVolumeStates) { + synchronized (mVolumesLock) { for (String path : mVolumeStates.keySet()) { String state = mVolumeStates.get(path); @@ -1313,12 +1463,15 @@ class MountService extends IMountService.Stub waitForReady(); validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary == null) return; + // TODO: Add support for multiple share methods /* * If the volume is mounted and we're enabling then unmount it */ - String path = Environment.getExternalStorageDirectory().getPath(); + String path = primary.getPath(); String vs = getVolumeState(path); String method = "ums"; if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { @@ -1348,14 +1501,20 @@ class MountService extends IMountService.Stub public boolean isUsbMassStorageEnabled() { waitForReady(); - return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums"); + + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary != null) { + return doGetVolumeShared(primary.getPath(), "ums"); + } else { + return false; + } } /** * @return state of the volume at the specified mount point */ public String getVolumeState(String mountPoint) { - synchronized (mVolumeStates) { + synchronized (mVolumesLock) { String state = mVolumeStates.get(mountPoint); if (state == null) { Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume"); @@ -1370,8 +1529,9 @@ class MountService extends IMountService.Stub } } + @Override public boolean isExternalStorageEmulated() { - return mEmulateExternalStorage; + return mEmulatedTemplate != null; } public int mountVolume(String path) { @@ -1437,7 +1597,9 @@ class MountService extends IMountService.Stub } private void warnOnNotMounted() { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + final StorageVolume primary = getPrimaryPhysicalVolume(); + if (primary != null + && Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()))) { Slog.w(TAG, "getSecureContainerList() called when storage not mounted"); } } @@ -1713,7 +1875,7 @@ class MountService extends IMountService.Stub return false; } - final int packageUid = mPms.getPackageUid(packageName, UserId.getUserId(callerUid)); + final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid)); if (DEBUG_OBB) { Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + @@ -1935,14 +2097,23 @@ class MountService extends IMountService.Stub } } - public Parcelable[] getVolumeList() { - synchronized(mVolumes) { - int size = mVolumes.size(); - Parcelable[] result = new Parcelable[size]; - for (int i = 0; i < size; i++) { - result[i] = mVolumes.get(i); + @Override + public StorageVolume[] getVolumeList() { + final int callingUserId = UserHandle.getCallingUserId(); + final boolean accessAll = (mContext.checkPermission( + android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, + Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED); + + synchronized (mVolumesLock) { + final ArrayList<StorageVolume> filtered = Lists.newArrayList(); + for (StorageVolume volume : mVolumes) { + final UserHandle owner = volume.getOwner(); + final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId; + if (accessAll || ownerMatch) { + filtered.add(volume); + } } - return result; + return filtered.toArray(new StorageVolume[filtered.size()]); } } @@ -2458,7 +2629,7 @@ class MountService extends IMountService.Stub pw.println(""); - synchronized (mVolumes) { + synchronized (mVolumesLock) { pw.println(" mVolumes:"); final int N = mVolumes.size(); diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java index f71125a..92af9a9 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -35,6 +35,9 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.LinkedList; /** @@ -482,102 +485,108 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo private static class ResponseQueue { - private static class Response { + private static class PendingCmd { public int cmdNum; - public LinkedList<NativeDaemonEvent> responses = new LinkedList<NativeDaemonEvent>(); + public BlockingQueue<NativeDaemonEvent> responses = + new ArrayBlockingQueue<NativeDaemonEvent>(10); public String request; - public Response(int c, String r) {cmdNum = c; request = r;} + + // The availableResponseCount member is used to track when we can remove this + // instance from the ResponseQueue. + // This is used under the protection of a sync of the mPendingCmds object. + // A positive value means we've had more writers retreive this object while + // a negative value means we've had more readers. When we've had an equal number + // (it goes to zero) we can remove this object from the mPendingCmds list. + // Note that we may have more responses for this command (and more readers + // coming), but that would result in a new PendingCmd instance being created + // and added with the same cmdNum. + // Also note that when this goes to zero it just means a parity of readers and + // writers have retrieved this object - not that they are done using it. The + // responses queue may well have more responses yet to be read or may get more + // responses added to it. But all those readers/writers have retreived and + // hold references to this instance already so it can be removed from + // mPendingCmds queue. + public int availableResponseCount; + public PendingCmd(int c, String r) {cmdNum = c; request = r;} } - private final LinkedList<Response> mResponses; + private final LinkedList<PendingCmd> mPendingCmds; private int mMaxCount; ResponseQueue(int maxCount) { - mResponses = new LinkedList<Response>(); + mPendingCmds = new LinkedList<PendingCmd>(); mMaxCount = maxCount; } public void add(int cmdNum, NativeDaemonEvent response) { - Response found = null; - synchronized (mResponses) { - for (Response r : mResponses) { - if (r.cmdNum == cmdNum) { - found = r; + PendingCmd found = null; + synchronized (mPendingCmds) { + for (PendingCmd pendingCmd : mPendingCmds) { + if (pendingCmd.cmdNum == cmdNum) { + found = pendingCmd; break; } } if (found == null) { // didn't find it - make sure our queue isn't too big before adding - // another.. - while (mResponses.size() >= mMaxCount) { + while (mPendingCmds.size() >= mMaxCount) { Slog.e("NativeDaemonConnector.ResponseQueue", - "more buffered than allowed: " + mResponses.size() + + "more buffered than allowed: " + mPendingCmds.size() + " >= " + mMaxCount); // let any waiter timeout waiting for this - Response r = mResponses.remove(); + PendingCmd pendingCmd = mPendingCmds.remove(); Slog.e("NativeDaemonConnector.ResponseQueue", - "Removing request: " + r.request + " (" + r.cmdNum + ")"); + "Removing request: " + pendingCmd.request + " (" + + pendingCmd.cmdNum + ")"); } - found = new Response(cmdNum, null); - mResponses.add(found); + found = new PendingCmd(cmdNum, null); + mPendingCmds.add(found); } - found.responses.add(response); - } - synchronized (found) { - found.notify(); + found.availableResponseCount++; + // if a matching remove call has already retrieved this we can remove this + // instance from our list + if (found.availableResponseCount == 0) mPendingCmds.remove(found); } + try { + found.responses.put(response); + } catch (InterruptedException e) { } } // note that the timeout does not count time in deep sleep. If you don't want // the device to sleep, hold a wakelock public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String origCmd) { - long endTime = SystemClock.uptimeMillis() + timeoutMs; - long nowTime; - Response found = null; - while (true) { - synchronized (mResponses) { - for (Response response : mResponses) { - if (response.cmdNum == cmdNum) { - found = response; - // how many response fragments are left - switch (response.responses.size()) { - case 0: // haven't got any - must wait - break; - case 1: // last one - remove this from the master list - mResponses.remove(response); // fall through - default: // take one and move on - response.request = origCmd; - return response.responses.remove(); - } - } - } - nowTime = SystemClock.uptimeMillis(); - if (endTime <= nowTime) { - Slog.e("NativeDaemonConnector.ResponseQueue", - "Timeout waiting for response"); - return null; - } - /* pre-allocate so we have something unique to wait on */ - if (found == null) { - found = new Response(cmdNum, origCmd); - mResponses.add(found); + PendingCmd found = null; + synchronized (mPendingCmds) { + for (PendingCmd pendingCmd : mPendingCmds) { + if (pendingCmd.cmdNum == cmdNum) { + found = pendingCmd; + break; } } - try { - synchronized (found) { - found.wait(endTime - nowTime); - } - } catch (InterruptedException e) { - // loop around to check if we're done or if it's time to stop waiting + if (found == null) { + found = new PendingCmd(cmdNum, origCmd); + mPendingCmds.add(found); } + found.availableResponseCount--; + // if a matching add call has already retrieved this we can remove this + // instance from our list + if (found.availableResponseCount == 0) mPendingCmds.remove(found); + } + NativeDaemonEvent result = null; + try { + result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) {} + if (result == null) { + Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response"); } + return result; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Pending requests:"); - synchronized (mResponses) { - for (Response response : mResponses) { - pw.println(" Cmd " + response.cmdNum + " - " + response.request); + synchronized (mPendingCmds) { + for (PendingCmd pendingCmd : mPendingCmds) { + pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.request); } } } diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 11644e3..3ddae3e 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -35,6 +35,7 @@ import static com.android.server.NetworkManagementService.NetdResponseCode.Tethe import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult; import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; +import android.bluetooth.BluetoothTetheringDataTracker; import android.content.Context; import android.net.INetworkManagementEventObserver; import android.net.InterfaceConfiguration; @@ -44,8 +45,10 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.os.Binder; import android.os.Handler; import android.os.INetworkManagementService; +import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; @@ -55,7 +58,9 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.net.NetworkStatsFactory; +import com.android.internal.util.Preconditions; import com.android.server.NativeDaemonConnector.Command; +import com.android.server.net.LockdownVpnTracker; import com.google.android.collect.Maps; import java.io.BufferedReader; @@ -91,6 +96,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final String ADD = "add"; private static final String REMOVE = "remove"; + private static final String ALLOW = "allow"; + private static final String DENY = "deny"; + private static final String DEFAULT = "default"; private static final String SECONDARY = "secondary"; @@ -121,6 +129,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub public static final int InterfaceChange = 600; public static final int BandwidthControl = 601; + public static final int InterfaceClassActivity = 613; } /** @@ -151,7 +160,23 @@ public class NetworkManagementService extends INetworkManagementService.Stub /** Set of UIDs with active reject rules. */ private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray(); + private Object mIdleTimerLock = new Object(); + /** Set of interfaces with active idle timers. */ + private static class IdleTimerParams { + public final int timeout; + public final String label; + public int networkCount; + + IdleTimerParams(int timeout, String label) { + this.timeout = timeout; + this.label = label; + this.networkCount = 1; + } + } + private HashMap<String, IdleTimerParams> mActiveIdleTimers = Maps.newHashMap(); + private volatile boolean mBandwidthControlEnabled; + private volatile boolean mFirewallEnabled; /** * Constructs a new NetworkManagementService instance @@ -278,6 +303,20 @@ public class NetworkManagementService extends INetworkManagementService.Stub } /** + * Notify our observers of a change in the data activity state of the interface + */ + private void notifyInterfaceClassActivity(String label, boolean active) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { + try { + mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged(label, active); + } catch (RemoteException e) { + } + } + mObservers.finishBroadcast(); + } + + /** * Prepare native daemon once connected, enabling modules and pushing any * existing in-memory rules. */ @@ -332,6 +371,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } } + + // TODO: Push any existing firewall state + setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled()); } // @@ -403,6 +445,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException( String.format("Invalid event from daemon (%s)", raw)); // break; + case NetdResponseCode.InterfaceClassActivity: + /* + * An network interface class state changed (active/idle) + * Format: "NNN IfaceClass <active/idle> <label>" + */ + if (cooked.length < 4 || !cooked[1].equals("IfaceClass")) { + throw new IllegalStateException( + String.format("Invalid event from daemon (%s)", raw)); + } + boolean isActive = cooked[2].equals("active"); + notifyInterfaceClassActivity(cooked[3], isActive); + return true; + // break; default: break; } return false; @@ -780,6 +835,36 @@ public class NetworkManagementService extends INetworkManagementService.Stub return event.getMessage().endsWith("started"); } + // TODO(BT) Remove + public void startReverseTethering(String iface) + throws IllegalStateException { + if (DBG) Slog.d(TAG, "startReverseTethering in"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + // cmd is "tether start first_start first_stop second_start second_stop ..." + // an odd number of addrs will fail + String cmd = "tether start-reverse"; + cmd += " " + iface; + if (DBG) Slog.d(TAG, "startReverseTethering cmd: " + cmd); + try { + mConnector.doCommand(cmd); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Unable to communicate to native daemon"); + } + BluetoothTetheringDataTracker.getInstance().startReverseTether(iface); + + } + + // TODO(BT) Remove + public void stopReverseTethering() throws IllegalStateException { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.doCommand("tether stop-reverse"); + } catch (NativeDaemonConnectorException e) { + throw new IllegalStateException("Unable to communicate to native daemon to stop tether"); + } + BluetoothTetheringDataTracker.getInstance().stopReverseTether(); + } + @Override public void tetherInterface(String iface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); @@ -923,14 +1008,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub @Override public void startAccessPoint( - WifiConfiguration wifiConfig, String wlanIface, String softapIface) { + WifiConfiguration wifiConfig, String wlanIface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { wifiFirmwareReload(wlanIface, "AP"); if (wifiConfig == null) { - mConnector.execute("softap", "set", wlanIface, softapIface); + mConnector.execute("softap", "set", wlanIface); } else { - mConnector.execute("softap", "set", wlanIface, softapIface, wifiConfig.SSID, + mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID, getSecurityType(wifiConfig), wifiConfig.preSharedKey); } mConnector.execute("softap", "startap"); @@ -966,7 +1051,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { mConnector.execute("softap", "stopap"); - mConnector.execute("softap", "stop", wlanIface); wifiFirmwareReload(wlanIface, "STA"); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); @@ -974,13 +1058,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) { + public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { if (wifiConfig == null) { - mConnector.execute("softap", "set", wlanIface, softapIface); + mConnector.execute("softap", "set", wlanIface); } else { - mConnector.execute("softap", "set", wlanIface, softapIface, wifiConfig.SSID, + mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID, getSecurityType(wifiConfig), wifiConfig.preSharedKey); } } catch (NativeDaemonConnectorException e) { @@ -989,6 +1073,51 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override + public void addIdleTimer(String iface, int timeout, String label) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + if (DBG) Slog.d(TAG, "Adding idletimer"); + + synchronized (mIdleTimerLock) { + IdleTimerParams params = mActiveIdleTimers.get(iface); + if (params != null) { + // the interface already has idletimer, update network count + params.networkCount++; + return; + } + + try { + mConnector.execute("idletimer", "add", iface, Integer.toString(timeout), label); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, label)); + } + } + + @Override + public void removeIdleTimer(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + if (DBG) Slog.d(TAG, "Removing idletimer"); + + synchronized (mIdleTimerLock) { + IdleTimerParams params = mActiveIdleTimers.get(iface); + if (params == null || --(params.networkCount) > 0) { + return; + } + + try { + mConnector.execute("idletimer", "remove", iface, + Integer.toString(params.timeout), params.label); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + mActiveIdleTimers.remove(iface); + } + } + + @Override public NetworkStats getNetworkStatsSummaryDev() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mStatsFactory.readNetworkStatsSummaryDev(); @@ -1307,7 +1436,79 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } - /** {@inheritDoc} */ + @Override + public void setFirewallEnabled(boolean enabled) { + enforceSystemUid(); + try { + mConnector.execute("firewall", enabled ? "enable" : "disable"); + mFirewallEnabled = enabled; + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public boolean isFirewallEnabled() { + enforceSystemUid(); + return mFirewallEnabled; + } + + @Override + public void setFirewallInterfaceRule(String iface, boolean allow) { + enforceSystemUid(); + Preconditions.checkState(mFirewallEnabled); + final String rule = allow ? ALLOW : DENY; + try { + mConnector.execute("firewall", "set_interface_rule", iface, rule); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void setFirewallEgressSourceRule(String addr, boolean allow) { + enforceSystemUid(); + Preconditions.checkState(mFirewallEnabled); + final String rule = allow ? ALLOW : DENY; + try { + mConnector.execute("firewall", "set_egress_source_rule", addr, rule); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void setFirewallEgressDestRule(String addr, int port, boolean allow) { + enforceSystemUid(); + Preconditions.checkState(mFirewallEnabled); + final String rule = allow ? ALLOW : DENY; + try { + mConnector.execute("firewall", "set_egress_dest_rule", addr, port, rule); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void setFirewallUidRule(int uid, boolean allow) { + enforceSystemUid(); + Preconditions.checkState(mFirewallEnabled); + final String rule = allow ? ALLOW : DENY; + try { + mConnector.execute("firewall", "set_uid_rule", uid, rule); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + private static void enforceSystemUid() { + final int uid = Binder.getCallingUid(); + if (uid != Process.SYSTEM_UID) { + throw new SecurityException("Only available to AID_SYSTEM"); + } + } + + @Override public void monitor() { if (mConnector != null) { mConnector.monitor(); @@ -1338,5 +1539,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub } pw.println("]"); } + + pw.print("Firewall enabled: "); pw.println(mFirewallEnabled); } } diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index f6d3b608..3caba1f 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -20,7 +20,9 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; +import android.app.ActivityManager; import android.app.ActivityManagerNative; +import android.app.AppGlobals; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; @@ -48,11 +50,13 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserId; +import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; +import android.service.dreams.IDreamManager; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.AtomicFile; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -61,7 +65,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; -import com.android.internal.os.AtomicFile; import com.android.internal.statusbar.StatusBarNotification; import com.android.internal.util.FastXmlSerializer; @@ -314,17 +317,20 @@ public class NotificationManagerService extends INotificationManager.Stub final int id; final int uid; final int initialPid; + final int userId; final Notification notification; final int score; IBinder statusBarKey; - NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, int score, Notification notification) + NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, + int userId, int score, Notification notification) { this.pkg = pkg; this.tag = tag; this.id = id; this.uid = uid; this.initialPid = initialPid; + this.userId = userId; this.score = score; this.notification = notification; } @@ -339,7 +345,7 @@ public class NotificationManagerService extends INotificationManager.Stub pw.println(prefix + " deleteIntent=" + notification.deleteIntent); pw.println(prefix + " tickerText=" + notification.tickerText); pw.println(prefix + " contentView=" + notification.contentView); - pw.println(prefix + " uid=" + uid); + pw.println(prefix + " uid=" + uid + " userId=" + userId); pw.println(prefix + " defaults=0x" + Integer.toHexString(notification.defaults)); pw.println(prefix + " flags=0x" + Integer.toHexString(notification.flags)); pw.println(prefix + " sound=" + notification.sound); @@ -426,18 +432,25 @@ public class NotificationManagerService extends INotificationManager.Stub } public void onClearAll() { - cancelAll(); + // XXX to be totally correct, the caller should tell us which user + // this is for. + cancelAll(ActivityManager.getCurrentUser()); } public void onNotificationClick(String pkg, String tag, int id) { + // XXX to be totally correct, the caller should tell us which user + // this is for. cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL, - Notification.FLAG_FOREGROUND_SERVICE, false); + Notification.FLAG_FOREGROUND_SERVICE, false, + ActivityManager.getCurrentUser()); } public void onNotificationClear(String pkg, String tag, int id) { + // XXX to be totally correct, the caller should tell us which user + // this is for. cancelNotification(pkg, tag, id, 0, Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, - true); + true, ActivityManager.getCurrentUser()); } public void onPanelRevealed() { @@ -476,7 +489,9 @@ public class NotificationManagerService extends INotificationManager.Stub int uid, int initialPid, String message) { Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")"); - cancelNotification(pkg, tag, id, 0, 0, false); + // XXX to be totally correct, the caller should tell us which user + // this is for. + cancelNotification(pkg, tag, id, 0, 0, false, UserHandle.getUserId(uid)); long ident = Binder.clearCallingIdentity(); try { ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg, @@ -528,7 +543,8 @@ public class NotificationManagerService extends INotificationManager.Stub } if (pkgList != null && (pkgList.length > 0)) { for (String pkgName : pkgList) { - cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart); + cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart, + UserHandle.USER_ALL); } } } else if (action.equals(Intent.ACTION_SCREEN_ON)) { @@ -541,6 +557,11 @@ public class NotificationManagerService extends INotificationManager.Stub mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals( TelephonyManager.EXTRA_STATE_OFFHOOK)); updateNotificationPulse(); + } else if (action.equals(Intent.ACTION_USER_STOPPED)) { + int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userHandle >= 0) { + cancelAllNotificationsInt(null, 0, 0, true, userHandle); + } } else if (action.equals(Intent.ACTION_USER_PRESENT)) { // turn off LED when user passes through lock screen mNotificationLight.turnOff(); @@ -616,6 +637,7 @@ public class NotificationManagerService extends INotificationManager.Stub filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_STOPPED); mContext.registerReceiver(mIntentReceiver, filter); IntentFilter pkgFilter = new IntentFilter(); pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -844,17 +866,11 @@ public class NotificationManagerService extends INotificationManager.Stub // Notifications // ============================================================================ - @Deprecated - public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut) - { - enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut); - } - public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification, - int[] idOut) + int[] idOut, int userId) { enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(), - tag, id, notification, idOut); + tag, id, notification, idOut, userId); } private final static int clamp(int x, int low, int high) { @@ -865,7 +881,7 @@ public class NotificationManagerService extends INotificationManager.Stub // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the // uid/pid of another application) public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid, - String tag, int id, Notification notification, int[] idOut) + String tag, int id, Notification notification, int[] idOut, int userId) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); @@ -873,6 +889,9 @@ public class NotificationManagerService extends INotificationManager.Stub checkCallerIsSystemOrSameApp(pkg); final boolean isSystemNotification = ("android".equals(pkg)); + userId = ActivityManager.handleIncomingUser(callingPid, + callingUid, userId, false, true, "enqueueNotification", pkg); + // Limit the number of notifications that any given package except the android // package can enqueue. Prevents DOS attacks and deals with leaks. if (!isSystemNotification) { @@ -947,12 +966,12 @@ public class NotificationManagerService extends INotificationManager.Stub synchronized (mNotificationList) { NotificationRecord r = new NotificationRecord(pkg, tag, id, - callingUid, callingPid, + callingUid, callingPid, userId, score, notification); NotificationRecord old = null; - int index = indexOfNotificationLocked(pkg, tag, id); + int index = indexOfNotificationLocked(pkg, tag, id, userId); if (index < 0) { mNotificationList.add(r); } else { @@ -1014,6 +1033,7 @@ public class NotificationManagerService extends INotificationManager.Stub if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) && (!(old != null && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) + && (r.userId == UserHandle.USER_ALL || r.userId == userId) && mSystemReady) { final AudioManager audioManager = (AudioManager) mContext @@ -1173,12 +1193,12 @@ public class NotificationManagerService extends INotificationManager.Stub * and none of the {@code mustNotHaveFlags}. */ private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags, - int mustNotHaveFlags, boolean sendDelete) { + int mustNotHaveFlags, boolean sendDelete, int userId) { EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, mustHaveFlags, mustNotHaveFlags); synchronized (mNotificationList) { - int index = indexOfNotificationLocked(pkg, tag, id); + int index = indexOfNotificationLocked(pkg, tag, id, userId); if (index >= 0) { NotificationRecord r = mNotificationList.get(index); @@ -1202,7 +1222,7 @@ public class NotificationManagerService extends INotificationManager.Stub * {@code mustHaveFlags}. */ boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags, - int mustNotHaveFlags, boolean doit) { + int mustNotHaveFlags, boolean doit, int userId) { EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, mustHaveFlags, mustNotHaveFlags); @@ -1211,6 +1231,9 @@ public class NotificationManagerService extends INotificationManager.Stub boolean canceledSomething = false; for (int i = N-1; i >= 0; --i) { NotificationRecord r = mNotificationList.get(i); + if (userId != UserHandle.USER_ALL && r.userId != userId) { + continue; + } if ((r.notification.flags & mustHaveFlags) != mustHaveFlags) { continue; } @@ -1234,30 +1257,30 @@ public class NotificationManagerService extends INotificationManager.Stub } } - @Deprecated - public void cancelNotification(String pkg, int id) { - cancelNotificationWithTag(pkg, null /* tag */, id); - } - - public void cancelNotificationWithTag(String pkg, String tag, int id) { + public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) { checkCallerIsSystemOrSameApp(pkg); + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, true, "cancelNotificationWithTag", pkg); // Don't allow client applications to cancel foreground service notis. cancelNotification(pkg, tag, id, 0, Binder.getCallingUid() == Process.SYSTEM_UID - ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false); + ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId); } - public void cancelAllNotifications(String pkg) { + public void cancelAllNotifications(String pkg, int userId) { checkCallerIsSystemOrSameApp(pkg); + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, true, true, "cancelAllNotifications", pkg); + // Calling from user space, don't allow the canceling of actively // running foreground services. - cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true); + cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId); } void checkCallerIsSystem() { int uid = Binder.getCallingUid(); - if (uid == Process.SYSTEM_UID || uid == 0) { + if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) { return; } throw new SecurityException("Disallowed call for uid " + uid); @@ -1265,27 +1288,31 @@ public class NotificationManagerService extends INotificationManager.Stub void checkCallerIsSystemOrSameApp(String pkg) { int uid = Binder.getCallingUid(); - if (uid == Process.SYSTEM_UID || uid == 0) { + if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) { return; } try { - ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo( - pkg, 0); - if (!UserId.isSameApp(ai.uid, uid)) { + ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo( + pkg, 0, UserHandle.getCallingUserId()); + if (!UserHandle.isSameApp(ai.uid, uid)) { throw new SecurityException("Calling uid " + uid + " gave package" + pkg + " which is owned by uid " + ai.uid); } - } catch (PackageManager.NameNotFoundException e) { - throw new SecurityException("Unknown package " + pkg); + } catch (RemoteException re) { + throw new SecurityException("Unknown package " + pkg + "\n" + re); } } - void cancelAll() { + void cancelAll(int userId) { synchronized (mNotificationList) { final int N = mNotificationList.size(); for (int i=N-1; i>=0; i--) { NotificationRecord r = mNotificationList.get(i); + if (r.userId != userId) { + continue; + } + if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR)) == 0) { mNotificationList.remove(i); @@ -1330,12 +1357,15 @@ public class NotificationManagerService extends INotificationManager.Stub } // lock on mNotificationList - private int indexOfNotificationLocked(String pkg, String tag, int id) + private int indexOfNotificationLocked(String pkg, String tag, int id, int userId) { ArrayList<NotificationRecord> list = mNotificationList; final int len = list.size(); for (int i=0; i<len; i++) { NotificationRecord r = list.get(i); + if (r.userId != userId || r.id != id) { + continue; + } if (tag == null) { if (r.tag != null) { continue; @@ -1345,7 +1375,7 @@ public class NotificationManagerService extends INotificationManager.Stub continue; } } - if (r.id == id && r.pkg.equals(pkg)) { + if (r.pkg.equals(pkg)) { return i; } } diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java index 87843d9..2a7a2eb 100644 --- a/services/java/com/android/server/NsdService.java +++ b/services/java/com/android/server/NsdService.java @@ -31,6 +31,7 @@ import android.os.HandlerThread; import android.os.Message; import android.os.Messenger; import android.os.IBinder; +import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; @@ -448,7 +449,7 @@ public class NsdService extends INsdManager.Stub { } else { intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED); } - mContext.sendStickyBroadcast(intent); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } private boolean isNsdEnabled() { diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java deleted file mode 100644 index 888ec69..0000000 --- a/services/java/com/android/server/PowerManagerService.java +++ /dev/null @@ -1,3405 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import com.android.internal.app.IBatteryStats; -import com.android.server.am.BatteryStatsService; -import com.android.server.pm.ShutdownThread; - -import android.app.ActivityManagerNative; -import android.app.IActivityManager; -import android.content.BroadcastReceiver; -import android.content.ContentQueryMap; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.database.Cursor; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.hardware.SystemSensorManager; -import android.os.BatteryManager; -import android.os.BatteryStats; -import android.os.Binder; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.IPowerManager; -import android.os.LocalPowerManager; -import android.os.Message; -import android.os.PowerManager; -import android.os.Process; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.WorkSource; -import android.provider.Settings; -import android.util.EventLog; -import android.util.Log; -import android.util.Slog; -import android.view.WindowManagerPolicy; -import static android.view.WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR; -import static android.provider.Settings.System.DIM_SCREEN; -import static android.provider.Settings.System.SCREEN_BRIGHTNESS; -import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE; -import static android.provider.Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ; -import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; -import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; -import static android.provider.Settings.System.STAY_ON_WHILE_PLUGGED_IN; -import static android.provider.Settings.System.WINDOW_ANIMATION_SCALE; -import static android.provider.Settings.System.TRANSITION_ANIMATION_SCALE; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Observable; -import java.util.Observer; - -public class PowerManagerService extends IPowerManager.Stub - implements LocalPowerManager, Watchdog.Monitor { - private static final int NOMINAL_FRAME_TIME_MS = 1000/60; - - private static final String TAG = "PowerManagerService"; - static final String PARTIAL_NAME = "PowerManagerService"; - - // Wake lock that ensures that the CPU is running. The screen might not be on. - private static final int PARTIAL_WAKE_LOCK_ID = 1; - - // Wake lock that ensures that the screen is on. - private static final int FULL_WAKE_LOCK_ID = 2; - - static final boolean DEBUG_SCREEN_ON = false; - - private static final boolean LOG_PARTIAL_WL = false; - - // Indicates whether touch-down cycles should be logged as part of the - // LOG_POWER_SCREEN_STATE log events - private static final boolean LOG_TOUCH_DOWNS = true; - - private static final int LOCK_MASK = PowerManager.PARTIAL_WAKE_LOCK - | PowerManager.SCREEN_DIM_WAKE_LOCK - | PowerManager.SCREEN_BRIGHT_WAKE_LOCK - | PowerManager.FULL_WAKE_LOCK - | PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK; - - // time since last state: time since last event: - // The short keylight delay comes from secure settings; this is the default. - private static final int SHORT_KEYLIGHT_DELAY_DEFAULT = 6000; // t+6 sec - private static final int MEDIUM_KEYLIGHT_DELAY = 15000; // t+15 sec - private static final int LONG_KEYLIGHT_DELAY = 6000; // t+6 sec - private static final int LONG_DIM_TIME = 7000; // t+N-5 sec - - // How long to wait to debounce light sensor changes in milliseconds - private static final int LIGHT_SENSOR_DELAY = 2000; - - // light sensor events rate in microseconds - private static final int LIGHT_SENSOR_RATE = 1000000; - - // Expansion of range of light values when applying scale from light - // sensor brightness setting, in the [0..255] brightness range. - private static final int LIGHT_SENSOR_RANGE_EXPANSION = 20; - - // Scaling factor of the light sensor brightness setting when applying - // it to the final brightness. - private static final int LIGHT_SENSOR_OFFSET_SCALE = 8; - - // For debouncing the proximity sensor in milliseconds - private static final int PROXIMITY_SENSOR_DELAY = 1000; - - // trigger proximity if distance is less than 5 cm - private static final float PROXIMITY_THRESHOLD = 5.0f; - - // Cached secure settings; see updateSettingsValues() - private int mShortKeylightDelay = SHORT_KEYLIGHT_DELAY_DEFAULT; - - // Default timeout for screen off, if not found in settings database = 15 seconds. - private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15000; - - // Screen brightness should always have a value, but just in case... - private static final int DEFAULT_SCREEN_BRIGHTNESS = 192; - - // Threshold for BRIGHTNESS_LOW_BATTERY (percentage) - // Screen will stay dim if battery level is <= LOW_BATTERY_THRESHOLD - private static final int LOW_BATTERY_THRESHOLD = 10; - - // flags for setPowerState - private static final int ALL_LIGHTS_OFF = 0x00000000; - private static final int SCREEN_ON_BIT = 0x00000001; - private static final int SCREEN_BRIGHT_BIT = 0x00000002; - private static final int BUTTON_BRIGHT_BIT = 0x00000004; - private static final int KEYBOARD_BRIGHT_BIT = 0x00000008; - private static final int BATTERY_LOW_BIT = 0x00000010; - - // values for setPowerState - - // SCREEN_OFF == everything off - private static final int SCREEN_OFF = 0x00000000; - - // SCREEN_DIM == screen on, screen backlight dim - private static final int SCREEN_DIM = SCREEN_ON_BIT; - - // SCREEN_BRIGHT == screen on, screen backlight bright - private static final int SCREEN_BRIGHT = SCREEN_ON_BIT | SCREEN_BRIGHT_BIT; - - // SCREEN_BUTTON_BRIGHT == screen on, screen and button backlights bright - private static final int SCREEN_BUTTON_BRIGHT = SCREEN_BRIGHT | BUTTON_BRIGHT_BIT; - - // SCREEN_BUTTON_BRIGHT == screen on, screen, button and keyboard backlights bright - private static final int ALL_BRIGHT = SCREEN_BUTTON_BRIGHT | KEYBOARD_BRIGHT_BIT; - - // used for noChangeLights in setPowerState() - private static final int LIGHTS_MASK = SCREEN_BRIGHT_BIT | BUTTON_BRIGHT_BIT | KEYBOARD_BRIGHT_BIT; - - // animate screen lights in PowerManager (as opposed to SurfaceFlinger) - boolean mAnimateScreenLights = true; - - static final int ANIM_STEPS = 60; // nominal # of frames at 60Hz - // Slower animation for autobrightness changes - static final int AUTOBRIGHTNESS_ANIM_STEPS = 2 * ANIM_STEPS; - // Even slower animation for autodimness changes. Set to max to effectively disable dimming. - // Note 100 is used to keep the mWindowScaleAnimation scaling below from overflowing an int. - static final int AUTODIMNESS_ANIM_STEPS = Integer.MAX_VALUE / (NOMINAL_FRAME_TIME_MS * 100); - // Number of steps when performing a more immediate brightness change. - static final int IMMEDIATE_ANIM_STEPS = 4; - - // These magic numbers are the initial state of the LEDs at boot. Ideally - // we should read them from the driver, but our current hardware returns 0 - // for the initial value. Oops! - static final int INITIAL_SCREEN_BRIGHTNESS = 255; - static final int INITIAL_BUTTON_BRIGHTNESS = PowerManager.BRIGHTNESS_OFF; - static final int INITIAL_KEYBOARD_BRIGHTNESS = PowerManager.BRIGHTNESS_OFF; - - private final int MY_UID; - private final int MY_PID; - - private boolean mDoneBooting = false; - private boolean mBootCompleted = false; - private boolean mHeadless = false; - private int mStayOnConditions = 0; - private final int[] mBroadcastQueue = new int[] { -1, -1, -1 }; - private final int[] mBroadcastWhy = new int[3]; - private boolean mPreparingForScreenOn = false; - private boolean mSkippedScreenOn = false; - private boolean mInitialized = false; - private int mPartialCount = 0; - private int mPowerState; - // mScreenOffReason can be WindowManagerPolicy.OFF_BECAUSE_OF_USER, - // WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT or WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR - private int mScreenOffReason; - private int mUserState; - private boolean mKeyboardVisible = false; - private boolean mUserActivityAllowed = true; - private int mProximityWakeLockCount = 0; - private boolean mProximitySensorEnabled = false; - private boolean mProximitySensorActive = false; - private int mProximityPendingValue = -1; // -1 == nothing, 0 == inactive, 1 == active - private long mLastProximityEventTime; - private int mScreenOffTimeoutSetting; - private int mMaximumScreenOffTimeout = Integer.MAX_VALUE; - private int mKeylightDelay; - private int mDimDelay; - private int mScreenOffDelay; - private int mWakeLockState; - private long mLastEventTime = 0; - private long mScreenOffTime; - private volatile WindowManagerPolicy mPolicy; - private final LockList mLocks = new LockList(); - private Intent mScreenOffIntent; - private Intent mScreenOnIntent; - private LightsService mLightsService; - private Context mContext; - private LightsService.Light mLcdLight; - private LightsService.Light mButtonLight; - private LightsService.Light mKeyboardLight; - private LightsService.Light mAttentionLight; - private UnsynchronizedWakeLock mBroadcastWakeLock; - private UnsynchronizedWakeLock mStayOnWhilePluggedInScreenDimLock; - private UnsynchronizedWakeLock mStayOnWhilePluggedInPartialLock; - private UnsynchronizedWakeLock mPreventScreenOnPartialLock; - private UnsynchronizedWakeLock mProximityPartialLock; - private HandlerThread mHandlerThread; - private Handler mScreenOffHandler; - private Handler mScreenBrightnessHandler; - private Handler mHandler; - private final TimeoutTask mTimeoutTask = new TimeoutTask(); - private ScreenBrightnessAnimator mScreenBrightnessAnimator; - private boolean mWaitingForFirstLightSensor = false; - private boolean mStillNeedSleepNotification; - private boolean mIsPowered = false; - private IActivityManager mActivityService; - private IBatteryStats mBatteryStats; - private BatteryService mBatteryService; - private SensorManager mSensorManager; - private Sensor mProximitySensor; - private Sensor mLightSensor; - private boolean mLightSensorEnabled; - private float mLightSensorValue = -1; - private boolean mProxIgnoredBecauseScreenTurnedOff = false; - private int mHighestLightSensorValue = -1; - private boolean mLightSensorPendingDecrease = false; - private boolean mLightSensorPendingIncrease = false; - private float mLightSensorPendingValue = -1; - private float mLightSensorAdjustSetting = 0; - private int mLightSensorScreenBrightness = -1; - private int mLightSensorButtonBrightness = -1; - private int mLightSensorKeyboardBrightness = -1; - private boolean mDimScreen = true; - private boolean mIsDocked = false; - private long mNextTimeout; - private volatile int mPokey = 0; - private volatile boolean mPokeAwakeOnSet = false; - private volatile boolean mInitComplete = false; - private final HashMap<IBinder,PokeLock> mPokeLocks = new HashMap<IBinder,PokeLock>(); - // mLastScreenOnTime is the time the screen was last turned on - private long mLastScreenOnTime; - private boolean mPreventScreenOn; - private int mScreenBrightnessSetting = DEFAULT_SCREEN_BRIGHTNESS; - private int mScreenBrightnessOverride = -1; - private int mButtonBrightnessOverride = -1; - private int mScreenBrightnessDim; - private boolean mUseSoftwareAutoBrightness; - private boolean mAutoBrightessEnabled; - private int[] mAutoBrightnessLevels; - private int[] mLcdBacklightValues; - private int[] mButtonBacklightValues; - private int[] mKeyboardBacklightValues; - private int mLightSensorWarmupTime; - boolean mUnplugTurnsOnScreen; - private int mWarningSpewThrottleCount; - private long mWarningSpewThrottleTime; - private int mAnimationSetting = ANIM_SETTING_OFF; - private float mWindowScaleAnimation; - - // Must match with the ISurfaceComposer constants in C++. - private static final int ANIM_SETTING_ON = 0x01; - private static final int ANIM_SETTING_OFF = 0x10; - - // Used when logging number and duration of touch-down cycles - private long mTotalTouchDownTime; - private long mLastTouchDown; - private int mTouchCycles; - - // could be either static or controllable at runtime - private static final boolean mSpew = false; - private static final boolean mDebugProximitySensor = (false || mSpew); - private static final boolean mDebugLightSensor = (false || mSpew); - private static final boolean mDebugLightAnimation = (false || mSpew); - - private native void nativeInit(); - private native void nativeSetPowerState(boolean screenOn, boolean screenBright); - private native void nativeStartSurfaceFlingerAnimation(int mode); - private static native void nativeAcquireWakeLock(int lock, String id); - private static native void nativeReleaseWakeLock(String id); - private static native int nativeSetScreenState(boolean on); - private static native void nativeShutdown(); - private static native void nativeReboot(String reason) throws IOException; - - /* - static PrintStream mLog; - static { - try { - mLog = new PrintStream("/data/power.log"); - } - catch (FileNotFoundException e) { - android.util.Slog.e(TAG, "Life is hard", e); - } - } - static class Log { - static void d(String tag, String s) { - mLog.println(s); - android.util.Slog.d(tag, s); - } - static void i(String tag, String s) { - mLog.println(s); - android.util.Slog.i(tag, s); - } - static void w(String tag, String s) { - mLog.println(s); - android.util.Slog.w(tag, s); - } - static void e(String tag, String s) { - mLog.println(s); - android.util.Slog.e(tag, s); - } - } - */ - - /** - * This class works around a deadlock between the lock in PowerManager.WakeLock - * and our synchronizing on mLocks. PowerManager.WakeLock synchronizes on its - * mToken object so it can be accessed from any thread, but it calls into here - * with its lock held. This class is essentially a reimplementation of - * PowerManager.WakeLock, but without that extra synchronized block, because we'll - * only call it with our own locks held. - */ - private class UnsynchronizedWakeLock { - int mFlags; - String mTag; - IBinder mToken; - int mCount = 0; - boolean mRefCounted; - boolean mHeld; - - UnsynchronizedWakeLock(int flags, String tag, boolean refCounted) { - mFlags = flags; - mTag = tag; - mToken = new Binder(); - mRefCounted = refCounted; - } - - public void acquire() { - if (!mRefCounted || mCount++ == 0) { - long ident = Binder.clearCallingIdentity(); - try { - PowerManagerService.this.acquireWakeLockLocked(mFlags, mToken, - MY_UID, MY_PID, mTag, null); - mHeld = true; - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - public void release() { - if (!mRefCounted || --mCount == 0) { - PowerManagerService.this.releaseWakeLockLocked(mToken, 0, false); - mHeld = false; - } - if (mCount < 0) { - throw new RuntimeException("WakeLock under-locked " + mTag); - } - } - - public boolean isHeld() - { - return mHeld; - } - - public String toString() { - return "UnsynchronizedWakeLock(mFlags=0x" + Integer.toHexString(mFlags) - + " mCount=" + mCount + " mHeld=" + mHeld + ")"; - } - } - - private final class BatteryReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mLocks) { - boolean wasPowered = mIsPowered; - mIsPowered = mBatteryService.isPowered(); - - if (mIsPowered != wasPowered) { - // update mStayOnWhilePluggedIn wake lock - updateWakeLockLocked(); - - // treat plugging and unplugging the devices as a user activity. - // users find it disconcerting when they unplug the device - // and it shuts off right away. - // to avoid turning on the screen when unplugging, we only trigger - // user activity when screen was already on. - // temporarily set mUserActivityAllowed to true so this will work - // even when the keyguard is on. - // However, you can also set config_unplugTurnsOnScreen to have it - // turn on. Some devices want this because they don't have a - // charging LED. - synchronized (mLocks) { - if (!wasPowered || (mPowerState & SCREEN_ON_BIT) != 0 || - mUnplugTurnsOnScreen) { - forceUserActivityLocked(); - } - } - } - } - } - } - - private final class BootCompletedReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - bootCompleted(); - } - } - - private final class DockReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED); - dockStateChanged(state); - } - } - - /** - * Set the setting that determines whether the device stays on when plugged in. - * The argument is a bit string, with each bit specifying a power source that, - * when the device is connected to that source, causes the device to stay on. - * See {@link android.os.BatteryManager} for the list of power sources that - * can be specified. Current values include {@link android.os.BatteryManager#BATTERY_PLUGGED_AC} - * and {@link android.os.BatteryManager#BATTERY_PLUGGED_USB} - * @param val an {@code int} containing the bits that specify which power sources - * should cause the device to stay on. - */ - public void setStayOnSetting(int val) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS, null); - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.STAY_ON_WHILE_PLUGGED_IN, val); - } - - public void setMaximumScreenOffTimeount(int timeMs) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, null); - synchronized (mLocks) { - mMaximumScreenOffTimeout = timeMs; - // recalculate everything - setScreenOffTimeoutsLocked(); - } - } - - int getStayOnConditionsLocked() { - return mMaximumScreenOffTimeout <= 0 || mMaximumScreenOffTimeout == Integer.MAX_VALUE - ? mStayOnConditions : 0; - } - - private class SettingsObserver implements Observer { - private int getInt(String name, int defValue) { - ContentValues values = mSettings.getValues(name); - Integer iVal = values != null ? values.getAsInteger(Settings.System.VALUE) : null; - return iVal != null ? iVal : defValue; - } - - private float getFloat(String name, float defValue) { - ContentValues values = mSettings.getValues(name); - Float fVal = values != null ? values.getAsFloat(Settings.System.VALUE) : null; - return fVal != null ? fVal : defValue; - } - - public void update(Observable o, Object arg) { - synchronized (mLocks) { - // STAY_ON_WHILE_PLUGGED_IN, default to when plugged into AC - mStayOnConditions = getInt(STAY_ON_WHILE_PLUGGED_IN, - BatteryManager.BATTERY_PLUGGED_AC); - updateWakeLockLocked(); - - // SCREEN_OFF_TIMEOUT, default to 15 seconds - mScreenOffTimeoutSetting = getInt(SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT); - - // DIM_SCREEN - //mDimScreen = getInt(DIM_SCREEN) != 0; - - mScreenBrightnessSetting = getInt(SCREEN_BRIGHTNESS, DEFAULT_SCREEN_BRIGHTNESS); - mLightSensorAdjustSetting = 0; //getFloat(SCREEN_AUTO_BRIGHTNESS_ADJ, 0); - - // SCREEN_BRIGHTNESS_MODE, default to manual - setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE, - Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)); - - // recalculate everything - setScreenOffTimeoutsLocked(); - - mWindowScaleAnimation = getFloat(WINDOW_ANIMATION_SCALE, 1.0f); - final float transitionScale = getFloat(TRANSITION_ANIMATION_SCALE, 1.0f); - mAnimationSetting = 0; - if (mWindowScaleAnimation > 0.5f) { - mAnimationSetting |= ANIM_SETTING_OFF; - } - if (transitionScale > 0.5f) { - // Uncomment this if you want the screen-on animation. - // mAnimationSetting |= ANIM_SETTING_ON; - } - } - } - } - - PowerManagerService() { - // Hack to get our uid... should have a func for this. - long token = Binder.clearCallingIdentity(); - MY_UID = Process.myUid(); - MY_PID = Process.myPid(); - Binder.restoreCallingIdentity(token); - - // assume nothing is on yet - mUserState = mPowerState = 0; - - // Add ourself to the Watchdog monitors. - Watchdog.getInstance().addMonitor(this); - - nativeInit(); - } - - private ContentQueryMap mSettings; - - void init(Context context, LightsService lights, IActivityManager activity, - BatteryService battery) { - mLightsService = lights; - mContext = context; - mActivityService = activity; - mBatteryStats = BatteryStatsService.getService(); - mBatteryService = battery; - - mLcdLight = lights.getLight(LightsService.LIGHT_ID_BACKLIGHT); - mButtonLight = lights.getLight(LightsService.LIGHT_ID_BUTTONS); - mKeyboardLight = lights.getLight(LightsService.LIGHT_ID_KEYBOARD); - mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION); - mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0")); - - mInitComplete = false; - mScreenBrightnessAnimator = new ScreenBrightnessAnimator("mScreenBrightnessUpdaterThread", - Process.THREAD_PRIORITY_DISPLAY); - mScreenBrightnessAnimator.start(); - - synchronized (mScreenBrightnessAnimator) { - while (!mInitComplete) { - try { - mScreenBrightnessAnimator.wait(); - } catch (InterruptedException e) { - // Ignore - } - } - } - - mInitComplete = false; - mHandlerThread = new HandlerThread("PowerManagerService") { - @Override - protected void onLooperPrepared() { - super.onLooperPrepared(); - initInThread(); - } - }; - mHandlerThread.start(); - - synchronized (mHandlerThread) { - while (!mInitComplete) { - try { - mHandlerThread.wait(); - } catch (InterruptedException e) { - // Ignore - } - } - } - - synchronized (mLocks) { - updateNativePowerStateLocked(); - // We make sure to start out with the screen on due to user activity. - // (They did just boot their device, after all.) - forceUserActivityLocked(); - mInitialized = true; - } - } - - void initInThread() { - mHandler = new Handler(); - - mBroadcastWakeLock = new UnsynchronizedWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, "sleep_broadcast", true); - mStayOnWhilePluggedInScreenDimLock = new UnsynchronizedWakeLock( - PowerManager.SCREEN_DIM_WAKE_LOCK, "StayOnWhilePluggedIn Screen Dim", false); - mStayOnWhilePluggedInPartialLock = new UnsynchronizedWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, "StayOnWhilePluggedIn Partial", false); - mPreventScreenOnPartialLock = new UnsynchronizedWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, "PreventScreenOn Partial", false); - mProximityPartialLock = new UnsynchronizedWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, "Proximity Partial", false); - - mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON); - mScreenOnIntent.addFlags( - Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); - mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF); - mScreenOffIntent.addFlags( - Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); - - Resources resources = mContext.getResources(); - - mAnimateScreenLights = resources.getBoolean( - com.android.internal.R.bool.config_animateScreenLights); - - mUnplugTurnsOnScreen = resources.getBoolean( - com.android.internal.R.bool.config_unplugTurnsOnScreen); - - mScreenBrightnessDim = resources.getInteger( - com.android.internal.R.integer.config_screenBrightnessDim); - - // read settings for auto-brightness - mUseSoftwareAutoBrightness = resources.getBoolean( - com.android.internal.R.bool.config_automatic_brightness_available); - if (mUseSoftwareAutoBrightness) { - mAutoBrightnessLevels = resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLevels); - mLcdBacklightValues = resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); - mButtonBacklightValues = resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessButtonBacklightValues); - mKeyboardBacklightValues = resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessKeyboardBacklightValues); - mLightSensorWarmupTime = resources.getInteger( - com.android.internal.R.integer.config_lightSensorWarmupTime); - } - - ContentResolver resolver = mContext.getContentResolver(); - Cursor settingsCursor = resolver.query(Settings.System.CONTENT_URI, null, - "(" + Settings.System.NAME + "=?) or (" - + Settings.System.NAME + "=?) or (" - + Settings.System.NAME + "=?) or (" - + Settings.System.NAME + "=?) or (" - + Settings.System.NAME + "=?) or (" - + Settings.System.NAME + "=?) or (" - + Settings.System.NAME + "=?) or (" - + Settings.System.NAME + "=?)", - new String[]{STAY_ON_WHILE_PLUGGED_IN, SCREEN_OFF_TIMEOUT, DIM_SCREEN, SCREEN_BRIGHTNESS, - SCREEN_BRIGHTNESS_MODE, /*SCREEN_AUTO_BRIGHTNESS_ADJ,*/ - WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE}, - null); - mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mHandler); - SettingsObserver settingsObserver = new SettingsObserver(); - mSettings.addObserver(settingsObserver); - - // pretend that the settings changed so we will get their initial state - settingsObserver.update(mSettings, null); - - // register for the battery changed notifications - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - mContext.registerReceiver(new BatteryReceiver(), filter); - filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BOOT_COMPLETED); - mContext.registerReceiver(new BootCompletedReceiver(), filter); - filter = new IntentFilter(); - filter.addAction(Intent.ACTION_DOCK_EVENT); - mContext.registerReceiver(new DockReceiver(), filter); - - // Listen for secure settings changes - mContext.getContentResolver().registerContentObserver( - Settings.Secure.CONTENT_URI, true, - new ContentObserver(new Handler()) { - public void onChange(boolean selfChange) { - updateSettingsValues(); - } - }); - updateSettingsValues(); - - synchronized (mHandlerThread) { - mInitComplete = true; - mHandlerThread.notifyAll(); - } - } - - /** - * Low-level function turn the device off immediately, without trying - * to be clean. Most people should use - * {@link com.android.server.pm.internal.app.ShutdownThread} for a clean shutdown. - */ - public static void lowLevelShutdown() { - nativeShutdown(); - } - - /** - * Low-level function to reboot the device. - * - * @param reason code to pass to the kernel (e.g. "recovery"), or null. - * @throws IOException if reboot fails for some reason (eg, lack of - * permission) - */ - public static void lowLevelReboot(String reason) throws IOException { - nativeReboot(reason); - } - - private class WakeLock implements IBinder.DeathRecipient - { - WakeLock(int f, IBinder b, String t, int u, int p) { - super(); - flags = f; - binder = b; - tag = t; - uid = u == MY_UID ? Process.SYSTEM_UID : u; - pid = p; - if (u != MY_UID || ( - !"KEEP_SCREEN_ON_FLAG".equals(tag) - && !"KeyInputQueue".equals(tag))) { - monitorType = (f & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK - ? BatteryStats.WAKE_TYPE_PARTIAL - : BatteryStats.WAKE_TYPE_FULL; - } else { - monitorType = -1; - } - try { - b.linkToDeath(this, 0); - } catch (RemoteException e) { - binderDied(); - } - } - public void binderDied() { - synchronized (mLocks) { - releaseWakeLockLocked(this.binder, 0, true); - } - } - final int flags; - final IBinder binder; - final String tag; - final int uid; - final int pid; - final int monitorType; - WorkSource ws; - boolean activated = true; - int minState; - } - - private void updateWakeLockLocked() { - final int stayOnConditions = getStayOnConditionsLocked(); - if (stayOnConditions != 0 && mBatteryService.isPowered(stayOnConditions)) { - // keep the device on if we're plugged in and mStayOnWhilePluggedIn is set. - mStayOnWhilePluggedInScreenDimLock.acquire(); - mStayOnWhilePluggedInPartialLock.acquire(); - } else { - mStayOnWhilePluggedInScreenDimLock.release(); - mStayOnWhilePluggedInPartialLock.release(); - } - } - - private boolean isScreenLock(int flags) - { - int n = flags & LOCK_MASK; - return n == PowerManager.FULL_WAKE_LOCK - || n == PowerManager.SCREEN_BRIGHT_WAKE_LOCK - || n == PowerManager.SCREEN_DIM_WAKE_LOCK - || n == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK; - } - - void enforceWakeSourcePermission(int uid, int pid) { - if (uid == Process.myUid()) { - return; - } - mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, - pid, uid, null); - } - - public void acquireWakeLock(int flags, IBinder lock, String tag, WorkSource ws) { - int uid = Binder.getCallingUid(); - int pid = Binder.getCallingPid(); - if (uid != Process.myUid()) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); - } - if (ws != null) { - enforceWakeSourcePermission(uid, pid); - } - long ident = Binder.clearCallingIdentity(); - try { - synchronized (mLocks) { - acquireWakeLockLocked(flags, lock, uid, pid, tag, ws); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - void noteStartWakeLocked(WakeLock wl, WorkSource ws) { - if (wl.monitorType >= 0) { - long origId = Binder.clearCallingIdentity(); - try { - if (ws != null) { - mBatteryStats.noteStartWakelockFromSource(ws, wl.pid, wl.tag, - wl.monitorType); - } else { - mBatteryStats.noteStartWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType); - } - } catch (RemoteException e) { - // Ignore - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - - void noteStopWakeLocked(WakeLock wl, WorkSource ws) { - if (wl.monitorType >= 0) { - long origId = Binder.clearCallingIdentity(); - try { - if (ws != null) { - mBatteryStats.noteStopWakelockFromSource(ws, wl.pid, wl.tag, - wl.monitorType); - } else { - mBatteryStats.noteStopWakelock(wl.uid, wl.pid, wl.tag, wl.monitorType); - } - } catch (RemoteException e) { - // Ignore - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - - public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag, - WorkSource ws) { - if (mSpew) { - Slog.d(TAG, "acquireWakeLock flags=0x" + Integer.toHexString(flags) + " tag=" + tag); - } - - if (ws != null && ws.size() == 0) { - ws = null; - } - - int index = mLocks.getIndex(lock); - WakeLock wl; - boolean newlock; - boolean diffsource; - WorkSource oldsource; - if (index < 0) { - wl = new WakeLock(flags, lock, tag, uid, pid); - switch (wl.flags & LOCK_MASK) - { - case PowerManager.FULL_WAKE_LOCK: - if (mUseSoftwareAutoBrightness) { - wl.minState = SCREEN_BRIGHT; - } else { - wl.minState = (mKeyboardVisible ? ALL_BRIGHT : SCREEN_BUTTON_BRIGHT); - } - break; - case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: - wl.minState = SCREEN_BRIGHT; - break; - case PowerManager.SCREEN_DIM_WAKE_LOCK: - wl.minState = SCREEN_DIM; - break; - case PowerManager.PARTIAL_WAKE_LOCK: - case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: - break; - default: - // just log and bail. we're in the server, so don't - // throw an exception. - Slog.e(TAG, "bad wakelock type for lock '" + tag + "' " - + " flags=" + flags); - return; - } - mLocks.addLock(wl); - if (ws != null) { - wl.ws = new WorkSource(ws); - } - newlock = true; - diffsource = false; - oldsource = null; - } else { - wl = mLocks.get(index); - newlock = false; - oldsource = wl.ws; - if (oldsource != null) { - if (ws == null) { - wl.ws = null; - diffsource = true; - } else { - diffsource = oldsource.diff(ws); - } - } else if (ws != null) { - diffsource = true; - } else { - diffsource = false; - } - if (diffsource) { - wl.ws = new WorkSource(ws); - } - } - if (isScreenLock(flags)) { - // if this causes a wakeup, we reactivate all of the locks and - // set it to whatever they want. otherwise, we modulate that - // by the current state so we never turn it more on than - // it already is. - if ((flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) { - mProximityWakeLockCount++; - if (mProximityWakeLockCount == 1) { - enableProximityLockLocked(); - } - } else { - if ((wl.flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { - int oldWakeLockState = mWakeLockState; - mWakeLockState = mLocks.reactivateScreenLocksLocked(); - - // Disable proximity sensor if if user presses power key while we are in the - // "waiting for proximity sensor to go negative" state. - if ((mWakeLockState & SCREEN_ON_BIT) != 0 - && mProximitySensorActive && mProximityWakeLockCount == 0) { - mProximitySensorActive = false; - } - - if (mSpew) { - Slog.d(TAG, "wakeup here mUserState=0x" + Integer.toHexString(mUserState) - + " mWakeLockState=0x" - + Integer.toHexString(mWakeLockState) - + " previous wakeLockState=0x" - + Integer.toHexString(oldWakeLockState)); - } - } else { - if (mSpew) { - Slog.d(TAG, "here mUserState=0x" + Integer.toHexString(mUserState) - + " mLocks.gatherState()=0x" - + Integer.toHexString(mLocks.gatherState()) - + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState)); - } - mWakeLockState = (mUserState | mWakeLockState) & mLocks.gatherState(); - } - setPowerState(mWakeLockState | mUserState); - } - } - else if ((flags & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK) { - if (newlock) { - mPartialCount++; - if (mPartialCount == 1) { - if (LOG_PARTIAL_WL) EventLog.writeEvent(EventLogTags.POWER_PARTIAL_WAKE_STATE, 1, tag); - } - } - nativeAcquireWakeLock(PARTIAL_WAKE_LOCK_ID, PARTIAL_NAME); - } - - if (diffsource) { - // If the lock sources have changed, need to first release the - // old ones. - noteStopWakeLocked(wl, oldsource); - } - if (newlock || diffsource) { - noteStartWakeLocked(wl, ws); - } - } - - public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) { - int uid = Binder.getCallingUid(); - int pid = Binder.getCallingPid(); - if (ws != null && ws.size() == 0) { - ws = null; - } - if (ws != null) { - enforceWakeSourcePermission(uid, pid); - } - synchronized (mLocks) { - int index = mLocks.getIndex(lock); - if (index < 0) { - throw new IllegalArgumentException("Wake lock not active"); - } - WakeLock wl = mLocks.get(index); - WorkSource oldsource = wl.ws; - wl.ws = ws != null ? new WorkSource(ws) : null; - noteStopWakeLocked(wl, oldsource); - noteStartWakeLocked(wl, ws); - } - } - - public void releaseWakeLock(IBinder lock, int flags) { - int uid = Binder.getCallingUid(); - if (uid != Process.myUid()) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); - } - - synchronized (mLocks) { - releaseWakeLockLocked(lock, flags, false); - } - } - - private void releaseWakeLockLocked(IBinder lock, int flags, boolean death) { - WakeLock wl = mLocks.removeLock(lock); - if (wl == null) { - return; - } - - if (mSpew) { - Slog.d(TAG, "releaseWakeLock flags=0x" - + Integer.toHexString(wl.flags) + " tag=" + wl.tag); - } - - if (isScreenLock(wl.flags)) { - if ((wl.flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) { - mProximityWakeLockCount--; - if (mProximityWakeLockCount == 0) { - if (mProximitySensorActive && - ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0)) { - // wait for proximity sensor to go negative before disabling sensor - if (mDebugProximitySensor) { - Slog.d(TAG, "waiting for proximity sensor to go negative"); - } - } else { - disableProximityLockLocked(); - } - } - } else { - mWakeLockState = mLocks.gatherState(); - // goes in the middle to reduce flicker - if ((wl.flags & PowerManager.ON_AFTER_RELEASE) != 0) { - userActivity(SystemClock.uptimeMillis(), -1, false, OTHER_EVENT, false, true); - } - setPowerState(mWakeLockState | mUserState); - } - } - else if ((wl.flags & LOCK_MASK) == PowerManager.PARTIAL_WAKE_LOCK) { - mPartialCount--; - if (mPartialCount == 0) { - if (LOG_PARTIAL_WL) EventLog.writeEvent(EventLogTags.POWER_PARTIAL_WAKE_STATE, 0, wl.tag); - nativeReleaseWakeLock(PARTIAL_NAME); - } - } - // Unlink the lock from the binder. - wl.binder.unlinkToDeath(wl, 0); - - noteStopWakeLocked(wl, wl.ws); - } - - private class PokeLock implements IBinder.DeathRecipient - { - PokeLock(int p, IBinder b, String t) { - super(); - this.pokey = p; - this.binder = b; - this.tag = t; - try { - b.linkToDeath(this, 0); - } catch (RemoteException e) { - binderDied(); - } - } - public void binderDied() { - setPokeLock(0, this.binder, this.tag); - } - int pokey; - IBinder binder; - String tag; - boolean awakeOnSet; - } - - public void setPokeLock(int pokey, IBinder token, String tag) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - if (token == null) { - Slog.e(TAG, "setPokeLock got null token for tag='" + tag + "'"); - return; - } - - if ((pokey & POKE_LOCK_TIMEOUT_MASK) == POKE_LOCK_TIMEOUT_MASK) { - throw new IllegalArgumentException("setPokeLock can't have both POKE_LOCK_SHORT_TIMEOUT" - + " and POKE_LOCK_MEDIUM_TIMEOUT"); - } - - synchronized (mLocks) { - if (pokey != 0) { - PokeLock p = mPokeLocks.get(token); - int oldPokey = 0; - if (p != null) { - oldPokey = p.pokey; - p.pokey = pokey; - } else { - p = new PokeLock(pokey, token, tag); - mPokeLocks.put(token, p); - } - int oldTimeout = oldPokey & POKE_LOCK_TIMEOUT_MASK; - int newTimeout = pokey & POKE_LOCK_TIMEOUT_MASK; - if (((mPowerState & SCREEN_ON_BIT) == 0) && (oldTimeout != newTimeout)) { - p.awakeOnSet = true; - } - } else { - PokeLock rLock = mPokeLocks.remove(token); - if (rLock != null) { - token.unlinkToDeath(rLock, 0); - } - } - - int oldPokey = mPokey; - int cumulative = 0; - boolean awakeOnSet = false; - for (PokeLock p: mPokeLocks.values()) { - cumulative |= p.pokey; - if (p.awakeOnSet) { - awakeOnSet = true; - } - } - mPokey = cumulative; - mPokeAwakeOnSet = awakeOnSet; - - int oldCumulativeTimeout = oldPokey & POKE_LOCK_TIMEOUT_MASK; - int newCumulativeTimeout = pokey & POKE_LOCK_TIMEOUT_MASK; - - if (oldCumulativeTimeout != newCumulativeTimeout) { - setScreenOffTimeoutsLocked(); - // reset the countdown timer, but use the existing nextState so it doesn't - // change anything - setTimeoutLocked(SystemClock.uptimeMillis(), mTimeoutTask.nextState); - } - } - } - - private static String lockType(int type) - { - switch (type) - { - case PowerManager.FULL_WAKE_LOCK: - return "FULL_WAKE_LOCK "; - case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: - return "SCREEN_BRIGHT_WAKE_LOCK "; - case PowerManager.SCREEN_DIM_WAKE_LOCK: - return "SCREEN_DIM_WAKE_LOCK "; - case PowerManager.PARTIAL_WAKE_LOCK: - return "PARTIAL_WAKE_LOCK "; - case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: - return "PROXIMITY_SCREEN_OFF_WAKE_LOCK"; - default: - return "??? "; - } - } - - private static String dumpPowerState(int state) { - return (((state & KEYBOARD_BRIGHT_BIT) != 0) - ? "KEYBOARD_BRIGHT_BIT " : "") - + (((state & SCREEN_BRIGHT_BIT) != 0) - ? "SCREEN_BRIGHT_BIT " : "") - + (((state & SCREEN_ON_BIT) != 0) - ? "SCREEN_ON_BIT " : "") - + (((state & BUTTON_BRIGHT_BIT) != 0) - ? "BUTTON_BRIGHT_BIT " : "") - + (((state & BATTERY_LOW_BIT) != 0) - ? "BATTERY_LOW_BIT " : ""); - } - - @Override - public 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 PowerManager from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - long now = SystemClock.uptimeMillis(); - - synchronized (mLocks) { - pw.println("Power Manager State:"); - pw.println(" mIsPowered=" + mIsPowered - + " mPowerState=" + mPowerState - + " mScreenOffTime=" + (SystemClock.elapsedRealtime()-mScreenOffTime) - + " ms"); - pw.println(" mPartialCount=" + mPartialCount); - pw.println(" mWakeLockState=" + dumpPowerState(mWakeLockState)); - pw.println(" mUserState=" + dumpPowerState(mUserState)); - pw.println(" mPowerState=" + dumpPowerState(mPowerState)); - pw.println(" mLocks.gather=" + dumpPowerState(mLocks.gatherState())); - pw.println(" mNextTimeout=" + mNextTimeout + " now=" + now - + " " + ((mNextTimeout-now)/1000) + "s from now"); - pw.println(" mDimScreen=" + mDimScreen - + " mStayOnConditions=" + mStayOnConditions - + " mPreparingForScreenOn=" + mPreparingForScreenOn - + " mSkippedScreenOn=" + mSkippedScreenOn); - pw.println(" mScreenOffReason=" + mScreenOffReason - + " mUserState=" + mUserState); - pw.println(" mBroadcastQueue={" + mBroadcastQueue[0] + ',' + mBroadcastQueue[1] - + ',' + mBroadcastQueue[2] + "}"); - pw.println(" mBroadcastWhy={" + mBroadcastWhy[0] + ',' + mBroadcastWhy[1] - + ',' + mBroadcastWhy[2] + "}"); - pw.println(" mPokey=" + mPokey + " mPokeAwakeonSet=" + mPokeAwakeOnSet); - pw.println(" mKeyboardVisible=" + mKeyboardVisible - + " mUserActivityAllowed=" + mUserActivityAllowed); - pw.println(" mKeylightDelay=" + mKeylightDelay + " mDimDelay=" + mDimDelay - + " mScreenOffDelay=" + mScreenOffDelay); - pw.println(" mPreventScreenOn=" + mPreventScreenOn - + " mScreenBrightnessOverride=" + mScreenBrightnessOverride - + " mButtonBrightnessOverride=" + mButtonBrightnessOverride); - pw.println(" mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting - + " mMaximumScreenOffTimeout=" + mMaximumScreenOffTimeout); - pw.println(" mLastScreenOnTime=" + mLastScreenOnTime); - pw.println(" mBroadcastWakeLock=" + mBroadcastWakeLock); - pw.println(" mStayOnWhilePluggedInScreenDimLock=" + mStayOnWhilePluggedInScreenDimLock); - pw.println(" mStayOnWhilePluggedInPartialLock=" + mStayOnWhilePluggedInPartialLock); - pw.println(" mPreventScreenOnPartialLock=" + mPreventScreenOnPartialLock); - pw.println(" mProximityPartialLock=" + mProximityPartialLock); - pw.println(" mProximityWakeLockCount=" + mProximityWakeLockCount); - pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled); - pw.println(" mProximitySensorActive=" + mProximitySensorActive); - pw.println(" mProximityPendingValue=" + mProximityPendingValue); - pw.println(" mLastProximityEventTime=" + mLastProximityEventTime); - pw.println(" mLightSensorEnabled=" + mLightSensorEnabled - + " mLightSensorAdjustSetting=" + mLightSensorAdjustSetting); - pw.println(" mLightSensorValue=" + mLightSensorValue - + " mLightSensorPendingValue=" + mLightSensorPendingValue); - pw.println(" mHighestLightSensorValue=" + mHighestLightSensorValue - + " mWaitingForFirstLightSensor=" + mWaitingForFirstLightSensor); - pw.println(" mLightSensorPendingDecrease=" + mLightSensorPendingDecrease - + " mLightSensorPendingIncrease=" + mLightSensorPendingIncrease); - pw.println(" mLightSensorScreenBrightness=" + mLightSensorScreenBrightness - + " mLightSensorButtonBrightness=" + mLightSensorButtonBrightness - + " mLightSensorKeyboardBrightness=" + mLightSensorKeyboardBrightness); - pw.println(" mUseSoftwareAutoBrightness=" + mUseSoftwareAutoBrightness); - pw.println(" mAutoBrightessEnabled=" + mAutoBrightessEnabled); - mScreenBrightnessAnimator.dump(pw, "mScreenBrightnessAnimator: "); - - int N = mLocks.size(); - pw.println(); - pw.println("mLocks.size=" + N + ":"); - for (int i=0; i<N; i++) { - WakeLock wl = mLocks.get(i); - String type = lockType(wl.flags & LOCK_MASK); - String acquireCausesWakeup = ""; - if ((wl.flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { - acquireCausesWakeup = "ACQUIRE_CAUSES_WAKEUP "; - } - String activated = ""; - if (wl.activated) { - activated = " activated"; - } - pw.println(" " + type + " '" + wl.tag + "'" + acquireCausesWakeup - + activated + " (minState=" + wl.minState + ", uid=" + wl.uid - + ", pid=" + wl.pid + ")"); - } - - pw.println(); - pw.println("mPokeLocks.size=" + mPokeLocks.size() + ":"); - for (PokeLock p: mPokeLocks.values()) { - pw.println(" poke lock '" + p.tag + "':" - + ((p.pokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0 - ? " POKE_LOCK_IGNORE_TOUCH_EVENTS" : "") - + ((p.pokey & POKE_LOCK_SHORT_TIMEOUT) != 0 - ? " POKE_LOCK_SHORT_TIMEOUT" : "") - + ((p.pokey & POKE_LOCK_MEDIUM_TIMEOUT) != 0 - ? " POKE_LOCK_MEDIUM_TIMEOUT" : "")); - } - - pw.println(); - } - } - - private void setTimeoutLocked(long now, int nextState) { - setTimeoutLocked(now, -1, nextState); - } - - // If they gave a timeoutOverride it is the number of seconds - // to screen-off. Figure out where in the countdown cycle we - // should jump to. - private void setTimeoutLocked(long now, final long originalTimeoutOverride, int nextState) { - long timeoutOverride = originalTimeoutOverride; - if (mBootCompleted) { - synchronized (mLocks) { - long when = 0; - if (timeoutOverride <= 0) { - switch (nextState) - { - case SCREEN_BRIGHT: - when = now + mKeylightDelay; - break; - case SCREEN_DIM: - if (mDimDelay >= 0) { - when = now + mDimDelay; - break; - } else { - Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim"); - } - case SCREEN_OFF: - synchronized (mLocks) { - when = now + mScreenOffDelay; - } - break; - default: - when = now; - break; - } - } else { - override: { - if (timeoutOverride <= mScreenOffDelay) { - when = now + timeoutOverride; - nextState = SCREEN_OFF; - break override; - } - timeoutOverride -= mScreenOffDelay; - - if (mDimDelay >= 0) { - if (timeoutOverride <= mDimDelay) { - when = now + timeoutOverride; - nextState = SCREEN_DIM; - break override; - } - timeoutOverride -= mDimDelay; - } - - when = now + timeoutOverride; - nextState = SCREEN_BRIGHT; - } - } - if (mSpew) { - Slog.d(TAG, "setTimeoutLocked now=" + now - + " timeoutOverride=" + timeoutOverride - + " nextState=" + nextState + " when=" + when); - } - - mHandler.removeCallbacks(mTimeoutTask); - mTimeoutTask.nextState = nextState; - mTimeoutTask.remainingTimeoutOverride = timeoutOverride > 0 - ? (originalTimeoutOverride - timeoutOverride) - : -1; - mHandler.postAtTime(mTimeoutTask, when); - mNextTimeout = when; // for debugging - } - } - } - - private void cancelTimerLocked() - { - mHandler.removeCallbacks(mTimeoutTask); - mTimeoutTask.nextState = -1; - } - - private class TimeoutTask implements Runnable - { - int nextState; // access should be synchronized on mLocks - long remainingTimeoutOverride; - public void run() - { - synchronized (mLocks) { - if (mSpew) { - Slog.d(TAG, "user activity timeout timed out nextState=" + this.nextState); - } - - if (nextState == -1) { - return; - } - - mUserState = this.nextState; - setPowerState(this.nextState | mWakeLockState); - - long now = SystemClock.uptimeMillis(); - - switch (this.nextState) - { - case SCREEN_BRIGHT: - if (mDimDelay >= 0) { - setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_DIM); - break; - } - case SCREEN_DIM: - setTimeoutLocked(now, remainingTimeoutOverride, SCREEN_OFF); - break; - } - } - } - } - - private void sendNotificationLocked(boolean on, int why) { - if (!mInitialized) { - // No notifications sent until first initialization is done. - // This is so that when we are moving from our initial state - // which looks like the screen was off to it being on, we do not - // go through the process of waiting for the higher-level user - // space to be ready before turning up the display brightness. - // (And also do not send needless broadcasts about the screen.) - return; - } - - if (DEBUG_SCREEN_ON) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "sendNotificationLocked: " + on, here); - } - - if (!on) { - mStillNeedSleepNotification = false; - } - - // Add to the queue. - int index = 0; - while (mBroadcastQueue[index] != -1) { - index++; - } - mBroadcastQueue[index] = on ? 1 : 0; - mBroadcastWhy[index] = why; - - // If we added it position 2, then there is a pair that can be stripped. - // If we added it position 1 and we're turning the screen off, we can strip - // the pair and do nothing, because the screen is already off, and therefore - // keyguard has already been enabled. - // However, if we added it at position 1 and we're turning it on, then position - // 0 was to turn it off, and we can't strip that, because keyguard needs to come - // on, so have to run the queue then. - if (index == 2) { - // While we're collapsing them, if it's going off, and the new reason - // is more significant than the first, then use the new one. - if (!on && mBroadcastWhy[0] > why) { - mBroadcastWhy[0] = why; - } - mBroadcastQueue[0] = on ? 1 : 0; - mBroadcastQueue[1] = -1; - mBroadcastQueue[2] = -1; - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 1, mBroadcastWakeLock.mCount); - mBroadcastWakeLock.release(); - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 1, mBroadcastWakeLock.mCount); - mBroadcastWakeLock.release(); - index = 0; - } - if (index == 1 && !on) { - mBroadcastQueue[0] = -1; - mBroadcastQueue[1] = -1; - index = -1; - // The wake lock was being held, but we're not actually going to do any - // broadcasts, so release the wake lock. - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 1, mBroadcastWakeLock.mCount); - mBroadcastWakeLock.release(); - } - - // The broadcast queue has changed; make sure the screen is on if it - // is now possible for it to be. - if (mSkippedScreenOn) { - updateLightsLocked(mPowerState, SCREEN_ON_BIT); - } - - // Now send the message. - if (index >= 0) { - // Acquire the broadcast wake lock before changing the power - // state. It will be release after the broadcast is sent. - // We always increment the ref count for each notification in the queue - // and always decrement when that notification is handled. - mBroadcastWakeLock.acquire(); - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, mBroadcastWakeLock.mCount); - mHandler.post(mNotificationTask); - } - } - - private WindowManagerPolicy.ScreenOnListener mScreenOnListener = - new WindowManagerPolicy.ScreenOnListener() { - public void onScreenOn() { - synchronized (mLocks) { - if (mPreparingForScreenOn) { - mPreparingForScreenOn = false; - updateLightsLocked(mPowerState, SCREEN_ON_BIT); - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, - 4, mBroadcastWakeLock.mCount); - mBroadcastWakeLock.release(); - } - } - } - }; - - private Runnable mNotificationTask = new Runnable() - { - public void run() - { - while (true) { - int value; - int why; - WindowManagerPolicy policy; - synchronized (mLocks) { - value = mBroadcastQueue[0]; - why = mBroadcastWhy[0]; - for (int i=0; i<2; i++) { - mBroadcastQueue[i] = mBroadcastQueue[i+1]; - mBroadcastWhy[i] = mBroadcastWhy[i+1]; - } - policy = getPolicyLocked(); - if (value == 1 && !mPreparingForScreenOn) { - mPreparingForScreenOn = true; - mBroadcastWakeLock.acquire(); - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, - mBroadcastWakeLock.mCount); - } - } - if (value == 1) { - mScreenOnStart = SystemClock.uptimeMillis(); - - policy.screenTurningOn(mScreenOnListener); - try { - ActivityManagerNative.getDefault().wakingUp(); - } catch (RemoteException e) { - // ignore it - } - - if (mSpew) { - Slog.d(TAG, "mBroadcastWakeLock=" + mBroadcastWakeLock); - } - if (mContext != null && ActivityManagerNative.isSystemReady()) { - mContext.sendOrderedBroadcast(mScreenOnIntent, null, - mScreenOnBroadcastDone, mHandler, 0, null, null); - } else { - synchronized (mLocks) { - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, - mBroadcastWakeLock.mCount); - mBroadcastWakeLock.release(); - } - } - } - else if (value == 0) { - mScreenOffStart = SystemClock.uptimeMillis(); - - policy.screenTurnedOff(why); - try { - ActivityManagerNative.getDefault().goingToSleep(); - } catch (RemoteException e) { - // ignore it. - } - - if (mContext != null && ActivityManagerNative.isSystemReady()) { - mContext.sendOrderedBroadcast(mScreenOffIntent, null, - mScreenOffBroadcastDone, mHandler, 0, null, null); - } else { - synchronized (mLocks) { - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, - mBroadcastWakeLock.mCount); - updateLightsLocked(mPowerState, SCREEN_ON_BIT); - mBroadcastWakeLock.release(); - } - } - } - else { - // If we're in this case, then this handler is running for a previous - // paired transaction. mBroadcastWakeLock will already have been released. - break; - } - } - } - }; - - long mScreenOnStart; - private BroadcastReceiver mScreenOnBroadcastDone = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - synchronized (mLocks) { - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 1, - SystemClock.uptimeMillis() - mScreenOnStart, mBroadcastWakeLock.mCount); - mBroadcastWakeLock.release(); - } - } - }; - - long mScreenOffStart; - private BroadcastReceiver mScreenOffBroadcastDone = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - synchronized (mLocks) { - EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0, - SystemClock.uptimeMillis() - mScreenOffStart, mBroadcastWakeLock.mCount); - mBroadcastWakeLock.release(); - } - } - }; - - void logPointerUpEvent() { - if (LOG_TOUCH_DOWNS) { - mTotalTouchDownTime += SystemClock.elapsedRealtime() - mLastTouchDown; - mLastTouchDown = 0; - } - } - - void logPointerDownEvent() { - if (LOG_TOUCH_DOWNS) { - // If we are not already timing a down/up sequence - if (mLastTouchDown == 0) { - mLastTouchDown = SystemClock.elapsedRealtime(); - mTouchCycles++; - } - } - } - - /** - * Prevents the screen from turning on even if it *should* turn on due - * to a subsequent full wake lock being acquired. - * <p> - * This is a temporary hack that allows an activity to "cover up" any - * display glitches that happen during the activity's startup - * sequence. (Specifically, this API was added to work around a - * cosmetic bug in the "incoming call" sequence, where the lock screen - * would flicker briefly before the incoming call UI became visible.) - * TODO: There ought to be a more elegant way of doing this, - * probably by having the PowerManager and ActivityManager - * work together to let apps specify that the screen on/off - * state should be synchronized with the Activity lifecycle. - * <p> - * Note that calling preventScreenOn(true) will NOT turn the screen - * off if it's currently on. (This API only affects *future* - * acquisitions of full wake locks.) - * But calling preventScreenOn(false) WILL turn the screen on if - * it's currently off because of a prior preventScreenOn(true) call. - * <p> - * Any call to preventScreenOn(true) MUST be followed promptly by a call - * to preventScreenOn(false). In fact, if the preventScreenOn(false) - * call doesn't occur within 5 seconds, we'll turn the screen back on - * ourselves (and log a warning about it); this prevents a buggy app - * from disabling the screen forever.) - * <p> - * TODO: this feature should really be controlled by a new type of poke - * lock (rather than an IPowerManager call). - */ - public void preventScreenOn(boolean prevent) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - - synchronized (mLocks) { - if (prevent) { - // First of all, grab a partial wake lock to - // make sure the CPU stays on during the entire - // preventScreenOn(true) -> preventScreenOn(false) sequence. - mPreventScreenOnPartialLock.acquire(); - - // Post a forceReenableScreen() call (for 5 seconds in the - // future) to make sure the matching preventScreenOn(false) call - // has happened by then. - mHandler.removeCallbacks(mForceReenableScreenTask); - mHandler.postDelayed(mForceReenableScreenTask, 5000); - - // Finally, set the flag that prevents the screen from turning on. - // (Below, in setPowerState(), we'll check mPreventScreenOn and - // we *won't* call setScreenStateLocked(true) if it's set.) - mPreventScreenOn = true; - } else { - // (Re)enable the screen. - mPreventScreenOn = false; - - // We're "undoing" a the prior preventScreenOn(true) call, so we - // no longer need the 5-second safeguard. - mHandler.removeCallbacks(mForceReenableScreenTask); - - // Forcibly turn on the screen if it's supposed to be on. (This - // handles the case where the screen is currently off because of - // a prior preventScreenOn(true) call.) - if (!mProximitySensorActive && (mPowerState & SCREEN_ON_BIT) != 0) { - if (mSpew) { - Slog.d(TAG, - "preventScreenOn: turning on after a prior preventScreenOn(true)!"); - } - int err = setScreenStateLocked(true); - if (err != 0) { - Slog.w(TAG, "preventScreenOn: error from setScreenStateLocked(): " + err); - } - } - - // Release the partial wake lock that we held during the - // preventScreenOn(true) -> preventScreenOn(false) sequence. - mPreventScreenOnPartialLock.release(); - } - } - } - - public void setScreenBrightnessOverride(int brightness) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - - if (mSpew) Slog.d(TAG, "setScreenBrightnessOverride " + brightness); - synchronized (mLocks) { - if (mScreenBrightnessOverride != brightness) { - mScreenBrightnessOverride = brightness; - if (isScreenOn()) { - updateLightsLocked(mPowerState, SCREEN_ON_BIT); - } - } - } - } - - public void setButtonBrightnessOverride(int brightness) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - - if (mSpew) Slog.d(TAG, "setButtonBrightnessOverride " + brightness); - synchronized (mLocks) { - if (mButtonBrightnessOverride != brightness) { - mButtonBrightnessOverride = brightness; - if (isScreenOn()) { - updateLightsLocked(mPowerState, BUTTON_BRIGHT_BIT | KEYBOARD_BRIGHT_BIT); - } - } - } - } - - /** - * Sanity-check that gets called 5 seconds after any call to - * preventScreenOn(true). This ensures that the original call - * is followed promptly by a call to preventScreenOn(false). - */ - private void forceReenableScreen() { - // We shouldn't get here at all if mPreventScreenOn is false, since - // we should have already removed any existing - // mForceReenableScreenTask messages... - if (!mPreventScreenOn) { - Slog.w(TAG, "forceReenableScreen: mPreventScreenOn is false, nothing to do"); - return; - } - - // Uh oh. It's been 5 seconds since a call to - // preventScreenOn(true) and we haven't re-enabled the screen yet. - // This means the app that called preventScreenOn(true) is either - // slow (i.e. it took more than 5 seconds to call preventScreenOn(false)), - // or buggy (i.e. it forgot to call preventScreenOn(false), or - // crashed before doing so.) - - // Log a warning, and forcibly turn the screen back on. - Slog.w(TAG, "App called preventScreenOn(true) but didn't promptly reenable the screen! " - + "Forcing the screen back on..."); - preventScreenOn(false); - } - - private Runnable mForceReenableScreenTask = new Runnable() { - public void run() { - forceReenableScreen(); - } - }; - - private int setScreenStateLocked(boolean on) { - if (DEBUG_SCREEN_ON) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Slog.i(TAG, "Set screen state: " + on, e); - } - if (on) { - if (mInitialized && ((mPowerState & SCREEN_ON_BIT) == 0 || mSkippedScreenOn)) { - // If we are turning the screen state on, but the screen - // light is currently off, then make sure that we set the - // light at this point to 0. This is the case where we are - // turning on the screen and waiting for the UI to be drawn - // before showing it to the user. We want the light off - // until it is ready to be shown to the user, not it using - // whatever the last value it had. - // Skip this if the screen is being turned on for the first time - // after boot (mInitialized is false). - if (DEBUG_SCREEN_ON) { - Slog.i(TAG, "Forcing brightness 0: mPowerState=0x" - + Integer.toHexString(mPowerState) - + " mSkippedScreenOn=" + mSkippedScreenOn); - } - mScreenBrightnessAnimator.animateTo(PowerManager.BRIGHTNESS_OFF, SCREEN_BRIGHT_BIT, 0); - } - } - int err = nativeSetScreenState(on); - if (err == 0) { - mLastScreenOnTime = (on ? SystemClock.elapsedRealtime() : 0); - if (mUseSoftwareAutoBrightness) { - enableLightSensorLocked(on); - if (on) { - // If AutoBrightness is enabled, set the brightness immediately after the - // next sensor value is received. - mWaitingForFirstLightSensor = mAutoBrightessEnabled; - } else { - // make sure button and key backlights are off too - mButtonLight.turnOff(); - mKeyboardLight.turnOff(); - } - } - } - return err; - } - - private void setPowerState(int state) - { - setPowerState(state, false, WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT); - } - - private void setPowerState(int newState, boolean noChangeLights, int reason) - { - synchronized (mLocks) { - int err; - - if (mSpew) { - Slog.d(TAG, "setPowerState: mPowerState=0x" + Integer.toHexString(mPowerState) - + " newState=0x" + Integer.toHexString(newState) - + " noChangeLights=" + noChangeLights - + " reason=" + reason); - } - - if (noChangeLights) { - newState = (newState & ~LIGHTS_MASK) | (mPowerState & LIGHTS_MASK); - } - if (mProximitySensorActive) { - // don't turn on the screen when the proximity sensor lock is held - newState = (newState & ~SCREEN_BRIGHT); - } - - if (batteryIsLow()) { - newState |= BATTERY_LOW_BIT; - } else { - newState &= ~BATTERY_LOW_BIT; - } - if (newState == mPowerState && mInitialized) { - return; - } - - if (!mBootCompleted && !mUseSoftwareAutoBrightness) { - newState |= ALL_BRIGHT; - } - - boolean oldScreenOn = (mPowerState & SCREEN_ON_BIT) != 0; - boolean newScreenOn = (newState & SCREEN_ON_BIT) != 0; - - if (mSpew) { - Slog.d(TAG, "setPowerState: mPowerState=" + mPowerState - + " newState=" + newState + " noChangeLights=" + noChangeLights); - Slog.d(TAG, " oldKeyboardBright=" + ((mPowerState & KEYBOARD_BRIGHT_BIT) != 0) - + " newKeyboardBright=" + ((newState & KEYBOARD_BRIGHT_BIT) != 0)); - Slog.d(TAG, " oldScreenBright=" + ((mPowerState & SCREEN_BRIGHT_BIT) != 0) - + " newScreenBright=" + ((newState & SCREEN_BRIGHT_BIT) != 0)); - Slog.d(TAG, " oldButtonBright=" + ((mPowerState & BUTTON_BRIGHT_BIT) != 0) - + " newButtonBright=" + ((newState & BUTTON_BRIGHT_BIT) != 0)); - Slog.d(TAG, " oldScreenOn=" + oldScreenOn - + " newScreenOn=" + newScreenOn); - Slog.d(TAG, " oldBatteryLow=" + ((mPowerState & BATTERY_LOW_BIT) != 0) - + " newBatteryLow=" + ((newState & BATTERY_LOW_BIT) != 0)); - } - - final boolean stateChanged = mPowerState != newState; - - if (stateChanged && reason == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT) { - if (mPolicy != null && mPolicy.isScreenSaverEnabled()) { - if (mSpew) { - Slog.d(TAG, "setPowerState: running screen saver instead of turning off screen"); - } - if (mPolicy.startScreenSaver()) { - // was successful - return; - } - } - } - - - if (oldScreenOn != newScreenOn) { - if (newScreenOn) { - // When the user presses the power button, we need to always send out the - // notification that it's going to sleep so the keyguard goes on. But - // we can't do that until the screen fades out, so we don't show the keyguard - // too early. - if (mStillNeedSleepNotification) { - sendNotificationLocked(false, WindowManagerPolicy.OFF_BECAUSE_OF_USER); - } - - // Turn on the screen UNLESS there was a prior - // preventScreenOn(true) request. (Note that the lifetime - // of a single preventScreenOn() request is limited to 5 - // seconds to prevent a buggy app from disabling the - // screen forever; see forceReenableScreen().) - boolean reallyTurnScreenOn = true; - if (mSpew) { - Slog.d(TAG, "- turning screen on... mPreventScreenOn = " - + mPreventScreenOn); - } - - if (mPreventScreenOn) { - if (mSpew) { - Slog.d(TAG, "- PREVENTING screen from really turning on!"); - } - reallyTurnScreenOn = false; - } - if (reallyTurnScreenOn) { - err = setScreenStateLocked(true); - long identity = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteScreenBrightness(getPreferredBrightness()); - mBatteryStats.noteScreenOn(); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling noteScreenOn on BatteryStatsService", e); - } finally { - Binder.restoreCallingIdentity(identity); - } - } else { - setScreenStateLocked(false); - // But continue as if we really did turn the screen on... - err = 0; - } - - mLastTouchDown = 0; - mTotalTouchDownTime = 0; - mTouchCycles = 0; - EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, reason, - mTotalTouchDownTime, mTouchCycles); - if (err == 0) { - sendNotificationLocked(true, -1); - // Update the lights *after* taking care of turning the - // screen on, so we do this after our notifications are - // enqueued and thus will delay turning on the screen light - // until the windows are correctly displayed. - if (stateChanged) { - updateLightsLocked(newState, 0); - } - mPowerState |= SCREEN_ON_BIT; - } - - } else { - // Update the lights *before* taking care of turning the - // screen off, so we can initiate any animations that are desired. - mScreenOffReason = reason; - if (stateChanged) { - updateLightsLocked(newState, 0); - } - - // cancel light sensor task - mHandler.removeCallbacks(mAutoBrightnessTask); - mLightSensorPendingDecrease = false; - mLightSensorPendingIncrease = false; - mScreenOffTime = SystemClock.elapsedRealtime(); - long identity = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteScreenOff(); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling noteScreenOff on BatteryStatsService", e); - } finally { - Binder.restoreCallingIdentity(identity); - } - mPowerState &= ~SCREEN_ON_BIT; - if (!mScreenBrightnessAnimator.isAnimating()) { - err = screenOffFinishedAnimatingLocked(reason); - } else { - err = 0; - mLastTouchDown = 0; - } - } - } else if (stateChanged) { - // Screen on/off didn't change, but lights may have. - updateLightsLocked(newState, 0); - } - - mPowerState = (mPowerState & ~LIGHTS_MASK) | (newState & LIGHTS_MASK); - - updateNativePowerStateLocked(); - } - } - - private void updateNativePowerStateLocked() { - if (!mHeadless) { - nativeSetPowerState( - (mPowerState & SCREEN_ON_BIT) != 0, - (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT); - } - } - - private int screenOffFinishedAnimatingLocked(int reason) { - // I don't think we need to check the current state here because all of these - // Power.setScreenState and sendNotificationLocked can both handle being - // called multiple times in the same state. -joeo - EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, reason, mTotalTouchDownTime, - mTouchCycles); - mLastTouchDown = 0; - int err = setScreenStateLocked(false); - if (err == 0) { - mScreenOffReason = reason; - sendNotificationLocked(false, reason); - } - return err; - } - - private boolean batteryIsLow() { - return (!mIsPowered && - mBatteryService.getBatteryLevel() <= LOW_BATTERY_THRESHOLD); - } - - private boolean shouldDeferScreenOnLocked() { - if (mPreparingForScreenOn) { - // Currently waiting for confirmation from the policy that it - // is okay to turn on the screen. Don't allow the screen to go - // on until that is done. - if (DEBUG_SCREEN_ON) Slog.i(TAG, - "updateLights: delaying screen on due to mPreparingForScreenOn"); - return true; - } else { - // If there is a screen-on command in the notification queue, we - // can't turn the screen on until it has been processed (and we - // have set mPreparingForScreenOn) or it has been dropped. - for (int i=0; i<mBroadcastQueue.length; i++) { - if (mBroadcastQueue[i] == 1) { - if (DEBUG_SCREEN_ON) Slog.i(TAG, - "updateLights: delaying screen on due to notification queue"); - return true; - } - } - } - return false; - } - - private void updateLightsLocked(int newState, int forceState) { - final int oldState = mPowerState; - - // If the screen is not currently on, we will want to delay actually - // turning the lights on if we are still getting the UI put up. - if ((oldState & SCREEN_ON_BIT) == 0 || mSkippedScreenOn) { - // Don't turn screen on until we know we are really ready to. - // This is to avoid letting the screen go on before things like the - // lock screen have been displayed. - if ((mSkippedScreenOn = shouldDeferScreenOnLocked())) { - newState &= ~(SCREEN_ON_BIT|SCREEN_BRIGHT_BIT); - } - } - - if ((newState & SCREEN_ON_BIT) != 0) { - // Only turn on the buttons or keyboard if the screen is also on. - // We should never see the buttons on but not the screen. - newState = applyButtonState(newState); - newState = applyKeyboardState(newState); - } - final int realDifference = (newState ^ oldState); - final int difference = realDifference | forceState; - if (difference == 0) { - return; - } - - int offMask = 0; - int dimMask = 0; - int onMask = 0; - - int preferredBrightness = getPreferredBrightness(); - - if ((difference & KEYBOARD_BRIGHT_BIT) != 0) { - if ((newState & KEYBOARD_BRIGHT_BIT) == 0) { - offMask |= KEYBOARD_BRIGHT_BIT; - } else { - onMask |= KEYBOARD_BRIGHT_BIT; - } - } - - if ((difference & BUTTON_BRIGHT_BIT) != 0) { - if ((newState & BUTTON_BRIGHT_BIT) == 0) { - offMask |= BUTTON_BRIGHT_BIT; - } else { - onMask |= BUTTON_BRIGHT_BIT; - } - } - - if ((difference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) { - int nominalCurrentValue = -1; - // If there was an actual difference in the light state, then - // figure out the "ideal" current value based on the previous - // state. Otherwise, this is a change due to the brightness - // override, so we want to animate from whatever the current - // value is. - if ((realDifference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) { - switch (oldState & (SCREEN_BRIGHT_BIT|SCREEN_ON_BIT)) { - case SCREEN_BRIGHT_BIT | SCREEN_ON_BIT: - nominalCurrentValue = preferredBrightness; - break; - case SCREEN_ON_BIT: - nominalCurrentValue = mScreenBrightnessDim; - break; - case 0: - nominalCurrentValue = PowerManager.BRIGHTNESS_OFF; - break; - case SCREEN_BRIGHT_BIT: - default: - // not possible - nominalCurrentValue = (int)mScreenBrightnessAnimator.getCurrentBrightness(); - break; - } - } - int brightness = preferredBrightness; - int steps = ANIM_STEPS; - if ((newState & SCREEN_BRIGHT_BIT) == 0) { - // dim or turn off backlight, depending on if the screen is on - // the scale is because the brightness ramp isn't linear and this biases - // it so the later parts take longer. - final float scale = 1.5f; - float ratio = (((float)mScreenBrightnessDim)/preferredBrightness); - if (ratio > 1.0f) ratio = 1.0f; - if ((newState & SCREEN_ON_BIT) == 0) { - if ((oldState & SCREEN_BRIGHT_BIT) != 0) { - // was bright - steps = ANIM_STEPS; - } else { - // was dim - steps = (int)(ANIM_STEPS*ratio*scale); - } - brightness = PowerManager.BRIGHTNESS_OFF; - } else { - if ((oldState & SCREEN_ON_BIT) != 0) { - // was bright - steps = (int)(ANIM_STEPS*(1.0f-ratio)*scale); - } else { - // was dim - steps = (int)(ANIM_STEPS*ratio); - } - final int stayOnConditions = getStayOnConditionsLocked(); - if (stayOnConditions != 0 && mBatteryService.isPowered(stayOnConditions)) { - // If the "stay on while plugged in" option is - // turned on, then the screen will often not - // automatically turn off while plugged in. To - // still have a sense of when it is inactive, we - // will then count going dim as turning off. - mScreenOffTime = SystemClock.elapsedRealtime(); - } - brightness = mScreenBrightnessDim; - } - } - if (mWaitingForFirstLightSensor && (newState & SCREEN_ON_BIT) != 0) { - steps = IMMEDIATE_ANIM_STEPS; - } - - long identity = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteScreenBrightness(brightness); - } catch (RemoteException e) { - // Nothing interesting to do. - } finally { - Binder.restoreCallingIdentity(identity); - } - if (!mSkippedScreenOn) { - int dt = steps * NOMINAL_FRAME_TIME_MS; - mScreenBrightnessAnimator.animateTo(brightness, SCREEN_BRIGHT_BIT, dt); - if (DEBUG_SCREEN_ON) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Slog.i(TAG, "Setting screen brightness: " + brightness, e); - } - } - } - - if (mSpew) { - Slog.d(TAG, "offMask=0x" + Integer.toHexString(offMask) - + " dimMask=0x" + Integer.toHexString(dimMask) - + " onMask=0x" + Integer.toHexString(onMask) - + " difference=0x" + Integer.toHexString(difference) - + " realDifference=0x" + Integer.toHexString(realDifference) - + " forceState=0x" + Integer.toHexString(forceState) - ); - } - - if (offMask != 0) { - if (mSpew) Slog.i(TAG, "Setting brightess off: " + offMask); - setLightBrightness(offMask, PowerManager.BRIGHTNESS_OFF); - } - if (dimMask != 0) { - int brightness = mScreenBrightnessDim; - if ((newState & BATTERY_LOW_BIT) != 0 && - brightness > PowerManager.BRIGHTNESS_LOW_BATTERY) { - brightness = PowerManager.BRIGHTNESS_LOW_BATTERY; - } - if (mSpew) Slog.i(TAG, "Setting brightess dim " + brightness + ": " + dimMask); - setLightBrightness(dimMask, brightness); - } - if (onMask != 0) { - int brightness = getPreferredBrightness(); - if ((newState & BATTERY_LOW_BIT) != 0 && - brightness > PowerManager.BRIGHTNESS_LOW_BATTERY) { - brightness = PowerManager.BRIGHTNESS_LOW_BATTERY; - } - if (mSpew) Slog.i(TAG, "Setting brightess on " + brightness + ": " + onMask); - setLightBrightness(onMask, brightness); - } - } - - /** - * Note: by design this class does not hold mLocks while calling native methods. - * Nor should it. Ever. - */ - class ScreenBrightnessAnimator extends HandlerThread { - static final int ANIMATE_LIGHTS = 10; - static final int ANIMATE_POWER_OFF = 11; - volatile int startValue; - volatile int endValue; - volatile int startSensorValue; - volatile int endSensorValue; - volatile int currentValue; - private int currentMask; - private int duration; - private long startTimeMillis; - private final String prefix; - - public ScreenBrightnessAnimator(String name, int priority) { - super(name, priority); - prefix = name; - } - - @Override - protected void onLooperPrepared() { - mScreenBrightnessHandler = new Handler() { - public void handleMessage(Message msg) { - int brightnessMode = (mAutoBrightessEnabled && !mInitialAnimation - ? LightsService.BRIGHTNESS_MODE_SENSOR - : LightsService.BRIGHTNESS_MODE_USER); - if (msg.what == ANIMATE_LIGHTS) { - final int mask = msg.arg1; - int value = msg.arg2; - long tStart = SystemClock.uptimeMillis(); - if ((mask & SCREEN_BRIGHT_BIT) != 0) { - if (mDebugLightAnimation) Slog.v(TAG, "Set brightness: " + value); - mLcdLight.setBrightness(value, brightnessMode); - } - long elapsed = SystemClock.uptimeMillis() - tStart; - if ((mask & BUTTON_BRIGHT_BIT) != 0) { - mButtonLight.setBrightness(value); - } - if ((mask & KEYBOARD_BRIGHT_BIT) != 0) { - mKeyboardLight.setBrightness(value); - } - - if (elapsed > 100) { - Slog.e(TAG, "Excessive delay setting brightness: " + elapsed - + "ms, mask=" + mask); - } - - // Throttle brightness updates to frame refresh rate - int delay = elapsed < NOMINAL_FRAME_TIME_MS ? NOMINAL_FRAME_TIME_MS : 1; - synchronized(this) { - currentValue = value; - } - animateInternal(mask, false, delay); - } else if (msg.what == ANIMATE_POWER_OFF) { - int mode = msg.arg1; - nativeStartSurfaceFlingerAnimation(mode); - } - } - }; - synchronized (this) { - mInitComplete = true; - notifyAll(); - } - } - - private void animateInternal(int mask, boolean turningOff, int delay) { - synchronized (this) { - if (currentValue != endValue) { - final long now = SystemClock.elapsedRealtime(); - final int elapsed = (int) (now - startTimeMillis); - int newValue; - if (elapsed < duration) { - int delta = endValue - startValue; - newValue = startValue + delta * elapsed / duration; - newValue = Math.max(PowerManager.BRIGHTNESS_OFF, newValue); - newValue = Math.min(PowerManager.BRIGHTNESS_ON, newValue); - // Optimization to delay next step until a change will occur. - if (delay > 0 && newValue == currentValue) { - final int timePerStep = duration / Math.abs(delta); - delay = Math.min(duration - elapsed, timePerStep); - newValue += delta < 0 ? -1 : 1; - } - // adjust the peak sensor value until we get to the target sensor value - delta = endSensorValue - startSensorValue; - mHighestLightSensorValue = startSensorValue + delta * elapsed / duration; - } else { - newValue = endValue; - mHighestLightSensorValue = endSensorValue; - if (endValue > 0) { - mInitialAnimation = false; - } - } - - if (mDebugLightAnimation) { - Slog.v(TAG, "Animating light: " + "start:" + startValue - + ", end:" + endValue + ", elapsed:" + elapsed - + ", duration:" + duration + ", current:" + currentValue - + ", newValue:" + newValue - + ", delay:" + delay - + ", highestSensor:" + mHighestLightSensorValue); - } - - if (turningOff && !mHeadless && !mAnimateScreenLights) { - int mode = mScreenOffReason == OFF_BECAUSE_OF_PROX_SENSOR - ? 0 : mAnimationSetting; - if (mDebugLightAnimation) { - Slog.v(TAG, "Doing power-off anim, mode=" + mode); - } - mScreenBrightnessHandler.obtainMessage(ANIMATE_POWER_OFF, mode, 0) - .sendToTarget(); - } - mScreenBrightnessHandler.removeMessages( - ScreenBrightnessAnimator.ANIMATE_LIGHTS); - Message msg = mScreenBrightnessHandler - .obtainMessage(ANIMATE_LIGHTS, mask, newValue); - mScreenBrightnessHandler.sendMessageDelayed(msg, delay); - } - } - } - - public void dump(PrintWriter pw, String string) { - pw.println(string); - pw.println(" animating: " + "start:" + startValue + ", end:" + endValue - + ", duration:" + duration + ", current:" + currentValue); - pw.println(" startSensorValue:" + startSensorValue - + " endSensorValue:" + endSensorValue); - pw.println(" startTimeMillis:" + startTimeMillis - + " now:" + SystemClock.elapsedRealtime()); - pw.println(" currentMask:" + dumpPowerState(currentMask)); - } - - public void animateTo(int target, int mask, int animationDuration) { - animateTo(target, mHighestLightSensorValue, mask, animationDuration); - } - - public void animateTo(int target, int sensorTarget, int mask, int animationDuration) { - synchronized(this) { - if ((mask & SCREEN_BRIGHT_BIT) == 0) { - // We only animate keyboard and button when passed in with SCREEN_BRIGHT_BIT. - if ((mask & BUTTON_BRIGHT_BIT) != 0) { - mButtonLight.setBrightness(target); - } - if ((mask & KEYBOARD_BRIGHT_BIT) != 0) { - mKeyboardLight.setBrightness(target); - } - return; - } - if (isAnimating() && (mask ^ currentMask) != 0) { - // current animation is unrelated to new animation, jump to final values - cancelAnimation(); - } - if (mInitialAnimation) { - // jump to final value in one step the first time the brightness is set - animationDuration = 0; - if (target > 0) { - mInitialAnimation = false; - } - } - startValue = currentValue; - endValue = target; - startSensorValue = mHighestLightSensorValue; - endSensorValue = sensorTarget; - currentMask = mask; - duration = (int) (mWindowScaleAnimation * animationDuration); - startTimeMillis = SystemClock.elapsedRealtime(); - - if (mDebugLightAnimation) { - Slog.v(TAG, "animateTo(target=" + target - + ", sensor=" + sensorTarget - + ", mask=" + mask - + ", duration=" + animationDuration +")" - + ", currentValue=" + currentValue - + ", startTime=" + startTimeMillis); - } - - if (target != currentValue) { - final boolean doScreenAnim = (mask & (SCREEN_BRIGHT_BIT | SCREEN_ON_BIT)) != 0; - final boolean turningOff = endValue == PowerManager.BRIGHTNESS_OFF; - if (turningOff && doScreenAnim) { - // Cancel all pending animations since we're turning off - mScreenBrightnessHandler.removeCallbacksAndMessages(null); - screenOffFinishedAnimatingLocked(mScreenOffReason); - duration = 200; // TODO: how long should this be? - } - if (doScreenAnim) { - animateInternal(mask, turningOff, 0); - } - // TODO: Handle keyboard light animation when we have devices that support it - } - } - } - - public int getCurrentBrightness() { - synchronized (this) { - return currentValue; - } - } - - public boolean isAnimating() { - synchronized (this) { - return currentValue != endValue; - } - } - - public void cancelAnimation() { - animateTo(endValue, currentMask, 0); - } - } - - private void setLightBrightness(int mask, int value) { - mScreenBrightnessAnimator.animateTo(value, mask, 0); - } - - private int getPreferredBrightness() { - if (mScreenBrightnessOverride >= 0) { - return mScreenBrightnessOverride; - } else if (mLightSensorScreenBrightness >= 0 && mUseSoftwareAutoBrightness - && mAutoBrightessEnabled) { - return mLightSensorScreenBrightness; - } - final int brightness = mScreenBrightnessSetting; - // Don't let applications turn the screen all the way off - return Math.max(brightness, mScreenBrightnessDim); - } - - private int applyButtonState(int state) { - int brightness = -1; - if ((state & BATTERY_LOW_BIT) != 0) { - // do not override brightness if the battery is low - return state; - } - if (mButtonBrightnessOverride >= 0) { - brightness = mButtonBrightnessOverride; - } else if (mLightSensorButtonBrightness >= 0 && mUseSoftwareAutoBrightness) { - brightness = mLightSensorButtonBrightness; - } - if (brightness > 0) { - return state | BUTTON_BRIGHT_BIT; - } else if (brightness == 0) { - return state & ~BUTTON_BRIGHT_BIT; - } else { - return state; - } - } - - private int applyKeyboardState(int state) { - int brightness = -1; - if ((state & BATTERY_LOW_BIT) != 0) { - // do not override brightness if the battery is low - return state; - } - if (!mKeyboardVisible) { - brightness = 0; - } else if (mButtonBrightnessOverride >= 0) { - brightness = mButtonBrightnessOverride; - } else if (mLightSensorKeyboardBrightness >= 0 && mUseSoftwareAutoBrightness) { - brightness = mLightSensorKeyboardBrightness; - } - if (brightness > 0) { - return state | KEYBOARD_BRIGHT_BIT; - } else if (brightness == 0) { - return state & ~KEYBOARD_BRIGHT_BIT; - } else { - return state; - } - } - - public boolean isScreenOn() { - synchronized (mLocks) { - return (mPowerState & SCREEN_ON_BIT) != 0; - } - } - - boolean isScreenBright() { - synchronized (mLocks) { - return (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT; - } - } - - private boolean isScreenTurningOffLocked() { - return (mScreenBrightnessAnimator.isAnimating() - && mScreenBrightnessAnimator.endValue == PowerManager.BRIGHTNESS_OFF - && (mScreenBrightnessAnimator.currentMask & SCREEN_BRIGHT_BIT) != 0); - } - - private boolean shouldLog(long time) { - synchronized (mLocks) { - if (time > (mWarningSpewThrottleTime + (60*60*1000))) { - mWarningSpewThrottleTime = time; - mWarningSpewThrottleCount = 0; - return true; - } else if (mWarningSpewThrottleCount < 30) { - mWarningSpewThrottleCount++; - return true; - } else { - return false; - } - } - } - - private void forceUserActivityLocked() { - if (isScreenTurningOffLocked()) { - // cancel animation so userActivity will succeed - mScreenBrightnessAnimator.cancelAnimation(); - } - boolean savedActivityAllowed = mUserActivityAllowed; - mUserActivityAllowed = true; - userActivity(SystemClock.uptimeMillis(), false); - mUserActivityAllowed = savedActivityAllowed; - } - - public void userActivityWithForce(long time, boolean noChangeLights, boolean force) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - userActivity(time, -1, noChangeLights, OTHER_EVENT, force, false); - } - - public void userActivity(long time, boolean noChangeLights) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) - != PackageManager.PERMISSION_GRANTED) { - if (shouldLog(time)) { - Slog.w(TAG, "Caller does not have DEVICE_POWER permission. pid=" - + Binder.getCallingPid() + " uid=" + Binder.getCallingUid()); - } - return; - } - - userActivity(time, -1, noChangeLights, OTHER_EVENT, false, false); - } - - public void userActivity(long time, boolean noChangeLights, int eventType) { - userActivity(time, -1, noChangeLights, eventType, false, false); - } - - public void userActivity(long time, boolean noChangeLights, int eventType, boolean force) { - userActivity(time, -1, noChangeLights, eventType, force, false); - } - - /* - * Reset the user activity timeout to now + timeout. This overrides whatever else is going - * on with user activity. Don't use this function. - */ - public void clearUserActivityTimeout(long now, long timeout) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - Slog.i(TAG, "clearUserActivity for " + timeout + "ms from now"); - userActivity(now, timeout, false, OTHER_EVENT, false, false); - } - - private void userActivity(long time, long timeoutOverride, boolean noChangeLights, - int eventType, boolean force, boolean ignoreIfScreenOff) { - - if (((mPokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0) && (eventType == TOUCH_EVENT)) { - if (false) { - Slog.d(TAG, "dropping touch mPokey=0x" + Integer.toHexString(mPokey)); - } - return; - } - - synchronized (mLocks) { - if (mSpew) { - Slog.d(TAG, "userActivity mLastEventTime=" + mLastEventTime + " time=" + time - + " mUserActivityAllowed=" + mUserActivityAllowed - + " mUserState=0x" + Integer.toHexString(mUserState) - + " mWakeLockState=0x" + Integer.toHexString(mWakeLockState) - + " mProximitySensorActive=" + mProximitySensorActive - + " timeoutOverride=" + timeoutOverride - + " force=" + force); - } - // ignore user activity if we are in the process of turning off the screen - if (isScreenTurningOffLocked()) { - Slog.d(TAG, "ignoring user activity while turning off screen"); - return; - } - // ignore if the caller doesn't want this to allow the screen to turn - // on, and the screen is currently off. - if (ignoreIfScreenOff && (mPowerState & SCREEN_ON_BIT) == 0) { - return; - } - // Disable proximity sensor if if user presses power key while we are in the - // "waiting for proximity sensor to go negative" state. - if (mProximitySensorActive && mProximityWakeLockCount == 0) { - mProximitySensorActive = false; - } - if (mLastEventTime <= time || force) { - mLastEventTime = time; - if ((mUserActivityAllowed && !mProximitySensorActive) || force) { - // Only turn on button backlights if a button was pressed - // and auto brightness is disabled - if (eventType == BUTTON_EVENT && !mUseSoftwareAutoBrightness) { - mUserState = (mKeyboardVisible ? ALL_BRIGHT : SCREEN_BUTTON_BRIGHT); - } else { - // don't clear button/keyboard backlights when the screen is touched. - mUserState |= SCREEN_BRIGHT; - } - - int uid = Binder.getCallingUid(); - long ident = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteUserActivity(uid, eventType); - } catch (RemoteException e) { - // Ignore - } finally { - Binder.restoreCallingIdentity(ident); - } - - mWakeLockState = mLocks.reactivateScreenLocksLocked(); - setPowerState(mUserState | mWakeLockState, noChangeLights, - WindowManagerPolicy.OFF_BECAUSE_OF_USER); - setTimeoutLocked(time, timeoutOverride, SCREEN_BRIGHT); - } - } - } - - if (mPolicy != null) { - mPolicy.userActivity(); - } - } - - private int getAutoBrightnessValue(int sensorValue, int[] values) { - try { - int i; - for (i = 0; i < mAutoBrightnessLevels.length; i++) { - if (sensorValue < mAutoBrightnessLevels[i]) { - break; - } - } - // This is the range of brightness values that we can use. - final int minval = values[0]; - final int maxval = values[mAutoBrightnessLevels.length]; - // This is the range we will be scaling. We put some padding - // at the low and high end to give the adjustment a little better - // impact on the actual observed value. - final int range = (maxval-minval) + LIGHT_SENSOR_RANGE_EXPANSION; - // This is the desired brightness value from 0.0 to 1.0. - float valf = ((values[i]-minval+(LIGHT_SENSOR_RANGE_EXPANSION/2))/(float)range); - // Apply a scaling to the value based on the adjustment. - if (mLightSensorAdjustSetting > 0 && mLightSensorAdjustSetting <= 1) { - float adj = (float)Math.sqrt(1.0f-mLightSensorAdjustSetting); - if (adj <= .00001) { - valf = 1; - } else { - valf /= adj; - } - } else if (mLightSensorAdjustSetting < 0 && mLightSensorAdjustSetting >= -1) { - float adj = (float)Math.sqrt(1.0f+mLightSensorAdjustSetting); - valf *= adj; - } - // Apply an additional offset to the value based on the adjustment. - valf += mLightSensorAdjustSetting/LIGHT_SENSOR_OFFSET_SCALE; - // Convert the 0.0-1.0 value back to a brightness integer. - int val = (int)((valf*range)+minval) - (LIGHT_SENSOR_RANGE_EXPANSION/2); - if (val < minval) val = minval; - else if (val > maxval) val = maxval; - return val; - } catch (Exception e) { - // guard against null pointer or index out of bounds errors - Slog.e(TAG, "Values array must be non-empty and must be one element longer than " - + "the auto-brightness levels array. Check config.xml.", e); - return 255; - } - } - - private Runnable mProximityTask = new Runnable() { - public void run() { - synchronized (mLocks) { - if (mProximityPendingValue != -1) { - proximityChangedLocked(mProximityPendingValue == 1); - mProximityPendingValue = -1; - } - if (mProximityPartialLock.isHeld()) { - mProximityPartialLock.release(); - } - } - } - }; - - private Runnable mAutoBrightnessTask = new Runnable() { - public void run() { - synchronized (mLocks) { - if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) { - int value = (int)mLightSensorPendingValue; - mLightSensorPendingDecrease = false; - mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value, false); - } - } - } - }; - - /** used to prevent lightsensor changes while turning on. */ - private boolean mInitialAnimation = true; - - private void dockStateChanged(int state) { - synchronized (mLocks) { - mIsDocked = (state != Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (mIsDocked) { - // allow brightness to decrease when docked - mHighestLightSensorValue = -1; - } - if ((mPowerState & SCREEN_ON_BIT) != 0) { - // force lights recalculation - int value = (int)mLightSensorValue; - mLightSensorValue = -1; - lightSensorChangedLocked(value, false); - } - } - } - - private void lightSensorChangedLocked(int value, boolean immediate) { - if (mDebugLightSensor) { - Slog.d(TAG, "lightSensorChangedLocked value=" + value + " immediate=" + immediate); - } - - // Don't do anything if the screen is off. - if ((mPowerState & SCREEN_ON_BIT) == 0) { - if (mDebugLightSensor) { - Slog.d(TAG, "dropping lightSensorChangedLocked because screen is off"); - } - return; - } - - if (mLightSensorValue != value) { - mLightSensorValue = value; - if ((mPowerState & BATTERY_LOW_BIT) == 0) { - // use maximum light sensor value seen since screen went on for LCD to avoid flicker - // we only do this if we are undocked, since lighting should be stable when - // stationary in a dock. - int lcdValue = getAutoBrightnessValue(value, mLcdBacklightValues); - int buttonValue = getAutoBrightnessValue(value, mButtonBacklightValues); - int keyboardValue; - if (mKeyboardVisible) { - keyboardValue = getAutoBrightnessValue(value, mKeyboardBacklightValues); - } else { - keyboardValue = 0; - } - mLightSensorScreenBrightness = lcdValue; - mLightSensorButtonBrightness = buttonValue; - mLightSensorKeyboardBrightness = keyboardValue; - - if (mDebugLightSensor) { - Slog.d(TAG, "lcdValue " + lcdValue); - Slog.d(TAG, "buttonValue " + buttonValue); - Slog.d(TAG, "keyboardValue " + keyboardValue); - } - - if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) { - if (!mSkippedScreenOn && !mInitialAnimation) { - final int steps; - if (immediate) { - steps = IMMEDIATE_ANIM_STEPS; - } else { - synchronized (mScreenBrightnessAnimator) { - if (mScreenBrightnessAnimator.currentValue <= lcdValue) { - steps = AUTOBRIGHTNESS_ANIM_STEPS; - } else { - steps = AUTODIMNESS_ANIM_STEPS; - } - } - } - mScreenBrightnessAnimator.animateTo(lcdValue, value, - SCREEN_BRIGHT_BIT, steps * NOMINAL_FRAME_TIME_MS); - } - } - if (mButtonBrightnessOverride < 0) { - mButtonLight.setBrightness(buttonValue); - } - if (mButtonBrightnessOverride < 0 || !mKeyboardVisible) { - mKeyboardLight.setBrightness(keyboardValue); - } - } - } - } - - /** - * The user requested that we go to sleep (probably with the power button). - * This overrides all wake locks that are held. - */ - public void goToSleep(long time) - { - goToSleepWithReason(time, WindowManagerPolicy.OFF_BECAUSE_OF_USER); - } - - /** - * The user requested that we go to sleep (probably with the power button). - * This overrides all wake locks that are held. - */ - public void goToSleepWithReason(long time, int reason) - { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - synchronized (mLocks) { - goToSleepLocked(time, reason); - } - } - - /** - * Reboot the device immediately, passing 'reason' (may be null) - * to the underlying __reboot system call. Should not return. - */ - public void reboot(String reason) - { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); - - if (mHandler == null || !ActivityManagerNative.isSystemReady()) { - throw new IllegalStateException("Too early to call reboot()"); - } - - final String finalReason = reason; - Runnable runnable = new Runnable() { - public void run() { - synchronized (this) { - ShutdownThread.reboot(mContext, finalReason, false); - } - - } - }; - // ShutdownThread must run on a looper capable of displaying the UI. - mHandler.post(runnable); - - // PowerManager.reboot() is documented not to return so just wait for the inevitable. - synchronized (runnable) { - while (true) { - try { - runnable.wait(); - } catch (InterruptedException e) { - } - } - } - } - - /** - * Crash the runtime (causing a complete restart of the Android framework). - * Requires REBOOT permission. Mostly for testing. Should not return. - */ - public void crash(final String message) - { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); - Thread t = new Thread("PowerManagerService.crash()") { - public void run() { throw new RuntimeException(message); } - }; - try { - t.start(); - t.join(); - } catch (InterruptedException e) { - Log.wtf(TAG, e); - } - } - - private void goToSleepLocked(long time, int reason) { - if (mSpew) { - Exception ex = new Exception(); - ex.fillInStackTrace(); - Slog.d(TAG, "goToSleep mLastEventTime=" + mLastEventTime + " time=" + time - + " reason=" + reason, ex); - } - - if (mLastEventTime <= time) { - mLastEventTime = time; - // cancel all of the wake locks - mWakeLockState = SCREEN_OFF; - int N = mLocks.size(); - int numCleared = 0; - boolean proxLock = false; - for (int i=0; i<N; i++) { - WakeLock wl = mLocks.get(i); - if (isScreenLock(wl.flags)) { - if (((wl.flags & LOCK_MASK) == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK) - && reason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) { - proxLock = true; - } else { - mLocks.get(i).activated = false; - numCleared++; - } - } - } - if (!proxLock) { - mProxIgnoredBecauseScreenTurnedOff = true; - if (mDebugProximitySensor) { - Slog.d(TAG, "setting mProxIgnoredBecauseScreenTurnedOff"); - } - } - EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numCleared); - mStillNeedSleepNotification = true; - mUserState = SCREEN_OFF; - setPowerState(SCREEN_OFF, false, reason); - cancelTimerLocked(); - } - } - - public long timeSinceScreenOn() { - synchronized (mLocks) { - if ((mPowerState & SCREEN_ON_BIT) != 0) { - return 0; - } - return SystemClock.elapsedRealtime() - mScreenOffTime; - } - } - - public void setKeyboardVisibility(boolean visible) { - synchronized (mLocks) { - if (mSpew) { - Slog.d(TAG, "setKeyboardVisibility: " + visible); - } - if (mKeyboardVisible != visible) { - mKeyboardVisible = visible; - // don't signal user activity if the screen is off; other code - // will take care of turning on due to a true change to the lid - // switch and synchronized with the lock screen. - if ((mPowerState & SCREEN_ON_BIT) != 0) { - if (mUseSoftwareAutoBrightness) { - // force recompute of backlight values - if (mLightSensorValue >= 0) { - int value = (int)mLightSensorValue; - mLightSensorValue = -1; - lightSensorChangedLocked(value, false); - } - } - userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true); - } - } - } - } - - /** - * When the keyguard is up, it manages the power state, and userActivity doesn't do anything. - * When disabling user activity we also reset user power state so the keyguard can reset its - * short screen timeout when keyguard is unhidden. - */ - public void enableUserActivity(boolean enabled) { - if (mSpew) { - Slog.d(TAG, "enableUserActivity " + enabled); - } - synchronized (mLocks) { - mUserActivityAllowed = enabled; - if (!enabled) { - // cancel timeout and clear mUserState so the keyguard can set a short timeout - setTimeoutLocked(SystemClock.uptimeMillis(), 0); - } - } - } - - private void setScreenBrightnessMode(int mode) { - synchronized (mLocks) { - boolean enabled = (mode == SCREEN_BRIGHTNESS_MODE_AUTOMATIC); - if (mUseSoftwareAutoBrightness && mAutoBrightessEnabled != enabled) { - mAutoBrightessEnabled = enabled; - // This will get us a new value - enableLightSensorLocked(mAutoBrightessEnabled && isScreenOn()); - } - } - } - - /** Sets the screen off timeouts: - * mKeylightDelay - * mDimDelay - * mScreenOffDelay - * */ - private void setScreenOffTimeoutsLocked() { - if ((mPokey & POKE_LOCK_SHORT_TIMEOUT) != 0) { - mKeylightDelay = mShortKeylightDelay; // Configurable via secure settings - mDimDelay = -1; - mScreenOffDelay = 0; - } else if ((mPokey & POKE_LOCK_MEDIUM_TIMEOUT) != 0) { - mKeylightDelay = MEDIUM_KEYLIGHT_DELAY; - mDimDelay = -1; - mScreenOffDelay = 0; - } else { - int totalDelay = mScreenOffTimeoutSetting; - if (totalDelay > mMaximumScreenOffTimeout) { - totalDelay = mMaximumScreenOffTimeout; - } - mKeylightDelay = LONG_KEYLIGHT_DELAY; - if (totalDelay < 0) { - // negative number means stay on as long as possible. - mScreenOffDelay = mMaximumScreenOffTimeout; - } else if (mKeylightDelay < totalDelay) { - // subtract the time that the keylight delay. This will give us the - // remainder of the time that we need to sleep to get the accurate - // screen off timeout. - mScreenOffDelay = totalDelay - mKeylightDelay; - } else { - mScreenOffDelay = 0; - } - if (mDimScreen && totalDelay >= (LONG_KEYLIGHT_DELAY + LONG_DIM_TIME)) { - mDimDelay = mScreenOffDelay - LONG_DIM_TIME; - mScreenOffDelay = LONG_DIM_TIME; - } else { - mDimDelay = -1; - } - } - if (mSpew) { - Slog.d(TAG, "setScreenOffTimeouts mKeylightDelay=" + mKeylightDelay - + " mDimDelay=" + mDimDelay + " mScreenOffDelay=" + mScreenOffDelay - + " mDimScreen=" + mDimScreen); - } - } - - /** - * Refreshes cached secure settings. Called once on startup, and - * on subsequent changes to secure settings. - */ - private void updateSettingsValues() { - mShortKeylightDelay = Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.SHORT_KEYLIGHT_DELAY_MS, - SHORT_KEYLIGHT_DELAY_DEFAULT); - // Slog.i(TAG, "updateSettingsValues(): mShortKeylightDelay now " + mShortKeylightDelay); - } - - private class LockList extends ArrayList<WakeLock> - { - void addLock(WakeLock wl) - { - int index = getIndex(wl.binder); - if (index < 0) { - this.add(wl); - } - } - - WakeLock removeLock(IBinder binder) - { - int index = getIndex(binder); - if (index >= 0) { - return this.remove(index); - } else { - return null; - } - } - - int getIndex(IBinder binder) - { - int N = this.size(); - for (int i=0; i<N; i++) { - if (this.get(i).binder == binder) { - return i; - } - } - return -1; - } - - int gatherState() - { - int result = 0; - int N = this.size(); - for (int i=0; i<N; i++) { - WakeLock wl = this.get(i); - if (wl.activated) { - if (isScreenLock(wl.flags)) { - result |= wl.minState; - } - } - } - return result; - } - - int reactivateScreenLocksLocked() - { - int result = 0; - int N = this.size(); - for (int i=0; i<N; i++) { - WakeLock wl = this.get(i); - if (isScreenLock(wl.flags)) { - wl.activated = true; - result |= wl.minState; - } - } - if (mDebugProximitySensor) { - Slog.d(TAG, "reactivateScreenLocksLocked mProxIgnoredBecauseScreenTurnedOff=" - + mProxIgnoredBecauseScreenTurnedOff); - } - mProxIgnoredBecauseScreenTurnedOff = false; - return result; - } - } - - public void setPolicy(WindowManagerPolicy p) { - synchronized (mLocks) { - mPolicy = p; - mLocks.notifyAll(); - } - } - - WindowManagerPolicy getPolicyLocked() { - while (mPolicy == null || !mDoneBooting) { - try { - mLocks.wait(); - } catch (InterruptedException e) { - // Ignore - } - } - return mPolicy; - } - - void systemReady() { - mSensorManager = new SystemSensorManager(mHandlerThread.getLooper()); - mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - // don't bother with the light sensor if auto brightness is handled in hardware - if (mUseSoftwareAutoBrightness) { - mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); - } - - // wait until sensors are enabled before turning on screen. - // some devices will not activate the light sensor properly on boot - // unless we do this. - if (mUseSoftwareAutoBrightness) { - // turn the screen on - setPowerState(SCREEN_BRIGHT); - } else { - // turn everything on - setPowerState(ALL_BRIGHT); - } - - synchronized (mLocks) { - Slog.d(TAG, "system ready!"); - mDoneBooting = true; - - enableLightSensorLocked(mUseSoftwareAutoBrightness && mAutoBrightessEnabled); - - long identity = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteScreenBrightness(getPreferredBrightness()); - mBatteryStats.noteScreenOn(); - } catch (RemoteException e) { - // Nothing interesting to do. - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - void bootCompleted() { - Slog.d(TAG, "bootCompleted"); - synchronized (mLocks) { - mBootCompleted = true; - userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true); - updateWakeLockLocked(); - mLocks.notifyAll(); - } - } - - // for watchdog - public void monitor() { - synchronized (mLocks) { } - } - - public int getSupportedWakeLockFlags() { - int result = PowerManager.PARTIAL_WAKE_LOCK - | PowerManager.FULL_WAKE_LOCK - | PowerManager.SCREEN_DIM_WAKE_LOCK; - - if (mProximitySensor != null) { - result |= PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK; - } - - return result; - } - - public void setBacklightBrightness(int brightness) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - // Don't let applications turn the screen all the way off - synchronized (mLocks) { - brightness = Math.max(brightness, mScreenBrightnessDim); - mLcdLight.setBrightness(brightness); - mKeyboardLight.setBrightness(mKeyboardVisible ? brightness : 0); - mButtonLight.setBrightness(brightness); - long identity = Binder.clearCallingIdentity(); - try { - mBatteryStats.noteScreenBrightness(brightness); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling noteScreenBrightness on BatteryStatsService", e); - } finally { - Binder.restoreCallingIdentity(identity); - } - mScreenBrightnessAnimator.animateTo(brightness, SCREEN_BRIGHT_BIT, 0); - } - } - - public void setAutoBrightnessAdjustment(float adj) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - synchronized (mLocks) { - mLightSensorAdjustSetting = adj; - if (mSensorManager != null && mLightSensorEnabled) { - // clear calling identity so sensor manager battery stats are accurate - long identity = Binder.clearCallingIdentity(); - try { - // force recompute of backlight values - if (mLightSensorValue >= 0) { - int value = (int)mLightSensorValue; - mLightSensorValue = -1; - handleLightSensorValue(value, true); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - } - - public void setAttentionLight(boolean on, int color) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); - mAttentionLight.setFlashing(color, LightsService.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0); - } - - private void enableProximityLockLocked() { - if (mDebugProximitySensor) { - Slog.d(TAG, "enableProximityLockLocked"); - } - if (!mProximitySensorEnabled) { - // clear calling identity so sensor manager battery stats are accurate - long identity = Binder.clearCallingIdentity(); - try { - mSensorManager.registerListener(mProximityListener, mProximitySensor, - SensorManager.SENSOR_DELAY_NORMAL); - mProximitySensorEnabled = true; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - private void disableProximityLockLocked() { - if (mDebugProximitySensor) { - Slog.d(TAG, "disableProximityLockLocked"); - } - if (mProximitySensorEnabled) { - // clear calling identity so sensor manager battery stats are accurate - long identity = Binder.clearCallingIdentity(); - try { - mSensorManager.unregisterListener(mProximityListener); - mHandler.removeCallbacks(mProximityTask); - if (mProximityPartialLock.isHeld()) { - mProximityPartialLock.release(); - } - mProximitySensorEnabled = false; - } finally { - Binder.restoreCallingIdentity(identity); - } - if (mProximitySensorActive) { - mProximitySensorActive = false; - if (mDebugProximitySensor) { - Slog.d(TAG, "disableProximityLockLocked mProxIgnoredBecauseScreenTurnedOff=" - + mProxIgnoredBecauseScreenTurnedOff); - } - if (!mProxIgnoredBecauseScreenTurnedOff) { - forceUserActivityLocked(); - } - } - } - } - - private void proximityChangedLocked(boolean active) { - if (mDebugProximitySensor) { - Slog.d(TAG, "proximityChangedLocked, active: " + active); - } - if (!mProximitySensorEnabled) { - Slog.d(TAG, "Ignoring proximity change after sensor is disabled"); - return; - } - if (active) { - if (mDebugProximitySensor) { - Slog.d(TAG, "b mProxIgnoredBecauseScreenTurnedOff=" - + mProxIgnoredBecauseScreenTurnedOff); - } - if (!mProxIgnoredBecauseScreenTurnedOff) { - goToSleepLocked(SystemClock.uptimeMillis(), - WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR); - } - mProximitySensorActive = true; - } else { - // proximity sensor negative events trigger as user activity. - // temporarily set mUserActivityAllowed to true so this will work - // even when the keyguard is on. - mProximitySensorActive = false; - if (mDebugProximitySensor) { - Slog.d(TAG, "b mProxIgnoredBecauseScreenTurnedOff=" - + mProxIgnoredBecauseScreenTurnedOff); - } - if (!mProxIgnoredBecauseScreenTurnedOff) { - forceUserActivityLocked(); - } - - if (mProximityWakeLockCount == 0) { - // disable sensor if we have no listeners left after proximity negative - disableProximityLockLocked(); - } - } - } - - private void enableLightSensorLocked(boolean enable) { - if (mDebugLightSensor) { - Slog.d(TAG, "enableLightSensorLocked enable=" + enable - + " mLightSensorEnabled=" + mLightSensorEnabled - + " mAutoBrightessEnabled=" + mAutoBrightessEnabled - + " mWaitingForFirstLightSensor=" + mWaitingForFirstLightSensor); - } - if (!mAutoBrightessEnabled) { - enable = false; - } - if (mSensorManager != null && mLightSensorEnabled != enable) { - mLightSensorEnabled = enable; - // clear calling identity so sensor manager battery stats are accurate - long identity = Binder.clearCallingIdentity(); - try { - if (enable) { - // reset our highest value when reenabling - mHighestLightSensorValue = -1; - // force recompute of backlight values - final int value = (int)mLightSensorValue; - if (value >= 0) { - mLightSensorValue = -1; - handleLightSensorValue(value, true); - } - mSensorManager.registerListener(mLightListener, mLightSensor, - LIGHT_SENSOR_RATE); - } else { - mSensorManager.unregisterListener(mLightListener); - mHandler.removeCallbacks(mAutoBrightnessTask); - mLightSensorPendingDecrease = false; - mLightSensorPendingIncrease = false; - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - SensorEventListener mProximityListener = new SensorEventListener() { - public void onSensorChanged(SensorEvent event) { - long milliseconds = SystemClock.elapsedRealtime(); - synchronized (mLocks) { - float distance = event.values[0]; - long timeSinceLastEvent = milliseconds - mLastProximityEventTime; - mLastProximityEventTime = milliseconds; - mHandler.removeCallbacks(mProximityTask); - boolean proximityTaskQueued = false; - - // compare against getMaximumRange to support sensors that only return 0 or 1 - boolean active = (distance >= 0.0 && distance < PROXIMITY_THRESHOLD && - distance < mProximitySensor.getMaximumRange()); - - if (mDebugProximitySensor) { - Slog.d(TAG, "mProximityListener.onSensorChanged active: " + active); - } - if (timeSinceLastEvent < PROXIMITY_SENSOR_DELAY) { - // enforce delaying atleast PROXIMITY_SENSOR_DELAY before processing - mProximityPendingValue = (active ? 1 : 0); - mHandler.postDelayed(mProximityTask, PROXIMITY_SENSOR_DELAY - timeSinceLastEvent); - proximityTaskQueued = true; - } else { - // process the value immediately - mProximityPendingValue = -1; - proximityChangedLocked(active); - } - - // update mProximityPartialLock state - boolean held = mProximityPartialLock.isHeld(); - if (!held && proximityTaskQueued) { - // hold wakelock until mProximityTask runs - mProximityPartialLock.acquire(); - } else if (held && !proximityTaskQueued) { - mProximityPartialLock.release(); - } - } - } - - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // ignore - } - }; - - private void handleLightSensorValue(int value, boolean immediate) { - long milliseconds = SystemClock.elapsedRealtime(); - if (mLightSensorValue == -1 - || milliseconds < mLastScreenOnTime + mLightSensorWarmupTime - || mWaitingForFirstLightSensor) { - // process the value immediately if screen has just turned on - mHandler.removeCallbacks(mAutoBrightnessTask); - mLightSensorPendingDecrease = false; - mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value, immediate); - } else { - if ((value > mLightSensorValue && mLightSensorPendingDecrease) || - (value < mLightSensorValue && mLightSensorPendingIncrease) || - (value == mLightSensorValue) || - (!mLightSensorPendingDecrease && !mLightSensorPendingIncrease)) { - // delay processing to debounce the sensor - mHandler.removeCallbacks(mAutoBrightnessTask); - mLightSensorPendingDecrease = (value < mLightSensorValue); - mLightSensorPendingIncrease = (value > mLightSensorValue); - if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) { - mLightSensorPendingValue = value; - mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY); - } - } else { - mLightSensorPendingValue = value; - } - } - } - - SensorEventListener mLightListener = new SensorEventListener() { - @Override - public void onSensorChanged(SensorEvent event) { - if (mDebugLightSensor) { - Slog.d(TAG, "onSensorChanged: light value: " + event.values[0]); - } - synchronized (mLocks) { - // ignore light sensor while screen is turning off - if (isScreenTurningOffLocked()) { - return; - } - handleLightSensorValue((int)event.values[0], mWaitingForFirstLightSensor); - if (mWaitingForFirstLightSensor && !mPreparingForScreenOn) { - if (mDebugLightAnimation) { - Slog.d(TAG, "onSensorChanged: Clearing mWaitingForFirstLightSensor."); - } - mWaitingForFirstLightSensor = false; - } - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // ignore - } - }; -} diff --git a/services/java/com/android/server/ServiceWatcher.java b/services/java/com/android/server/ServiceWatcher.java new file mode 100644 index 0000000..0dfaa05 --- /dev/null +++ b/services/java/com/android/server/ServiceWatcher.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.Signature; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +import com.android.internal.content.PackageMonitor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +/** + * Find the best Service, and bind to it. + * Handles run-time package changes. + */ +public class ServiceWatcher implements ServiceConnection { + private static final boolean D = false; + private static final String EXTRA_VERSION = "version"; + + private final String mTag; + private final Context mContext; + private final PackageManager mPm; + private final List<HashSet<Signature>> mSignatureSets; + private final String mAction; + private final Runnable mNewServiceWork; + private final Handler mHandler; + + private Object mLock = new Object(); + + // all fields below synchronized on mLock + private IBinder mBinder; // connected service + private String mPackageName; // current best package + private int mVersion; // current best version + + public ServiceWatcher(Context context, String logTag, String action, + List<String> initialPackageNames, Runnable newServiceWork, Handler handler) { + mContext = context; + mTag = logTag; + mAction = action; + mPm = mContext.getPackageManager(); + mNewServiceWork = newServiceWork; + mHandler = handler; + + mSignatureSets = new ArrayList<HashSet<Signature>>(); + for (int i=0; i < initialPackageNames.size(); i++) { + String pkg = initialPackageNames.get(i); + HashSet<Signature> set = new HashSet<Signature>(); + try { + Signature[] sigs = + mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures; + set.addAll(Arrays.asList(sigs)); + mSignatureSets.add(set); + } catch (NameNotFoundException e) { + Log.w(logTag, pkg + " not found"); + } + } + + } + + public boolean start() { + if (!bindBestPackage(null)) return false; + + mPackageMonitor.register(mContext, null, true); + return true; + } + + /** + * Searches and binds to the best package, or do nothing + * if the best package is already bound. + * Only checks the named package, or checks all packages if it + * is null. + * Return true if a new package was found to bind to. + */ + private boolean bindBestPackage(String justCheckThisPackage) { + Intent intent = new Intent(mAction); + if (justCheckThisPackage != null) { + intent.setPackage(justCheckThisPackage); + } + List<ResolveInfo> rInfos = mPm.queryIntentServices(new Intent(mAction), + PackageManager.GET_META_DATA); + int bestVersion = Integer.MIN_VALUE; + String bestPackage = null; + for (ResolveInfo rInfo : rInfos) { + String packageName = rInfo.serviceInfo.packageName; + + // check signature + try { + PackageInfo pInfo; + pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + if (!isSignatureMatch(pInfo.signatures)) { + Log.w(mTag, packageName + " resolves service " + mAction + + ", but has wrong signature, ignoring"); + continue; + } + } catch (NameNotFoundException e) { + Log.wtf(mTag, e); + continue; + } + + // check version + int version = 0; + if (rInfo.serviceInfo.metaData != null) { + version = rInfo.serviceInfo.metaData.getInt(EXTRA_VERSION, 0); + } + if (version > mVersion) { + bestVersion = version; + bestPackage = packageName; + } + } + + if (D) Log.d(mTag, String.format("bindBestPackage %s found %d, %s", + (justCheckThisPackage == null ? "" : "(" + justCheckThisPackage + ") "), + rInfos.size(), + (bestPackage == null ? "no new best package" : "new best packge: " + bestPackage))); + + if (bestPackage != null) { + bindToPackage(bestPackage, bestVersion); + return true; + } + return false; + } + + private void unbind() { + String pkg; + synchronized (mLock) { + pkg = mPackageName; + mPackageName = null; + mVersion = Integer.MIN_VALUE; + } + if (pkg != null) { + if (D) Log.d(mTag, "unbinding " + pkg); + mContext.unbindService(this); + } + } + + private void bindToPackage(String packageName, int version) { + unbind(); + Intent intent = new Intent(mAction); + intent.setPackage(packageName); + synchronized (mLock) { + mPackageName = packageName; + mVersion = version; + } + if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ")"); + mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT); + } + + private boolean isSignatureMatch(Signature[] signatures) { + if (signatures == null) return false; + + // build hashset of input to test against + HashSet<Signature> inputSet = new HashSet<Signature>(); + for (Signature s : signatures) { + inputSet.add(s); + } + + // test input against each of the signature sets + for (HashSet<Signature> referenceSet : mSignatureSets) { + if (referenceSet.equals(inputSet)) { + return true; + } + } + return false; + } + + private final PackageMonitor mPackageMonitor = new PackageMonitor() { + /** + * Called when package has been reinstalled + */ + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + // package updated, make sure to rebind + unbind(); + } + // check the updated package in case it is better + bindBestPackage(packageName); + } + + @Override + public void onPackageAdded(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + // package updated, make sure to rebind + unbind(); + } + // check the new package is case it is better + bindBestPackage(packageName); + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + if (packageName.equals(mPackageName)) { + unbind(); + // the currently bound package was removed, + // need to search for a new package + bindBestPackage(null); + } + } + }; + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + synchronized (mLock) { + String packageName = name.getPackageName(); + if (packageName.equals(mPackageName)) { + if (D) Log.d(mTag, packageName + " connected"); + mBinder = binder; + if (mHandler !=null && mNewServiceWork != null) { + mHandler.post(mNewServiceWork); + } + } else { + Log.w(mTag, "unexpected onServiceConnected: " + packageName); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + String packageName = name.getPackageName(); + if (D) Log.d(mTag, packageName + " disconnected"); + + if (packageName.equals(mPackageName)) { + mBinder = null; + } + } + } + + public String getBestPackageName() { + synchronized (mLock) { + return mPackageName; + } + } + + public int getBestVersion() { + synchronized (mLock) { + return mVersion; + } + } + + public IBinder getBinder() { + synchronized (mLock) { + return mBinder; + } + } +} diff --git a/services/java/com/android/server/ShutdownActivity.java b/services/java/com/android/server/ShutdownActivity.java index d85abe6..a4341b7 100644 --- a/services/java/com/android/server/ShutdownActivity.java +++ b/services/java/com/android/server/ShutdownActivity.java @@ -17,13 +17,12 @@ package com.android.server; import android.app.Activity; -import android.content.BroadcastReceiver; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.util.Slog; -import com.android.server.pm.ShutdownThread; +import com.android.server.power.ShutdownThread; public class ShutdownActivity extends Activity { diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index 78c0c12..9f53fad 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -27,7 +27,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; -import android.view.View; import com.android.internal.statusbar.IStatusBar; import com.android.internal.statusbar.IStatusBarService; diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java index a7a583c..8cf273d 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -24,8 +24,10 @@ import android.app.backup.FullBackup; import android.app.backup.FullBackupDataOutput; import android.app.backup.WallpaperBackupHelper; import android.content.Context; +import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.ServiceManager; +import android.os.UserHandle; import android.util.Slog; @@ -45,11 +47,13 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String WALLPAPER_INFO_FILENAME = "wallpaper_info.xml"; // TODO: Will need to change if backing up non-primary user's wallpaper - private static final String WALLPAPER_IMAGE_DIR = "/data/system/users/0"; + private static final String WALLPAPER_IMAGE_DIR = + Environment.getUserSystemDirectory(UserHandle.USER_OWNER).getAbsolutePath(); private static final String WALLPAPER_IMAGE = WallpaperBackupHelper.WALLPAPER_IMAGE; // TODO: Will need to change if backing up non-primary user's wallpaper - private static final String WALLPAPER_INFO_DIR = "/data/system/users/0"; + private static final String WALLPAPER_INFO_DIR = + Environment.getUserSystemDirectory(UserHandle.USER_OWNER).getAbsolutePath(); private static final String WALLPAPER_INFO = WallpaperBackupHelper.WALLPAPER_INFO; // Use old keys to keep legacy data compatibility and avoid writing two wallpapers private static final String WALLPAPER_IMAGE_KEY = WallpaperBackupHelper.WALLPAPER_IMAGE_KEY; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e55e7fe..90783b7 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -28,6 +28,8 @@ import android.content.pm.IPackageManager; import android.content.res.Configuration; import android.media.AudioService; import android.net.wifi.p2p.WifiP2pService; +import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.os.SchedulingPolicyService; @@ -35,9 +37,6 @@ import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; -import android.provider.Settings; -import android.server.BluetoothA2dpService; -import android.server.BluetoothService; import android.server.search.SearchManagerService; import android.service.dreams.DreamManagerService; import android.util.DisplayMetrics; @@ -51,11 +50,16 @@ import com.android.internal.os.SamplingProfilerIntegration; import com.android.internal.widget.LockSettingsService; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.am.ActivityManagerService; +import com.android.server.am.BatteryStatsService; +import com.android.server.display.DisplayManagerService; import com.android.server.input.InputManagerService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.NetworkStatsService; +import com.android.server.pm.Installer; import com.android.server.pm.PackageManagerService; -import com.android.server.pm.ShutdownThread; +import com.android.server.pm.UserManagerService; +import com.android.server.power.PowerManagerService; +import com.android.server.power.ShutdownThread; import com.android.server.usb.UsbService; import com.android.server.wm.WindowManagerService; @@ -114,13 +118,16 @@ class ServerThread extends Thread { : Integer.parseInt(factoryTestStr); final boolean headless = "1".equals(SystemProperties.get("ro.config.headless", "0")); + Installer installer = null; AccountManagerService accountManager = null; ContentService contentService = null; LightsService lights = null; PowerManagerService power = null; + DisplayManagerService display = null; BatteryService battery = null; VibratorService vibrator = null; AlarmManagerService alarm = null; + MountService mountService = null; NetworkManagementService networkManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; @@ -131,11 +138,11 @@ class ServerThread extends Thread { IPackageManager pm = null; Context context = null; WindowManagerService wm = null; - BluetoothService bluetooth = null; - BluetoothA2dpService bluetoothA2dp = null; + BluetoothManagerService bluetooth = null; DockObserver dock = null; UsbService usb = null; SerialService serial = null; + TwilightService twilight = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; @@ -143,8 +150,60 @@ class ServerThread extends Thread { CommonTimeManagementService commonTimeMgmtService = null; InputManagerService inputManager = null; + // Create a shared handler thread for UI within the system server. + // This thread is used by at least the following components: + // - WindowManagerPolicy + // - KeyguardViewManager + // - DisplayManagerService + HandlerThread uiHandlerThread = new HandlerThread("UI"); + uiHandlerThread.start(); + Handler uiHandler = new Handler(uiHandlerThread.getLooper()); + uiHandler.post(new Runnable() { + @Override + public void run() { + //Looper.myLooper().setMessageLogging(new LogPrinter( + // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_FOREGROUND); + android.os.Process.setCanSelfBackground(false); + + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for UI Looper"); + } + } + }); + + // Create a handler thread just for the window manager to enjoy. + HandlerThread wmHandlerThread = new HandlerThread("WindowManager"); + wmHandlerThread.start(); + Handler wmHandler = new Handler(wmHandlerThread.getLooper()); + wmHandler.post(new Runnable() { + @Override + public void run() { + //Looper.myLooper().setMessageLogging(new LogPrinter( + // android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM)); + android.os.Process.setThreadPriority( + android.os.Process.THREAD_PRIORITY_DISPLAY); + android.os.Process.setCanSelfBackground(false); + + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i(TAG, "Enabled StrictMode logging for UI Looper"); + } + } + }); + // Critical services... + boolean onlyCore = false; try { + // Wait for installd to finished starting up so that it has a chance to + // create critical directories such as /data/user with the appropriate + // permissions. We need this to complete before we initialize other services. + Slog.i(TAG, "Waiting for installd to be ready."); + installer = new Installer(); + installer.ping(); + Slog.i(TAG, "Entropy Mixer"); ServiceManager.addService("entropy", new EntropyMixer()); @@ -155,6 +214,10 @@ class ServerThread extends Thread { Slog.i(TAG, "Activity Manager"); context = ActivityManagerService.main(factoryTest); + Slog.i(TAG, "Display Manager"); + display = new DisplayManagerService(context, wmHandler, uiHandler); + ServiceManager.addService(Context.DISPLAY_SERVICE, display, true); + Slog.i(TAG, "Telephony Registry"); ServiceManager.addService("telephony.registry", new TelephonyRegistry(context)); @@ -164,10 +227,14 @@ class ServerThread extends Thread { AttributeCache.init(context); + if (!display.waitForDefaultDisplay()) { + reportWtf("Timeout waiting for default display to be initialized.", + new Throwable()); + } + Slog.i(TAG, "Package Manager"); // Only run "core" apps if we're encrypting the device. String cryptState = SystemProperties.get("vold.decrypt"); - boolean onlyCore = false; if (ENCRYPTING_STATE.equals(cryptState)) { Slog.w(TAG, "Detected encryption in progress - only parsing core apps"); onlyCore = true; @@ -176,7 +243,7 @@ class ServerThread extends Thread { onlyCore = true; } - pm = PackageManagerService.main(context, + pm = PackageManagerService.main(context, installer, factoryTest != SystemServer.FACTORY_TEST_OFF, onlyCore); boolean firstBoot = false; @@ -186,6 +253,11 @@ class ServerThread extends Thread { } ActivityManagerService.setSystemProcess(); + + Slog.i(TAG, "User Service"); + ServiceManager.addService(Context.USER_SERVICE, + UserManagerService.getInstance()); + mContentResolver = context.getContentResolver(); @@ -218,7 +290,8 @@ class ServerThread extends Thread { // only initialize the power service after we have started the // lights service, content providers and the battery service. - power.init(context, lights, ActivityManagerService.self(), battery); + power.init(context, lights, ActivityManagerService.self(), battery, + BatteryStatsService.getService(), display); Slog.i(TAG, "Alarm Manager"); alarm = new AlarmManagerService(context); @@ -229,7 +302,8 @@ class ServerThread extends Thread { ActivityManagerService.self()); Slog.i(TAG, "Window Manager"); - wm = WindowManagerService.main(context, power, + wm = WindowManagerService.main(context, power, display, + uiHandler, wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL, !firstBoot, onlyCore); ServiceManager.addService(Context.WINDOW_SERVICE, wm); @@ -237,6 +311,8 @@ class ServerThread extends Thread { ServiceManager.addService(Context.INPUT_SERVICE, inputManager); ActivityManagerService.self().setWindowManager(wm); + display.setWindowManager(wm); + display.setInputManager(inputManager); // Skip Bluetooth if we have an emulator kernel // TODO: Use a more reliable check to see if this product should @@ -246,23 +322,9 @@ class ServerThread extends Thread { } else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) { Slog.i(TAG, "No Bluetooth Service (factory test)"); } else { - Slog.i(TAG, "Bluetooth Service"); - bluetooth = new BluetoothService(context); - ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth); - bluetooth.initAfterRegistration(); - - if (!"0".equals(SystemProperties.get("system_init.startaudioservice"))) { - bluetoothA2dp = new BluetoothA2dpService(context, bluetooth); - ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE, - bluetoothA2dp); - bluetooth.initAfterA2dpRegistration(); - } - - int bluetoothOn = Settings.Secure.getInt(mContentResolver, - Settings.Secure.BLUETOOTH_ON, 0); - if (bluetoothOn != 0) { - bluetooth.enable(); - } + Slog.i(TAG, "Bluetooth Manager Service"); + bluetooth = new BluetoothManagerService(context); + ServiceManager.addService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, bluetooth); } } catch (RuntimeException e) { @@ -322,7 +384,6 @@ class ServerThread extends Thread { } if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { - MountService mountService = null; if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) { try { /* @@ -555,7 +616,7 @@ class ServerThread extends Thread { try { Slog.i(TAG, "Dock Observer"); // Listen for dock station changes - dock = new DockObserver(context, power); + dock = new DockObserver(context); } catch (Throwable e) { reportWtf("starting DockObserver", e); } @@ -587,9 +648,16 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Twilight Service"); + twilight = new TwilightService(context); + } catch (Throwable e) { + reportWtf("starting TwilightService", e); + } + + try { Slog.i(TAG, "UI Mode Manager Service"); // Listen for UI mode changes - uiMode = new UiModeManagerService(context); + uiMode = new UiModeManagerService(context, twilight); } catch (Throwable e) { reportWtf("starting UiModeManagerService", e); } @@ -710,6 +778,12 @@ class ServerThread extends Thread { } try { + lockSettings.systemReady(); + } catch (Throwable e) { + reportWtf("making Lock Settings Service ready", e); + } + + try { wm.systemReady(); } catch (Throwable e) { reportWtf("making Window Manager Service ready", e); @@ -728,20 +802,27 @@ class ServerThread extends Thread { w.getDefaultDisplay().getMetrics(metrics); context.getResources().updateConfiguration(config, metrics); - power.systemReady(); + try { + power.systemReady(twilight); + } catch (Throwable e) { + reportWtf("making Power Manager Service ready", e); + } + try { pm.systemReady(); } catch (Throwable e) { reportWtf("making Package Manager Service ready", e); } + try { - lockSettings.systemReady(); + display.systemReady(safeMode, onlyCore); } catch (Throwable e) { - reportWtf("making Lock Settings Service ready", e); + reportWtf("making Display Manager Service ready", e); } // These are needed to propagate to the runnable below. final Context contextF = context; + final MountService mountServiceF = mountService; final BatteryService batteryF = battery; final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; @@ -750,6 +831,7 @@ class ServerThread extends Thread { final DockObserver dockF = dock; final UsbService usbF = usb; final ThrottleService throttleF = throttle; + final TwilightService twilightF = twilight; final UiModeManagerService uiModeF = uiMode; final AppWidgetService appWidgetF = appWidget; final WallpaperManagerService wallpaperF = wallpaper; @@ -763,7 +845,6 @@ class ServerThread extends Thread { final StatusBarManagerService statusBarF = statusBar; final DreamManagerService dreamyF = dreamy; final InputManagerService inputManagerF = inputManager; - final BluetoothService bluetoothF = bluetooth; // 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 @@ -776,6 +857,11 @@ class ServerThread extends Thread { if (!headless) startSystemUi(contextF); try { + if (mountServiceF != null) mountServiceF.systemReady(); + } catch (Throwable e) { + reportWtf("making Mount Service ready", e); + } + try { if (batteryF != null) batteryF.systemReady(); } catch (Throwable e) { reportWtf("making Battery Service ready", e); @@ -811,6 +897,11 @@ class ServerThread extends Thread { reportWtf("making USB Service ready", e); } try { + if (twilightF != null) twilightF.systemReady(); + } catch (Throwable e) { + reportWtf("makin Twilight Service ready", e); + } + try { if (uiModeF != null) uiModeF.systemReady(); } catch (Throwable e) { reportWtf("making UI Mode Service ready", e); @@ -876,7 +967,8 @@ class ServerThread extends Thread { reportWtf("making DreamManagerService ready", e); } try { - if (inputManagerF != null) inputManagerF.systemReady(bluetoothF); + // TODO(BT) Pass parameter to input manager + if (inputManagerF != null) inputManagerF.systemReady(); } catch (Throwable e) { reportWtf("making InputManagerService ready", e); } diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index c23a1d9..8361477 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -25,6 +25,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.telephony.CellLocation; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; @@ -35,6 +36,7 @@ import android.text.TextUtils; import android.util.Slog; import java.util.ArrayList; +import java.util.List; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.NetworkInterface; @@ -109,7 +111,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN; - private CellInfo mCellInfo = null; + private List<CellInfo> mCellInfo = null; static final int PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | @@ -242,7 +244,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } if ((events & PhoneStateListener.LISTEN_CELL_INFO) != 0) { try { - r.callback.onCellInfoChanged(new CellInfo(mCellInfo)); + r.callback.onCellInfoChanged(mCellInfo); } catch (RemoteException ex) { remove(r.binder); } @@ -336,7 +338,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { broadcastSignalStrengthChanged(signalStrength); } - public void notifyCellInfo(CellInfo cellInfo) { + public void notifyCellInfo(List<CellInfo> cellInfo) { if (!checkNotifyPermission("notifyCellInfo()")) { return; } @@ -346,7 +348,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { for (Record r : mRecords) { if ((r.events & PhoneStateListener.LISTEN_CELL_INFO) != 0) { try { - r.callback.onCellInfoChanged(new CellInfo(cellInfo)); + r.callback.onCellInfoChanged(cellInfo); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -587,7 +589,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { Bundle data = new Bundle(); state.fillInNotifierBundle(data); intent.putExtras(data); - mContext.sendStickyBroadcast(intent); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } private void broadcastSignalStrengthChanged(SignalStrength signalStrength) { @@ -605,7 +607,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { Bundle data = new Bundle(); signalStrength.fillInNotifierBundle(data); intent.putExtras(data); - mContext.sendStickyBroadcast(intent); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } private void broadcastCallStateChanged(int state, String incomingNumber) { @@ -628,7 +630,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!TextUtils.isEmpty(incomingNumber)) { intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber); } - mContext.sendBroadcast(intent, android.Manifest.permission.READ_PHONE_STATE); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + android.Manifest.permission.READ_PHONE_STATE); } private void broadcastDataConnectionStateChanged(int state, @@ -661,14 +664,14 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { intent.putExtra(PhoneConstants.DATA_APN_KEY, apn); intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType); - mContext.sendStickyBroadcast(intent); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } private void broadcastDataConnectionFailed(String reason, String apnType) { Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); intent.putExtra(PhoneConstants.FAILURE_REASON_KEY, reason); intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType); - mContext.sendStickyBroadcast(intent); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } private boolean checkNotifyPermission(String method) { diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index f35a5af..49f39fe 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -44,6 +44,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -195,6 +196,7 @@ public class ThrottleService extends IThrottleManager.Stub { public void interfaceRemoved(String iface) {} public void limitReached(String limitName, String iface) {} + public void interfaceClassDataActivityChanged(String label, boolean active) {} } @@ -367,7 +369,7 @@ public class ThrottleService extends IThrottleManager.Stub { } if (mPollStickyBroadcast != null) { - mContext.removeStickyBroadcast(mPollStickyBroadcast); + mContext.removeStickyBroadcastAsUser(mPollStickyBroadcast, UserHandle.ALL); } } @@ -493,7 +495,7 @@ public class ThrottleService extends IThrottleManager.Stub { onPollAlarm(); Intent broadcast = new Intent(ThrottleManager.POLICY_CHANGED_ACTION); - mContext.sendBroadcast(broadcast); + mContext.sendBroadcastAsUser(broadcast, UserHandle.ALL); } private void onPollAlarm() { @@ -562,7 +564,7 @@ public class ThrottleService extends IThrottleManager.Stub { broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_WRITE, periodTx); broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, getPeriodStartTime(mIface)); broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, getResetTime(mIface)); - mContext.sendStickyBroadcast(broadcast); + mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL); mPollStickyBroadcast = broadcast; mAlarmManager.cancel(mPendingPollIntent); @@ -620,7 +622,7 @@ public class ThrottleService extends IThrottleManager.Stub { Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION); broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue.get()); - mContext.sendStickyBroadcast(broadcast); + mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL); } // else already up! } else { @@ -698,7 +700,7 @@ public class ThrottleService extends IThrottleManager.Stub { } Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION); broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, -1); - mContext.sendStickyBroadcast(broadcast); + mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL); mNotificationManager.cancel(R.drawable.stat_sys_throttled); mWarningNotificationSent = false; } diff --git a/services/java/com/android/server/TwilightService.java b/services/java/com/android/server/TwilightService.java new file mode 100644 index 0000000..a7bce54 --- /dev/null +++ b/services/java/com/android/server/TwilightService.java @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.text.format.DateUtils; +import android.text.format.Time; +import android.util.Slog; + +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; + +import libcore.util.Objects; + +/** + * Figures out whether it's twilight time based on the user's location. + * + * Used by the UI mode manager and other components to adjust night mode + * effects based on sunrise and sunset. + */ +public final class TwilightService { + private static final String TAG = "TwilightService"; + + private static final boolean DEBUG = false; + + private static final String ACTION_UPDATE_TWILIGHT_STATE = + "com.android.server.action.UPDATE_TWILIGHT_STATE"; + + private final Context mContext; + private final AlarmManager mAlarmManager; + private final LocationManager mLocationManager; + private final LocationHandler mLocationHandler; + + private final Object mLock = new Object(); + + private final ArrayList<TwilightListenerRecord> mListeners = + new ArrayList<TwilightListenerRecord>(); + + private boolean mSystemReady; + + private TwilightState mTwilightState; + + public TwilightService(Context context) { + mContext = context; + + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + mLocationManager = (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE); + mLocationHandler = new LocationHandler(); + } + + void systemReady() { + synchronized (mLock) { + mSystemReady = true; + + IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + filter.addAction(ACTION_UPDATE_TWILIGHT_STATE); + mContext.registerReceiver(mUpdateLocationReceiver, filter); + + if (!mListeners.isEmpty()) { + mLocationHandler.enableLocationUpdates(); + } + } + } + + /** + * Gets the current twilight state. + * + * @return The current twilight state, or null if no information is available. + */ + public TwilightState getCurrentState() { + synchronized (mLock) { + return mTwilightState; + } + } + + /** + * Listens for twilight time. + * + * @param listener The listener. + * @param handler The handler on which to post calls into the listener. + */ + public void registerListener(TwilightListener listener, Handler handler) { + synchronized (mLock) { + mListeners.add(new TwilightListenerRecord(listener, handler)); + + if (mSystemReady && mListeners.size() == 1) { + mLocationHandler.enableLocationUpdates(); + } + } + } + + private void setTwilightState(TwilightState state) { + synchronized (mLock) { + if (!Objects.equal(mTwilightState, state)) { + if (DEBUG) { + Slog.d(TAG, "Twilight state changed: " + state); + } + + mTwilightState = state; + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).post(); + } + } + } + } + + // The user has moved if the accuracy circles of the two locations don't overlap. + private static boolean hasMoved(Location from, Location to) { + if (to == null) { + return false; + } + + if (from == null) { + return true; + } + + // if new location is older than the current one, the device hasn't moved. + if (to.getElapsedRealtimeNano() < from.getElapsedRealtimeNano()) { + return false; + } + + // Get the distance between the two points. + float distance = from.distanceTo(to); + + // Get the total accuracy radius for both locations. + float totalAccuracy = from.getAccuracy() + to.getAccuracy(); + + // If the distance is greater than the combined accuracy of the two + // points then they can't overlap and hence the user has moved. + return distance >= totalAccuracy; + } + + /** + * Describes whether it is day or night. + * This object is immutable. + */ + public static final class TwilightState { + private final boolean mIsNight; + private final long mYesterdaySunset; + private final long mTodaySunrise; + private final long mTodaySunset; + private final long mTomorrowSunrise; + + TwilightState(boolean isNight, + long yesterdaySunset, + long todaySunrise, long todaySunset, + long tomorrowSunrise) { + mIsNight = isNight; + mYesterdaySunset = yesterdaySunset; + mTodaySunrise = todaySunrise; + mTodaySunset = todaySunset; + mTomorrowSunrise = tomorrowSunrise; + } + + /** + * Returns true if it is currently night time. + */ + public boolean isNight() { + return mIsNight; + } + + /** + * Returns the time of yesterday's sunset in the System.currentTimeMillis() timebase, + * or -1 if the sun never sets. + */ + public long getYesterdaySunset() { + return mYesterdaySunset; + } + + /** + * Returns the time of today's sunrise in the System.currentTimeMillis() timebase, + * or -1 if the sun never rises. + */ + public long getTodaySunrise() { + return mTodaySunrise; + } + + /** + * Returns the time of today's sunset in the System.currentTimeMillis() timebase, + * or -1 if the sun never sets. + */ + public long getTodaySunset() { + return mTodaySunset; + } + + /** + * Returns the time of tomorrow's sunrise in the System.currentTimeMillis() timebase, + * or -1 if the sun never rises. + */ + public long getTomorrowSunrise() { + return mTomorrowSunrise; + } + + @Override + public boolean equals(Object o) { + return o instanceof TwilightState && equals((TwilightState)o); + } + + public boolean equals(TwilightState other) { + return other != null + && mIsNight == other.mIsNight + && mYesterdaySunset == other.mYesterdaySunset + && mTodaySunrise == other.mTodaySunrise + && mTodaySunset == other.mTodaySunset + && mTomorrowSunrise == other.mTomorrowSunrise; + } + + @Override + public int hashCode() { + return 0; // don't care + } + + @Override + public String toString() { + DateFormat f = DateFormat.getDateTimeInstance(); + return "{TwilightState: isNight=" + mIsNight + + ", mYesterdaySunset=" + f.format(new Date(mYesterdaySunset)) + + ", mTodaySunrise=" + f.format(new Date(mTodaySunrise)) + + ", mTodaySunset=" + f.format(new Date(mTodaySunset)) + + ", mTomorrowSunrise=" + f.format(new Date(mTomorrowSunrise)) + + "}"; + } + } + + /** + * Listener for changes in twilight state. + */ + public interface TwilightListener { + public void onTwilightStateChanged(); + } + + private static final class TwilightListenerRecord implements Runnable { + private final TwilightListener mListener; + private final Handler mHandler; + + public TwilightListenerRecord(TwilightListener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + public void post() { + mHandler.post(this); + } + + @Override + public void run() { + mListener.onTwilightStateChanged(); + } + } + + private final class LocationHandler extends Handler { + private static final int MSG_ENABLE_LOCATION_UPDATES = 1; + private static final int MSG_GET_NEW_LOCATION_UPDATE = 2; + private static final int MSG_PROCESS_NEW_LOCATION = 3; + private static final int MSG_DO_TWILIGHT_UPDATE = 4; + + private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS; + private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS; + private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20; + private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000; + private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = + 15 * DateUtils.MINUTE_IN_MILLIS; + private static final double FACTOR_GMT_OFFSET_LONGITUDE = + 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS; + + private boolean mPassiveListenerEnabled; + private boolean mNetworkListenerEnabled; + private boolean mDidFirstInit; + private long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS; + private long mLastUpdateInterval; + private Location mLocation; + private final TwilightCalculator mTwilightCalculator = new TwilightCalculator(); + + public void processNewLocation(Location location) { + Message msg = obtainMessage(MSG_PROCESS_NEW_LOCATION, location); + sendMessage(msg); + } + + public void enableLocationUpdates() { + sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); + } + + public void requestLocationUpdate() { + sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); + } + + public void requestTwilightUpdate() { + sendEmptyMessage(MSG_DO_TWILIGHT_UPDATE); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PROCESS_NEW_LOCATION: { + final Location location = (Location)msg.obj; + final boolean hasMoved = hasMoved(mLocation, location); + final boolean hasBetterAccuracy = mLocation == null + || location.getAccuracy() < mLocation.getAccuracy(); + if (DEBUG) { + Slog.d(TAG, "Processing new location: " + location + + ", hasMoved=" + hasMoved + + ", hasBetterAccuracy=" + hasBetterAccuracy); + } + if (hasMoved || hasBetterAccuracy) { + setLocation(location); + } + break; + } + + case MSG_GET_NEW_LOCATION_UPDATE: + if (!mNetworkListenerEnabled) { + // Don't do anything -- we are still trying to get a + // location. + return; + } + if ((mLastNetworkRegisterTime + MIN_LOCATION_UPDATE_MS) >= + SystemClock.elapsedRealtime()) { + // Don't do anything -- it hasn't been long enough + // since we last requested an update. + return; + } + + // Unregister the current location monitor, so we can + // register a new one for it to get an immediate update. + mNetworkListenerEnabled = false; + mLocationManager.removeUpdates(mEmptyLocationListener); + + // Fall through to re-register listener. + case MSG_ENABLE_LOCATION_UPDATES: + // enable network provider to receive at least location updates for a given + // distance. + boolean networkLocationEnabled; + try { + networkLocationEnabled = + mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + } catch (Exception e) { + // we may get IllegalArgumentException if network location provider + // does not exist or is not yet installed. + networkLocationEnabled = false; + } + if (!mNetworkListenerEnabled && networkLocationEnabled) { + mNetworkListenerEnabled = true; + mLastNetworkRegisterTime = SystemClock.elapsedRealtime(); + mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, + LOCATION_UPDATE_MS, 0, mEmptyLocationListener); + + if (!mDidFirstInit) { + mDidFirstInit = true; + if (mLocation == null) { + retrieveLocation(); + } + } + } + + // enable passive provider to receive updates from location fixes (gps + // and network). + boolean passiveLocationEnabled; + try { + passiveLocationEnabled = + mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); + } catch (Exception e) { + // we may get IllegalArgumentException if passive location provider + // does not exist or is not yet installed. + passiveLocationEnabled = false; + } + + if (!mPassiveListenerEnabled && passiveLocationEnabled) { + mPassiveListenerEnabled = true; + mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, + 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener); + } + + if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) { + mLastUpdateInterval *= 1.5; + if (mLastUpdateInterval == 0) { + mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN; + } else if (mLastUpdateInterval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) { + mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX; + } + sendEmptyMessageDelayed(MSG_ENABLE_LOCATION_UPDATES, mLastUpdateInterval); + } + break; + + case MSG_DO_TWILIGHT_UPDATE: + updateTwilightState(); + break; + } + } + + private void retrieveLocation() { + Location location = null; + final Iterator<String> providers = + mLocationManager.getProviders(new Criteria(), true).iterator(); + while (providers.hasNext()) { + final Location lastKnownLocation = + mLocationManager.getLastKnownLocation(providers.next()); + // pick the most recent location + if (location == null || (lastKnownLocation != null && + location.getElapsedRealtimeNano() < + lastKnownLocation.getElapsedRealtimeNano())) { + location = lastKnownLocation; + } + } + + // In the case there is no location available (e.g. GPS fix or network location + // is not available yet), the longitude of the location is estimated using the timezone, + // latitude and accuracy are set to get a good average. + if (location == null) { + Time currentTime = new Time(); + currentTime.set(System.currentTimeMillis()); + double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * + (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0)); + location = new Location("fake"); + location.setLongitude(lngOffset); + location.setLatitude(0); + location.setAccuracy(417000.0f); + location.setTime(System.currentTimeMillis()); + location.setElapsedRealtimeNano(SystemClock.elapsedRealtimeNano()); + + if (DEBUG) { + Slog.d(TAG, "Estimated location from timezone: " + location); + } + } + + setLocation(location); + } + + private void setLocation(Location location) { + mLocation = location; + updateTwilightState(); + } + + private void updateTwilightState() { + if (mLocation == null) { + setTwilightState(null); + return; + } + + final long now = System.currentTimeMillis(); + + // calculate yesterday's twilight + mTwilightCalculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS, + mLocation.getLatitude(), mLocation.getLongitude()); + final long yesterdaySunset = mTwilightCalculator.mSunset; + + // calculate today's twilight + mTwilightCalculator.calculateTwilight(now, + mLocation.getLatitude(), mLocation.getLongitude()); + final boolean isNight = (mTwilightCalculator.mState == TwilightCalculator.NIGHT); + final long todaySunrise = mTwilightCalculator.mSunrise; + final long todaySunset = mTwilightCalculator.mSunset; + + // calculate tomorrow's twilight + mTwilightCalculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS, + mLocation.getLatitude(), mLocation.getLongitude()); + final long tomorrowSunrise = mTwilightCalculator.mSunrise; + + // set twilight state + TwilightState state = new TwilightState(isNight, yesterdaySunset, + todaySunrise, todaySunset, tomorrowSunrise); + if (DEBUG) { + Slog.d(TAG, "Updating twilight state: " + state); + } + setTwilightState(state); + + // schedule next update + long nextUpdate = 0; + if (todaySunrise == -1 || todaySunset == -1) { + // In the case the day or night never ends the update is scheduled 12 hours later. + nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS; + } else { + // add some extra time to be on the safe side. + nextUpdate += DateUtils.MINUTE_IN_MILLIS; + + if (now > todaySunset) { + nextUpdate += tomorrowSunrise; + } else if (now > todaySunrise) { + nextUpdate += todaySunset; + } else { + nextUpdate += todaySunrise; + } + } + + if (DEBUG) { + Slog.d(TAG, "Next update in " + (nextUpdate - now) + " ms"); + } + + Intent updateIntent = new Intent(ACTION_UPDATE_TWILIGHT_STATE); + PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, updateIntent, 0); + mAlarmManager.cancel(pendingIntent); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent); + } + }; + + private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction()) + && !intent.getBooleanExtra("state", false)) { + // Airplane mode is now off! + mLocationHandler.requestLocationUpdate(); + return; + } + + // Time zone has changed or alarm expired. + mLocationHandler.requestTwilightUpdate(); + } + }; + + // A LocationListener to initialize the network location provider. The location updates + // are handled through the passive location provider. + private final LocationListener mEmptyLocationListener = new LocationListener() { + public void onLocationChanged(Location location) { + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + + private final LocationListener mLocationListener = new LocationListener() { + public void onLocationChanged(Location location) { + mLocationHandler.processNewLocation(location); + } + + public void onProviderDisabled(String provider) { + } + + public void onProviderEnabled(String provider) { + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; +} diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index d1f92a7..3e83baa 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -18,7 +18,6 @@ package com.android.server; import android.app.Activity; import android.app.ActivityManagerNative; -import android.app.AlarmManager; import android.app.IUiModeManager; import android.app.Notification; import android.app.NotificationManager; @@ -32,55 +31,34 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.location.Criteria; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; import android.os.BatteryManager; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; -import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; -import android.text.format.DateUtils; -import android.text.format.Time; import android.util.Slog; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.Iterator; import com.android.internal.R; import com.android.internal.app.DisableCarModeActivity; +import com.android.server.TwilightService.TwilightState; class UiModeManagerService extends IUiModeManager.Stub { private static final String TAG = UiModeManager.class.getSimpleName(); private static final boolean LOG = false; - private static final String KEY_LAST_UPDATE_INTERVAL = "LAST_UPDATE_INTERVAL"; - // Enable launching of applications when entering the dock. private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true; private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true; - private static final int MSG_UPDATE_TWILIGHT = 0; - private static final int MSG_ENABLE_LOCATION_UPDATES = 1; - private static final int MSG_GET_NEW_LOCATION_UPDATE = 2; - - private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS; - private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS; - private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20; - private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000; - private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX = 15 * DateUtils.MINUTE_IN_MILLIS; - private static final double FACTOR_GMT_OFFSET_LONGITUDE = 1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS; - - private static final String ACTION_UPDATE_NIGHT_MODE = "com.android.server.action.UPDATE_NIGHT_MODE"; - private final Context mContext; + private final TwilightService mTwilightService; + private final Handler mHandler = new Handler(); final Object mLock = new Object(); @@ -106,10 +84,6 @@ class UiModeManagerService extends IUiModeManager.Stub { private NotificationManager mNotificationManager; - private AlarmManager mAlarmManager; - - private LocationManager mLocationManager; - private Location mLocation; private StatusBarManager mStatusBarManager; private final PowerManager.WakeLock mWakeLock; @@ -191,7 +165,7 @@ class UiModeManagerService extends IUiModeManager.Stub { try { ActivityManagerNative.getDefault().startActivityWithConfig( null, homeIntent, null, null, null, 0, 0, - newConfig, null); + newConfig, null, UserHandle.USER_CURRENT); mHoldingConfiguration = false; } catch (RemoteException e) { Slog.w(TAG, e.getCause()); @@ -206,15 +180,6 @@ class UiModeManagerService extends IUiModeManager.Stub { } }; - private final BroadcastReceiver mTwilightUpdateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { - mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT); - } - } - }; - private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -236,113 +201,24 @@ class UiModeManagerService extends IUiModeManager.Stub { } }; - private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() { + private final TwilightService.TwilightListener mTwilightListener = + new TwilightService.TwilightListener() { @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) { - if (!intent.getBooleanExtra("state", false)) { - // Airplane mode is now off! - mHandler.sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); - } - } else { - // Time zone has changed! - mHandler.sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE); - } - } - }; - - // A LocationListener to initialize the network location provider. The location updates - // are handled through the passive location provider. - private final LocationListener mEmptyLocationListener = new LocationListener() { - public void onLocationChanged(Location location) { - } - - public void onProviderDisabled(String provider) { - } - - public void onProviderEnabled(String provider) { - } - - public void onStatusChanged(String provider, int status, Bundle extras) { - } - }; - - private final LocationListener mLocationListener = new LocationListener() { - - public void onLocationChanged(Location location) { - final boolean hasMoved = hasMoved(location); - final boolean hasBetterAccuracy = mLocation == null - || location.getAccuracy() < mLocation.getAccuracy(); - if (hasMoved || hasBetterAccuracy) { - synchronized (mLock) { - mLocation = location; - if (hasMoved && isDoingNightMode() - && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { - mHandler.sendEmptyMessage(MSG_UPDATE_TWILIGHT); - } - } - } - } - - public void onProviderDisabled(String provider) { - } - - public void onProviderEnabled(String provider) { - } - - public void onStatusChanged(String provider, int status, Bundle extras) { - } - - /* - * The user has moved if the accuracy circles of the two locations - * don't overlap. - */ - private boolean hasMoved(Location location) { - if (location == null) { - return false; - } - if (mLocation == null) { - return true; - } - - /* if new location is older than the current one, the devices hasn't - * moved. - */ - if (location.getTime() < mLocation.getTime()) { - return false; - } - - /* Get the distance between the two points */ - float distance = mLocation.distanceTo(location); - - /* Get the total accuracy radius for both locations */ - float totalAccuracy = mLocation.getAccuracy() + location.getAccuracy(); - - /* If the distance is greater than the combined accuracy of the two - * points then they can't overlap and hence the user has moved. - */ - return distance >= totalAccuracy; + public void onTwilightStateChanged() { + updateTwilight(); } }; - public UiModeManagerService(Context context) { + public UiModeManagerService(Context context, TwilightService twilight) { mContext = context; + mTwilightService = twilight; ServiceManager.addService(Context.UI_MODE_SERVICE, this); - mAlarmManager = - (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); - mLocationManager = - (LocationManager)mContext.getSystemService(Context.LOCATION_SERVICE); - mContext.registerReceiver(mTwilightUpdateReceiver, - new IntentFilter(ACTION_UPDATE_NIGHT_MODE)); mContext.registerReceiver(mDockModeReceiver, new IntentFilter(Intent.ACTION_DOCK_EVENT)); mContext.registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - mContext.registerReceiver(mUpdateLocationReceiver, filter); PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); @@ -360,6 +236,8 @@ class UiModeManagerService extends IUiModeManager.Stub { mNightMode = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO); + + mTwilightService.registerListener(mTwilightListener, mHandler); } public void disableCarMode(int flags) { @@ -419,8 +297,8 @@ class UiModeManagerService extends IUiModeManager.Stub { synchronized (mLock) { mSystemReady = true; mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; + updateComputedNightModeLocked(); updateLocked(0, 0); - mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); } } @@ -467,7 +345,7 @@ class UiModeManagerService extends IUiModeManager.Stub { } if (mCarModeEnabled) { if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) { - updateTwilightLocked(); + updateComputedNightModeLocked(); uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES : Configuration.UI_MODE_NIGHT_NO; } else { @@ -520,7 +398,7 @@ class UiModeManagerService extends IUiModeManager.Stub { adjustStatusBarCarModeLocked(); if (oldAction != null) { - mContext.sendBroadcast(new Intent(oldAction)); + mContext.sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL); } mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR; action = UiModeManager.ACTION_ENTER_CAR_MODE; @@ -528,7 +406,7 @@ class UiModeManagerService extends IUiModeManager.Stub { } else if (isDeskDockState(mDockState)) { if (!isDeskDockState(mLastBroadcastState)) { if (oldAction != null) { - mContext.sendBroadcast(new Intent(oldAction)); + mContext.sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL); } mLastBroadcastState = mDockState; action = UiModeManager.ACTION_ENTER_DESK_MODE; @@ -554,7 +432,7 @@ class UiModeManagerService extends IUiModeManager.Stub { Intent intent = new Intent(action); intent.putExtra("enableFlags", enableFlags); intent.putExtra("disableFlags", disableFlags); - mContext.sendOrderedBroadcast(intent, null, + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null, mResultReceiver, null, Activity.RESULT_OK, null, null); // Attempting to make this transition a little more clean, we are going // to hold off on doing a configuration change until we have finished @@ -585,7 +463,7 @@ class UiModeManagerService extends IUiModeManager.Stub { if (homeIntent != null) { try { - mContext.startActivity(homeIntent); + mContext.startActivityAsUser(homeIntent, UserHandle.CURRENT); } catch (ActivityNotFoundException e) { } } @@ -651,189 +529,20 @@ class UiModeManagerService extends IUiModeManager.Stub { } } - private final Handler mHandler = new Handler() { - - boolean mPassiveListenerEnabled; - boolean mNetworkListenerEnabled; - boolean mDidFirstInit; - long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS; - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_TWILIGHT: - synchronized (mLock) { - if (isDoingNightMode() && mLocation != null - && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { - updateTwilightLocked(); - updateLocked(0, 0); - } - } - break; - case MSG_GET_NEW_LOCATION_UPDATE: - if (!mNetworkListenerEnabled) { - // Don't do anything -- we are still trying to get a - // location. - return; - } - if ((mLastNetworkRegisterTime+MIN_LOCATION_UPDATE_MS) - >= SystemClock.elapsedRealtime()) { - // Don't do anything -- it hasn't been long enough - // since we last requested an update. - return; - } - - // Unregister the current location monitor, so we can - // register a new one for it to get an immediate update. - mNetworkListenerEnabled = false; - mLocationManager.removeUpdates(mEmptyLocationListener); - - // Fall through to re-register listener. - case MSG_ENABLE_LOCATION_UPDATES: - // enable network provider to receive at least location updates for a given - // distance. - boolean networkLocationEnabled; - try { - networkLocationEnabled = - mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); - } catch (Exception e) { - // we may get IllegalArgumentException if network location provider - // does not exist or is not yet installed. - networkLocationEnabled = false; - } - if (!mNetworkListenerEnabled && networkLocationEnabled) { - mNetworkListenerEnabled = true; - mLastNetworkRegisterTime = SystemClock.elapsedRealtime(); - mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, - LOCATION_UPDATE_MS, 0, mEmptyLocationListener); - - if (!mDidFirstInit) { - mDidFirstInit = true; - if (mLocation == null) { - retrieveLocation(); - } - synchronized (mLock) { - if (isDoingNightMode() && mLocation != null - && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { - updateTwilightLocked(); - updateLocked(0, 0); - } - } - } - } - // enable passive provider to receive updates from location fixes (gps - // and network). - boolean passiveLocationEnabled; - try { - passiveLocationEnabled = - mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); - } catch (Exception e) { - // we may get IllegalArgumentException if passive location provider - // does not exist or is not yet installed. - passiveLocationEnabled = false; - } - if (!mPassiveListenerEnabled && passiveLocationEnabled) { - mPassiveListenerEnabled = true; - mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, - 0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener); - } - if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) { - long interval = msg.getData().getLong(KEY_LAST_UPDATE_INTERVAL); - interval *= 1.5; - if (interval == 0) { - interval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN; - } else if (interval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) { - interval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX; - } - Bundle bundle = new Bundle(); - bundle.putLong(KEY_LAST_UPDATE_INTERVAL, interval); - Message newMsg = mHandler.obtainMessage(MSG_ENABLE_LOCATION_UPDATES); - newMsg.setData(bundle); - mHandler.sendMessageDelayed(newMsg, interval); - } - break; - } - } - - private void retrieveLocation() { - Location location = null; - final Iterator<String> providers = - mLocationManager.getProviders(new Criteria(), true).iterator(); - while (providers.hasNext()) { - final Location lastKnownLocation = - mLocationManager.getLastKnownLocation(providers.next()); - // pick the most recent location - if (location == null || (lastKnownLocation != null && - location.getTime() < lastKnownLocation.getTime())) { - location = lastKnownLocation; - } - } - // In the case there is no location available (e.g. GPS fix or network location - // is not available yet), the longitude of the location is estimated using the timezone, - // latitude and accuracy are set to get a good average. - if (location == null) { - Time currentTime = new Time(); - currentTime.set(System.currentTimeMillis()); - double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE * - (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0)); - location = new Location("fake"); - location.setLongitude(lngOffset); - location.setLatitude(0); - location.setAccuracy(417000.0f); - location.setTime(System.currentTimeMillis()); - } - synchronized (mLock) { - mLocation = location; + private void updateTwilight() { + synchronized (mLock) { + if (isDoingNightMode() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { + updateComputedNightModeLocked(); + updateLocked(0, 0); } } - }; - - void updateTwilightLocked() { - if (mLocation == null) { - return; - } - final long currentTime = System.currentTimeMillis(); - boolean nightMode; - // calculate current twilight - TwilightCalculator tw = new TwilightCalculator(); - tw.calculateTwilight(currentTime, - mLocation.getLatitude(), mLocation.getLongitude()); - if (tw.mState == TwilightCalculator.DAY) { - nightMode = false; - } else { - nightMode = true; - } - - // schedule next update - long nextUpdate = 0; - if (tw.mSunrise == -1 || tw.mSunset == -1) { - // In the case the day or night never ends the update is scheduled 12 hours later. - nextUpdate = currentTime + 12 * DateUtils.HOUR_IN_MILLIS; - } else { - final int mLastTwilightState = tw.mState; - // add some extra time to be on the save side. - nextUpdate += DateUtils.MINUTE_IN_MILLIS; - if (currentTime > tw.mSunset) { - // next update should be on the following day - tw.calculateTwilight(currentTime - + DateUtils.DAY_IN_MILLIS, mLocation.getLatitude(), - mLocation.getLongitude()); - } + } - if (mLastTwilightState == TwilightCalculator.NIGHT) { - nextUpdate += tw.mSunrise; - } else { - nextUpdate += tw.mSunset; - } + private void updateComputedNightModeLocked() { + TwilightState state = mTwilightService.getCurrentState(); + if (state != null) { + mComputedNightMode = state.isNight(); } - - Intent updateIntent = new Intent(ACTION_UPDATE_NIGHT_MODE); - PendingIntent pendingIntent = - PendingIntent.getBroadcast(mContext, 0, updateIntent, 0); - mAlarmManager.cancel(pendingIntent); - mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent); - - mComputedNightMode = nightMode; } @Override @@ -858,9 +567,8 @@ class UiModeManagerService extends IUiModeManager.Stub { pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode)); pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration); pw.print(" mSystemReady="); pw.println(mSystemReady); - if (mLocation != null) { - pw.print(" mLocation="); pw.println(mLocation); - } + pw.print(" mTwilightService.getCurrentState()="); + pw.println(mTwilightService.getCurrentState()); } } } diff --git a/services/java/com/android/server/UpdateLockService.java b/services/java/com/android/server/UpdateLockService.java index 1ffd196..0f778cd 100644 --- a/services/java/com/android/server/UpdateLockService.java +++ b/services/java/com/android/server/UpdateLockService.java @@ -27,6 +27,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.TokenWatcher; import android.os.UpdateLock; +import android.os.UserHandle; import android.util.Slog; import java.io.FileDescriptor; @@ -78,7 +79,7 @@ public class UpdateLockService extends IUpdateLock.Stub { .putExtra(UpdateLock.NOW_IS_CONVENIENT, state) .putExtra(UpdateLock.TIMESTAMP, System.currentTimeMillis()) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendStickyBroadcast(intent); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } finally { Binder.restoreCallingIdentity(oldIdent); } diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 8a08277..b027c1f 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -16,9 +16,11 @@ package com.android.server; -import static android.os.FileObserver.*; import static android.os.ParcelFileDescriptor.*; +import android.app.ActivityManagerNative; +import android.app.AppGlobals; +import android.app.IUserSwitchObserver; import android.app.IWallpaperManager; import android.app.IWallpaperManagerCallback; import android.app.PendingIntent; @@ -31,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -41,6 +44,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.FileUtils; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.FileObserver; import android.os.ParcelFileDescriptor; @@ -48,7 +52,7 @@ import android.os.RemoteCallbackList; import android.os.SELinux; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.UserId; +import android.os.UserHandle; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.IWallpaperService; @@ -77,7 +81,6 @@ import org.xmlpull.v1.XmlSerializer; import com.android.internal.content.PackageMonitor; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; -import com.android.server.am.ActivityManagerService; class WallpaperManagerService extends IWallpaperManager.Stub { static final String TAG = "WallpaperService"; @@ -90,8 +93,6 @@ class WallpaperManagerService extends IWallpaperManager.Stub { * restarting it vs. just reverting to the static wallpaper. */ static final long MIN_WALLPAPER_CRASH_TIME = 10000; - - static final File WALLPAPER_BASE_DIR = new File("/data/system/users"); static final String WALLPAPER = "wallpaper"; static final String WALLPAPER_INFO = "wallpaper_info.xml"; @@ -136,7 +137,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { mWallpaper.imageWallpaperPending = false; } bindWallpaperComponentLocked(mWallpaper.imageWallpaperComponent, true, - false, mWallpaper); + false, mWallpaper, null); saveSettingsLocked(mWallpaper); } } @@ -146,6 +147,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { final Context mContext; final IWindowManager mIWindowManager; + final IPackageManager mIPackageManager; final MyPackageMonitor mMonitor; WallpaperData mLastWallpaper; @@ -213,12 +215,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub { IWallpaperService mService; IWallpaperEngine mEngine; WallpaperData mWallpaper; + IRemoteCallback mReply; public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) { mInfo = info; mWallpaper = wallpaper; } - + + @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { if (mWallpaper.connection == this) { @@ -234,6 +238,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } + @Override public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { mService = null; @@ -245,16 +250,35 @@ class WallpaperManagerService extends IWallpaperManager.Stub { > SystemClock.uptimeMillis() && mWallpaper.userId == mCurrentUserId) { Slog.w(TAG, "Reverting to built-in wallpaper!"); - clearWallpaperLocked(true, mWallpaper.userId); + clearWallpaperLocked(true, mWallpaper.userId, null); } } } } + @Override public void attachEngine(IWallpaperEngine engine) { - mEngine = engine; + synchronized (mLock) { + mEngine = engine; + } } + @Override + public void engineShown(IWallpaperEngine engine) { + synchronized (mLock) { + if (mReply != null) { + long ident = Binder.clearCallingIdentity(); + try { + mReply.sendResult(null); + } catch (RemoteException e) { + Binder.restoreCallingIdentity(ident); + } + mReply = null; + } + } + } + + @Override public ParcelFileDescriptor setWallpaper(String name) { synchronized (mLock) { if (mWallpaper.connection == this) { @@ -278,9 +302,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub { clearWallpaperComponentLocked(wallpaper); // Do this only for the current user's wallpaper if (wallpaper.userId == mCurrentUserId - && !bindWallpaperComponentLocked(comp, false, false, wallpaper)) { + && !bindWallpaperComponentLocked(comp, false, false, + wallpaper, null)) { Slog.w(TAG, "Wallpaper no longer available; reverting to default"); - clearWallpaperLocked(false, wallpaper.userId); + clearWallpaperLocked(false, wallpaper.userId, null); } } } @@ -348,7 +373,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (doit) { Slog.w(TAG, "Wallpaper uninstalled, removing: " + wallpaper.wallpaperComponent); - clearWallpaperLocked(false, wallpaper.userId); + clearWallpaperLocked(false, wallpaper.userId, null); } } } @@ -368,7 +393,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } catch (NameNotFoundException e) { Slog.w(TAG, "Wallpaper component gone, removing: " + wallpaper.wallpaperComponent); - clearWallpaperLocked(false, wallpaper.userId); + clearWallpaperLocked(false, wallpaper.userId, null); } } if (wallpaper.nextWallpaperComponent != null @@ -389,14 +414,15 @@ class WallpaperManagerService extends IWallpaperManager.Stub { mContext = context; mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); + mIPackageManager = AppGlobals.getPackageManager(); mMonitor = new MyPackageMonitor(); mMonitor.register(context, null, true); - WALLPAPER_BASE_DIR.mkdirs(); - loadSettingsLocked(0); + getWallpaperDir(UserHandle.USER_OWNER).mkdirs(); + loadSettingsLocked(UserHandle.USER_OWNER); } private static File getWallpaperDir(int userId) { - return new File(WALLPAPER_BASE_DIR + "/" + userId); + return Environment.getUserSystemDirectory(userId); } @Override @@ -410,29 +436,44 @@ class WallpaperManagerService extends IWallpaperManager.Stub { public void systemReady() { if (DEBUG) Slog.v(TAG, "systemReady"); - WallpaperData wallpaper = mWallpaperMap.get(0); - switchWallpaper(wallpaper); + WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER); + switchWallpaper(wallpaper, null); wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper); wallpaper.wallpaperObserver.startWatching(); IntentFilter userFilter = new IntentFilter(); - userFilter.addAction(Intent.ACTION_USER_SWITCHED); userFilter.addAction(Intent.ACTION_USER_REMOVED); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - switchUser(intent.getIntExtra(Intent.EXTRA_USERID, 0)); - } else if (Intent.ACTION_USER_REMOVED.equals(action)) { - removeUser(intent.getIntExtra(Intent.EXTRA_USERID, 0)); + if (Intent.ACTION_USER_REMOVED.equals(action)) { + removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); } } }, userFilter); + try { + ActivityManagerNative.getDefault().registerUserSwitchObserver( + new IUserSwitchObserver.Stub() { + @Override + public void onUserSwitching(int newUserId, IRemoteCallback reply) { + switchUser(newUserId, reply); + } + + @Override + public void onUserSwitchComplete(int newUserId) throws RemoteException { + } + }); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } String getName() { - return mWallpaperMap.get(0).name; + synchronized (mLock) { + return mWallpaperMap.get(0).name; + } } void removeUser(int userId) { @@ -449,7 +490,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } - void switchUser(int userId) { + void switchUser(int userId, IRemoteCallback reply) { synchronized (mLock) { mCurrentUserId = userId; WallpaperData wallpaper = mWallpaperMap.get(userId); @@ -460,35 +501,35 @@ class WallpaperManagerService extends IWallpaperManager.Stub { wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper); wallpaper.wallpaperObserver.startWatching(); } - switchWallpaper(wallpaper); + switchWallpaper(wallpaper, reply); } } - void switchWallpaper(WallpaperData wallpaper) { + void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) { synchronized (mLock) { RuntimeException e = null; try { ComponentName cname = wallpaper.wallpaperComponent != null ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; - if (bindWallpaperComponentLocked(cname, true, false, wallpaper)) { + if (bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) { return; } } catch (RuntimeException e1) { e = e1; } Slog.w(TAG, "Failure starting previous wallpaper", e); - clearWallpaperLocked(false, wallpaper.userId); + clearWallpaperLocked(false, wallpaper.userId, reply); } } public void clearWallpaper() { if (DEBUG) Slog.v(TAG, "clearWallpaper"); synchronized (mLock) { - clearWallpaperLocked(false, UserId.getCallingUserId()); + clearWallpaperLocked(false, UserHandle.getCallingUserId(), null); } } - void clearWallpaperLocked(boolean defaultFailed, int userId) { + void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) { WallpaperData wallpaper = mWallpaperMap.get(userId); File f = new File(getWallpaperDir(userId), WALLPAPER); if (f.exists()) { @@ -501,7 +542,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (userId != mCurrentUserId) return; if (bindWallpaperComponentLocked(defaultFailed ? wallpaper.imageWallpaperComponent - : null, true, false, wallpaper)) { + : null, true, false, wallpaper, reply)) { return; } } catch (IllegalArgumentException e1) { @@ -516,21 +557,38 @@ class WallpaperManagerService extends IWallpaperManager.Stub { // wallpaper. Slog.e(TAG, "Default wallpaper component not found!", e); clearWallpaperComponentLocked(wallpaper); + if (reply != null) { + try { + reply.sendResult(null); + } catch (RemoteException e1) { + } + } } - public void setDimensionHints(int width, int height) throws RemoteException { - checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); - - int userId = UserId.getCallingUserId(); - WallpaperData wallpaper = mWallpaperMap.get(userId); - if (wallpaper == null) { - throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); - } - if (width <= 0 || height <= 0) { - throw new IllegalArgumentException("width and height must be > 0"); + public boolean hasNamedWallpaper(String name) { + synchronized (mLock) { + for (int i=0; i<mWallpaperMap.size(); i++) { + WallpaperData wd = mWallpaperMap.valueAt(i); + if (name.equals(wd.name)) { + return true; + } + } } + return false; + } + public void setDimensionHints(int width, int height) throws RemoteException { + checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); synchronized (mLock) { + int userId = UserHandle.getCallingUserId(); + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper == null) { + throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); + } + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("width and height must be > 0"); + } + if (width != wallpaper.width || height != wallpaper.height) { wallpaper.width = width; wallpaper.height = height; @@ -552,14 +610,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub { public int getWidthHint() throws RemoteException { synchronized (mLock) { - WallpaperData wallpaper = mWallpaperMap.get(UserId.getCallingUserId()); + WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId()); return wallpaper.width; } } public int getHeightHint() throws RemoteException { synchronized (mLock) { - WallpaperData wallpaper = mWallpaperMap.get(UserId.getCallingUserId()); + WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId()); return wallpaper.height; } } @@ -574,7 +632,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (callingUid == android.os.Process.SYSTEM_UID) { wallpaperUserId = mCurrentUserId; } else { - wallpaperUserId = UserId.getUserId(callingUid); + wallpaperUserId = UserHandle.getUserId(callingUid); } WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId); try { @@ -597,7 +655,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } public WallpaperInfo getWallpaperInfo() { - int userId = UserId.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); synchronized (mLock) { WallpaperData wallpaper = mWallpaperMap.get(userId); if (wallpaper.connection != null) { @@ -608,14 +666,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } public ParcelFileDescriptor setWallpaper(String name) { - if (DEBUG) Slog.v(TAG, "setWallpaper"); - int userId = UserId.getCallingUserId(); - WallpaperData wallpaper = mWallpaperMap.get(userId); - if (wallpaper == null) { - throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); - } checkPermission(android.Manifest.permission.SET_WALLPAPER); synchronized (mLock) { + if (DEBUG) Slog.v(TAG, "setWallpaper"); + int userId = UserHandle.getCallingUserId(); + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper == null) { + throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); + } final long ident = Binder.clearCallingIdentity(); try { ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper); @@ -655,18 +713,18 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } public void setWallpaperComponent(ComponentName name) { - if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name); - int userId = UserId.getCallingUserId(); - WallpaperData wallpaper = mWallpaperMap.get(userId); - if (wallpaper == null) { - throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); - } checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); synchronized (mLock) { + if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name); + int userId = UserHandle.getCallingUserId(); + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper == null) { + throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); + } final long ident = Binder.clearCallingIdentity(); try { wallpaper.imageWallpaperPending = false; - bindWallpaperComponentLocked(name, false, true, wallpaper); + bindWallpaperComponentLocked(name, false, true, wallpaper, null); } finally { Binder.restoreCallingIdentity(ident); } @@ -674,7 +732,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, - boolean fromUser, WallpaperData wallpaper) { + boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) { if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName); // Has the component changed? if (!force) { @@ -710,8 +768,9 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (DEBUG) Slog.v(TAG, "Using image wallpaper"); } } - ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, - PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS); + int serviceUserId = wallpaper.userId; + ServiceInfo si = mIPackageManager.getServiceInfo(componentName, + PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId); if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) { String msg = "Selected service does not require " + android.Manifest.permission.BIND_WALLPAPER @@ -728,8 +787,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub { Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); if (componentName != null && !componentName.equals(wallpaper.imageWallpaperComponent)) { // Make sure the selected service is actually a wallpaper service. - List<ResolveInfo> ris = mContext.getPackageManager() - .queryIntentServices(intent, PackageManager.GET_META_DATA); + List<ResolveInfo> ris = + mIPackageManager.queryIntentServices(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.GET_META_DATA, serviceUserId); for (int i=0; i<ris.size(); i++) { ServiceInfo rsi = ris.get(i).serviceInfo; if (rsi.name.equals(si.name) && @@ -767,18 +828,13 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (DEBUG) Slog.v(TAG, "Binding to:" + componentName); WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper); intent.setComponent(componentName); - int serviceUserId = wallpaper.userId; - // Because the image wallpaper is running in the system ui - if (componentName.equals(wallpaper.imageWallpaperComponent)) { - serviceUserId = 0; - } intent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.wallpaper_binding_label); - intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( + intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser( mContext, 0, Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER), mContext.getText(com.android.internal.R.string.chooser_wallpaper)), - 0)); + 0, null, new UserHandle(serviceUserId))); if (!mContext.bindService(intent, newConn, Context.BIND_AUTO_CREATE, serviceUserId)) { String msg = "Unable to bind service: " + componentName; @@ -794,6 +850,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { wallpaper.wallpaperComponent = componentName; wallpaper.connection = newConn; wallpaper.lastDiedTime = SystemClock.uptimeMillis(); + newConn.mReply = reply; try { if (wallpaper.userId == mCurrentUserId) { if (DEBUG) @@ -804,8 +861,8 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } catch (RemoteException e) { } - } catch (PackageManager.NameNotFoundException e) { - String msg = "Unknown component " + componentName; + } catch (RemoteException e) { + String msg = "Remote exception for " + componentName + "\n" + e; if (fromUser) { throw new IllegalArgumentException(msg); } @@ -817,6 +874,13 @@ class WallpaperManagerService extends IWallpaperManager.Stub { void detachWallpaperLocked(WallpaperData wallpaper) { if (wallpaper.connection != null) { + if (wallpaper.connection.mReply != null) { + try { + wallpaper.connection.mReply.sendResult(null); + } catch (RemoteException e) { + } + wallpaper.connection.mReply = null; + } if (wallpaper.connection.mEngine != null) { try { wallpaper.connection.mEngine.destroy(); @@ -849,7 +913,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } catch (RemoteException e) { Slog.w(TAG, "Failed attaching wallpaper; clearing", e); if (!wallpaper.wallpaperUpdating) { - bindWallpaperComponentLocked(null, false, false, wallpaper); + bindWallpaperComponentLocked(null, false, false, wallpaper, null); } } } @@ -867,7 +931,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } wallpaper.callbacks.finishBroadcast(); final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId)); } private void checkPermission(String permission) { @@ -878,7 +942,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } private static JournaledFile makeJournaledFile(int userId) { - final String base = getWallpaperDir(userId) + "/" + WALLPAPER_INFO; + final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath(); return new JournaledFile(new File(base), new File(base + ".tmp")); } @@ -1032,11 +1096,11 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (wallpaper.nextWallpaperComponent != null && !wallpaper.nextWallpaperComponent.equals(wallpaper.imageWallpaperComponent)) { if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false, - wallpaper)) { + wallpaper, null)) { // No such live wallpaper or other failure; fall back to the default // live wallpaper (since the profile being restored indicated that the // user had selected a live rather than static one). - bindWallpaperComponentLocked(null, false, false, wallpaper); + bindWallpaperComponentLocked(null, false, false, wallpaper, null); } success = true; } else { @@ -1052,7 +1116,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success); if (success) { bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false, - wallpaper); + wallpaper, null); } } } diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index c239382..9edfad6 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -17,6 +17,7 @@ package com.android.server; import com.android.server.am.ActivityManagerService; +import com.android.server.power.PowerManagerService; import android.app.AlarmManager; import android.app.PendingIntent; @@ -348,7 +349,7 @@ public class Watchdog extends Thread { } if (mMinScreenOff >= 0 && (mPower == null || - mPower.timeSinceScreenOn() < mMinScreenOff)) { + mPower.timeSinceScreenWasLastOn() < mMinScreenOff)) { return "screen"; } diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 1f03d17..5c38e63 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.app.ActivityManager; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; @@ -55,9 +56,11 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import java.util.ArrayList; @@ -110,10 +113,6 @@ public class WifiService extends IWifiManager.Stub { private int mScanLocksAcquired; private int mScanLocksReleased; - /* A mapping from UID to scan count */ - private HashMap<Integer, Integer> mScanCount = - new HashMap<Integer, Integer>(); - private final List<Multicaster> mMulticasters = new ArrayList<Multicaster>(); private int mMulticastEnabled; @@ -136,8 +135,8 @@ public class WifiService extends IWifiManager.Stub { private static final int POLL_TRAFFIC_STATS_INTERVAL_MSECS = 1000; /** - * See {@link Settings.Secure#WIFI_IDLE_MS}. This is the default value if a - * Settings.Secure value is not present. This timeout value is chosen as + * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a + * Settings.Global value is not present. This timeout value is chosen as * the approximate point at which the battery drain caused by Wi-Fi * being enabled but not active exceeds the battery drain caused by * re-establishing a connection to the mobile data network. @@ -161,6 +160,10 @@ public class WifiService extends IWifiManager.Stub { /* Tracks whether wifi is enabled from WifiStateMachine's perspective */ private boolean mWifiEnabled; + /* The work source (UID) that triggered the current WIFI scan, synchronized + * on this */ + private WorkSource mScanWorkSource; + private boolean mIsReceiverRegistered = false; @@ -239,7 +242,7 @@ public class WifiService extends IWifiManager.Stub { switch (msg.what) { case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: { if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - Slog.d(TAG, "New client listening to asynchronous messages"); + if (DBG) Slog.d(TAG, "New client listening to asynchronous messages"); mClients.add((AsyncChannel) msg.obj); } else { Slog.e(TAG, "Client connection failure, error=" + msg.arg1); @@ -248,9 +251,9 @@ public class WifiService extends IWifiManager.Stub { } case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { - Slog.d(TAG, "Send failed, client connection lost"); + if (DBG) Slog.d(TAG, "Send failed, client connection lost"); } else { - Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); + if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); } mClients.remove((AsyncChannel) msg.obj); break; @@ -302,6 +305,10 @@ public class WifiService extends IWifiManager.Stub { mWifiStateMachine.sendMessage(Message.obtain(msg)); break; } + case WifiManager.RSSI_PKTCNT_FETCH: { + mWifiStateMachine.sendMessage(Message.obtain(msg)); + break; + } default: { Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg); break; @@ -407,12 +414,14 @@ public class WifiService extends IWifiManager.Stub { switch(mNetworkInfo.getDetailedState()) { case CONNECTED: case DISCONNECTED: + case CAPTIVE_PORTAL_CHECK: evaluateTrafficStatsPolling(); resetNotification(); break; } } else if (intent.getAction().equals( WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + noteScanEnd(); checkAndSetNotification(); } } @@ -424,12 +433,50 @@ public class WifiService extends IWifiManager.Stub { mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper()); // Setting is in seconds - NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l; + NOTIFICATION_REPEAT_DELAY_MS = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l; mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler()); mNotificationEnabledSettingObserver.register(); } + /** Tell battery stats about a new WIFI scan */ + private void noteScanStart() { + WorkSource scanWorkSource = null; + synchronized (WifiService.this) { + if (mScanWorkSource != null) { + // Scan already in progress, don't add this one to battery stats + return; + } + scanWorkSource = new WorkSource(Binder.getCallingUid()); + mScanWorkSource = scanWorkSource; + } + + long id = Binder.clearCallingIdentity(); + try { + mBatteryStats.noteWifiScanStartedFromSource(scanWorkSource); + } catch (RemoteException e) { + Log.w(TAG, e); + } finally { + Binder.restoreCallingIdentity(id); + } + } + + /** Tell battery stats that the current WIFI scan has completed */ + private void noteScanEnd() { + WorkSource scanWorkSource = null; + synchronized (WifiService.this) { + scanWorkSource = mScanWorkSource; + mScanWorkSource = null; + } + if (scanWorkSource != null) { + try { + mBatteryStats.noteWifiScanStoppedFromSource(scanWorkSource); + } catch (RemoteException e) { + Log.w(TAG, e); + } + } + } + /** * Check if Wi-Fi needs to be enabled and start * if needed @@ -457,9 +504,9 @@ public class WifiService extends IWifiManager.Stub { final ContentResolver cr = mContext.getContentResolver(); int wifiSavedState = 0; try { - wifiSavedState = Settings.Secure.getInt(cr, Settings.Secure.WIFI_SAVED_STATE); + wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE); if(wifiSavedState == 1) - Settings.Secure.putInt(cr, Settings.Secure.WIFI_SAVED_STATE, 0); + Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0); } catch (Settings.SettingNotFoundException e) { ; } @@ -469,9 +516,9 @@ public class WifiService extends IWifiManager.Stub { private int getPersistedWifiState() { final ContentResolver cr = mContext.getContentResolver(); try { - return Settings.Secure.getInt(cr, Settings.Secure.WIFI_ON); + return Settings.Global.getInt(cr, Settings.Global.WIFI_ON); } catch (Settings.SettingNotFoundException e) { - Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, WIFI_DISABLED); + Settings.Global.putInt(cr, Settings.Global.WIFI_ON, WIFI_DISABLED); return WIFI_DISABLED; } } @@ -519,7 +566,7 @@ public class WifiService extends IWifiManager.Stub { private void persistWifiState(int state) { final ContentResolver cr = mContext.getContentResolver(); mPersistWifiState.set(state); - Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, state); + Settings.Global.putInt(cr, Settings.Global.WIFI_ON, state); } /** @@ -541,16 +588,8 @@ public class WifiService extends IWifiManager.Stub { */ public void startScan(boolean forceActive) { enforceChangePermission(); - - int uid = Binder.getCallingUid(); - int count = 0; - synchronized (mScanCount) { - if (mScanCount.containsKey(uid)) { - count = mScanCount.get(uid); - } - mScanCount.put(uid, ++count); - } mWifiStateMachine.startScan(forceActive); + noteScanStart(); } private void enforceAccessPermission() { @@ -570,6 +609,12 @@ public class WifiService extends IWifiManager.Stub { "WifiService"); } + private void enforceConnectivityInternalPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONNECTIVITY_INTERNAL, + "ConnectivityService"); + } + /** * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)} * @param enable {@code true} to enable, {@code false} to disable. @@ -595,8 +640,11 @@ public class WifiService extends IWifiManager.Stub { */ long ident = Binder.clearCallingIdentity(); - handleWifiToggled(enable); - Binder.restoreCallingIdentity(ident); + try { + handleWifiToggled(enable); + } finally { + Binder.restoreCallingIdentity(ident); + } if (enable) { if (!mIsReceiverRegistered) { @@ -791,7 +839,18 @@ public class WifiService extends IWifiManager.Stub { */ public List<ScanResult> getScanResults() { enforceAccessPermission(); - return mWifiStateMachine.syncGetScanResultsList(); + int userId = UserHandle.getCallingUserId(); + long ident = Binder.clearCallingIdentity(); + try { + int currentUser = ActivityManager.getCurrentUser(); + if (userId != currentUser) { + return new ArrayList<ScanResult>(); + } else { + return mWifiStateMachine.syncGetScanResultsList(); + } + } finally { + Binder.restoreCallingIdentity(ident); + } } /** @@ -874,7 +933,7 @@ public class WifiService extends IWifiManager.Stub { * */ public void startWifi() { - enforceChangePermission(); + enforceConnectivityInternalPermission(); /* TODO: may be add permissions for access only to connectivity service * TODO: if a start issued, keep wifi alive until a stop issued irrespective * of WifiLock & device idle status unless wifi enabled status is toggled @@ -884,20 +943,24 @@ public class WifiService extends IWifiManager.Stub { mWifiStateMachine.reconnectCommand(); } + public void captivePortalCheckComplete() { + enforceConnectivityInternalPermission(); + mWifiStateMachine.captivePortalCheckComplete(); + } + /** * see {@link android.net.wifi.WifiManager#stopWifi} * */ public void stopWifi() { - enforceChangePermission(); - /* TODO: may be add permissions for access only to connectivity service + enforceConnectivityInternalPermission(); + /* * TODO: if a stop is issued, wifi is brought up only by startWifi * unless wifi enabled status is toggled */ mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode); } - /** * see {@link android.net.wifi.WifiManager#addToBlacklist} * @@ -923,10 +986,6 @@ public class WifiService extends IWifiManager.Stub { * an AsyncChannel communication with WifiService */ public Messenger getWifiServiceMessenger() { - /* Enforce the highest permissions - TODO: when we consider exposing the asynchronous API, think about - how to provide both access and change permissions seperately - */ enforceAccessPermission(); enforceChangePermission(); return new Messenger(mAsyncServiceHandler); @@ -953,11 +1012,11 @@ public class WifiService extends IWifiManager.Stub { String action = intent.getAction(); long idleMillis = - Settings.Secure.getLong(mContext.getContentResolver(), - Settings.Secure.WIFI_IDLE_MS, DEFAULT_IDLE_MS); + Settings.Global.getLong(mContext.getContentResolver(), + Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS); int stayAwakeConditions = - Settings.System.getInt(mContext.getContentResolver(), - Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0); + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); if (action.equals(Intent.ACTION_SCREEN_ON)) { if (DBG) { Slog.d(TAG, "ACTION_SCREEN_ON"); @@ -1011,12 +1070,6 @@ public class WifiService extends IWifiManager.Stub { mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); } - //Start scan stats tracking when device unplugged - if (pluggedType == 0) { - synchronized (mScanCount) { - mScanCount.clear(); - } - } mPluggedType = pluggedType; } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, @@ -1142,17 +1195,17 @@ public class WifiService extends IWifiManager.Stub { } private boolean isAirplaneSensitive() { - String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_RADIOS); + String airplaneModeRadios = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_RADIOS); return airplaneModeRadios == null - || airplaneModeRadios.contains(Settings.System.RADIO_WIFI); + || airplaneModeRadios.contains(Settings.Global.RADIO_WIFI); } private boolean isAirplaneToggleable() { - String toggleableRadios = Settings.System.getString(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + String toggleableRadios = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); return toggleableRadios != null - && toggleableRadios.contains(Settings.System.RADIO_WIFI); + && toggleableRadios.contains(Settings.Global.RADIO_WIFI); } /** @@ -1161,8 +1214,8 @@ public class WifiService extends IWifiManager.Stub { * @return {@code true} if airplane mode is on. */ private boolean isAirplaneModeOn() { - return isAirplaneSensitive() && Settings.System.getInt(mContext.getContentResolver(), - Settings.System.AIRPLANE_MODE_ON, 0) == 1; + return isAirplaneSensitive() && Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; } @Override @@ -1176,8 +1229,8 @@ public class WifiService extends IWifiManager.Stub { } pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName()); pw.println("Stay-awake conditions: " + - Settings.System.getInt(mContext.getContentResolver(), - Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0)); + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0)); pw.println(); pw.println("Internal state:"); @@ -1207,13 +1260,6 @@ public class WifiService extends IWifiManager.Stub { pw.println("Locks held:"); mLocks.dump(pw); - pw.println("Scan count since last plugged in"); - synchronized (mScanCount) { - for(int sc : mScanCount.keySet()) { - pw.println("UID: " + sc + " Scan count: " + mScanCount.get(sc)); - } - } - pw.println(); pw.println("WifiWatchdogStateMachine dump"); mWifiWatchdogStateMachine.dump(pw); @@ -1333,10 +1379,8 @@ public class WifiService extends IWifiManager.Stub { switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); - break; case WifiManager.WIFI_MODE_SCAN_ONLY: - mBatteryStats.noteScanWifiLockAcquiredFromSource(wifiLock.mWorkSource); + mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); break; } } @@ -1345,10 +1389,8 @@ public class WifiService extends IWifiManager.Stub { switch(wifiLock.mMode) { case WifiManager.WIFI_MODE_FULL: case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); - break; case WifiManager.WIFI_MODE_SCAN_ONLY: - mBatteryStats.noteScanWifiLockReleasedFromSource(wifiLock.mWorkSource); + mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); break; } } @@ -1753,8 +1795,8 @@ public class WifiService extends IWifiManager.Stub { public void register() { ContentResolver cr = mContext.getContentResolver(); - cr.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); + cr.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); mNotificationEnabled = getValue(); } @@ -1767,8 +1809,8 @@ public class WifiService extends IWifiManager.Stub { } private boolean getValue() { - return Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1; + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1; } } diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java index 96ac493..56c0fdf 100644 --- a/services/java/com/android/server/WiredAccessoryObserver.java +++ b/services/java/com/android/server/WiredAccessoryObserver.java @@ -16,12 +16,12 @@ package com.android.server; -import android.app.ActivityManagerNative; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -39,7 +39,7 @@ import java.util.List; /** * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock. */ -class WiredAccessoryObserver extends UEventObserver { +final class WiredAccessoryObserver extends UEventObserver { private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); private static final boolean LOG = true; private static final int BIT_HEADSET = (1 << 0); @@ -50,122 +50,32 @@ class WiredAccessoryObserver extends UEventObserver { private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC| BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL| BIT_HDMI_AUDIO); - private static final int HEADSETS_WITH_MIC = BIT_HEADSET; - private static class UEventInfo { - private final String mDevName; - private final int mState1Bits; - private final int mState2Bits; - - public UEventInfo(String devName, int state1Bits, int state2Bits) { - mDevName = devName; - mState1Bits = state1Bits; - mState2Bits = state2Bits; - } - - public String getDevName() { return mDevName; } - - public String getDevPath() { - return String.format("/devices/virtual/switch/%s", mDevName); - } - - public String getSwitchStatePath() { - return String.format("/sys/class/switch/%s/state", mDevName); - } - - public boolean checkSwitchExists() { - File f = new File(getSwitchStatePath()); - return ((null != f) && f.exists()); - } - - public int computeNewHeadsetState(int headsetState, int switchState) { - int preserveMask = ~(mState1Bits | mState2Bits); - int setBits = ((switchState == 1) ? mState1Bits : - ((switchState == 2) ? mState2Bits : 0)); - - return ((headsetState & preserveMask) | setBits); - } - } - - private static List<UEventInfo> makeObservedUEventList() { - List<UEventInfo> retVal = new ArrayList<UEventInfo>(); - UEventInfo uei; - - // Monitor h2w - uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC); - if (uei.checkSwitchExists()) { - retVal.add(uei); - } else { - Slog.w(TAG, "This kernel does not have wired headset support"); - } - - // Monitor USB - uei = new UEventInfo("usb_audio", BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL); - if (uei.checkSwitchExists()) { - retVal.add(uei); - } else { - Slog.w(TAG, "This kernel does not have usb audio support"); - } + private final Object mLock = new Object(); - // Monitor HDMI - // - // If the kernel has support for the "hdmi_audio" switch, use that. It will be signalled - // only when the HDMI driver has a video mode configured, and the downstream sink indicates - // support for audio in its EDID. - // - // If the kernel does not have an "hdmi_audio" switch, just fall back on the older "hdmi" - // switch instead. - uei = new UEventInfo("hdmi_audio", BIT_HDMI_AUDIO, 0); - if (uei.checkSwitchExists()) { - retVal.add(uei); - } else { - uei = new UEventInfo("hdmi", BIT_HDMI_AUDIO, 0); - if (uei.checkSwitchExists()) { - retVal.add(uei); - } else { - Slog.w(TAG, "This kernel does not have HDMI audio support"); - } - } - - return retVal; - } - - private static List<UEventInfo> uEventInfo = makeObservedUEventList(); + private final Context mContext; + private final WakeLock mWakeLock; // held while there is a pending route change + private final AudioManager mAudioManager; + private final List<UEventInfo> mUEventInfo; private int mHeadsetState; private int mPrevHeadsetState; private String mHeadsetName; - private final Context mContext; - private final WakeLock mWakeLock; // held while there is a pending route change - - private final AudioManager mAudioManager; - public WiredAccessoryObserver(Context context) { mContext = context; + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver"); mWakeLock.setReferenceCounted(false); mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); + mUEventInfo = makeObservedUEventList(); + context.registerReceiver(new BootCompletedReceiver(), new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); } - private final class BootCompletedReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - // At any given time accessories could be inserted - // one on the board, one on the dock and one on HDMI: - // observe three UEVENTs - init(); // set initial status - for (int i = 0; i < uEventInfo.size(); ++i) { - UEventInfo uei = uEventInfo.get(i); - startObserving("DEVPATH="+uei.getDevPath()); - } - } - } - @Override public void onUEvent(UEventObserver.UEvent event) { if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString()); @@ -174,56 +84,64 @@ class WiredAccessoryObserver extends UEventObserver { String devPath = event.get("DEVPATH"); String name = event.get("SWITCH_NAME"); int state = Integer.parseInt(event.get("SWITCH_STATE")); - updateState(devPath, name, state); + synchronized (mLock) { + updateStateLocked(devPath, name, state); + } } catch (NumberFormatException e) { Slog.e(TAG, "Could not parse switch state from event " + event); } } - private synchronized final void updateState(String devPath, String name, int state) - { - for (int i = 0; i < uEventInfo.size(); ++i) { - UEventInfo uei = uEventInfo.get(i); - if (devPath.equals(uei.getDevPath())) { - update(name, uei.computeNewHeadsetState(mHeadsetState, state)); - return; + private void bootCompleted() { + synchronized (mLock) { + char[] buffer = new char[1024]; + mPrevHeadsetState = mHeadsetState; + + if (LOG) Slog.v(TAG, "init()"); + + for (int i = 0; i < mUEventInfo.size(); ++i) { + UEventInfo uei = mUEventInfo.get(i); + try { + int curState; + FileReader file = new FileReader(uei.getSwitchStatePath()); + int len = file.read(buffer, 0, 1024); + file.close(); + curState = Integer.valueOf((new String(buffer, 0, len)).trim()); + + if (curState > 0) { + updateStateLocked(uei.getDevPath(), uei.getDevName(), curState); + } + } catch (FileNotFoundException e) { + Slog.w(TAG, uei.getSwitchStatePath() + + " not found while attempting to determine initial switch state"); + } catch (Exception e) { + Slog.e(TAG, "" , e); + } } } - } - - private synchronized final void init() { - char[] buffer = new char[1024]; - mPrevHeadsetState = mHeadsetState; - - if (LOG) Slog.v(TAG, "init()"); - for (int i = 0; i < uEventInfo.size(); ++i) { - UEventInfo uei = uEventInfo.get(i); - try { - int curState; - FileReader file = new FileReader(uei.getSwitchStatePath()); - int len = file.read(buffer, 0, 1024); - file.close(); - curState = Integer.valueOf((new String(buffer, 0, len)).trim()); - - if (curState > 0) { - updateState(uei.getDevPath(), uei.getDevName(), curState); - } + // At any given time accessories could be inserted + // one on the board, one on the dock and one on HDMI: + // observe three UEVENTs + for (int i = 0; i < mUEventInfo.size(); ++i) { + UEventInfo uei = mUEventInfo.get(i); + startObserving("DEVPATH="+uei.getDevPath()); + } + } - } catch (FileNotFoundException e) { - Slog.w(TAG, uei.getSwitchStatePath() + - " not found while attempting to determine initial switch state"); - } catch (Exception e) { - Slog.e(TAG, "" , e); + private void updateStateLocked(String devPath, String name, int state) { + for (int i = 0; i < mUEventInfo.size(); ++i) { + UEventInfo uei = mUEventInfo.get(i); + if (devPath.equals(uei.getDevPath())) { + updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state)); + return; } } } - private synchronized final void update(String newName, int newState) { + private void updateLocked(String newName, int newState) { // Retain only relevant bits int headsetState = newState & SUPPORTED_HEADSETS; - int newOrOld = headsetState | mHeadsetState; - int delay = 0; int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG; int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL; int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC); @@ -254,28 +172,26 @@ class WiredAccessoryObserver extends UEventObserver { mHeadsetState = headsetState; mWakeLock.acquire(); - mHandler.sendMessage(mHandler.obtainMessage(0, - mHeadsetState, - mPrevHeadsetState, - mHeadsetName)); + + Message msg = mHandler.obtainMessage(0, mHeadsetState, mPrevHeadsetState, mHeadsetName); + mHandler.sendMessage(msg); } - private synchronized final void setDevicesState(int headsetState, - int prevHeadsetState, - String headsetName) { - int allHeadsets = SUPPORTED_HEADSETS; - for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { - if ((curHeadset & allHeadsets) != 0) { - setDeviceState(curHeadset, headsetState, prevHeadsetState, headsetName); - allHeadsets &= ~curHeadset; + private void setDevicesState( + int headsetState, int prevHeadsetState, String headsetName) { + synchronized (mLock) { + int allHeadsets = SUPPORTED_HEADSETS; + for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) { + if ((curHeadset & allHeadsets) != 0) { + setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName); + allHeadsets &= ~curHeadset; + } } } } - private final void setDeviceState(int headset, - int headsetState, - int prevHeadsetState, - String headsetName) { + private void setDeviceStateLocked(int headset, + int headsetState, int prevHeadsetState, String headsetName) { if ((headsetState & headset) != (prevHeadsetState & headset)) { int device; int state; @@ -308,11 +224,96 @@ class WiredAccessoryObserver extends UEventObserver { } } - private final Handler mHandler = new Handler() { + private static List<UEventInfo> makeObservedUEventList() { + List<UEventInfo> retVal = new ArrayList<UEventInfo>(); + UEventInfo uei; + + // Monitor h2w + uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + Slog.w(TAG, "This kernel does not have wired headset support"); + } + + // Monitor USB + uei = new UEventInfo("usb_audio", BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + Slog.w(TAG, "This kernel does not have usb audio support"); + } + + // Monitor HDMI + // + // If the kernel has support for the "hdmi_audio" switch, use that. It will be signalled + // only when the HDMI driver has a video mode configured, and the downstream sink indicates + // support for audio in its EDID. + // + // If the kernel does not have an "hdmi_audio" switch, just fall back on the older "hdmi" + // switch instead. + uei = new UEventInfo("hdmi_audio", BIT_HDMI_AUDIO, 0); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + uei = new UEventInfo("hdmi", BIT_HDMI_AUDIO, 0); + if (uei.checkSwitchExists()) { + retVal.add(uei); + } else { + Slog.w(TAG, "This kernel does not have HDMI audio support"); + } + } + + return retVal; + } + + private final Handler mHandler = new Handler(Looper.myLooper(), null, true) { @Override public void handleMessage(Message msg) { setDevicesState(msg.arg1, msg.arg2, (String)msg.obj); mWakeLock.release(); } }; + + private static final class UEventInfo { + private final String mDevName; + private final int mState1Bits; + private final int mState2Bits; + + public UEventInfo(String devName, int state1Bits, int state2Bits) { + mDevName = devName; + mState1Bits = state1Bits; + mState2Bits = state2Bits; + } + + public String getDevName() { return mDevName; } + + public String getDevPath() { + return String.format("/devices/virtual/switch/%s", mDevName); + } + + public String getSwitchStatePath() { + return String.format("/sys/class/switch/%s/state", mDevName); + } + + public boolean checkSwitchExists() { + File f = new File(getSwitchStatePath()); + return ((null != f) && f.exists()); + } + + public int computeNewHeadsetState(int headsetState, int switchState) { + int preserveMask = ~(mState1Bits | mState2Bits); + int setBits = ((switchState == 1) ? mState1Bits : + ((switchState == 2) ? mState2Bits : 0)); + + return ((headsetState & preserveMask) | setBits); + } + } + + private final class BootCompletedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + bootCompleted(); + } + } } diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java index 8fa6722..f1a03de 100644 --- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -16,68 +16,59 @@ package com.android.server.accessibility; -import com.android.server.input.InputFilter; - import android.content.Context; import android.os.PowerManager; import android.util.Slog; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputFilter; import android.view.MotionEvent; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; -/** - * Input filter for accessibility. - * - * Currently just a stub but will eventually implement touch exploration, etc. - */ -public class AccessibilityInputFilter extends InputFilter { - private static final String TAG = "AccessibilityInputFilter"; +class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation { + + private static final String TAG = AccessibilityInputFilter.class.getSimpleName(); + private static final boolean DEBUG = false; + private static final int UNDEFINED_DEVICE_ID = -1; + + /** + * Flag for enabling the screen magnification feature. + * + * @see #setEnabledFeatures(int) + */ + static final int FLAG_FEATURE_SCREEN_MAGNIFIER = 0x00000001; + + /** + * Flag for enabling the touch exploration feature. + * + * @see #setEnabledFeatures(int) + */ + static final int FLAG_FEATURE_TOUCH_EXPLORATION = 0x00000002; + private final Context mContext; private final PowerManager mPm; private final AccessibilityManagerService mAms; - /** - * This is an interface for explorers that take a {@link MotionEvent} - * stream and perform touch exploration of the screen content. - */ - public interface Explorer { - /** - * Handles a {@link MotionEvent}. - * - * @param event The event to handle. - * @param policyFlags The policy flags associated with the event. - */ - public void onMotionEvent(MotionEvent event, int policyFlags); - - /** - * Requests that the explorer clears its internal state. - * - * @param event The last received event. - * @param policyFlags The policy flags associated with the event. - */ - public void clear(MotionEvent event, int policyFlags); - - /** - * Requests that the explorer clears its internal state. - */ - public void clear(); - } + private int mCurrentDeviceId; - private TouchExplorer mTouchExplorer; + private boolean mInstalled; - private int mTouchscreenSourceDeviceId; + private int mEnabledFeatures; + + private TouchExplorer mTouchExplorer; + private ScreenMagnifier mScreenMagnifier; + private EventStreamTransformation mEventHandler; - public AccessibilityInputFilter(Context context, AccessibilityManagerService service) { + AccessibilityInputFilter(Context context, AccessibilityManagerService service) { super(context.getMainLooper()); mContext = context; mAms = service; - mPm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); } @Override @@ -85,7 +76,9 @@ public class AccessibilityInputFilter extends InputFilter { if (DEBUG) { Slog.d(TAG, "Accessibility input filter installed."); } - mTouchExplorer = new TouchExplorer(this, mContext, mAms); + mInstalled = true; + disableFeatures(); + enableFeatures(); super.onInstalled(); } @@ -94,7 +87,8 @@ public class AccessibilityInputFilter extends InputFilter { if (DEBUG) { Slog.d(TAG, "Accessibility input filter uninstalled."); } - mTouchExplorer.clear(); + mInstalled = false; + disableFeatures(); super.onUninstalled(); } @@ -104,27 +98,104 @@ public class AccessibilityInputFilter extends InputFilter { Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); } - if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { - MotionEvent motionEvent = (MotionEvent) event; - int deviceId = event.getDeviceId(); - if (mTouchscreenSourceDeviceId != deviceId) { - mTouchscreenSourceDeviceId = deviceId; - mTouchExplorer.clear(motionEvent, policyFlags); + if (mEventHandler == null) { + super.onInputEvent(event, policyFlags); + return; + } + if (event.getSource() != InputDevice.SOURCE_TOUCHSCREEN) { + super.onInputEvent(event, policyFlags); + return; + } + if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { + mEventHandler.clear(); + super.onInputEvent(event, policyFlags); + return; + } + final int deviceId = event.getDeviceId(); + if (mCurrentDeviceId != deviceId) { + if (mCurrentDeviceId != UNDEFINED_DEVICE_ID) { + mEventHandler.clear(); } - if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) != 0) { - mPm.userActivity(event.getEventTime(), false); - mTouchExplorer.onMotionEvent(motionEvent, policyFlags); + mCurrentDeviceId = deviceId; + } + mPm.userActivity(event.getEventTime(), false); + MotionEvent motionEvent = (MotionEvent) event; + mEventHandler.onMotionEvent(motionEvent, policyFlags); + } + + @Override + public void onMotionEvent(MotionEvent event, int policyFlags) { + sendInputEvent(event, policyFlags); + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + // TODO Implement this to inject the accessibility event + // into the accessibility manager service similarly + // to how this is done for input events. + } + + @Override + public void setNext(EventStreamTransformation sink) { + /* do nothing */ + } + + @Override + public void clear() { + /* do nothing */ + } + + void setEnabledFeatures(int enabledFeatures) { + if (mEnabledFeatures == enabledFeatures) { + return; + } + if (mInstalled) { + disableFeatures(); + } + mEnabledFeatures = enabledFeatures; + if (mInstalled) { + enableFeatures(); + } + } + + void notifyAccessibilityEvent(AccessibilityEvent event) { + if (mEventHandler != null) { + mEventHandler.onAccessibilityEvent(event); + } + } + + private void enableFeatures() { + if ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { + mEventHandler = mScreenMagnifier = new ScreenMagnifier(mContext); + mEventHandler.setNext(this); + } + if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { + mTouchExplorer = new TouchExplorer(mContext, mAms); + mTouchExplorer.setNext(this); + if (mEventHandler != null) { + mEventHandler.setNext(mTouchExplorer); } else { - mTouchExplorer.clear(motionEvent, policyFlags); + mEventHandler = mTouchExplorer; } - } else { - super.onInputEvent(event, policyFlags); } } - public void onAccessibilityEvent(AccessibilityEvent event) { + private void disableFeatures() { if (mTouchExplorer != null) { - mTouchExplorer.onAccessibilityEvent(event); + mTouchExplorer.clear(); + mTouchExplorer.onDestroy(); + mTouchExplorer = null; + } + if (mScreenMagnifier != null) { + mScreenMagnifier.clear(); + mScreenMagnifier.onDestroy(); + mScreenMagnifier = null; } + mEventHandler = null; + } + + @Override + public void onDestroy() { + /* ignore */ } } diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index e42ec84..e7f3599 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -48,6 +48,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; @@ -58,9 +59,11 @@ import android.text.TextUtils.SimpleStringSplitter; import android.util.Slog; import android.util.SparseArray; import android.view.IWindow; +import android.view.IWindowManager; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.WindowInfo; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityInteractionClient; @@ -74,7 +77,6 @@ import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.R; import com.android.internal.content.PackageMonitor; import com.android.internal.statusbar.IStatusBarService; -import com.android.server.wm.WindowManagerService; import org.xmlpull.v1.XmlPullParserException; @@ -115,6 +117,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 3; + private static final int MSG_SEND_UPDATE_INPUT_FILTER = 4; + private static int sIdCounter = 0; private static int sNextWindowId; @@ -157,20 +161,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private boolean mIsTouchExplorationEnabled; - private final WindowManagerService mWindowManagerService; + private boolean mIsScreenMagnificationEnabled; + + private final IWindowManager mWindowManager; private final SecurityPolicy mSecurityPolicy; - private final MainHanler mMainHandler; + private final MainHandler mMainHandler; private Service mUiAutomationService; private Service mQueryBridge; - private boolean mTouchExplorationGestureEnded; - - private boolean mTouchExplorationGestureStarted; - private AlertDialog mEnableTouchExplorationDialog; /** @@ -181,10 +183,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public AccessibilityManagerService(Context context) { mContext = context; mPackageManager = mContext.getPackageManager(); - mWindowManagerService = (WindowManagerService) ServiceManager.getService( - Context.WINDOW_SERVICE); + mWindowManager = (IWindowManager) ServiceManager.getService(Context.WINDOW_SERVICE); mSecurityPolicy = new SecurityPolicy(); - mMainHandler = new MainHanler(); + mMainHandler = new MainHandler(mContext.getMainLooper()); registerPackageChangeAndBootCompletedBroadcastReceiver(); registerSettingsContentObservers(); } @@ -202,7 +203,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { synchronized (mLock) { // We will update when the automation service dies. if (mUiAutomationService == null) { - populateAccessibilityServiceListLocked(); + populateInstalledAccessibilityServiceLocked(); manageServicesLocked(); } } @@ -263,18 +264,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { synchronized (mLock) { // We will update when the automation service dies. if (mUiAutomationService == null) { - populateAccessibilityServiceListLocked(); - populateEnabledAccessibilityServicesLocked(); - populateTouchExplorationGrantedAccessibilityServicesLocked(); - handleAccessibilityEnabledSettingChangedLocked(); - handleTouchExplorationEnabledSettingChangedLocked(); - updateInputFilterLocked(); - sendStateToClientsLocked(); + updateInternalStateLocked(); } } return; } - super.onReceive(context, intent); } }; @@ -330,6 +324,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } }); + Uri accessibilityScreenMagnificationEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + contentResolver.registerContentObserver(accessibilityScreenMagnificationEnabledUri, false, + new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + synchronized (mLock) { + // We will update when the automation service dies. + if (mUiAutomationService == null) { + handleScreenMagnificationEnabledSettingChangedLocked(); + updateInputFilterLocked(); + sendStateToClientsLocked(); + } + } + } + }); + Uri accessibilityServicesUri = Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); contentResolver.registerContentObserver(accessibilityServicesUri, false, @@ -358,8 +370,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // We will update when the automation service dies. if (mUiAutomationService == null) { populateTouchExplorationGrantedAccessibilityServicesLocked(); - unbindAllServicesLocked(); - manageServicesLocked(); + handleTouchExplorationGrantedAccessibilityServicesChangedLocked(); } } } @@ -385,18 +396,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } public boolean sendAccessibilityEvent(AccessibilityEvent event) { - final int eventType = event.getEventType(); - - // The event for gesture start should be strictly before the - // first hover enter event for the gesture. - if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER - && mTouchExplorationGestureStarted) { - mTouchExplorationGestureStarted = false; - AccessibilityEvent gestureStartEvent = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); - sendAccessibilityEvent(gestureStartEvent); - } - synchronized (mLock) { if (mSecurityPolicy.canDispatchAccessibilityEvent(event)) { mSecurityPolicy.updateActiveWindowAndEventSourceLocked(event); @@ -406,22 +405,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (mHasInputFilter && mInputFilter != null) { mMainHandler.obtainMessage(MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, AccessibilityEvent.obtain(event)).sendToTarget(); - } event.recycle(); mHandledFeedbackTypes = 0; } - - // The event for gesture end should be strictly after the - // last hover exit event for the gesture. - if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT - && mTouchExplorationGestureEnded) { - mTouchExplorationGestureEnded = false; - AccessibilityEvent gestureEndEvent = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); - sendAccessibilityEvent(gestureEndEvent); - } - return (OWN_PROCESS_ID != Binder.getCallingPid()); } @@ -584,15 +571,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { * * @param outBounds The output to which to write the bounds. */ - void getActiveWindowBounds(Rect outBounds) { + boolean getActiveWindowBounds(Rect outBounds) { synchronized (mLock) { final int windowId = mSecurityPolicy.mActiveWindowId; IBinder token = mWindowIdToWindowTokenMap.get(windowId); - mWindowManagerService.getWindowFrame(token, outBounds); + try { + WindowInfo info = mWindowManager.getWindowInfo(token); + if (info != null) { + outBounds.set(info.frame); + return true; + } + } catch (RemoteException re) { + /* ignore */ + } + return false; } } - int getActiveWindowId() { + public int getActiveWindowId() { return mSecurityPolicy.mActiveWindowId; } @@ -604,14 +600,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return mQueryBridge; } - public void touchExplorationGestureEnded() { - mTouchExplorationGestureEnded = true; - } - - public void touchExplorationGestureStarted() { - mTouchExplorationGestureStarted = true; - } - private boolean notifyGestureLocked(int gestureId, boolean isDefault) { // TODO: Now we are giving the gestures to the last enabled // service that can handle them which is the last one @@ -624,7 +612,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // enabled accessibility services. for (int i = mServices.size() - 1; i >= 0; i--) { Service service = mServices.get(i); - if (service.mReqeustTouchExplorationMode && service.mIsDefault == isDefault) { + if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) { service.notifyGesture(gestureId); return true; } @@ -648,7 +636,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { /** * Populates the cached list of installed {@link AccessibilityService}s. */ - private void populateAccessibilityServiceListLocked() { + private void populateInstalledAccessibilityServiceLocked() { mInstalledServices.clear(); List<ResolveInfo> installedServices = mPackageManager.queryIntentServices( @@ -658,11 +646,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { for (int i = 0, count = installedServices.size(); i < count; i++) { ResolveInfo resolveInfo = installedServices.get(i); ServiceInfo serviceInfo = resolveInfo.serviceInfo; - // For now we are enforcing this if the target version is JellyBean or - // higher and in a later release we will enforce this for everyone. - if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN - && !android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE.equals( - serviceInfo.permission)) { + if (!android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE.equals(serviceInfo.permission)) { Slog.w(LOG_TAG, "Skipping accessibilty service " + new ComponentName( serviceInfo.packageName, serviceInfo.name).flattenToShortString() + ": it does not require the permission " @@ -958,23 +942,26 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } /** - * Updates the touch exploration state. + * Updates the state of the input filter. */ private void updateInputFilterLocked() { - if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) { - if (!mHasInputFilter) { - mHasInputFilter = true; - if (mInputFilter == null) { - mInputFilter = new AccessibilityInputFilter(mContext, this); - } - mWindowManagerService.setInputFilter(mInputFilter); - } - return; - } - if (mHasInputFilter) { - mHasInputFilter = false; - mWindowManagerService.setInputFilter(null); - } + mMainHandler.obtainMessage(MSG_SEND_UPDATE_INPUT_FILTER).sendToTarget(); + } + + /** + * Updated the internal state of this service to match the current settings. + */ + private void updateInternalStateLocked() { + populateInstalledAccessibilityServiceLocked(); + populateEnabledAccessibilityServicesLocked(); + populateTouchExplorationGrantedAccessibilityServicesLocked(); + + handleTouchExplorationEnabledSettingChangedLocked(); + handleScreenMagnificationEnabledSettingChangedLocked(); + handleAccessibilityEnabledSettingChangedLocked(); + + updateInputFilterLocked(); + sendStateToClientsLocked(); } /** @@ -1000,6 +987,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1; } + /** + * Updates the state based on the screen magnification enabled setting. + */ + private void handleScreenMagnificationEnabledSettingChangedLocked() { + mIsScreenMagnificationEnabled = Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1; + } + + private void handleTouchExplorationGrantedAccessibilityServicesChangedLocked() { + final int serviceCount = mServices.size(); + for (int i = 0; i < serviceCount; i++) { + Service service = mServices.get(i); + if (service.mRequestTouchExplorationMode + && mTouchExplorationGrantedServices.contains(service.mComponentName)) { + tryEnableTouchExplorationLocked(service); + return; + } + } + if (mIsTouchExplorationEnabled) { + mMainHandler.obtainMessage(MSG_TOGGLE_TOUCH_EXPLORATION, 0, + 0).sendToTarget(); + } + } + private void tryEnableTouchExplorationLocked(final Service service) { if (!mIsTouchExplorationEnabled && service.mRequestTouchExplorationMode) { final boolean canToggleTouchExploration = mTouchExplorationGrantedServices.contains( @@ -1055,7 +1067,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - private class MainHanler extends Handler { + private class MainHandler extends Handler { + + public MainHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { final int type = msg.what; @@ -1112,10 +1129,50 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER: { AccessibilityEvent event = (AccessibilityEvent) msg.obj; if (mHasInputFilter && mInputFilter != null) { - mInputFilter.onAccessibilityEvent(event); + mInputFilter.notifyAccessibilityEvent(event); } event.recycle(); } break; + case MSG_SEND_UPDATE_INPUT_FILTER: { + boolean setInputFilter = false; + AccessibilityInputFilter inputFilter = null; + synchronized (mLock) { + if ((mIsAccessibilityEnabled && mIsTouchExplorationEnabled) + || mIsScreenMagnificationEnabled) { + if (!mHasInputFilter) { + mHasInputFilter = true; + if (mInputFilter == null) { + mInputFilter = new AccessibilityInputFilter(mContext, + AccessibilityManagerService.this); + } + inputFilter = mInputFilter; + setInputFilter = true; + } + int flags = 0; + if (mIsScreenMagnificationEnabled) { + flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; + } + if (mIsTouchExplorationEnabled) { + flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; + } + mInputFilter.setEnabledFeatures(flags); + } else { + if (mHasInputFilter) { + mHasInputFilter = false; + mInputFilter.setEnabledFeatures(0); + inputFilter = null; + setInputFilter = true; + } + } + } + if (setInputFilter) { + try { + mWindowManager.setInputFilter(inputFilter); + } catch (RemoteException re) { + /* ignore */ + } + } + } break; } } } @@ -1163,8 +1220,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { boolean mCanRetrieveScreenContent; - boolean mReqeustTouchExplorationMode; - boolean mIsAutomation; final Rect mTempBounds = new Rect(); @@ -1204,7 +1259,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mIsAutomation = isAutomation; if (!isAutomation) { mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent(); - mReqeustTouchExplorationMode = + mRequestTouchExplorationMode = (accessibilityServiceInfo.flags & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; mIntent = new Intent().setComponent(mComponentName); @@ -1334,8 +1389,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); - final int windowLeft; - final int windowTop; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); @@ -1348,10 +1401,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return 0; } } - IBinder token = mWindowIdToWindowTokenMap.get(resolvedWindowId); - mWindowManagerService.getWindowFrame(token, mTempBounds); - windowLeft = mTempBounds.left; - windowTop = mTempBounds.top; } final int flags = (mIncludeNotImportantViews) ? AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; @@ -1359,8 +1408,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final long identityToken = Binder.clearCallingIdentity(); try { connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId, - windowLeft, windowTop, interactionId, callback, flags, interrogatingPid, - interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid); + return getCompatibilityScale(resolvedWindowId); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); @@ -1368,7 +1417,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(resolvedWindowId); + return 0; } @Override @@ -1377,8 +1426,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); - final int windowLeft; - final int windowTop; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); @@ -1392,19 +1439,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return 0; } } - IBinder token = mWindowIdToWindowTokenMap.get(resolvedWindowId); - mWindowManagerService.getWindowFrame(token, mTempBounds); - windowLeft = mTempBounds.left; - windowTop = mTempBounds.top; } final int flags = (mIncludeNotImportantViews) ? AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, windowLeft, - windowTop, interactionId, callback, flags, interrogatingPid, + connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, + interactionId, callback, flags, interrogatingPid, interrogatingTid); + return getCompatibilityScale(resolvedWindowId); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); @@ -1412,7 +1456,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(resolvedWindowId); + return 0; } @Override @@ -1421,8 +1465,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { IAccessibilityInteractionConnectionCallback callback, int flags, long interrogatingTid) throws RemoteException { final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); - final int windowLeft; - final int windowTop; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); @@ -1436,10 +1478,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return 0; } } - IBinder token = mWindowIdToWindowTokenMap.get(resolvedWindowId); - mWindowManagerService.getWindowFrame(token, mTempBounds); - windowLeft = mTempBounds.left; - windowTop = mTempBounds.top; } final int allFlags = flags | ((mIncludeNotImportantViews) ? AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0); @@ -1447,8 +1485,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final long identityToken = Binder.clearCallingIdentity(); try { connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, - windowLeft, windowTop, interactionId, callback, allFlags, interrogatingPid, - interrogatingTid); + interactionId, callback, allFlags, interrogatingPid, interrogatingTid); + return getCompatibilityScale(resolvedWindowId); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); @@ -1456,7 +1494,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(resolvedWindowId); + return 0; } @Override @@ -1465,8 +1503,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); - final int windowLeft; - final int windowTop; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); @@ -1480,18 +1516,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return 0; } } - IBinder token = mWindowIdToWindowTokenMap.get(resolvedWindowId); - mWindowManagerService.getWindowFrame(token, mTempBounds); - windowLeft = mTempBounds.left; - windowTop = mTempBounds.top; } final int flags = (mIncludeNotImportantViews) ? AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findFocus(accessibilityNodeId, focusType, windowLeft, windowTop, - interactionId, callback, flags, interrogatingPid, interrogatingTid); + connection.findFocus(accessibilityNodeId, focusType, interactionId, callback, + flags, interrogatingPid, interrogatingTid); + return getCompatibilityScale(resolvedWindowId); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling findAccessibilityFocus()"); @@ -1499,7 +1532,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(resolvedWindowId); + return 0; } @Override @@ -1508,8 +1541,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); - final int windowLeft; - final int windowTop; IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); @@ -1523,18 +1554,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return 0; } } - IBinder token = mWindowIdToWindowTokenMap.get(resolvedWindowId); - mWindowManagerService.getWindowFrame(token, mTempBounds); - windowLeft = mTempBounds.left; - windowTop = mTempBounds.top; } final int flags = (mIncludeNotImportantViews) ? AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.focusSearch(accessibilityNodeId, direction, windowLeft, windowTop, - interactionId, callback, flags, interrogatingPid, interrogatingTid); + connection.focusSearch(accessibilityNodeId, direction, interactionId, callback, + flags, interrogatingPid, interrogatingTid); + return getCompatibilityScale(resolvedWindowId); } catch (RemoteException re) { if (DEBUG) { Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()"); @@ -1542,7 +1570,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(resolvedWindowId); + return 0; } @Override @@ -1630,18 +1658,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { // the state based on values in the settings database. if (mIsAutomation) { mUiAutomationService = null; - - populateEnabledAccessibilityServicesLocked(); - populateTouchExplorationGrantedAccessibilityServicesLocked(); - - handleAccessibilityEnabledSettingChangedLocked(); - sendStateToClientsLocked(); - - handleTouchExplorationEnabledSettingChangedLocked(); - updateInputFilterLocked(); - - populateAccessibilityServiceListLocked(); - manageServicesLocked(); + updateInternalStateLocked(); } } } @@ -1820,7 +1837,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private float getCompatibilityScale(int windowId) { IBinder windowToken = mWindowIdToWindowTokenMap.get(windowId); - return mWindowManagerService.getWindowCompatibilityScale(windowToken); + try { + return mWindowManager.getWindowCompatibilityScale(windowToken); + } catch (RemoteException re) { + /* ignore */ + } + return 1.0f; } } @@ -1939,18 +1961,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } private int getFocusedWindowId() { - // We call this only on window focus change or after touch - // exploration gesture end and the shown windows are not that - // many, so the linear look up is just fine. - IBinder token = mWindowManagerService.getFocusedWindowClientToken(); - if (token != null) { - SparseArray<IBinder> windows = mWindowIdToWindowTokenMap; - final int windowCount = windows.size(); - for (int i = 0; i < windowCount; i++) { - if (windows.valueAt(i) == token) { - return windows.keyAt(i); + final long identity = Binder.clearCallingIdentity(); + try { + // We call this only on window focus change or after touch + // exploration gesture end and the shown windows are not that + // many, so the linear look up is just fine. + IBinder token = mWindowManager.getFocusedWindowToken(); + if (token != null) { + SparseArray<IBinder> windows = mWindowIdToWindowTokenMap; + final int windowCount = windows.size(); + for (int i = 0; i < windowCount; i++) { + if (windows.valueAt(i) == token) { + return windows.keyAt(i); + } } } + } catch (RemoteException re) { + /* ignore */ + } finally { + Binder.restoreCallingIdentity(identity); } return -1; } diff --git a/services/java/com/android/server/accessibility/EventStreamTransformation.java b/services/java/com/android/server/accessibility/EventStreamTransformation.java new file mode 100644 index 0000000..b715570 --- /dev/null +++ b/services/java/com/android/server/accessibility/EventStreamTransformation.java @@ -0,0 +1,90 @@ +/* + ** Copyright 2012, 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.accessibility; + +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.accessibility.AccessibilityEvent; + +/** + * Interface for classes that can handle and potentially transform a stream of + * motion and accessibility events. Instances implementing this interface are + * ordered in a sequence to implement a transformation chain. An instance may + * consume, modify, and generate events. It is responsible to deliver the + * output events to the next transformation in the sequence set via + * {@link #setNext(EventStreamTransformation)}. + * + * Note that since instances implementing this interface are transformations + * of the event stream, an instance should work against the event stream + * potentially modified by previous ones. Hence, the order of transformations + * is important. + * + * It is a responsibility of each handler that decides to react to an event + * sequence and prevent any subsequent ones from performing an action to send + * the appropriate cancel event given it has delegated a part of the events + * that belong to the current gesture. This will ensure that subsequent + * transformations will not be left in an inconsistent state and the applications + * see a consistent event stream. + * + * For example, to cancel a {@link KeyEvent} the handler has to emit an event + * with action {@link KeyEvent#ACTION_UP} with the additional flag + * {@link KeyEvent#FLAG_CANCELED}. To cancel a {@link MotionEvent} the handler + * has to send an event with action {@link MotionEvent#ACTION_CANCEL}. + * + * It is a responsibility of each handler that received a cancel event to clear its + * internal state and to propagate the event to the next one to enable subsequent + * transformations to clear their internal state. + * + * It is a responsibility for each transformation to start handling events only + * after an event that designates the start of a well-formed event sequence. + * For example, if it received a down motion event followed by a cancel motion + * event, it should not handle subsequent move and up events until it gets a down. + */ +interface EventStreamTransformation { + + /** + * Receives a motion event. + * + * @param event The motion event. + * @param policyFlags Policy flags for the event. + */ + public void onMotionEvent(MotionEvent event, int policyFlags); + + /** + * Receives an accessibility event. + * + * @param event The accessibility event. + */ + public void onAccessibilityEvent(AccessibilityEvent event); + + /** + * Sets the next transformation. + * + * @param next The next transformation. + */ + public void setNext(EventStreamTransformation next); + + /** + * Clears the internal state of this transformation. + */ + public void clear(); + + /** + * Destroys this transformation. + */ + public void onDestroy(); +} diff --git a/services/java/com/android/server/accessibility/GestureUtils.java b/services/java/com/android/server/accessibility/GestureUtils.java new file mode 100644 index 0000000..b68b09f --- /dev/null +++ b/services/java/com/android/server/accessibility/GestureUtils.java @@ -0,0 +1,102 @@ +package com.android.server.accessibility; + +import android.util.MathUtils; +import android.view.MotionEvent; + +/** + * Some helper functions for gesture detection. + */ +final class GestureUtils { + + private GestureUtils() { + /* cannot be instantiated */ + } + + public static boolean isTap(MotionEvent down, MotionEvent up, int tapTimeSlop, + int tapDistanceSlop, int actionIndex) { + return eventsWithinTimeAndDistanceSlop(down, up, tapTimeSlop, tapDistanceSlop, actionIndex); + } + + public static boolean isMultiTap(MotionEvent firstUp, MotionEvent secondUp, + int multiTapTimeSlop, int multiTapDistanceSlop, int actionIndex) { + return eventsWithinTimeAndDistanceSlop(firstUp, secondUp, multiTapTimeSlop, + multiTapDistanceSlop, actionIndex); + } + + private static boolean eventsWithinTimeAndDistanceSlop(MotionEvent first, MotionEvent second, + int timeout, int distance, int actionIndex) { + if (isTimedOut(first, second, timeout)) { + return false; + } + final double deltaMove = computeDistance(first, second, actionIndex); + if (deltaMove >= distance) { + return false; + } + return true; + } + + public static double computeDistance(MotionEvent first, MotionEvent second, int pointerIndex) { + return MathUtils.dist(first.getX(pointerIndex), first.getY(pointerIndex), + second.getX(pointerIndex), second.getY(pointerIndex)); + } + + public static boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) { + final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime(); + return (deltaTime >= timeout); + } + + public static boolean isSamePointerContext(MotionEvent first, MotionEvent second) { + return (first.getPointerIdBits() == second.getPointerIdBits() + && first.getPointerId(first.getActionIndex()) + == second.getPointerId(second.getActionIndex())); + } + + /** + * Determines whether a two pointer gesture is a dragging one. + * + * @param event The event with the pointer data. + * @return True if the gesture is a dragging one. + */ + public static boolean isDraggingGesture(float firstPtrDownX, float firstPtrDownY, + float secondPtrDownX, float secondPtrDownY, float firstPtrX, float firstPtrY, + float secondPtrX, float secondPtrY, float maxDraggingAngleCos) { + + // Check if the pointers are moving in the same direction. + final float firstDeltaX = firstPtrX - firstPtrDownX; + final float firstDeltaY = firstPtrY - firstPtrDownY; + + if (firstDeltaX == 0 && firstDeltaY == 0) { + return true; + } + + final float firstMagnitude = + (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY); + final float firstXNormalized = + (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX; + final float firstYNormalized = + (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY; + + final float secondDeltaX = secondPtrX - secondPtrDownX; + final float secondDeltaY = secondPtrY - secondPtrDownY; + + if (secondDeltaX == 0 && secondDeltaY == 0) { + return true; + } + + final float secondMagnitude = + (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY); + final float secondXNormalized = + (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX; + final float secondYNormalized = + (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY; + + final float angleCos = + firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized; + + if (angleCos < maxDraggingAngleCos) { + return false; + } + + return true; + } +} diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java new file mode 100644 index 0000000..48781ac --- /dev/null +++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java @@ -0,0 +1,1798 @@ +/* + * Copyright (C) 2012 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.accessibility; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.animation.ValueAnimator; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff.Mode; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Property; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.Gravity; +import android.view.IDisplayContentChangeListener; +import android.view.IWindowManager; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerCoords; +import android.view.MotionEvent.PointerProperties; +import android.view.ScaleGestureDetector; +import android.view.ScaleGestureDetector.OnScaleGestureListener; +import android.view.Surface; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.WindowInfo; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +import com.android.internal.R; +import com.android.internal.os.SomeArgs; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * This class handles the screen magnification when accessibility is enabled. + * The behavior is as follows: + * + * 1. Triple tap toggles permanent screen magnification which is magnifying + * the area around the location of the triple tap. One can think of the + * location of the triple tap as the center of the magnified viewport. + * For example, a triple tap when not magnified would magnify the screen + * and leave it in a magnified state. A triple tapping when magnified would + * clear magnification and leave the screen in a not magnified state. + * + * 2. Triple tap and hold would magnify the screen if not magnified and enable + * viewport dragging mode until the finger goes up. One can think of this + * mode as a way to move the magnified viewport since the area around the + * moving finger will be magnified to fit the screen. For example, if the + * screen was not magnified and the user triple taps and holds the screen + * would magnify and the viewport will follow the user's finger. When the + * finger goes up the screen will clear zoom out. If the same user interaction + * is performed when the screen is magnified, the viewport movement will + * be the same but when the finger goes up the screen will stay magnified. + * In other words, the initial magnified state is sticky. + * + * 3. Pinching with any number of additional fingers when viewport dragging + * is enabled, i.e. the user triple tapped and holds, would adjust the + * magnification scale which will become the current default magnification + * scale. The next time the user magnifies the same magnification scale + * would be used. + * + * 4. When in a permanent magnified state the user can use two or more fingers + * to pan the viewport. Note that in this mode the content is panned as + * opposed to the viewport dragging mode in which the viewport is moved. + * + * 5. When in a permanent magnified state the user can use three or more + * fingers to change the magnification scale which will become the current + * default magnification scale. The next time the user magnifies the same + * magnification scale would be used. + * + * 6. The magnification scale will be persisted in settings and in the cloud. + */ +public final class ScreenMagnifier implements EventStreamTransformation { + + private static final boolean DEBUG_STATE_TRANSITIONS = false; + private static final boolean DEBUG_DETECTING = false; + private static final boolean DEBUG_TRANSFORMATION = false; + private static final boolean DEBUG_PANNING = false; + private static final boolean DEBUG_SCALING = false; + private static final boolean DEBUG_VIEWPORT_WINDOW = false; + private static final boolean DEBUG_WINDOW_TRANSITIONS = false; + private static final boolean DEBUG_ROTATION = false; + private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false; + + private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName(); + + private static final int STATE_DELEGATING = 1; + private static final int STATE_DETECTING = 2; + private static final int STATE_VIEWPORT_DRAGGING = 3; + private static final int STATE_MAGNIFIED_INTERACTION = 4; + + private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; + private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1; + private static final float DEFAULT_WINDOW_ANIMATION_SCALE = 1.0f; + + private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50; + + private final IWindowManager mWindowManagerService = IWindowManager.Stub.asInterface( + ServiceManager.getService("window")); + private final WindowManager mWindowManager; + private final DisplayProvider mDisplayProvider; + + private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler(); + private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler; + private final StateViewportDraggingHandler mStateViewportDraggingHandler = + new StateViewportDraggingHandler(); + + private final Interpolator mInterpolator = new DecelerateInterpolator(2.5f); + + private final MagnificationController mMagnificationController; + private final DisplayContentObserver mDisplayContentObserver; + private final ScreenStateObserver mScreenStateObserver; + private final Viewport mViewport; + + private final int mTapTimeSlop = ViewConfiguration.getTapTimeout(); + private final int mMultiTapTimeSlop = + ViewConfiguration.getDoubleTapTimeout() - MULTI_TAP_TIME_SLOP_ADJUSTMENT; + private final int mTapDistanceSlop; + private final int mMultiTapDistanceSlop; + + private final int mShortAnimationDuration; + private final int mLongAnimationDuration; + private final float mWindowAnimationScale; + + private final Context mContext; + + private EventStreamTransformation mNext; + + private int mCurrentState; + private int mPreviousState; + private boolean mTranslationEnabledBeforePan; + + private PointerCoords[] mTempPointerCoords; + private PointerProperties[] mTempPointerProperties; + + public ScreenMagnifier(Context context) { + mContext = context; + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + + mShortAnimationDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_shortAnimTime); + mLongAnimationDuration = context.getResources().getInteger( + com.android.internal.R.integer.config_longAnimTime); + mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); + mWindowAnimationScale = Settings.System.getFloat(context.getContentResolver(), + Settings.System.WINDOW_ANIMATION_SCALE, DEFAULT_WINDOW_ANIMATION_SCALE); + + mMagnificationController = new MagnificationController(mShortAnimationDuration); + mDisplayProvider = new DisplayProvider(context, mWindowManager); + mViewport = new Viewport(mContext, mWindowManager, mWindowManagerService, + mDisplayProvider, mInterpolator, mShortAnimationDuration); + mDisplayContentObserver = new DisplayContentObserver(mContext, mViewport, + mMagnificationController, mWindowManagerService, mDisplayProvider, + mLongAnimationDuration, mWindowAnimationScale); + mScreenStateObserver = new ScreenStateObserver(mContext, mViewport, + mMagnificationController); + + mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler( + context); + + transitionToState(STATE_DETECTING); + } + + @Override + public void onMotionEvent(MotionEvent event, int policyFlags) { + mMagnifiedContentInteractonStateHandler.onMotionEvent(event); + switch (mCurrentState) { + case STATE_DELEGATING: { + handleMotionEventStateDelegating(event, policyFlags); + } break; + case STATE_DETECTING: { + mDetectingStateHandler.onMotionEvent(event, policyFlags); + } break; + case STATE_VIEWPORT_DRAGGING: { + mStateViewportDraggingHandler.onMotionEvent(event, policyFlags); + } break; + case STATE_MAGNIFIED_INTERACTION: { + // mMagnifiedContentInteractonStateHandler handles events only + // if this is the current state since it uses ScaleGestureDetecotr + // and a GestureDetector which need well formed event stream. + } break; + default: { + throw new IllegalStateException("Unknown state: " + mCurrentState); + } + } + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } + } + + @Override + public void setNext(EventStreamTransformation next) { + mNext = next; + } + + @Override + public void clear() { + mCurrentState = STATE_DETECTING; + mDetectingStateHandler.clear(); + mStateViewportDraggingHandler.clear(); + mMagnifiedContentInteractonStateHandler.clear(); + if (mNext != null) { + mNext.clear(); + } + } + + @Override + public void onDestroy() { + mMagnificationController.setScaleAndMagnifiedRegionCenter(1.0f, + 0, 0, true); + mViewport.setFrameShown(false, true); + mDisplayProvider.destroy(); + mDisplayContentObserver.destroy(); + mScreenStateObserver.destroy(); + } + + private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + if (mDetectingStateHandler.mDelayedEventQueue == null) { + transitionToState(STATE_DETECTING); + } + } + if (mNext != null) { + // If the event is within the magnified portion of the screen we have + // to change its location to be where the user thinks he is poking the + // UI which may have been magnified and panned. + final float eventX = event.getX(); + final float eventY = event.getY(); + if (mMagnificationController.isMagnifying() + && mViewport.getBounds().contains((int) eventX, (int) eventY)) { + final float scale = mMagnificationController.getScale(); + final float scaledOffsetX = mMagnificationController.getScaledOffsetX(); + final float scaledOffsetY = mMagnificationController.getScaledOffsetY(); + final int pointerCount = event.getPointerCount(); + PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount); + PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount); + for (int i = 0; i < pointerCount; i++) { + event.getPointerCoords(i, coords[i]); + coords[i].x = (coords[i].x - scaledOffsetX) / scale; + coords[i].y = (coords[i].y - scaledOffsetY) / scale; + event.getPointerProperties(i, properties[i]); + } + event = MotionEvent.obtain(event.getDownTime(), + event.getEventTime(), event.getAction(), pointerCount, properties, + coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(), + event.getFlags()); + } + mNext.onMotionEvent(event, policyFlags); + } + } + + private PointerCoords[] getTempPointerCoordsWithMinSize(int size) { + final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0; + if (oldSize < size) { + PointerCoords[] oldTempPointerCoords = mTempPointerCoords; + mTempPointerCoords = new PointerCoords[size]; + if (oldTempPointerCoords != null) { + System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize); + } + } + for (int i = oldSize; i < size; i++) { + mTempPointerCoords[i] = new PointerCoords(); + } + return mTempPointerCoords; + } + + private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) { + final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0; + if (oldSize < size) { + PointerProperties[] oldTempPointerProperties = mTempPointerProperties; + mTempPointerProperties = new PointerProperties[size]; + if (oldTempPointerProperties != null) { + System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize); + } + } + for (int i = oldSize; i < size; i++) { + mTempPointerProperties[i] = new PointerProperties(); + } + return mTempPointerProperties; + } + + private void transitionToState(int state) { + if (DEBUG_STATE_TRANSITIONS) { + switch (state) { + case STATE_DELEGATING: { + Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING"); + } break; + case STATE_DETECTING: { + Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING"); + } break; + case STATE_VIEWPORT_DRAGGING: { + Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING"); + } break; + case STATE_MAGNIFIED_INTERACTION: { + Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION"); + } break; + default: { + throw new IllegalArgumentException("Unknown state: " + state); + } + } + } + mPreviousState = mCurrentState; + mCurrentState = state; + } + + private final class MagnifiedContentInteractonStateHandler + extends SimpleOnGestureListener implements OnScaleGestureListener { + private static final float MIN_SCALE = 1.3f; + private static final float MAX_SCALE = 5.0f; + + private static final float SCALING_THRESHOLD = 0.3f; + + private final ScaleGestureDetector mScaleGestureDetector; + private final GestureDetector mGestureDetector; + + private float mInitialScaleFactor = -1; + private boolean mScaling; + + public MagnifiedContentInteractonStateHandler(Context context) { + mScaleGestureDetector = new ScaleGestureDetector(context, this); + mGestureDetector = new GestureDetector(context, this); + } + + public void onMotionEvent(MotionEvent event) { + mScaleGestureDetector.onTouchEvent(event); + mGestureDetector.onTouchEvent(event); + if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { + return; + } + if (event.getActionMasked() == MotionEvent.ACTION_UP) { + clear(); + final float scale = mMagnificationController.getScale(); + if (scale != getPersistedScale()) { + persistScale(scale); + } + if (mPreviousState == STATE_VIEWPORT_DRAGGING) { + transitionToState(STATE_VIEWPORT_DRAGGING); + } else { + transitionToState(STATE_DETECTING); + } + } + } + + @Override + public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX, + float distanceY) { + if (mCurrentState != STATE_MAGNIFIED_INTERACTION) { + return true; + } + final float scale = mMagnificationController.getScale(); + final float scrollX = distanceX / scale; + final float scrollY = distanceY / scale; + final float centerX = mMagnificationController.getMagnifiedRegionCenterX() + scrollX; + final float centerY = mMagnificationController.getMagnifiedRegionCenterY() + scrollY; + if (DEBUG_PANNING) { + Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX + + " scrollY: " + scrollY); + } + mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false); + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + if (!mScaling) { + if (mInitialScaleFactor < 0) { + mInitialScaleFactor = detector.getScaleFactor(); + } else { + final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor; + if (Math.abs(deltaScale) > SCALING_THRESHOLD) { + mScaling = true; + return true; + } + } + return false; + } + final float newScale = mMagnificationController.getScale() + * detector.getScaleFactor(); + final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE); + if (DEBUG_SCALING) { + Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale); + } + mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(), + detector.getFocusY(), false); + return true; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + return (mCurrentState == STATE_MAGNIFIED_INTERACTION); + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + clear(); + } + + private void clear() { + mInitialScaleFactor = -1; + mScaling = false; + } + } + + private final class StateViewportDraggingHandler { + private boolean mLastMoveOutsideMagnifiedRegion; + + private void onMotionEvent(MotionEvent event, int policyFlags) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN"); + } + case MotionEvent.ACTION_POINTER_DOWN: { + clear(); + transitionToState(STATE_MAGNIFIED_INTERACTION); + } break; + case MotionEvent.ACTION_MOVE: { + if (event.getPointerCount() != 1) { + throw new IllegalStateException("Should have one pointer down."); + } + final float eventX = event.getX(); + final float eventY = event.getY(); + if (mViewport.getBounds().contains((int) eventX, (int) eventY)) { + if (mLastMoveOutsideMagnifiedRegion) { + mLastMoveOutsideMagnifiedRegion = false; + mMagnificationController.setMagnifiedRegionCenter(eventX, + eventY, true); + } else { + mMagnificationController.setMagnifiedRegionCenter(eventX, + eventY, false); + } + } else { + mLastMoveOutsideMagnifiedRegion = true; + } + } break; + case MotionEvent.ACTION_UP: { + if (!mTranslationEnabledBeforePan) { + mMagnificationController.reset(true); + mViewport.setFrameShown(false, true); + } + clear(); + transitionToState(STATE_DETECTING); + } break; + case MotionEvent.ACTION_POINTER_UP: { + throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP"); + } + } + } + + public void clear() { + mLastMoveOutsideMagnifiedRegion = false; + } + } + + private final class DetectingStateHandler { + + private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1; + + private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; + + private static final int ACTION_TAP_COUNT = 3; + + private MotionEventInfo mDelayedEventQueue; + + private MotionEvent mLastDownEvent; + private MotionEvent mLastTapUpEvent; + private int mTapCount; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message message) { + final int type = message.what; + switch (type) { + case MESSAGE_ON_ACTION_TAP_AND_HOLD: { + MotionEvent event = (MotionEvent) message.obj; + final int policyFlags = message.arg1; + onActionTapAndHold(event, policyFlags); + } break; + case MESSAGE_TRANSITION_TO_DELEGATING_STATE: { + transitionToState(STATE_DELEGATING); + sendDelayedMotionEvents(); + clear(); + } break; + default: { + throw new IllegalArgumentException("Unknown message type: " + type); + } + } + } + }; + + public void onMotionEvent(MotionEvent event, int policyFlags) { + cacheDelayedMotionEvent(event, policyFlags); + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + if (!mViewport.getBounds().contains((int) event.getX(), + (int) event.getY())) { + transitionToDelegatingStateAndClear(); + return; + } + if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null + && GestureUtils.isMultiTap(mLastDownEvent, event, + mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { + Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, + policyFlags, 0, event); + mHandler.sendMessageDelayed(message, + ViewConfiguration.getLongPressTimeout()); + } else if (mTapCount < ACTION_TAP_COUNT) { + Message message = mHandler.obtainMessage( + MESSAGE_TRANSITION_TO_DELEGATING_STATE); + mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); + } + clearLastDownEvent(); + mLastDownEvent = MotionEvent.obtain(event); + } break; + case MotionEvent.ACTION_POINTER_DOWN: { + if (mMagnificationController.isMagnifying()) { + transitionToState(STATE_MAGNIFIED_INTERACTION); + clear(); + } else { + transitionToDelegatingStateAndClear(); + } + } break; + case MotionEvent.ACTION_MOVE: { + if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) { + final double distance = GestureUtils.computeDistance(mLastDownEvent, + event, 0); + if (Math.abs(distance) > mTapDistanceSlop) { + transitionToDelegatingStateAndClear(); + } + } + } break; + case MotionEvent.ACTION_UP: { + if (mLastDownEvent == null) { + return; + } + mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); + if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) { + transitionToDelegatingStateAndClear(); + return; + } + if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, + mTapDistanceSlop, 0)) { + transitionToDelegatingStateAndClear(); + return; + } + if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent, + event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { + transitionToDelegatingStateAndClear(); + return; + } + mTapCount++; + if (DEBUG_DETECTING) { + Slog.i(LOG_TAG, "Tap count:" + mTapCount); + } + if (mTapCount == ACTION_TAP_COUNT) { + clear(); + onActionTap(event, policyFlags); + return; + } + clearLastTapUpEvent(); + mLastTapUpEvent = MotionEvent.obtain(event); + } break; + case MotionEvent.ACTION_POINTER_UP: { + /* do nothing */ + } break; + } + } + + public void clear() { + mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); + clearTapDetectionState(); + clearDelayedMotionEvents(); + } + + private void clearTapDetectionState() { + mTapCount = 0; + clearLastTapUpEvent(); + clearLastDownEvent(); + } + + private void clearLastTapUpEvent() { + if (mLastTapUpEvent != null) { + mLastTapUpEvent.recycle(); + mLastTapUpEvent = null; + } + } + + private void clearLastDownEvent() { + if (mLastDownEvent != null) { + mLastDownEvent.recycle(); + mLastDownEvent = null; + } + } + + private void cacheDelayedMotionEvent(MotionEvent event, int policyFlags) { + MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags); + if (mDelayedEventQueue == null) { + mDelayedEventQueue = info; + } else { + MotionEventInfo tail = mDelayedEventQueue; + while (tail.mNext != null) { + tail = tail.mNext; + } + tail.mNext = info; + } + } + + private void sendDelayedMotionEvents() { + while (mDelayedEventQueue != null) { + MotionEventInfo info = mDelayedEventQueue; + mDelayedEventQueue = info.mNext; + ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mPolicyFlags); + info.recycle(); + } + } + + private void clearDelayedMotionEvents() { + while (mDelayedEventQueue != null) { + MotionEventInfo info = mDelayedEventQueue; + mDelayedEventQueue = info.mNext; + info.recycle(); + } + } + + private void transitionToDelegatingStateAndClear() { + transitionToState(STATE_DELEGATING); + sendDelayedMotionEvents(); + clear(); + } + + private void onActionTap(MotionEvent up, int policyFlags) { + if (DEBUG_DETECTING) { + Slog.i(LOG_TAG, "onActionTap()"); + } + if (!mMagnificationController.isMagnifying()) { + mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), + up.getX(), up.getY(), true); + mViewport.setFrameShown(true, true); + } else { + mMagnificationController.reset(true); + mViewport.setFrameShown(false, true); + } + } + + private void onActionTapAndHold(MotionEvent down, int policyFlags) { + if (DEBUG_DETECTING) { + Slog.i(LOG_TAG, "onActionTapAndHold()"); + } + clear(); + mTranslationEnabledBeforePan = mMagnificationController.isMagnifying(); + mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), + down.getX(), down.getY(), true); + mViewport.setFrameShown(true, true); + transitionToState(STATE_VIEWPORT_DRAGGING); + } + } + + private void persistScale(final float scale) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + Settings.Secure.putFloat(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale); + return null; + } + }.execute(); + } + + private float getPersistedScale() { + return Settings.Secure.getFloat(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, + DEFAULT_MAGNIFICATION_SCALE); + } + + private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) { + return (Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, + DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1); + } + + private static final class MotionEventInfo { + + private static final int MAX_POOL_SIZE = 10; + + private static final Object sLock = new Object(); + private static MotionEventInfo sPool; + private static int sPoolSize; + + private MotionEventInfo mNext; + private boolean mInPool; + + public MotionEvent mEvent; + public int mPolicyFlags; + + public static MotionEventInfo obtain(MotionEvent event, int policyFlags) { + synchronized (sLock) { + MotionEventInfo info; + if (sPoolSize > 0) { + sPoolSize--; + info = sPool; + sPool = info.mNext; + info.mNext = null; + info.mInPool = false; + } else { + info = new MotionEventInfo(); + } + info.initialize(event, policyFlags); + return info; + } + } + + private void initialize(MotionEvent event, int policyFlags) { + mEvent = MotionEvent.obtain(event); + mPolicyFlags = policyFlags; + } + + public void recycle() { + synchronized (sLock) { + if (mInPool) { + throw new IllegalStateException("Already recycled."); + } + clear(); + if (sPoolSize < MAX_POOL_SIZE) { + sPoolSize++; + mNext = sPool; + sPool = this; + mInPool = true; + } + } + } + + private void clear() { + mEvent.recycle(); + mEvent = null; + mPolicyFlags = 0; + } + } + + private static final class ScreenStateObserver extends BroadcastReceiver { + + private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MESSAGE_ON_SCREEN_STATE_CHANGE: { + String action = (String) message.obj; + handleOnScreenStateChange(action); + } break; + } + } + }; + + private final Context mContext; + private final Viewport mViewport; + private final MagnificationController mMagnificationController; + + public ScreenStateObserver(Context context, Viewport viewport, + MagnificationController magnificationController) { + mContext = context; + mViewport = viewport; + mMagnificationController = magnificationController; + mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + } + + public void destroy() { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE, + intent.getAction()).sendToTarget(); + } + + private void handleOnScreenStateChange(String action) { + if (action.equals(Intent.ACTION_SCREEN_OFF) + && mMagnificationController.isMagnifying() + && isScreenMagnificationAutoUpdateEnabled(mContext)) { + mMagnificationController.reset(false); + mViewport.setFrameShown(false, false); + } + } + } + + private static final class DisplayContentObserver { + + private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1; + private static final int MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS = 2; + private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 3; + private static final int MESSAGE_ON_WINDOW_TRANSITION = 4; + private static final int MESSAGE_ON_ROTATION_CHANGED = 5; + + private final Handler mHandler = new MyHandler(); + + private final Rect mTempRect = new Rect(); + + private final IDisplayContentChangeListener mDisplayContentChangeListener; + + private final Context mContext; + private final Viewport mViewport; + private final MagnificationController mMagnificationController; + private final IWindowManager mWindowManagerService; + private final DisplayProvider mDisplayProvider; + private final long mLongAnimationDuration; + private final float mWindowAnimationScale; + + public DisplayContentObserver(Context context, Viewport viewport, + MagnificationController magnificationController, + IWindowManager windowManagerService, DisplayProvider displayProvider, + long longAnimationDuration, float windowAnimationScale) { + mContext = context; + mViewport = viewport; + mMagnificationController = magnificationController; + mWindowManagerService = windowManagerService; + mDisplayProvider = displayProvider; + mLongAnimationDuration = longAnimationDuration; + mWindowAnimationScale = windowAnimationScale; + + mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() { + @Override + public void onWindowTransition(int displayId, int transition, WindowInfo info) { + mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION, transition, 0, + WindowInfo.obtain(info)).sendToTarget(); + } + + @Override + public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle, + boolean immediate) { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = rectangle.left; + args.argi2 = rectangle.top; + args.argi3 = rectangle.right; + args.argi4 = rectangle.bottom; + mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0, + immediate ? 1 : 0, args).sendToTarget(); + } + + @Override + public void onRotationChanged(int rotation) throws RemoteException { + mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0) + .sendToTarget(); + } + }; + + try { + mWindowManagerService.addDisplayContentChangeListener( + mDisplayProvider.getDisplay().getDisplayId(), + mDisplayContentChangeListener); + } catch (RemoteException re) { + /* ignore */ + } + } + + public void destroy() { + try { + mWindowManagerService.removeDisplayContentChangeListener( + mDisplayProvider.getDisplay().getDisplayId(), + mDisplayContentChangeListener); + } catch (RemoteException re) { + /* ignore*/ + } + } + + private void handleOnRotationChanged(int rotation) { + if (DEBUG_ROTATION) { + Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation)); + } + resetMagnificationIfNeeded(); + mViewport.setFrameShown(false, false); + mViewport.rotationChanged(); + mViewport.recomputeBounds(false); + if (mMagnificationController.isMagnifying()) { + final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale); + Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME); + mHandler.sendMessageDelayed(message, delay); + } + } + + private void handleOnWindowTransition(int transition, WindowInfo info) { + if (DEBUG_WINDOW_TRANSITIONS) { + Slog.i(LOG_TAG, "Window transitioning: " + + windowTransitionToString(transition)); + } + try { + final boolean magnifying = mMagnificationController.isMagnifying(); + if (magnifying) { + switch (transition) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: + case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: + case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: + case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: { + resetMagnificationIfNeeded(); + } + } + } + if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR + || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD + || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG + || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD + || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) { + switch (transition) { + case WindowManagerPolicy.TRANSIT_ENTER: + case WindowManagerPolicy.TRANSIT_SHOW: + case WindowManagerPolicy.TRANSIT_EXIT: + case WindowManagerPolicy.TRANSIT_HIDE: { + mViewport.recomputeBounds(mMagnificationController.isMagnifying()); + } break; + } + } else { + switch (transition) { + case WindowManagerPolicy.TRANSIT_ENTER: + case WindowManagerPolicy.TRANSIT_SHOW: { + if (!magnifying || !isScreenMagnificationAutoUpdateEnabled(mContext)) { + break; + } + final int type = info.type; + switch (type) { + // TODO: Are these all the windows we want to make + // visible when they appear on the screen? + // Do we need to take some of them out? + case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: + case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: + case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: + case WindowManager.LayoutParams.TYPE_SEARCH_BAR: + case WindowManager.LayoutParams.TYPE_PHONE: + case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: + case WindowManager.LayoutParams.TYPE_TOAST: + case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: + case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: + case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: + case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: + case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: + case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: + case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: { + Rect magnifiedRegionBounds = mMagnificationController + .getMagnifiedRegionBounds(); + Rect touchableRegion = info.touchableRegion; + if (!magnifiedRegionBounds.intersect(touchableRegion)) { + ensureRectangleInMagnifiedRegionBounds( + magnifiedRegionBounds, touchableRegion); + } + } break; + } break; + } + } + } + } finally { + if (info != null) { + info.recycle(); + } + } + } + + private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) { + if (!mMagnificationController.isMagnifying()) { + return; + } + Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds(); + if (magnifiedRegionBounds.contains(rectangle)) { + return; + } + ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle); + } + + private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds, + Rect rectangle) { + if (!Rect.intersects(rectangle, mViewport.getBounds())) { + return; + } + final float scrollX; + final float scrollY; + if (rectangle.width() > magnifiedRegionBounds.width()) { + scrollX = rectangle.left - magnifiedRegionBounds.left; + } else if (rectangle.left < magnifiedRegionBounds.left) { + scrollX = rectangle.left - magnifiedRegionBounds.left; + } else if (rectangle.right > magnifiedRegionBounds.right) { + scrollX = rectangle.right - magnifiedRegionBounds.right; + } else { + scrollX = 0; + } + if (rectangle.height() > magnifiedRegionBounds.height()) { + scrollY = rectangle.top - magnifiedRegionBounds.top; + } else if (rectangle.top < magnifiedRegionBounds.top) { + scrollY = rectangle.top - magnifiedRegionBounds.top; + } else if (rectangle.bottom > magnifiedRegionBounds.bottom) { + scrollY = rectangle.bottom - magnifiedRegionBounds.bottom; + } else { + scrollY = 0; + } + final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX() + + scrollX; + final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY() + + scrollY; + mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY, + true); + } + + private void resetMagnificationIfNeeded() { + if (mMagnificationController.isMagnifying() + && isScreenMagnificationAutoUpdateEnabled(mContext)) { + mMagnificationController.reset(true); + mViewport.setFrameShown(false, true); + } + } + + private String windowTransitionToString(int transition) { + switch (transition) { + case WindowManagerPolicy.TRANSIT_UNSET: { + return "TRANSIT_UNSET"; + } + case WindowManagerPolicy.TRANSIT_NONE: { + return "TRANSIT_NONE"; + } + case WindowManagerPolicy.TRANSIT_ENTER: { + return "TRANSIT_ENTER"; + } + case WindowManagerPolicy.TRANSIT_EXIT: { + return "TRANSIT_EXIT"; + } + case WindowManagerPolicy.TRANSIT_SHOW: { + return "TRANSIT_SHOW"; + } + case WindowManagerPolicy.TRANSIT_EXIT_MASK: { + return "TRANSIT_EXIT_MASK"; + } + case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: { + return "TRANSIT_PREVIEW_DONE"; + } + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: { + return "TRANSIT_ACTIVITY_OPEN"; + } + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: { + return "TRANSIT_ACTIVITY_CLOSE"; + } + case WindowManagerPolicy.TRANSIT_TASK_OPEN: { + return "TRANSIT_TASK_OPEN"; + } + case WindowManagerPolicy.TRANSIT_TASK_CLOSE: { + return "TRANSIT_TASK_CLOSE"; + } + case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: { + return "TRANSIT_TASK_TO_FRONT"; + } + case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: { + return "TRANSIT_TASK_TO_BACK"; + } + case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: { + return "TRANSIT_WALLPAPER_CLOSE"; + } + case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: { + return "TRANSIT_WALLPAPER_OPEN"; + } + case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: { + return "TRANSIT_WALLPAPER_INTRA_OPEN"; + } + case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: { + return "TRANSIT_WALLPAPER_INTRA_CLOSE"; + } + default: { + return "<UNKNOWN>"; + } + } + } + + private String rotationToString(int rotation) { + switch (rotation) { + case Surface.ROTATION_0: { + return "ROTATION_0"; + } + case Surface.ROTATION_90: { + return "ROATATION_90"; + } + case Surface.ROTATION_180: { + return "ROATATION_180"; + } + case Surface.ROTATION_270: { + return "ROATATION_270"; + } + default: { + throw new IllegalArgumentException("Invalid rotation: " + + rotation); + } + } + } + + private final class MyHandler extends Handler { + @Override + public void handleMessage(Message message) { + final int action = message.what; + switch (action) { + case MESSAGE_SHOW_VIEWPORT_FRAME: { + mViewport.setFrameShown(true, true); + } break; + case MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS: { + final boolean animate = message.arg1 == 1; + mViewport.recomputeBounds(animate); + } break; + case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: { + SomeArgs args = (SomeArgs) message.obj; + try { + mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4); + final boolean immediate = (message.arg1 == 1); + handleOnRectangleOnScreenRequested(mTempRect, immediate); + } finally { + args.recycle(); + } + } break; + case MESSAGE_ON_WINDOW_TRANSITION: { + final int transition = message.arg1; + WindowInfo info = (WindowInfo) message.obj; + handleOnWindowTransition(transition, info); + } break; + case MESSAGE_ON_ROTATION_CHANGED: { + final int rotation = message.arg1; + handleOnRotationChanged(rotation); + } break; + default: { + throw new IllegalArgumentException("Unknown message: " + action); + } + } + } + } + } + + private final class MagnificationController { + + private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION = + "accessibilityTransformation"; + + private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec(); + + private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec(); + + private final Rect mTempRect = new Rect(); + + private final ValueAnimator mTransformationAnimator; + + public MagnificationController(int animationDuration) { + Property<MagnificationController, MagnificationSpec> property = + Property.of(MagnificationController.class, MagnificationSpec.class, + PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION); + TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() { + private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec(); + @Override + public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec, + MagnificationSpec toSpec) { + MagnificationSpec result = mTempTransformationSpec; + result.mScale = fromSpec.mScale + + (toSpec.mScale - fromSpec.mScale) * fraction; + result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX + + (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX) + * fraction; + result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY + + (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY) + * fraction; + result.mScaledOffsetX = fromSpec.mScaledOffsetX + + (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX) + * fraction; + result.mScaledOffsetY = fromSpec.mScaledOffsetY + + (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY) + * fraction; + return result; + } + }; + mTransformationAnimator = ObjectAnimator.ofObject(this, property, + evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec); + mTransformationAnimator.setDuration((long) (animationDuration)); + mTransformationAnimator.setInterpolator(mInterpolator); + } + + public boolean isMagnifying() { + return mCurrentMagnificationSpec.mScale > 1.0f; + } + + public void reset(boolean animate) { + if (mTransformationAnimator.isRunning()) { + mTransformationAnimator.cancel(); + } + mCurrentMagnificationSpec.reset(); + if (animate) { + animateAccessibilityTranformation(mSentMagnificationSpec, + mCurrentMagnificationSpec); + } else { + setAccessibilityTransformation(mCurrentMagnificationSpec); + } + } + + public Rect getMagnifiedRegionBounds() { + mTempRect.set(mViewport.getBounds()); + mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX, + (int) -mCurrentMagnificationSpec.mScaledOffsetY); + mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale); + return mTempRect; + } + + public float getScale() { + return mCurrentMagnificationSpec.mScale; + } + + public float getMagnifiedRegionCenterX() { + return mCurrentMagnificationSpec.mMagnifiedRegionCenterX; + } + + public float getMagnifiedRegionCenterY() { + return mCurrentMagnificationSpec.mMagnifiedRegionCenterY; + } + + public float getScaledOffsetX() { + return mCurrentMagnificationSpec.mScaledOffsetX; + } + + public float getScaledOffsetY() { + return mCurrentMagnificationSpec.mScaledOffsetY; + } + + public void setScale(float scale, float pivotX, float pivotY, boolean animate) { + MagnificationSpec spec = mCurrentMagnificationSpec; + final float oldScale = spec.mScale; + final float oldCenterX = spec.mMagnifiedRegionCenterX; + final float oldCenterY = spec.mMagnifiedRegionCenterY; + final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale; + final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale; + final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); + final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); + final float centerX = normPivotX + offsetX; + final float centerY = normPivotY + offsetY; + setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate); + } + + public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) { + setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY, + animate); + } + + public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY, + boolean animate) { + if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0 + && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX, + centerX) == 0 + && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY, + centerY) == 0) { + return; + } + if (mTransformationAnimator.isRunning()) { + mTransformationAnimator.cancel(); + } + if (DEBUG_MAGNIFICATION_CONTROLLER) { + Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX + + " centerY: " + centerY); + } + mCurrentMagnificationSpec.initialize(scale, centerX, centerY); + if (animate) { + animateAccessibilityTranformation(mSentMagnificationSpec, + mCurrentMagnificationSpec); + } else { + setAccessibilityTransformation(mCurrentMagnificationSpec); + } + } + + private void animateAccessibilityTranformation(MagnificationSpec fromSpec, + MagnificationSpec toSpec) { + mTransformationAnimator.setObjectValues(fromSpec, toSpec); + mTransformationAnimator.start(); + } + + @SuppressWarnings("unused") + // Called from an animator. + public MagnificationSpec getAccessibilityTransformation() { + return mSentMagnificationSpec; + } + + public void setAccessibilityTransformation(MagnificationSpec transformation) { + if (DEBUG_TRANSFORMATION) { + Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale + + " offsetX: " + transformation.mScaledOffsetX + + " offsetY: " + transformation.mScaledOffsetY); + } + try { + mSentMagnificationSpec.updateFrom(transformation); + mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(), + transformation.mScale, transformation.mScaledOffsetX, + transformation.mScaledOffsetY); + } catch (RemoteException re) { + /* ignore */ + } + } + + private class MagnificationSpec { + + private static final float DEFAULT_SCALE = 1.0f; + + public float mScale = DEFAULT_SCALE; + + public float mMagnifiedRegionCenterX; + + public float mMagnifiedRegionCenterY; + + public float mScaledOffsetX; + + public float mScaledOffsetY; + + public void initialize(float scale, float magnifiedRegionCenterX, + float magnifiedRegionCenterY) { + mScale = scale; + + final int viewportWidth = mViewport.getBounds().width(); + final int viewportHeight = mViewport.getBounds().height(); + final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale; + final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale; + final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX; + final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY; + + mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX, + minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX); + mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY, + minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY); + + mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2); + mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2); + } + + public void updateFrom(MagnificationSpec other) { + mScale = other.mScale; + mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX; + mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY; + mScaledOffsetX = other.mScaledOffsetX; + mScaledOffsetY = other.mScaledOffsetY; + } + + public void reset() { + mScale = DEFAULT_SCALE; + mMagnifiedRegionCenterX = 0; + mMagnifiedRegionCenterY = 0; + mScaledOffsetX = 0; + mScaledOffsetY = 0; + } + } + } + + private static final class Viewport { + + private static final String PROPERTY_NAME_ALPHA = "alpha"; + + private static final String PROPERTY_NAME_BOUNDS = "bounds"; + + private static final int MIN_ALPHA = 0; + + private static final int MAX_ALPHA = 255; + + private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>(); + + private final Rect mTempRect1 = new Rect(); + private final Rect mTempRect2 = new Rect(); + private final Rect mTempRect3 = new Rect(); + + private final IWindowManager mWindowManagerService; + private final DisplayProvider mDisplayProvider; + + private final ViewportWindow mViewportFrame; + + private final ValueAnimator mResizeFrameAnimator; + + private final ValueAnimator mShowHideFrameAnimator; + + public Viewport(Context context, WindowManager windowManager, + IWindowManager windowManagerService, DisplayProvider displayInfoProvider, + Interpolator animationInterpolator, long animationDuration) { + mWindowManagerService = windowManagerService; + mDisplayProvider = displayInfoProvider; + mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider); + + mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA, + MIN_ALPHA, MAX_ALPHA); + mShowHideFrameAnimator.setInterpolator(animationInterpolator); + mShowHideFrameAnimator.setDuration(animationDuration); + mShowHideFrameAnimator.addListener(new AnimatorListener() { + @Override + public void onAnimationEnd(Animator animation) { + if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) { + mViewportFrame.hide(); + } + } + @Override + public void onAnimationStart(Animator animation) { + /* do nothing - stub */ + } + @Override + public void onAnimationCancel(Animator animation) { + /* do nothing - stub */ + } + @Override + public void onAnimationRepeat(Animator animation) { + /* do nothing - stub */ + } + }); + + Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class, + Rect.class, PROPERTY_NAME_BOUNDS); + TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() { + private final Rect mReusableResultRect = new Rect(); + @Override + public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) { + Rect result = mReusableResultRect; + result.left = (int) (fromFrame.left + + (toFrame.left - fromFrame.left) * fraction); + result.top = (int) (fromFrame.top + + (toFrame.top - fromFrame.top) * fraction); + result.right = (int) (fromFrame.right + + (toFrame.right - fromFrame.right) * fraction); + result.bottom = (int) (fromFrame.bottom + + (toFrame.bottom - fromFrame.bottom) * fraction); + return result; + } + }; + mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property, + evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds); + mResizeFrameAnimator.setDuration((long) (animationDuration)); + mResizeFrameAnimator.setInterpolator(animationInterpolator); + + recomputeBounds(false); + } + + private final Comparator<WindowInfo> mWindowInfoInverseComparator = + new Comparator<WindowInfo>() { + @Override + public int compare(WindowInfo lhs, WindowInfo rhs) { + if (lhs.layer != rhs.layer) { + return rhs.layer - lhs.layer; + } + if (lhs.touchableRegion.top != rhs.touchableRegion.top) { + return rhs.touchableRegion.top - lhs.touchableRegion.top; + } + if (lhs.touchableRegion.left != rhs.touchableRegion.left) { + return rhs.touchableRegion.left - lhs.touchableRegion.left; + } + if (lhs.touchableRegion.right != rhs.touchableRegion.right) { + return rhs.touchableRegion.right - lhs.touchableRegion.right; + } + if (lhs.touchableRegion.bottom != rhs.touchableRegion.bottom) { + return rhs.touchableRegion.bottom - lhs.touchableRegion.bottom; + } + return 0; + } + }; + + public void recomputeBounds(boolean animate) { + Rect magnifiedFrame = mTempRect1; + magnifiedFrame.set(0, 0, 0, 0); + + Rect notMagnifiedFrame = mTempRect2; + notMagnifiedFrame.set(0, 0, 0, 0); + + ArrayList<WindowInfo> infos = mTempWindowInfoList; + infos.clear(); + int windowCount = 0; + try { + mWindowManagerService.getVisibleWindowsForDisplay( + mDisplayProvider.getDisplay().getDisplayId(), infos); + Collections.sort(infos, mWindowInfoInverseComparator); + windowCount = infos.size(); + for (int i = 0; i < windowCount; i++) { + WindowInfo info = infos.get(i); + if (info.type == WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) { + continue; + } + if (isWindowMagnified(info.type)) { + Rect clippedFrame = mTempRect3; + clippedFrame.set(info.touchableRegion); + subtract(clippedFrame, notMagnifiedFrame); + magnifiedFrame.union(clippedFrame); + } else { + Rect clippedFrame = mTempRect3; + clippedFrame.set(info.touchableRegion); + subtract(clippedFrame, magnifiedFrame); + notMagnifiedFrame.union(clippedFrame); + } + if (magnifiedFrame.bottom >= notMagnifiedFrame.top) { + break; + } + } + } catch (RemoteException re) { + /* ignore */ + } finally { + for (int i = windowCount - 1; i >= 0; i--) { + infos.remove(i).recycle(); + } + } + + final int displayWidth = mDisplayProvider.getDisplayInfo().logicalWidth; + final int displayHeight = mDisplayProvider.getDisplayInfo().logicalHeight; + magnifiedFrame.intersect(0, 0, displayWidth, displayHeight); + + resize(magnifiedFrame, animate); + } + + private boolean isWindowMagnified(int type) { + return (type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR + && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD + && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); + } + + public void rotationChanged() { + mViewportFrame.rotationChanged(); + } + + public Rect getBounds() { + return mViewportFrame.getBounds(); + } + + public void setFrameShown(boolean shown, boolean animate) { + if (mViewportFrame.isShown() == shown) { + return; + } + if (animate) { + if (mShowHideFrameAnimator.isRunning()) { + mShowHideFrameAnimator.reverse(); + } else { + if (shown) { + mViewportFrame.show(); + mShowHideFrameAnimator.start(); + } else { + mShowHideFrameAnimator.reverse(); + } + } + } else { + mShowHideFrameAnimator.cancel(); + if (shown) { + mViewportFrame.show(); + } else { + mViewportFrame.hide(); + } + } + } + + private void resize(Rect bounds, boolean animate) { + if (mViewportFrame.getBounds().equals(bounds)) { + return; + } + if (animate) { + if (mResizeFrameAnimator.isRunning()) { + mResizeFrameAnimator.cancel(); + } + mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds); + mResizeFrameAnimator.start(); + } else { + mViewportFrame.setBounds(bounds); + } + } + + private boolean subtract(Rect lhs, Rect rhs) { + if (lhs.right < rhs.left || lhs.left > rhs.right + || lhs.bottom < rhs.top || lhs.top > rhs.bottom) { + return false; + } + if (lhs.left < rhs.left) { + lhs.right = rhs.left; + } + if (lhs.top < rhs.top) { + lhs.bottom = rhs.top; + } + if (lhs.right > rhs.right) { + lhs.left = rhs.right; + } + if (lhs.bottom > rhs.bottom) { + lhs.top = rhs.bottom; + } + return true; + } + + private static final class ViewportWindow { + private static final String WINDOW_TITLE = "Magnification Overlay"; + + private final WindowManager mWindowManager; + private final DisplayProvider mDisplayProvider; + + private final ContentView mWindowContent; + private final WindowManager.LayoutParams mWindowParams; + + private final Rect mBounds = new Rect(); + private boolean mShown; + private int mAlpha; + + public ViewportWindow(Context context, WindowManager windowManager, + DisplayProvider displayProvider) { + mWindowManager = windowManager; + mDisplayProvider = displayProvider; + + ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mWindowContent = new ContentView(context); + mWindowContent.setLayoutParams(contentParams); + mWindowContent.setBackgroundColor(R.color.transparent); + + mWindowParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY); + mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + mWindowParams.setTitle(WINDOW_TITLE); + mWindowParams.gravity = Gravity.CENTER; + mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth; + mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight; + mWindowParams.format = PixelFormat.TRANSLUCENT; + } + + public boolean isShown() { + return mShown; + } + + public void show() { + if (mShown) { + return; + } + mShown = true; + mWindowManager.addView(mWindowContent, mWindowParams); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportWindow shown."); + } + } + + public void hide() { + if (!mShown) { + return; + } + mShown = false; + mWindowManager.removeView(mWindowContent); + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportWindow hidden."); + } + } + + @SuppressWarnings("unused") + // Called reflectively from an animator. + public int getAlpha() { + return mAlpha; + } + + @SuppressWarnings("unused") + // Called reflectively from an animator. + public void setAlpha(int alpha) { + if (mAlpha == alpha) { + return; + } + mAlpha = alpha; + if (mShown) { + mWindowContent.invalidate(); + } + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha); + } + } + + public Rect getBounds() { + return mBounds; + } + + public void rotationChanged() { + mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth; + mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight; + if (mShown) { + mWindowManager.updateViewLayout(mWindowContent, mWindowParams); + } + } + + public void setBounds(Rect bounds) { + if (mBounds.equals(bounds)) { + return; + } + mBounds.set(bounds); + if (mShown) { + mWindowContent.invalidate(); + } + if (DEBUG_VIEWPORT_WINDOW) { + Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds); + } + } + + private final class ContentView extends View { + private final Drawable mHighlightFrame; + + public ContentView(Context context) { + super(context); + mHighlightFrame = context.getResources().getDrawable( + R.drawable.magnified_region_frame); + } + + @Override + public void onDraw(Canvas canvas) { + canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); + mHighlightFrame.setBounds(mBounds); + mHighlightFrame.setAlpha(mAlpha); + mHighlightFrame.draw(canvas); + } + } + } + } + + private static class DisplayProvider implements DisplayListener { + private final WindowManager mWindowManager; + private final DisplayManager mDisplayManager; + private final Display mDefaultDisplay; + private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); + + public DisplayProvider(Context context, WindowManager windowManager) { + mWindowManager = windowManager; + mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + mDefaultDisplay = mWindowManager.getDefaultDisplay(); + mDisplayManager.registerDisplayListener(this, null); + updateDisplayInfo(); + } + + public DisplayInfo getDisplayInfo() { + return mDefaultDisplayInfo; + } + + public Display getDisplay() { + return mDefaultDisplay; + } + + private void updateDisplayInfo() { + if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { + Slog.e(LOG_TAG, "Default display is not valid."); + } + } + + public void destroy() { + mDisplayManager.unregisterDisplayListener(this); + } + + @Override + public void onDisplayAdded(int displayId) { + /* do noting */ + } + + @Override + public void onDisplayRemoved(int displayId) { + // Having no default display + } + + @Override + public void onDisplayChanged(int displayId) { + updateDisplayInfo(); + } + } +} diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index 48c6b2a..cb6b31a 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -25,6 +25,7 @@ import android.gesture.GestureStore; import android.gesture.GestureStroke; import android.gesture.Prediction; import android.graphics.Rect; +import android.os.Build; import android.os.Handler; import android.os.SystemClock; import android.util.Slog; @@ -35,9 +36,9 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import com.android.internal.R; -import com.android.server.input.InputFilter; import java.util.ArrayList; import java.util.Arrays; @@ -64,7 +65,7 @@ import java.util.Arrays; * * @hide */ -public class TouchExplorer { +class TouchExplorer implements EventStreamTransformation { private static final boolean DEBUG = false; @@ -120,10 +121,6 @@ public class TouchExplorer { // Slop between the first and second tap to be a double tap. private final int mDoubleTapSlop; - // The InputFilter this tracker is associated with i.e. the filter - // which delegates event processing to this touch explorer. - private final InputFilter mInputFilter; - // The current state of the touch explorer. private int mCurrentState = STATE_TOUCH_EXPLORING; @@ -155,6 +152,9 @@ public class TouchExplorer { // The scaled velocity above which we detect gestures. private final int mScaledGestureDetectionVelocity; + // The handler to which to delegate events. + private EventStreamTransformation mNext; + // Helper to track gesture velocity. private VelocityTracker mVelocityTracker; @@ -170,6 +170,9 @@ public class TouchExplorer { // Temporary rectangle to avoid instantiation. private final Rect mTempRect = new Rect(); + // Context in which this explorer operates. + private final Context mContext; + // The X of the previous event. private float mPreviousX; @@ -200,20 +203,25 @@ public class TouchExplorer { // The id of the last touch explored window. private int mLastTouchedWindowId; + // Whether touch exploration gesture has ended. + private boolean mTouchExplorationGestureEnded; + + // Whether touch interaction has ended. + private boolean mTouchInteractionEnded; + /** * Creates a new instance. * * @param inputFilter The input filter associated with this explorer. * @param context A context handle for accessing resources. */ - public TouchExplorer(InputFilter inputFilter, Context context, - AccessibilityManagerService service) { + public TouchExplorer(Context context, AccessibilityManagerService service) { + mContext = context; mAms = service; mReceivedPointerTracker = new ReceivedPointerTracker(context); mInjectedPointerTracker = new InjectedPointerTracker(); - mInputFilter = inputFilter; mTapTimeout = ViewConfiguration.getTapTimeout(); - mDetermineUserIntentTimeout = (int) (mTapTimeout * 1.5f); + mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout(); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); @@ -242,7 +250,11 @@ public class TouchExplorer { } } - public void clear(MotionEvent event, int policyFlags) { + public void onDestroy() { + // TODO: Implement + } + + private void clear(MotionEvent event, int policyFlags) { switch (mCurrentState) { case STATE_TOUCH_EXPLORING: { // If a touch exploration gesture is in progress send events for its end. @@ -278,8 +290,17 @@ public class TouchExplorer { mLongPressingPointerDeltaX = 0; mLongPressingPointerDeltaY = 0; mCurrentState = STATE_TOUCH_EXPLORING; + if (mNext != null) { + mNext.clear(); + } } + @Override + public void setNext(EventStreamTransformation next) { + mNext = next; + } + + @Override public void onMotionEvent(MotionEvent event, int policyFlags) { if (DEBUG) { Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x" @@ -308,9 +329,26 @@ public class TouchExplorer { } public void onAccessibilityEvent(AccessibilityEvent event) { + final int eventType = event.getEventType(); + + // The event for gesture end should be strictly after the + // last hover exit event. + if (mTouchExplorationGestureEnded + && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { + mTouchExplorationGestureEnded = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); + } + + // The event for touch interaction end should be strictly after the + // last hover exit and the touch exploration gesture end events. + if (mTouchInteractionEnded + && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { + mTouchInteractionEnded = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + } + // If a new window opens or the accessibility focus moves we no longer // want to click/long press on the last touch explored location. - final int eventType = event.getEventType(); switch (eventType) { case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { @@ -325,6 +363,9 @@ public class TouchExplorer { mLastTouchedWindowId = event.getWindowId(); } break; } + if (mNext != null) { + mNext.onAccessibilityEvent(event); + } } /** @@ -346,6 +387,15 @@ public class TouchExplorer { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: + // The delayed enter not delivered implies that we have delivered + // TYPE_TOUCH_INTERACTION_START and not TYPE_TOUCH_INTERACTION_END, + // therefore we need to deliver the interaction end event here. + if (mSendHoverEnterDelayed.isPending()) { + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + } + // Announce the start of a new touch interaction. + sendAccessibilityEvent( + AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); // Pre-feed the motion events to the gesture detector since we // have a distance slop before getting into gesture detection // mode and not using the points within this slop significantly @@ -384,7 +434,7 @@ public class TouchExplorer { // to detect what the user is trying to do. final int pointerId = receivedTracker.getPrimaryActivePointerId(); final int pointerIdBits = (1 << pointerId); - mSendHoverEnterDelayed.post(event, pointerIdBits, policyFlags); + mSendHoverEnterDelayed.post(event, true, pointerIdBits, policyFlags); } break; default: { /* do nothing - let the code for ACTION_MOVE decide what to do */ @@ -431,6 +481,10 @@ public class TouchExplorer { mSendHoverExitDelayed.remove(); mPerformLongPressDelayed.remove(); mExitGestureDetectionModeDelayed.post(); + // Send accessibility event to announce the start + // of gesture recognition. + sendAccessibilityEvent( + AccessibilityEvent.TYPE_GESTURE_DETECTION_START); } else { // We have just decided that the user is touch, // exploring so start sending events. @@ -539,7 +593,8 @@ public class TouchExplorer { // If we have not delivered the enter schedule exit. if (mSendHoverEnterDelayed.isPending()) { - mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); + mSendHoverEnterDelayed.mTouchExplorationInProgress = false; + mSendHoverExitDelayed.post(event, false, pointerIdBits, policyFlags); } else { // The user is touch exploring so we send events for end. sendExitEventsIfNeeded(policyFlags); @@ -644,6 +699,9 @@ public class TouchExplorer { } } break; case MotionEvent.ACTION_UP: { + // Announce the end of a new touch interaction. + sendAccessibilityEvent( + AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); mCurrentState = STATE_TOUCH_EXPLORING; } break; case MotionEvent.ACTION_CANCEL: { @@ -675,6 +733,10 @@ public class TouchExplorer { } } break; case MotionEvent.ACTION_UP: + // Announce the end of a new touch interaction. + sendAccessibilityEvent( + AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + //$FALL-THROUGH$ case MotionEvent.ACTION_POINTER_UP: { mLongPressingPointerId = -1; mLongPressingPointerDeltaX = 0; @@ -713,6 +775,13 @@ public class TouchExplorer { } } break; case MotionEvent.ACTION_UP: { + // Announce the end of gesture recognition. + sendAccessibilityEvent( + AccessibilityEvent.TYPE_GESTURE_DETECTION_END); + // Announce the end of a new touch interaction. + sendAccessibilityEvent( + AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + float x = event.getX(); float y = event.getY(); mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); @@ -748,6 +817,19 @@ public class TouchExplorer { } /** + * Sends an accessibility event of the given type. + * + * @param type The event type. + */ + private void sendAccessibilityEvent(int type) { + AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); + if (accessibilityManager.isEnabled()) { + AccessibilityEvent event = AccessibilityEvent.obtain(type); + accessibilityManager.sendAccessibilityEvent(event); + } + } + + /** * Sends down events to the view hierarchy for all active pointers which are * not already being delivered i.e. pointers that are not yet injected. * @@ -795,7 +877,8 @@ public class TouchExplorer { MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) { final int pointerIdBits = event.getPointerIdBits(); - mAms.touchExplorationGestureEnded(); + mTouchExplorationGestureEnded = true; + mTouchInteractionEnded = true; sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags); } } @@ -810,7 +893,6 @@ public class TouchExplorer { MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) { final int pointerIdBits = event.getPointerIdBits(); - mAms.touchExplorationGestureStarted(); sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags); } } @@ -958,7 +1040,9 @@ public class TouchExplorer { // Make sure that the user will see the event. policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; - mInputFilter.sendInputEvent(event, policyFlags); + if (mNext != null) { + mNext.onMotionEvent(event, policyFlags); + } mInjectedPointerTracker.onMotionEvent(event); @@ -1008,11 +1092,13 @@ public class TouchExplorer { private MotionEvent mFirstTapEvent; public void onMotionEvent(MotionEvent event, int policyFlags) { + final int actionIndex = event.getActionIndex(); final int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: { - if (mFirstTapEvent != null && !isSamePointerContext(mFirstTapEvent, event)) { + if (mFirstTapEvent != null + && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) { clear(); } mDownEvent = MotionEvent.obtain(event); @@ -1022,19 +1108,21 @@ public class TouchExplorer { if (mDownEvent == null) { return; } - if (!isSamePointerContext(mDownEvent, event)) { + if (!GestureUtils.isSamePointerContext(mDownEvent, event)) { clear(); return; } - if (isTap(mDownEvent, event)) { - if (mFirstTapEvent == null || isTimedOut(mFirstTapEvent, event, - mDoubleTapTimeout)) { + if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop, + actionIndex)) { + if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent, + event, mDoubleTapTimeout)) { mFirstTapEvent = MotionEvent.obtain(event); mDownEvent.recycle(); mDownEvent = null; return; } - if (isDoubleTap(mFirstTapEvent, event)) { + if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout, + mDoubleTapSlop, actionIndex)) { onDoubleTap(event, policyFlags); mFirstTapEvent.recycle(); mFirstTapEvent = null; @@ -1062,16 +1150,24 @@ public class TouchExplorer { return; } + if (Build.IS_DEBUGGABLE) { + if (mSendHoverEnterDelayed.isPending()) { + throw new IllegalStateException("mSendHoverEnterDelayed must not be pending."); + } + if (mSendHoverExitDelayed.isPending()) { + throw new IllegalStateException("mSendHoverExitDelayed must not be pending."); + } + if (!mPerformLongPressDelayed.isPending()) { + throw new IllegalStateException( + "mPerformLongPressDelayed must not be pending."); + } + } + // Remove pending event deliveries. - mSendHoverEnterDelayed.remove(); - mSendHoverExitDelayed.remove(); mPerformLongPressDelayed.remove(); - // This is a tap so do not send hover events since - // this events will result in firing the corresponding - // accessibility events confusing the user about what - // is actually clicked. - sendExitEventsIfNeeded(policyFlags); + // The touch interaction has ended since we will send a click. + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); int clickLocationX; int clickLocationY; @@ -1140,42 +1236,6 @@ public class TouchExplorer { } } - public boolean isTap(MotionEvent down, MotionEvent up) { - return eventsWithinTimeoutAndDistance(down, up, mTapTimeout, mTouchSlop); - } - - private boolean isDoubleTap(MotionEvent firstUp, MotionEvent secondUp) { - return eventsWithinTimeoutAndDistance(firstUp, secondUp, mDoubleTapTimeout, - mDoubleTapSlop); - } - - private boolean eventsWithinTimeoutAndDistance(MotionEvent first, MotionEvent second, - int timeout, int distance) { - if (isTimedOut(first, second, timeout)) { - return false; - } - final int downPtrIndex = first.getActionIndex(); - final int upPtrIndex = second.getActionIndex(); - final float deltaX = second.getX(upPtrIndex) - first.getX(downPtrIndex); - final float deltaY = second.getY(upPtrIndex) - first.getY(downPtrIndex); - final double deltaMove = Math.hypot(deltaX, deltaY); - if (deltaMove >= distance) { - return false; - } - return true; - } - - private boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) { - final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime(); - return (deltaTime >= timeout); - } - - private boolean isSamePointerContext(MotionEvent first, MotionEvent second) { - return (first.getPointerIdBits() == second.getPointerIdBits() - && first.getPointerId(first.getActionIndex()) - == second.getPointerId(second.getActionIndex())); - } - public boolean firstTapDetected() { return mFirstTapEvent != null && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout; @@ -1201,47 +1261,14 @@ public class TouchExplorer { final float secondPtrX = event.getX(secondPtrIndex); final float secondPtrY = event.getY(secondPtrIndex); - // Check if the pointers are moving in the same direction. - final float firstDeltaX = - firstPtrX - receivedTracker.getReceivedPointerDownX(firstPtrIndex); - final float firstDeltaY = - firstPtrY - receivedTracker.getReceivedPointerDownY(firstPtrIndex); + final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex); + final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex); + final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex); + final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex); - if (firstDeltaX == 0 && firstDeltaY == 0) { - return true; - } - - final float firstMagnitude = - (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY); - final float firstXNormalized = - (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX; - final float firstYNormalized = - (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY; - - final float secondDeltaX = - secondPtrX - receivedTracker.getReceivedPointerDownX(secondPtrIndex); - final float secondDeltaY = - secondPtrY - receivedTracker.getReceivedPointerDownY(secondPtrIndex); - - if (secondDeltaX == 0 && secondDeltaY == 0) { - return true; - } - - final float secondMagnitude = - (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY); - final float secondXNormalized = - (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX; - final float secondYNormalized = - (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY; - - final float angleCos = - firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized; - - if (angleCos < MAX_DRAGGING_ANGLE_COS) { - return false; - } - - return true; + return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX, + secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY, + MAX_DRAGGING_ANGLE_COS); } /** @@ -1308,13 +1335,13 @@ public class TouchExplorer { } public void remove() { - if (isPenidng()) { + if (isPending()) { mHandler.removeCallbacks(this); clear(); } } - private boolean isPenidng() { + public boolean isPending() { return (mEvent != null); } @@ -1377,7 +1404,7 @@ public class TouchExplorer { } private void clear() { - if (!isPenidng()) { + if (!isPending()) { return; } mEvent.recycle(); @@ -1398,15 +1425,18 @@ public class TouchExplorer { private MotionEvent mPrototype; private int mPointerIdBits; private int mPolicyFlags; + private boolean mTouchExplorationInProgress; public SendHoverDelayed(int hoverAction, boolean gestureStarted) { mHoverAction = hoverAction; mGestureStarted = gestureStarted; } - public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) { + public void post(MotionEvent prototype, boolean touchExplorationInProgress, + int pointerIdBits, int policyFlags) { remove(); mPrototype = MotionEvent.obtain(prototype); + mTouchExplorationInProgress = touchExplorationInProgress; mPointerIdBits = pointerIdBits; mPolicyFlags = policyFlags; mHandler.postDelayed(this, mDetermineUserIntentTimeout); @@ -1443,6 +1473,7 @@ public class TouchExplorer { mPrototype = null; mPointerIdBits = -1; mPolicyFlags = 0; + mTouchExplorationInProgress = false; } public void forceSendAndRemove() { @@ -1459,10 +1490,17 @@ public class TouchExplorer { Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ? "touchExplorationGestureStarted" : "touchExplorationGestureEnded"); } - if (mGestureStarted) { - mAms.touchExplorationGestureStarted(); + if (mTouchExplorationInProgress) { + if (mGestureStarted) { + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); + } else { + mTouchExplorationGestureEnded = true; + mTouchInteractionEnded = true; + } } else { - mAms.touchExplorationGestureEnded(); + if (!mGestureStarted) { + mTouchInteractionEnded = true; + } } sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags); clear(); diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java new file mode 100644 index 0000000..aefc264 --- /dev/null +++ b/services/java/com/android/server/am/ActiveServices.java @@ -0,0 +1,2158 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import com.android.internal.os.BatteryStatsImpl; +import com.android.server.am.ActivityManagerService.ItemMatcher; +import com.android.server.am.ActivityManagerService.NeededUriGrants; + +import android.app.ActivityManager; +import android.app.AppGlobals; +import android.app.IApplicationThread; +import android.app.IServiceConnection; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.UserInfo; +import android.os.Binder; +import android.os.IBinder; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.TimeUtils; + +public class ActiveServices { + static final boolean DEBUG_SERVICE = ActivityManagerService.DEBUG_SERVICE; + static final boolean DEBUG_SERVICE_EXECUTING = ActivityManagerService.DEBUG_SERVICE_EXECUTING; + static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU; + static final String TAG = ActivityManagerService.TAG; + static final String TAG_MU = ActivityManagerService.TAG_MU; + + // How long we wait for a service to finish executing. + static final int SERVICE_TIMEOUT = 20*1000; + + // 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; + + // How long a service needs to be running until it will start back at + // SERVICE_RESTART_DURATION after being killed. + static final int SERVICE_RESET_RUN_DURATION = 60*1000; + + // Multiplying factor to increase restart duration time by, for each time + // a service is killed before it has run for SERVICE_RESET_RUN_DURATION. + static final int SERVICE_RESTART_DURATION_FACTOR = 4; + + // The minimum amount of time between restarting services that we allow. + // That is, when multiple services are restarting, we won't allow each + // to restart less than this amount of time from the last one. + static final int SERVICE_MIN_RESTART_TIME_BETWEEN = 10*1000; + + // Maximum amount of time for there to be no activity on a service before + // we consider it non-essential and allow its process to go on the + // LRU background list. + static final int MAX_SERVICE_INACTIVITY = 30*60*1000; + + final ActivityManagerService mAm; + + final ServiceMap mServiceMap = new ServiceMap(); + + /** + * All currently bound service connections. Keys are the IBinder of + * the client's IServiceConnection. + */ + final HashMap<IBinder, ArrayList<ConnectionRecord>> mServiceConnections + = new HashMap<IBinder, ArrayList<ConnectionRecord>>(); + + /** + * List of services that we have been asked to start, + * but haven't yet been able to. It is used to hold start requests + * while waiting for their corresponding application thread to get + * going. + */ + final ArrayList<ServiceRecord> mPendingServices + = new ArrayList<ServiceRecord>(); + + /** + * List of services that are scheduled to restart following a crash. + */ + final ArrayList<ServiceRecord> mRestartingServices + = new ArrayList<ServiceRecord>(); + + /** + * List of services that are in the process of being stopped. + */ + final ArrayList<ServiceRecord> mStoppingServices + = new ArrayList<ServiceRecord>(); + + static class ServiceMap { + + private final SparseArray<HashMap<ComponentName, ServiceRecord>> mServicesByNamePerUser + = new SparseArray<HashMap<ComponentName, ServiceRecord>>(); + private final SparseArray<HashMap<Intent.FilterComparison, ServiceRecord>> + mServicesByIntentPerUser = new SparseArray< + HashMap<Intent.FilterComparison, ServiceRecord>>(); + + ServiceRecord getServiceByName(ComponentName name, int callingUser) { + // TODO: Deal with global services + if (DEBUG_MU) + Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser); + return getServices(callingUser).get(name); + } + + ServiceRecord getServiceByName(ComponentName name) { + return getServiceByName(name, -1); + } + + ServiceRecord getServiceByIntent(Intent.FilterComparison filter, int callingUser) { + // TODO: Deal with global services + if (DEBUG_MU) + Slog.v(TAG_MU, "getServiceByIntent(" + filter + "), callingUser = " + callingUser); + return getServicesByIntent(callingUser).get(filter); + } + + ServiceRecord getServiceByIntent(Intent.FilterComparison filter) { + return getServiceByIntent(filter, -1); + } + + void putServiceByName(ComponentName name, int callingUser, ServiceRecord value) { + // TODO: Deal with global services + getServices(callingUser).put(name, value); + } + + void putServiceByIntent(Intent.FilterComparison filter, int callingUser, + ServiceRecord value) { + // TODO: Deal with global services + getServicesByIntent(callingUser).put(filter, value); + } + + void removeServiceByName(ComponentName name, int callingUser) { + // TODO: Deal with global services + ServiceRecord removed = getServices(callingUser).remove(name); + if (DEBUG_MU) + Slog.v(TAG, "removeServiceByName user=" + callingUser + " name=" + name + + " removed=" + removed); + } + + void removeServiceByIntent(Intent.FilterComparison filter, int callingUser) { + // TODO: Deal with global services + ServiceRecord removed = getServicesByIntent(callingUser).remove(filter); + if (DEBUG_MU) + Slog.v(TAG_MU, "removeServiceByIntent user=" + callingUser + " intent=" + filter + + " removed=" + removed); + } + + Collection<ServiceRecord> getAllServices(int callingUser) { + // TODO: Deal with global services + return getServices(callingUser).values(); + } + + private HashMap<ComponentName, ServiceRecord> getServices(int callingUser) { + HashMap<ComponentName, ServiceRecord> map = mServicesByNamePerUser.get(callingUser); + if (map == null) { + map = new HashMap<ComponentName, ServiceRecord>(); + mServicesByNamePerUser.put(callingUser, map); + } + return map; + } + + private HashMap<Intent.FilterComparison, ServiceRecord> getServicesByIntent( + int callingUser) { + HashMap<Intent.FilterComparison, ServiceRecord> map + = mServicesByIntentPerUser.get(callingUser); + if (map == null) { + map = new HashMap<Intent.FilterComparison, ServiceRecord>(); + mServicesByIntentPerUser.put(callingUser, map); + } + return map; + } + } + + public ActiveServices(ActivityManagerService service) { + mAm = service; + } + + ComponentName startServiceLocked(IApplicationThread caller, + Intent service, String resolvedType, + int callingPid, int callingUid, int userId) { + if (DEBUG_SERVICE) Slog.v(TAG, "startService: " + service + + " type=" + resolvedType + " args=" + service.getExtras()); + + if (caller != null) { + final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); + if (callerApp == null) { + throw new SecurityException( + "Unable to find app for caller " + caller + + " (pid=" + Binder.getCallingPid() + + ") when starting service " + service); + } + } + + ServiceLookupResult res = + retrieveServiceLocked(service, resolvedType, + callingPid, callingUid, userId, true); + if (res == null) { + return null; + } + if (res.record == null) { + return new ComponentName("!", res.permission != null + ? res.permission : "private to package"); + } + ServiceRecord r = res.record; + NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked( + callingUid, r.packageName, service, service.getFlags(), null); + if (unscheduleServiceRestartLocked(r)) { + if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); + } + r.startRequested = true; + r.callStart = false; + r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), + service, neededGrants)); + r.lastActivity = SystemClock.uptimeMillis(); + synchronized (r.stats.getBatteryStats()) { + r.stats.startRunningLocked(); + } + if (!bringUpServiceLocked(r, service.getFlags(), false)) { + return new ComponentName("!", "Service process is bad"); + } + return r.name; + } + + private void stopServiceLocked(ServiceRecord service) { + synchronized (service.stats.getBatteryStats()) { + service.stats.stopRunningLocked(); + } + service.startRequested = false; + service.callStart = false; + bringDownServiceLocked(service, false); + } + + int stopServiceLocked(IApplicationThread caller, Intent service, + String resolvedType, int userId) { + if (DEBUG_SERVICE) Slog.v(TAG, "stopService: " + service + + " type=" + resolvedType); + + final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); + if (caller != null && callerApp == null) { + throw new SecurityException( + "Unable to find app for caller " + caller + + " (pid=" + Binder.getCallingPid() + + ") when stopping service " + service); + } + + // If this service is active, make sure it is stopped. + ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, + Binder.getCallingPid(), Binder.getCallingUid(), userId, false); + if (r != null) { + if (r.record != null) { + final long origId = Binder.clearCallingIdentity(); + try { + stopServiceLocked(r.record); + } finally { + Binder.restoreCallingIdentity(origId); + } + return 1; + } + return -1; + } + + return 0; + } + + IBinder peekServiceLocked(Intent service, String resolvedType) { + ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, + Binder.getCallingPid(), Binder.getCallingUid(), + UserHandle.getCallingUserId(), false); + + IBinder ret = null; + if (r != null) { + // r.record is null if findServiceLocked() failed the caller permission check + if (r.record == null) { + throw new SecurityException( + "Permission Denial: Accessing service " + r.record.name + + " from pid=" + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + r.permission); + } + IntentBindRecord ib = r.record.bindings.get(r.record.intent); + if (ib != null) { + ret = ib.binder; + } + } + + return ret; + } + + boolean stopServiceTokenLocked(ComponentName className, IBinder token, + int startId) { + if (DEBUG_SERVICE) Slog.v(TAG, "stopServiceToken: " + className + + " " + token + " startId=" + startId); + ServiceRecord r = findServiceLocked(className, token, UserHandle.getCallingUserId()); + if (r != null) { + if (startId >= 0) { + // Asked to only stop if done with all work. Note that + // to avoid leaks, we will take this as dropping all + // start items up to and including this one. + ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); + if (si != null) { + while (r.deliveredStarts.size() > 0) { + ServiceRecord.StartItem cur = r.deliveredStarts.remove(0); + cur.removeUriPermissionsLocked(); + if (cur == si) { + break; + } + } + } + + if (r.getLastStartId() != startId) { + return false; + } + + if (r.deliveredStarts.size() > 0) { + Slog.w(TAG, "stopServiceToken startId " + startId + + " is last, but have " + r.deliveredStarts.size() + + " remaining args"); + } + } + + synchronized (r.stats.getBatteryStats()) { + r.stats.stopRunningLocked(); + r.startRequested = false; + r.callStart = false; + } + final long origId = Binder.clearCallingIdentity(); + bringDownServiceLocked(r, false); + Binder.restoreCallingIdentity(origId); + return true; + } + return false; + } + + public void setServiceForegroundLocked(ComponentName className, IBinder token, + int id, Notification notification, boolean removeNotification) { + final int userId = UserHandle.getCallingUserId(); + final long origId = Binder.clearCallingIdentity(); + try { + ServiceRecord r = findServiceLocked(className, token, userId); + if (r != null) { + if (id != 0) { + if (notification == null) { + throw new IllegalArgumentException("null notification"); + } + if (r.foregroundId != id) { + r.cancelNotification(); + r.foregroundId = id; + } + notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; + r.foregroundNoti = notification; + r.isForeground = true; + r.postNotification(); + if (r.app != null) { + updateServiceForegroundLocked(r.app, true); + } + } else { + if (r.isForeground) { + r.isForeground = false; + if (r.app != null) { + mAm.updateLruProcessLocked(r.app, false, true); + updateServiceForegroundLocked(r.app, true); + } + } + if (removeNotification) { + r.cancelNotification(); + r.foregroundId = 0; + r.foregroundNoti = null; + } + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) { + boolean anyForeground = false; + for (ServiceRecord sr : proc.services) { + if (sr.isForeground) { + anyForeground = true; + break; + } + } + if (anyForeground != proc.foregroundServices) { + proc.foregroundServices = anyForeground; + if (oomAdj) { + mAm.updateOomAdjLocked(); + } + } + } + + int bindServiceLocked(IApplicationThread caller, IBinder token, + Intent service, String resolvedType, + IServiceConnection connection, int flags, int userId) { + if (DEBUG_SERVICE) Slog.v(TAG, "bindService: " + service + + " type=" + resolvedType + " conn=" + connection.asBinder() + + " flags=0x" + Integer.toHexString(flags)); + final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); + if (callerApp == null) { + throw new SecurityException( + "Unable to find app for caller " + caller + + " (pid=" + Binder.getCallingPid() + + ") when binding service " + service); + } + + ActivityRecord activity = null; + if (token != null) { + activity = mAm.mMainStack.isInStackLocked(token); + if (activity == null) { + Slog.w(TAG, "Binding with unknown activity: " + token); + return 0; + } + } + + int clientLabel = 0; + PendingIntent clientIntent = null; + + if (callerApp.info.uid == Process.SYSTEM_UID) { + // Hacky kind of thing -- allow system stuff to tell us + // what they are, so we can report this elsewhere for + // others to know why certain services are running. + try { + clientIntent = (PendingIntent)service.getParcelableExtra( + Intent.EXTRA_CLIENT_INTENT); + } catch (RuntimeException e) { + } + if (clientIntent != null) { + clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0); + if (clientLabel != 0) { + // There are no useful extras in the intent, trash them. + // System code calling with this stuff just needs to know + // this will happen. + service = service.cloneFilter(); + } + } + } + + ServiceLookupResult res = + retrieveServiceLocked(service, resolvedType, + Binder.getCallingPid(), Binder.getCallingUid(), userId, true); + if (res == null) { + return 0; + } + if (res.record == null) { + return -1; + } + ServiceRecord s = res.record; + + final long origId = Binder.clearCallingIdentity(); + + try { + if (unscheduleServiceRestartLocked(s)) { + if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: " + + s); + } + + AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); + ConnectionRecord c = new ConnectionRecord(b, activity, + connection, flags, clientLabel, clientIntent); + + IBinder binder = connection.asBinder(); + ArrayList<ConnectionRecord> clist = s.connections.get(binder); + if (clist == null) { + clist = new ArrayList<ConnectionRecord>(); + s.connections.put(binder, clist); + } + clist.add(c); + b.connections.add(c); + if (activity != null) { + if (activity.connections == null) { + activity.connections = new HashSet<ConnectionRecord>(); + } + activity.connections.add(c); + } + b.client.connections.add(c); + if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { + b.client.hasAboveClient = true; + } + clist = mServiceConnections.get(binder); + if (clist == null) { + clist = new ArrayList<ConnectionRecord>(); + mServiceConnections.put(binder, clist); + } + clist.add(c); + + if ((flags&Context.BIND_AUTO_CREATE) != 0) { + s.lastActivity = SystemClock.uptimeMillis(); + if (!bringUpServiceLocked(s, service.getFlags(), false)) { + return 0; + } + } + + if (s.app != null) { + // This could have made the service more important. + mAm.updateOomAdjLocked(s.app); + } + + if (DEBUG_SERVICE) Slog.v(TAG, "Bind " + s + " with " + b + + ": received=" + b.intent.received + + " apps=" + b.intent.apps.size() + + " doRebind=" + b.intent.doRebind); + + if (s.app != null && b.intent.received) { + // Service is already running, so we can immediately + // publish the connection. + try { + c.conn.connected(s.name, b.intent.binder); + } catch (Exception e) { + Slog.w(TAG, "Failure sending service " + s.shortName + + " to connection " + c.conn.asBinder() + + " (in " + c.binding.client.processName + ")", e); + } + + // If this is the first app connected back to this binding, + // and the service had previously asked to be told when + // rebound, then do so. + if (b.intent.apps.size() == 1 && b.intent.doRebind) { + requestServiceBindingLocked(s, b.intent, true); + } + } else if (!b.intent.requested) { + requestServiceBindingLocked(s, b.intent, false); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return 1; + } + + void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) { + final long origId = Binder.clearCallingIdentity(); + try { + if (DEBUG_SERVICE) Slog.v(TAG, "PUBLISHING " + r + + " " + intent + ": " + service); + if (r != null) { + Intent.FilterComparison filter + = new Intent.FilterComparison(intent); + IntentBindRecord b = r.bindings.get(filter); + if (b != null && !b.received) { + b.binder = service; + b.requested = true; + b.received = true; + if (r.connections.size() > 0) { + Iterator<ArrayList<ConnectionRecord>> it + = r.connections.values().iterator(); + while (it.hasNext()) { + ArrayList<ConnectionRecord> clist = it.next(); + for (int i=0; i<clist.size(); i++) { + ConnectionRecord c = clist.get(i); + if (!filter.equals(c.binding.intent.intent)) { + if (DEBUG_SERVICE) Slog.v( + TAG, "Not publishing to: " + c); + if (DEBUG_SERVICE) Slog.v( + TAG, "Bound intent: " + c.binding.intent.intent); + if (DEBUG_SERVICE) Slog.v( + TAG, "Published intent: " + intent); + continue; + } + if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c); + try { + c.conn.connected(r.name, service); + } catch (Exception e) { + Slog.w(TAG, "Failure sending service " + r.name + + " to connection " + c.conn.asBinder() + + " (in " + c.binding.client.processName + ")", e); + } + } + } + } + } + + serviceDoneExecutingLocked(r, mStoppingServices.contains(r)); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + boolean unbindServiceLocked(IServiceConnection connection) { + IBinder binder = connection.asBinder(); + if (DEBUG_SERVICE) Slog.v(TAG, "unbindService: conn=" + binder); + ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder); + if (clist == null) { + Slog.w(TAG, "Unbind failed: could not find connection for " + + connection.asBinder()); + return false; + } + + final long origId = Binder.clearCallingIdentity(); + try { + while (clist.size() > 0) { + ConnectionRecord r = clist.get(0); + removeConnectionLocked(r, null, null); + + if (r.binding.service.app != null) { + // This could have made the service less important. + mAm.updateOomAdjLocked(r.binding.service.app); + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return true; + } + + void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind) { + final long origId = Binder.clearCallingIdentity(); + try { + if (r != null) { + Intent.FilterComparison filter + = new Intent.FilterComparison(intent); + IntentBindRecord b = r.bindings.get(filter); + if (DEBUG_SERVICE) Slog.v(TAG, "unbindFinished in " + r + + " at " + b + ": apps=" + + (b != null ? b.apps.size() : 0)); + + boolean inStopping = mStoppingServices.contains(r); + if (b != null) { + if (b.apps.size() > 0 && !inStopping) { + // Applications have already bound since the last + // unbind, so just rebind right here. + requestServiceBindingLocked(r, b, true); + } else { + // Note to tell the service the next time there is + // a new client. + b.doRebind = true; + } + } + + serviceDoneExecutingLocked(r, inStopping); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + private final ServiceRecord findServiceLocked(ComponentName name, + IBinder token, int userId) { + ServiceRecord r = mServiceMap.getServiceByName(name, userId); + return r == token ? r : null; + } + + private final class ServiceLookupResult { + final ServiceRecord record; + final String permission; + + ServiceLookupResult(ServiceRecord _record, String _permission) { + record = _record; + permission = _permission; + } + } + + private class ServiceRestarter implements Runnable { + private ServiceRecord mService; + + void setService(ServiceRecord service) { + mService = service; + } + + public void run() { + synchronized(mAm) { + performServiceRestartLocked(mService); + } + } + } + + private ServiceLookupResult retrieveServiceLocked(Intent service, + String resolvedType, int callingPid, int callingUid, int userId, + boolean createIfNeeded) { + ServiceRecord r = null; + if (DEBUG_SERVICE) Slog.v(TAG, "retrieveServiceLocked: " + service + + " type=" + resolvedType + " callingUid=" + callingUid); + + userId = mAm.handleIncomingUserLocked(callingPid, callingUid, userId, + false, true, "service", null); + + if (service.getComponent() != null) { + r = mServiceMap.getServiceByName(service.getComponent(), userId); + } + if (r == null) { + Intent.FilterComparison filter = new Intent.FilterComparison(service); + r = mServiceMap.getServiceByIntent(filter, userId); + } + if (r == null) { + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveService( + service, resolvedType, + ActivityManagerService.STOCK_PM_FLAGS, userId); + ServiceInfo sInfo = + rInfo != null ? rInfo.serviceInfo : null; + if (sInfo == null) { + Slog.w(TAG, "Unable to start service " + service + " U=" + userId + + ": not found"); + return null; + } + ComponentName name = new ComponentName( + sInfo.applicationInfo.packageName, sInfo.name); + if (userId > 0) { + if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo, + sInfo.name, sInfo.flags)) { + userId = 0; + } + sInfo = new ServiceInfo(sInfo); + sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId); + } + r = mServiceMap.getServiceByName(name, userId); + if (r == null && createIfNeeded) { + Intent.FilterComparison filter = new Intent.FilterComparison( + service.cloneFilter()); + ServiceRestarter res = new ServiceRestarter(); + BatteryStatsImpl.Uid.Pkg.Serv ss = null; + BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + ss = stats.getServiceStatsLocked( + sInfo.applicationInfo.uid, sInfo.packageName, + sInfo.name); + } + r = new ServiceRecord(mAm, ss, name, filter, sInfo, res); + res.setService(r); + mServiceMap.putServiceByName(name, UserHandle.getUserId(r.appInfo.uid), r); + mServiceMap.putServiceByIntent(filter, UserHandle.getUserId(r.appInfo.uid), r); + + // Make sure this component isn't in the pending list. + int N = mPendingServices.size(); + for (int i=0; i<N; i++) { + ServiceRecord pr = mPendingServices.get(i); + if (pr.name.equals(name)) { + mPendingServices.remove(i); + i--; + N--; + } + } + } + } catch (RemoteException ex) { + // pm is in same process, this will never happen. + } + } + if (r != null) { + if (mAm.checkComponentPermission(r.permission, + callingPid, callingUid, r.appInfo.uid, r.exported) + != PackageManager.PERMISSION_GRANTED) { + if (!r.exported) { + Slog.w(TAG, "Permission Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " that is not exported from uid " + r.appInfo.uid); + return new ServiceLookupResult(null, "not exported from uid " + + r.appInfo.uid); + } + Slog.w(TAG, "Permission Denial: Accessing service " + r.name + + " from pid=" + callingPid + + ", uid=" + callingUid + + " requires " + r.permission); + return new ServiceLookupResult(null, r.permission); + } + return new ServiceLookupResult(r, null); + } + return null; + } + + private final void bumpServiceExecutingLocked(ServiceRecord r, String why) { + if (DEBUG_SERVICE) Log.v(TAG, ">>> EXECUTING " + + why + " of " + r + " in app " + r.app); + else if (DEBUG_SERVICE_EXECUTING) Log.v(TAG, ">>> EXECUTING " + + why + " of " + r.shortName); + long now = SystemClock.uptimeMillis(); + if (r.executeNesting == 0 && r.app != null) { + if (r.app.executingServices.size() == 0) { + Message msg = mAm.mHandler.obtainMessage( + ActivityManagerService.SERVICE_TIMEOUT_MSG); + msg.obj = r.app; + mAm.mHandler.sendMessageAtTime(msg, now+SERVICE_TIMEOUT); + } + r.app.executingServices.add(r); + } + r.executeNesting++; + r.executingStart = now; + } + + private final boolean requestServiceBindingLocked(ServiceRecord r, + IntentBindRecord i, boolean rebind) { + if (r.app == null || r.app.thread == null) { + // If service is not currently running, can't yet bind. + return false; + } + if ((!i.requested || rebind) && i.apps.size() > 0) { + try { + bumpServiceExecutingLocked(r, "bind"); + r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind); + if (!rebind) { + i.requested = true; + } + i.hasBound = true; + i.doRebind = false; + } catch (RemoteException e) { + if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while binding " + r); + return false; + } + } + return true; + } + + private final boolean scheduleServiceRestartLocked(ServiceRecord r, + boolean allowCancel) { + boolean canceled = false; + + final long now = SystemClock.uptimeMillis(); + + if ((r.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) == 0) { + long minDuration = SERVICE_RESTART_DURATION; + long resetTime = SERVICE_RESET_RUN_DURATION; + + // Any delivered but not yet finished starts should be put back + // on the pending list. + final int N = r.deliveredStarts.size(); + if (N > 0) { + for (int i=N-1; i>=0; i--) { + ServiceRecord.StartItem si = r.deliveredStarts.get(i); + si.removeUriPermissionsLocked(); + if (si.intent == null) { + // We'll generate this again if needed. + } else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT + && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) { + r.pendingStarts.add(0, si); + long dur = SystemClock.uptimeMillis() - si.deliveredTime; + dur *= 2; + if (minDuration < dur) minDuration = dur; + if (resetTime < dur) resetTime = dur; + } else { + Slog.w(TAG, "Canceling start item " + si.intent + " in service " + + r.name); + canceled = true; + } + } + r.deliveredStarts.clear(); + } + + r.totalRestartCount++; + if (r.restartDelay == 0) { + r.restartCount++; + r.restartDelay = minDuration; + } else { + // If it has been a "reasonably long time" since the service + // was started, then reset our restart duration back to + // the beginning, so we don't infinitely increase the duration + // on a service that just occasionally gets killed (which is + // a normal case, due to process being killed to reclaim memory). + if (now > (r.restartTime+resetTime)) { + 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.nextRestartTime = now + r.restartDelay; + + // Make sure that we don't end up restarting a bunch of services + // all at the same time. + boolean repeat; + do { + repeat = false; + for (int i=mRestartingServices.size()-1; i>=0; i--) { + ServiceRecord r2 = mRestartingServices.get(i); + if (r2 != r && r.nextRestartTime + >= (r2.nextRestartTime-SERVICE_MIN_RESTART_TIME_BETWEEN) + && r.nextRestartTime + < (r2.nextRestartTime+SERVICE_MIN_RESTART_TIME_BETWEEN)) { + r.nextRestartTime = r2.nextRestartTime + SERVICE_MIN_RESTART_TIME_BETWEEN; + r.restartDelay = r.nextRestartTime - now; + repeat = true; + break; + } + } + } while (repeat); + + } else { + // Persistent processes are immediately restrted, so there is no + // reason to hold of on restarting their services. + r.totalRestartCount++; + r.restartCount = 0; + r.restartDelay = 0; + r.nextRestartTime = now; + } + + if (!mRestartingServices.contains(r)) { + mRestartingServices.add(r); + } + + r.cancelNotification(); + + mAm.mHandler.removeCallbacks(r.restarter); + mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime); + r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay; + Slog.w(TAG, "Scheduling restart of crashed service " + + r.shortName + " in " + r.restartDelay + "ms"); + EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART, + r.shortName, r.restartDelay); + + return canceled; + } + + final void performServiceRestartLocked(ServiceRecord r) { + if (!mRestartingServices.contains(r)) { + return; + } + bringUpServiceLocked(r, r.intent.getIntent().getFlags(), true); + } + + private final boolean unscheduleServiceRestartLocked(ServiceRecord r) { + if (r.restartDelay == 0) { + return false; + } + r.resetRestartCounter(); + mRestartingServices.remove(r); + mAm.mHandler.removeCallbacks(r.restarter); + return true; + } + + private final boolean bringUpServiceLocked(ServiceRecord r, + int intentFlags, boolean whileRestarting) { + //Slog.i(TAG, "Bring up service:"); + //r.dump(" "); + + if (r.app != null && r.app.thread != null) { + sendServiceArgsLocked(r, false); + return true; + } + + if (!whileRestarting && r.restartDelay > 0) { + // If waiting for a restart, then do nothing. + return true; + } + + if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up " + r + " " + r.intent); + + // We are now bringing the service up, so no longer in the + // restarting state. + mRestartingServices.remove(r); + + // Make sure that the user who owns this service is started. If not, + // we don't want to allow it to run. + if (mAm.mStartedUsers.get(r.userId) == null) { + Slog.w(TAG, "Unable to launch app " + + r.appInfo.packageName + "/" + + r.appInfo.uid + " for service " + + r.intent.getIntent() + ": user " + r.userId + " is stopped"); + bringDownServiceLocked(r, true); + return false; + } + + // Service is now being launched, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + r.packageName, false, r.userId); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + r.packageName + ": " + e); + } + + final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; + final String procName = r.processName; + ProcessRecord app; + + if (!isolated) { + app = mAm.getProcessRecordLocked(procName, r.appInfo.uid); + if (DEBUG_MU) + Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app); + if (app != null && app.thread != null) { + try { + app.addPackage(r.appInfo.packageName); + realStartServiceLocked(r, app); + return true; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when starting service " + r.shortName, e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + } else { + // If this service runs in an isolated process, then each time + // we call startProcessLocked() we will get a new isolated + // process, starting another process if we are currently waiting + // for a previous process to come up. To deal with this, we store + // in the service any current isolated process it is running in or + // waiting to have come up. + app = r.isolatedProc; + } + + // Not running -- get it started, and enqueue this service record + // to be executed when the app comes up. + if (app == null) { + if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, + "service", r.name, false, isolated)) == null) { + Slog.w(TAG, "Unable to launch app " + + r.appInfo.packageName + "/" + + r.appInfo.uid + " for service " + + r.intent.getIntent() + ": process is bad"); + bringDownServiceLocked(r, true); + return false; + } + if (isolated) { + r.isolatedProc = app; + } + } + + if (!mPendingServices.contains(r)) { + mPendingServices.add(r); + } + + return true; + } + + private final void requestServiceBindingsLocked(ServiceRecord r) { + Iterator<IntentBindRecord> bindings = r.bindings.values().iterator(); + while (bindings.hasNext()) { + IntentBindRecord i = bindings.next(); + if (!requestServiceBindingLocked(r, i, false)) { + break; + } + } + } + + private final void realStartServiceLocked(ServiceRecord r, + ProcessRecord app) throws RemoteException { + if (app.thread == null) { + throw new RemoteException(); + } + if (DEBUG_MU) + Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid + + ", ProcessRecord.uid = " + app.uid); + r.app = app; + r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); + + app.services.add(r); + bumpServiceExecutingLocked(r, "create"); + mAm.updateLruProcessLocked(app, true, true); + + boolean created = false; + try { + mAm.mStringBuilder.setLength(0); + r.intent.getIntent().toShortString(mAm.mStringBuilder, true, false, true, false); + EventLog.writeEvent(EventLogTags.AM_CREATE_SERVICE, + System.identityHashCode(r), r.shortName, + mAm.mStringBuilder.toString(), r.app.pid); + synchronized (r.stats.getBatteryStats()) { + r.stats.startLaunchedLocked(); + } + mAm.ensurePackageDexOpt(r.serviceInfo.packageName); + app.thread.scheduleCreateService(r, r.serviceInfo, + mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo)); + r.postNotification(); + created = true; + } finally { + if (!created) { + app.services.remove(r); + scheduleServiceRestartLocked(r, false); + } + } + + requestServiceBindingsLocked(r); + + // If the service is in the started state, and there are no + // pending arguments, then fake up one so its onStartCommand() will + // be called. + if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { + r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), + null, null)); + } + + sendServiceArgsLocked(r, true); + } + + private final void sendServiceArgsLocked(ServiceRecord r, + boolean oomAdjusted) { + final int N = r.pendingStarts.size(); + if (N == 0) { + return; + } + + while (r.pendingStarts.size() > 0) { + try { + ServiceRecord.StartItem si = r.pendingStarts.remove(0); + if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to: " + + r + " " + r.intent + " args=" + si.intent); + if (si.intent == null && N > 1) { + // If somehow we got a dummy null intent in the middle, + // then skip it. DO NOT skip a null intent when it is + // the only one in the list -- this is to support the + // onStartCommand(null) case. + continue; + } + si.deliveredTime = SystemClock.uptimeMillis(); + r.deliveredStarts.add(si); + si.deliveryCount++; + if (si.neededGrants != null) { + mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, + si.getUriPermissionsLocked()); + } + bumpServiceExecutingLocked(r, "start"); + if (!oomAdjusted) { + oomAdjusted = true; + mAm.updateOomAdjLocked(r.app); + } + int flags = 0; + if (si.deliveryCount > 1) { + flags |= Service.START_FLAG_RETRY; + } + if (si.doneExecutingCount > 0) { + flags |= Service.START_FLAG_REDELIVERY; + } + r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); + } catch (RemoteException e) { + // Remote process gone... we'll let the normal cleanup take + // care of this. + if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r); + break; + } catch (Exception e) { + Slog.w(TAG, "Unexpected exception", e); + break; + } + } + } + + private final void bringDownServiceLocked(ServiceRecord r, boolean force) { + //Slog.i(TAG, "Bring down service:"); + //r.dump(" "); + + // Does it still need to run? + if (!force && r.startRequested) { + return; + } + if (r.connections.size() > 0) { + if (!force) { + // XXX should probably keep a count of the number of auto-create + // connections directly in the service. + Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); + while (it.hasNext()) { + ArrayList<ConnectionRecord> cr = it.next(); + for (int i=0; i<cr.size(); i++) { + if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) { + return; + } + } + } + } + + // Report to all of the connections that the service is no longer + // available. + Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); + while (it.hasNext()) { + ArrayList<ConnectionRecord> c = it.next(); + for (int i=0; i<c.size(); i++) { + ConnectionRecord cr = c.get(i); + // There is still a connection to the service that is + // being brought down. Mark it as dead. + cr.serviceDead = true; + try { + cr.conn.connected(r.name, null); + } catch (Exception e) { + Slog.w(TAG, "Failure disconnecting service " + r.name + + " to connection " + c.get(i).conn.asBinder() + + " (in " + c.get(i).binding.client.processName + ")", e); + } + } + } + } + + // Tell the service that it has been unbound. + if (r.bindings.size() > 0 && r.app != null && r.app.thread != null) { + Iterator<IntentBindRecord> it = r.bindings.values().iterator(); + while (it.hasNext()) { + IntentBindRecord ibr = it.next(); + if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down binding " + ibr + + ": hasBound=" + ibr.hasBound); + if (r.app != null && r.app.thread != null && ibr.hasBound) { + try { + bumpServiceExecutingLocked(r, "bring down unbind"); + mAm.updateOomAdjLocked(r.app); + ibr.hasBound = false; + r.app.thread.scheduleUnbindService(r, + ibr.intent.getIntent()); + } catch (Exception e) { + Slog.w(TAG, "Exception when unbinding service " + + r.shortName, e); + serviceDoneExecutingLocked(r, true); + } + } + } + } + + if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down " + r + " " + r.intent); + EventLog.writeEvent(EventLogTags.AM_DESTROY_SERVICE, + System.identityHashCode(r), r.shortName, + (r.app != null) ? r.app.pid : -1); + + mServiceMap.removeServiceByName(r.name, r.userId); + mServiceMap.removeServiceByIntent(r.intent, r.userId); + r.totalRestartCount = 0; + unscheduleServiceRestartLocked(r); + + // Also make sure it is not on the pending list. + int N = mPendingServices.size(); + for (int i=0; i<N; i++) { + if (mPendingServices.get(i) == r) { + mPendingServices.remove(i); + if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r); + i--; + N--; + } + } + + r.cancelNotification(); + r.isForeground = false; + r.foregroundId = 0; + r.foregroundNoti = null; + + // Clear start entries. + r.clearDeliveredStartsLocked(); + r.pendingStarts.clear(); + + if (r.app != null) { + synchronized (r.stats.getBatteryStats()) { + r.stats.stopLaunchedLocked(); + } + r.app.services.remove(r); + if (r.app.thread != null) { + try { + bumpServiceExecutingLocked(r, "stop"); + mStoppingServices.add(r); + mAm.updateOomAdjLocked(r.app); + r.app.thread.scheduleStopService(r); + } catch (Exception e) { + Slog.w(TAG, "Exception when stopping service " + + r.shortName, e); + serviceDoneExecutingLocked(r, true); + } + updateServiceForegroundLocked(r.app, false); + } else { + if (DEBUG_SERVICE) Slog.v( + TAG, "Removed service that has no process: " + r); + } + } else { + if (DEBUG_SERVICE) Slog.v( + TAG, "Removed service that is not running: " + r); + } + + if (r.bindings.size() > 0) { + r.bindings.clear(); + } + + if (r.restarter instanceof ServiceRestarter) { + ((ServiceRestarter)r.restarter).setService(null); + } + } + + void removeConnectionLocked( + ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) { + IBinder binder = c.conn.asBinder(); + AppBindRecord b = c.binding; + ServiceRecord s = b.service; + ArrayList<ConnectionRecord> clist = s.connections.get(binder); + if (clist != null) { + clist.remove(c); + if (clist.size() == 0) { + s.connections.remove(binder); + } + } + b.connections.remove(c); + if (c.activity != null && c.activity != skipAct) { + if (c.activity.connections != null) { + c.activity.connections.remove(c); + } + } + if (b.client != skipApp) { + b.client.connections.remove(c); + if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { + b.client.updateHasAboveClientLocked(); + } + } + clist = mServiceConnections.get(binder); + if (clist != null) { + clist.remove(c); + if (clist.size() == 0) { + mServiceConnections.remove(binder); + } + } + + if (b.connections.size() == 0) { + b.intent.apps.remove(b.client); + } + + if (!c.serviceDead) { + if (DEBUG_SERVICE) Slog.v(TAG, "Disconnecting binding " + b.intent + + ": shouldUnbind=" + b.intent.hasBound); + if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0 + && b.intent.hasBound) { + try { + bumpServiceExecutingLocked(s, "unbind"); + mAm.updateOomAdjLocked(s.app); + b.intent.hasBound = false; + // Assume the client doesn't want to know about a rebind; + // we will deal with that later if it asks for one. + b.intent.doRebind = false; + s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent()); + } catch (Exception e) { + Slog.w(TAG, "Exception when unbinding service " + s.shortName, e); + serviceDoneExecutingLocked(s, true); + } + } + + if ((c.flags&Context.BIND_AUTO_CREATE) != 0) { + bringDownServiceLocked(s, false); + } + } + } + + void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) { + boolean inStopping = mStoppingServices.contains(r); + if (r != null) { + if (type == 1) { + // This is a call from a service start... take care of + // book-keeping. + r.callStart = true; + switch (res) { + case Service.START_STICKY_COMPATIBILITY: + case Service.START_STICKY: { + // We are done with the associated start arguments. + r.findDeliveredStart(startId, true); + // Don't stop if killed. + r.stopIfKilled = false; + break; + } + case Service.START_NOT_STICKY: { + // We are done with the associated start arguments. + r.findDeliveredStart(startId, true); + if (r.getLastStartId() == startId) { + // There is no more work, and this service + // doesn't want to hang around if killed. + r.stopIfKilled = true; + } + break; + } + case Service.START_REDELIVER_INTENT: { + // We'll keep this item until they explicitly + // call stop for it, but keep track of the fact + // that it was delivered. + ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); + if (si != null) { + si.deliveryCount = 0; + si.doneExecutingCount++; + // Don't stop if killed. + r.stopIfKilled = true; + } + break; + } + case Service.START_TASK_REMOVED_COMPLETE: { + // Special processing for onTaskRemoved(). Don't + // impact normal onStartCommand() processing. + r.findDeliveredStart(startId, true); + break; + } + default: + throw new IllegalArgumentException( + "Unknown service start result: " + res); + } + if (res == Service.START_STICKY_COMPATIBILITY) { + r.callStart = false; + } + } + final long origId = Binder.clearCallingIdentity(); + serviceDoneExecutingLocked(r, inStopping); + Binder.restoreCallingIdentity(origId); + } else { + Slog.w(TAG, "Done executing unknown service from pid " + + Binder.getCallingPid()); + } + } + + private void serviceDoneExecutingLocked(ServiceRecord r, boolean inStopping) { + if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r + + ": nesting=" + r.executeNesting + + ", inStopping=" + inStopping + ", app=" + r.app); + else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName); + r.executeNesting--; + if (r.executeNesting <= 0 && r.app != null) { + if (DEBUG_SERVICE) Slog.v(TAG, + "Nesting at 0 of " + r.shortName); + r.app.executingServices.remove(r); + if (r.app.executingServices.size() == 0) { + if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG, + "No more executingServices of " + r.shortName); + mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); + } + if (inStopping) { + if (DEBUG_SERVICE) Slog.v(TAG, + "doneExecuting remove stopping " + r); + mStoppingServices.remove(r); + r.bindings.clear(); + } + mAm.updateOomAdjLocked(r.app); + } + } + + boolean attachApplicationLocked(ProcessRecord proc, String processName) throws Exception { + boolean didSomething = false; + // Collect any services that are waiting for this process to come up. + if (mPendingServices.size() > 0) { + ServiceRecord sr = null; + try { + for (int i=0; i<mPendingServices.size(); i++) { + sr = mPendingServices.get(i); + if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid + || !processName.equals(sr.processName))) { + continue; + } + + mPendingServices.remove(i); + i--; + realStartServiceLocked(sr, proc); + didSomething = true; + } + } catch (Exception e) { + Slog.w(TAG, "Exception in new application when starting service " + + sr.shortName, e); + throw e; + } + } + // Also, if there are any services that are waiting to restart and + // would run in this process, now is a good time to start them. It would + // be weird to bring up the process but arbitrarily not let the services + // run at this point just because their restart time hasn't come up. + if (mRestartingServices.size() > 0) { + ServiceRecord sr = null; + for (int i=0; i<mRestartingServices.size(); i++) { + sr = mRestartingServices.get(i); + if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid + || !processName.equals(sr.processName))) { + continue; + } + mAm.mHandler.removeCallbacks(sr.restarter); + mAm.mHandler.post(sr.restarter); + } + } + return didSomething; + } + + void processStartTimedOutLocked(ProcessRecord proc) { + for (int i=0; i<mPendingServices.size(); i++) { + ServiceRecord sr = mPendingServices.get(i); + if ((proc.uid == sr.appInfo.uid + && proc.processName.equals(sr.processName)) + || sr.isolatedProc == proc) { + Slog.w(TAG, "Forcing bringing down service: " + sr); + sr.isolatedProc = null; + mPendingServices.remove(i); + i--; + bringDownServiceLocked(sr, true); + } + } + } + + private boolean collectForceStopServicesLocked(String name, int userId, + boolean evenPersistent, boolean doit, + HashMap<ComponentName, ServiceRecord> services, + ArrayList<ServiceRecord> result) { + boolean didSomething = false; + for (ServiceRecord service : services.values()) { + if ((name == null || service.packageName.equals(name)) + && (service.app == null || evenPersistent || !service.app.persistent)) { + if (!doit) { + return true; + } + didSomething = true; + Slog.i(TAG, " Force stopping service " + service); + if (service.app != null) { + service.app.removed = true; + } + service.app = null; + service.isolatedProc = null; + result.add(service); + } + } + return didSomething; + } + + boolean forceStopLocked(String name, int userId, boolean evenPersistent, boolean doit) { + boolean didSomething = false; + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + if (userId == UserHandle.USER_ALL) { + for (int i=0; i<mServiceMap.mServicesByNamePerUser.size(); i++) { + didSomething |= collectForceStopServicesLocked(name, userId, evenPersistent, + doit, mServiceMap.mServicesByNamePerUser.valueAt(i), services); + if (!doit && didSomething) { + return true; + } + } + } else { + HashMap<ComponentName, ServiceRecord> items + = mServiceMap.mServicesByNamePerUser.get(userId); + if (items != null) { + didSomething = collectForceStopServicesLocked(name, userId, evenPersistent, + doit, items, services); + } + } + + int N = services.size(); + for (int i=0; i<N; i++) { + bringDownServiceLocked(services.get(i), true); + } + return didSomething; + } + + void cleanUpRemovedTaskLocked(TaskRecord tr, ComponentName component, Intent baseIntent) { + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + for (ServiceRecord sr : mServiceMap.getAllServices(tr.userId)) { + if (sr.packageName.equals(component.getPackageName())) { + services.add(sr); + } + } + + // Take care of any running services associated with the app. + for (int i=0; i<services.size(); i++) { + ServiceRecord sr = services.get(i); + if (sr.startRequested) { + if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) { + Slog.i(TAG, "Stopping service " + sr.shortName + ": remove task"); + stopServiceLocked(sr); + } else { + sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, + sr.makeNextStartId(), baseIntent, null)); + if (sr.app != null && sr.app.thread != null) { + sendServiceArgsLocked(sr, false); + } + } + } + } + } + + final void killServicesLocked(ProcessRecord app, + boolean allowRestart) { + // Report disconnected services. + if (false) { + // XXX we are letting the client link to the service for + // death notifications. + if (app.services.size() > 0) { + Iterator<ServiceRecord> it = app.services.iterator(); + while (it.hasNext()) { + ServiceRecord r = it.next(); + if (r.connections.size() > 0) { + Iterator<ArrayList<ConnectionRecord>> jt + = r.connections.values().iterator(); + while (jt.hasNext()) { + ArrayList<ConnectionRecord> cl = jt.next(); + for (int i=0; i<cl.size(); i++) { + ConnectionRecord c = cl.get(i); + if (c.binding.client != app) { + try { + //c.conn.connected(r.className, null); + } catch (Exception e) { + // todo: this should be asynchronous! + Slog.w(TAG, "Exception thrown disconnected servce " + + r.shortName + + " from app " + app.processName, e); + } + } + } + } + } + } + } + } + + // Clean up any connections this application has to other services. + if (app.connections.size() > 0) { + Iterator<ConnectionRecord> it = app.connections.iterator(); + while (it.hasNext()) { + ConnectionRecord r = it.next(); + removeConnectionLocked(r, app, null); + } + } + app.connections.clear(); + + if (app.services.size() != 0) { + // Any services running in the application need to be placed + // back in the pending list. + Iterator<ServiceRecord> it = app.services.iterator(); + while (it.hasNext()) { + ServiceRecord sr = it.next(); + synchronized (sr.stats.getBatteryStats()) { + sr.stats.stopLaunchedLocked(); + } + sr.app = null; + sr.isolatedProc = null; + sr.executeNesting = 0; + if (mStoppingServices.remove(sr)) { + if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove stopping " + sr); + } + + boolean hasClients = sr.bindings.size() > 0; + if (hasClients) { + Iterator<IntentBindRecord> bindings + = sr.bindings.values().iterator(); + while (bindings.hasNext()) { + IntentBindRecord b = bindings.next(); + if (DEBUG_SERVICE) Slog.v(TAG, "Killing binding " + b + + ": shouldUnbind=" + b.hasBound); + b.binder = null; + b.requested = b.received = b.hasBound = false; + } + } + + if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) == 0) { + Slog.w(TAG, "Service crashed " + sr.crashCount + + " times, stopping: " + sr); + EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, + sr.crashCount, sr.shortName, app.pid); + bringDownServiceLocked(sr, true); + } else if (!allowRestart) { + bringDownServiceLocked(sr, true); + } else { + boolean canceled = scheduleServiceRestartLocked(sr, true); + + // Should the service remain running? Note that in the + // extreme case of so many attempts to deliver a command + // that it failed we also will stop it here. + if (sr.startRequested && (sr.stopIfKilled || canceled)) { + if (sr.pendingStarts.size() == 0) { + sr.startRequested = false; + if (!hasClients) { + // Whoops, no reason to restart! + bringDownServiceLocked(sr, true); + } + } + } + } + } + + if (!allowRestart) { + app.services.clear(); + } + } + + // Make sure we have no more records on the stopping list. + int i = mStoppingServices.size(); + while (i > 0) { + i--; + ServiceRecord sr = mStoppingServices.get(i); + if (sr.app == app) { + mStoppingServices.remove(i); + if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove stopping " + sr); + } + } + + app.executingServices.clear(); + } + + ActivityManager.RunningServiceInfo makeRunningServiceInfoLocked(ServiceRecord r) { + ActivityManager.RunningServiceInfo info = + new ActivityManager.RunningServiceInfo(); + info.service = r.name; + if (r.app != null) { + info.pid = r.app.pid; + } + info.uid = r.appInfo.uid; + info.process = r.processName; + info.foreground = r.isForeground; + info.activeSince = r.createTime; + info.started = r.startRequested; + info.clientCount = r.connections.size(); + info.crashCount = r.crashCount; + info.lastActivityTime = r.lastActivity; + if (r.isForeground) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_FOREGROUND; + } + if (r.startRequested) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_STARTED; + } + if (r.app != null && r.app.pid == ActivityManagerService.MY_PID) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_SYSTEM_PROCESS; + } + if (r.app != null && r.app.persistent) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS; + } + + for (ArrayList<ConnectionRecord> connl : r.connections.values()) { + for (int i=0; i<connl.size(); i++) { + ConnectionRecord conn = connl.get(i); + if (conn.clientLabel != 0) { + info.clientPackage = conn.binding.client.info.packageName; + info.clientLabel = conn.clientLabel; + return info; + } + } + } + return info; + } + + List<ActivityManager.RunningServiceInfo> getRunningServiceInfoLocked(int maxNum, + int flags) { + ArrayList<ActivityManager.RunningServiceInfo> res + = new ArrayList<ActivityManager.RunningServiceInfo>(); + + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + if (ActivityManager.checkUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + uid) == PackageManager.PERMISSION_GRANTED) { + int[] users = mAm.getUsersLocked(); + for (int ui=0; ui<users.length && res.size() < maxNum; ui++) { + if (mServiceMap.getAllServices(users[ui]).size() > 0) { + Iterator<ServiceRecord> it = mServiceMap.getAllServices( + users[ui]).iterator(); + while (it.hasNext() && res.size() < maxNum) { + res.add(makeRunningServiceInfoLocked(it.next())); + } + } + } + + for (int i=0; i<mRestartingServices.size() && res.size() < maxNum; i++) { + ServiceRecord r = mRestartingServices.get(i); + ActivityManager.RunningServiceInfo info = + makeRunningServiceInfoLocked(r); + info.restarting = r.nextRestartTime; + res.add(info); + } + } else { + int userId = UserHandle.getUserId(uid); + if (mServiceMap.getAllServices(userId).size() > 0) { + Iterator<ServiceRecord> it + = mServiceMap.getAllServices(userId).iterator(); + while (it.hasNext() && res.size() < maxNum) { + res.add(makeRunningServiceInfoLocked(it.next())); + } + } + + for (int i=0; i<mRestartingServices.size() && res.size() < maxNum; i++) { + ServiceRecord r = mRestartingServices.get(i); + if (r.userId == userId) { + ActivityManager.RunningServiceInfo info = + makeRunningServiceInfoLocked(r); + info.restarting = r.nextRestartTime; + res.add(info); + } + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + + return res; + } + + public PendingIntent getRunningServiceControlPanelLocked(ComponentName name) { + int userId = UserHandle.getUserId(Binder.getCallingUid()); + ServiceRecord r = mServiceMap.getServiceByName(name, userId); + if (r != null) { + for (ArrayList<ConnectionRecord> conn : r.connections.values()) { + for (int i=0; i<conn.size(); i++) { + if (conn.get(i).clientIntent != null) { + return conn.get(i).clientIntent; + } + } + } + } + return null; + } + + void serviceTimeout(ProcessRecord proc) { + String anrMessage = null; + + synchronized(this) { + if (proc.executingServices.size() == 0 || proc.thread == null) { + return; + } + long maxTime = SystemClock.uptimeMillis() - SERVICE_TIMEOUT; + Iterator<ServiceRecord> it = proc.executingServices.iterator(); + ServiceRecord timeout = null; + long nextTime = 0; + while (it.hasNext()) { + ServiceRecord sr = it.next(); + if (sr.executingStart < maxTime) { + timeout = sr; + break; + } + if (sr.executingStart > nextTime) { + nextTime = sr.executingStart; + } + } + if (timeout != null && mAm.mLruProcesses.contains(proc)) { + Slog.w(TAG, "Timeout executing service: " + timeout); + anrMessage = "Executing service " + timeout.shortName; + } else { + Message msg = mAm.mHandler.obtainMessage( + ActivityManagerService.SERVICE_TIMEOUT_MSG); + msg.obj = proc; + mAm.mHandler.sendMessageAtTime(msg, nextTime+SERVICE_TIMEOUT); + } + } + + if (anrMessage != null) { + mAm.appNotResponding(proc, null, null, anrMessage); + } + } + + /** + * Prints a list of ServiceRecords (dumpsys activity services) + */ + boolean dumpServicesLocked(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { + boolean needSep = false; + + ItemMatcher matcher = new ItemMatcher(); + matcher.build(args, opti); + + pw.println("ACTIVITY MANAGER SERVICES (dumpsys activity services)"); + try { + int[] users = mAm.getUsersLocked(); + for (int user : users) { + if (mServiceMap.getAllServices(user).size() > 0) { + boolean printed = false; + long nowReal = SystemClock.elapsedRealtime(); + Iterator<ServiceRecord> it = mServiceMap.getAllServices( + user).iterator(); + needSep = false; + while (it.hasNext()) { + ServiceRecord r = it.next(); + if (!matcher.match(r, r.name)) { + continue; + } + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (!printed) { + if (user != 0) { + pw.println(); + } + pw.println(" User " + user + " active services:"); + printed = true; + } + if (needSep) { + pw.println(); + } + pw.print(" * "); + pw.println(r); + if (dumpAll) { + r.dump(pw, " "); + needSep = true; + } else { + pw.print(" app="); + pw.println(r.app); + pw.print(" created="); + TimeUtils.formatDuration(r.createTime, nowReal, pw); + pw.print(" started="); + pw.print(r.startRequested); + pw.print(" connections="); + pw.println(r.connections.size()); + if (r.connections.size() > 0) { + pw.println(" Connections:"); + for (ArrayList<ConnectionRecord> clist : r.connections.values()) { + for (int i = 0; i < clist.size(); i++) { + ConnectionRecord conn = clist.get(i); + pw.print(" "); + pw.print(conn.binding.intent.intent.getIntent() + .toShortString(false, false, false, false)); + pw.print(" -> "); + ProcessRecord proc = conn.binding.client; + pw.println(proc != null ? proc.toShortString() : "null"); + } + } + } + } + if (dumpClient && r.app != null && r.app.thread != null) { + pw.println(" Client:"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), + r, args); + tp.setBufferPrefix(" "); + // Short timeout, since blocking here can + // deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(" Failure while dumping the service: " + e); + } catch (RemoteException e) { + pw.println(" Got a RemoteException while dumping the service"); + } + needSep = true; + } + } + needSep = printed; + } + } + } catch (Exception e) { + Log.w(TAG, "Exception in dumpServicesLocked: " + e); + } + + if (mPendingServices.size() > 0) { + boolean printed = false; + for (int i=0; i<mPendingServices.size(); i++) { + ServiceRecord r = mPendingServices.get(i); + if (!matcher.match(r, r.name)) { + continue; + } + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (!printed) { + if (needSep) pw.println(" "); + needSep = true; + pw.println(" Pending services:"); + printed = true; + } + pw.print(" * Pending "); pw.println(r); + r.dump(pw, " "); + } + needSep = true; + } + + if (mRestartingServices.size() > 0) { + boolean printed = false; + for (int i=0; i<mRestartingServices.size(); i++) { + ServiceRecord r = mRestartingServices.get(i); + if (!matcher.match(r, r.name)) { + continue; + } + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (!printed) { + if (needSep) pw.println(" "); + needSep = true; + pw.println(" Restarting services:"); + printed = true; + } + pw.print(" * Restarting "); pw.println(r); + r.dump(pw, " "); + } + needSep = true; + } + + if (mStoppingServices.size() > 0) { + boolean printed = false; + for (int i=0; i<mStoppingServices.size(); i++) { + ServiceRecord r = mStoppingServices.get(i); + if (!matcher.match(r, r.name)) { + continue; + } + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (!printed) { + if (needSep) pw.println(" "); + needSep = true; + pw.println(" Stopping services:"); + printed = true; + } + pw.print(" * Stopping "); pw.println(r); + r.dump(pw, " "); + } + needSep = true; + } + + if (dumpAll) { + if (mServiceConnections.size() > 0) { + boolean printed = false; + Iterator<ArrayList<ConnectionRecord>> it + = mServiceConnections.values().iterator(); + while (it.hasNext()) { + ArrayList<ConnectionRecord> r = it.next(); + for (int i=0; i<r.size(); i++) { + ConnectionRecord cr = r.get(i); + if (!matcher.match(cr.binding.service, cr.binding.service.name)) { + continue; + } + if (dumpPackage != null && (cr.binding.client == null + || !dumpPackage.equals(cr.binding.client.info.packageName))) { + continue; + } + if (!printed) { + if (needSep) pw.println(" "); + needSep = true; + pw.println(" Connection bindings to services:"); + printed = true; + } + pw.print(" * "); pw.println(cr); + cr.dump(pw, " "); + } + } + needSep = true; + } + } + + return needSep; + } + + /** + * There are three ways to call this: + * - no service specified: dump all the services + * - a flattened component name that matched an existing service was specified as the + * first arg: dump that one service + * - the first arg isn't the flattened component name of an existing service: + * dump all services whose component contains the first arg as a substring + */ + protected boolean dumpService(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + + synchronized (this) { + int[] users = mAm.getUsersLocked(); + if ("all".equals(name)) { + for (int user : users) { + for (ServiceRecord r1 : mServiceMap.getAllServices(user)) { + services.add(r1); + } + } + } else { + ComponentName componentName = name != null + ? ComponentName.unflattenFromString(name) : null; + int objectId = 0; + if (componentName == null) { + // Not a '/' separated full component name; maybe an object ID? + try { + objectId = Integer.parseInt(name, 16); + name = null; + componentName = null; + } catch (RuntimeException e) { + } + } + + for (int user : users) { + for (ServiceRecord r1 : mServiceMap.getAllServices(user)) { + if (componentName != null) { + if (r1.name.equals(componentName)) { + services.add(r1); + } + } else if (name != null) { + if (r1.name.flattenToString().contains(name)) { + services.add(r1); + } + } else if (System.identityHashCode(r1) == objectId) { + services.add(r1); + } + } + } + } + } + + if (services.size() <= 0) { + return false; + } + + boolean needSep = false; + for (int i=0; i<services.size(); i++) { + if (needSep) { + pw.println(); + } + needSep = true; + dumpService("", fd, pw, services.get(i), args, dumpAll); + } + return true; + } + + /** + * Invokes IApplicationThread.dumpService() on the thread of the specified service if + * there is a thread associated with the service. + */ + private void dumpService(String prefix, FileDescriptor fd, PrintWriter pw, + final ServiceRecord r, String[] args, boolean dumpAll) { + String innerPrefix = prefix + " "; + synchronized (this) { + pw.print(prefix); pw.print("SERVICE "); + pw.print(r.shortName); pw.print(" "); + pw.print(Integer.toHexString(System.identityHashCode(r))); + pw.print(" pid="); + if (r.app != null) pw.println(r.app.pid); + else pw.println("(not running)"); + if (dumpAll) { + r.dump(pw, innerPrefix); + } + } + if (r.app != null && r.app.thread != null) { + pw.print(prefix); pw.println(" Client:"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), r, args); + tp.setBufferPrefix(prefix + " "); + tp.go(fd); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(prefix + " Failure while dumping the service: " + e); + } catch (RemoteException e) { + pw.println(prefix + " Got a RemoteException while dumping the service"); + } + } + } + +} diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 5d74cf3..ce5424b 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -27,6 +27,7 @@ import com.android.server.ProcessMap; import com.android.server.SystemServer; import com.android.server.Watchdog; import com.android.server.am.ActivityStack.ActivityState; +import com.android.server.pm.UserManagerService; import com.android.server.wm.WindowManagerService; import dalvik.system.Zygote; @@ -46,12 +47,13 @@ import android.app.IInstrumentationWatcher; import android.app.INotificationManager; import android.app.IProcessObserver; import android.app.IServiceConnection; +import android.app.IStopUserCallback; import android.app.IThumbnailReceiver; +import android.app.IUserSwitchObserver; import android.app.Instrumentation; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.Service; import android.app.backup.IBackupManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; @@ -76,12 +78,12 @@ import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.UserInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -99,6 +101,8 @@ import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.IPermissionController; +import android.os.IRemoteCallback; +import android.os.IUserManager; import android.os.Looper; import android.os.Message; import android.os.Parcel; @@ -111,7 +115,7 @@ import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.UserId; +import android.os.UserHandle; import android.provider.Settings; import android.text.format.Time; import android.util.EventLog; @@ -120,7 +124,6 @@ import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Gravity; import android.view.LayoutInflater; @@ -144,7 +147,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -153,7 +155,6 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -242,37 +243,16 @@ public final class ActivityManagerService extends ActivityManagerNative static final int BROADCAST_FG_TIMEOUT = 10*1000; static final int BROADCAST_BG_TIMEOUT = 60*1000; - // How long we wait for a service to finish executing. - static final int SERVICE_TIMEOUT = 20*1000; - - // 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; - - // How long a service needs to be running until it will start back at - // SERVICE_RESTART_DURATION after being killed. - static final int SERVICE_RESET_RUN_DURATION = 60*1000; - - // Multiplying factor to increase restart duration time by, for each time - // a service is killed before it has run for SERVICE_RESET_RUN_DURATION. - static final int SERVICE_RESTART_DURATION_FACTOR = 4; - - // The minimum amount of time between restarting services that we allow. - // That is, when multiple services are restarting, we won't allow each - // to restart less than this amount of time from the last one. - static final int SERVICE_MIN_RESTART_TIME_BETWEEN = 10*1000; - - // Maximum amount of time for there to be no activity on a service before - // we consider it non-essential and allow its process to go on the - // LRU background list. - static final int MAX_SERVICE_INACTIVITY = 30*60*1000; - // How long we wait until we timeout on key dispatching. static final int KEY_DISPATCHING_TIMEOUT = 5*1000; // How long we wait until we timeout on key dispatching during instrumentation. static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60*1000; + // Amount of time we wait for observers to handle a user switch before + // giving up on them and unfreezing the screen. + static final int USER_SWITCH_TIMEOUT = 2*1000; + static final int MY_PID = Process.myPid(); static final String[] EMPTY_STRING_ARRAY = new String[0]; @@ -454,6 +434,27 @@ public final class ActivityManagerService extends ActivityManagerNative long mPreviousProcessVisibleTime; /** + * Which uses have been started, so are allowed to run code. + */ + final SparseArray<UserStartedState> mStartedUsers = new SparseArray<UserStartedState>(); + + /** + * LRU list of history of current users. Most recently current is at the end. + */ + final ArrayList<Integer> mUserLru = new ArrayList<Integer>(); + + /** + * Registered observers of the user switching mechanics. + */ + final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers + = new RemoteCallbackList<IUserSwitchObserver>(); + + /** + * Currently active user switch. + */ + Object mCurUserSwitchCallback; + + /** * Packages that the user has asked to have run in screen size * compatibility mode instead of filling the screen. */ @@ -514,48 +515,36 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + protected BroadcastFilter newResult(BroadcastFilter filter, int match, int userId) { + if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL + || userId == filter.owningUserId) { + return super.newResult(filter, match, userId); + } + return null; + } + + @Override + protected BroadcastFilter[] newArray(int size) { + return new BroadcastFilter[size]; + } + + @Override protected String packageForFilter(BroadcastFilter filter) { return filter.packageName; } }; /** - * State of all active sticky broadcasts. Keys are the action of the + * State of all active sticky broadcasts per user. Keys are the action of the * sticky Intent, values are an ArrayList of all broadcasted intents with - * that action (which should usually be one). - */ - final HashMap<String, ArrayList<Intent>> mStickyBroadcasts = - new HashMap<String, ArrayList<Intent>>(); - - final ServiceMap mServiceMap = new ServiceMap(); - - /** - * All currently bound service connections. Keys are the IBinder of - * the client's IServiceConnection. - */ - final HashMap<IBinder, ArrayList<ConnectionRecord>> mServiceConnections - = new HashMap<IBinder, ArrayList<ConnectionRecord>>(); - - /** - * List of services that we have been asked to start, - * but haven't yet been able to. It is used to hold start requests - * while waiting for their corresponding application thread to get - * going. - */ - final ArrayList<ServiceRecord> mPendingServices - = new ArrayList<ServiceRecord>(); - - /** - * List of services that are scheduled to restart following a crash. + * that action (which should usually be one). The SparseArray is keyed + * by the user ID the sticky is for, and can include UserHandle.USER_ALL + * for stickies that are sent to all users. */ - final ArrayList<ServiceRecord> mRestartingServices - = new ArrayList<ServiceRecord>(); + final SparseArray<HashMap<String, ArrayList<Intent>>> mStickyBroadcasts = + new SparseArray<HashMap<String, ArrayList<Intent>>>(); - /** - * List of services that are in the process of being stopped. - */ - final ArrayList<ServiceRecord> mStoppingServices - = new ArrayList<ServiceRecord>(); + final ActiveServices mServices; /** * Backup/restore process management @@ -575,7 +564,7 @@ public final class ActivityManagerService extends ActivityManagerNative */ final ArrayList mCancelledThumbnails = new ArrayList(); - final ProviderMap mProviderMap = new ProviderMap(); + final ProviderMap mProviderMap; /** * List of content providers who have clients waiting for them. The @@ -733,6 +722,18 @@ public final class ActivityManagerService extends ActivityManagerNative int mLruSeq = 0; /** + * Keep track of the non-hidden/empty process we last found, to help + * determine how to distribute hidden/empty processes next time. + */ + int mNumNonHiddenProcs = 0; + + /** + * Keep track of the number of hidden procs, to balance oom adj + * distribution between those and empty procs. + */ + int mNumHiddenProcs = 0; + + /** * Keep track of the number of service processes we last found, to * determine on the next iteration which should be B services. */ @@ -827,6 +828,9 @@ public final class ActivityManagerService extends ActivityManagerNative static ActivityManagerService mSelf; static ActivityThread mSystemThread; + private int mCurrentUserId; + private UserManagerService mUserManager; + private final class AppDeathRecipient implements IBinder.DeathRecipient { final ProcessRecord mApp; final int mPid; @@ -876,6 +880,9 @@ public final class ActivityManagerService extends ActivityManagerNative static final int DISPATCH_PROCESSES_CHANGED = 31; static final int DISPATCH_PROCESS_DIED = 32; static final int REPORT_MEM_USAGE = 33; + static final int REPORT_USER_SWITCH_MSG = 34; + static final int CONTINUE_USER_SWITCH_MSG = 35; + static final int USER_SWITCH_TIMEOUT_MSG = 36; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1010,10 +1017,10 @@ public final class ActivityManagerService extends ActivityManagerNative mDidDexOpt = false; Message nmsg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG); nmsg.obj = msg.obj; - mHandler.sendMessageDelayed(nmsg, SERVICE_TIMEOUT); + mHandler.sendMessageDelayed(nmsg, ActiveServices.SERVICE_TIMEOUT); return; } - serviceTimeout((ProcessRecord)msg.obj); + mServices.serviceTimeout((ProcessRecord)msg.obj); } break; case UPDATE_TIME_ZONE: { synchronized (ActivityManagerService.this) { @@ -1111,11 +1118,11 @@ public final class ActivityManagerService extends ActivityManagerNative } break; case KILL_APPLICATION_MSG: { synchronized (ActivityManagerService.this) { - int uid = msg.arg1; + int appid = msg.arg1; boolean restart = (msg.arg2 == 1); String pkg = (String) msg.obj; - forceStopPackageLocked(pkg, uid, restart, false, true, false, - UserId.getUserId(uid)); + forceStopPackageLocked(pkg, appid, restart, false, true, false, + UserHandle.USER_ALL); } } break; case FINALIZE_PENDING_INTENT_MSG: { @@ -1147,13 +1154,15 @@ public final class ActivityManagerService extends ActivityManagerNative notification.vibrate = null; notification.setLatestEventInfo(context, text, mContext.getText(R.string.heavy_weight_notification_detail), - PendingIntent.getActivity(mContext, 0, root.intent, - PendingIntent.FLAG_CANCEL_CURRENT)); + PendingIntent.getActivityAsUser(mContext, 0, root.intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, + new UserHandle(root.userId))); try { int[] outId = new int[1]; - inm.enqueueNotification("android", R.string.heavy_weight_notification, - notification, outId); + inm.enqueueNotificationWithTag("android", null, + R.string.heavy_weight_notification, + notification, outId, root.userId); } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Error showing notification for heavy-weight app", e); @@ -1169,8 +1178,8 @@ public final class ActivityManagerService extends ActivityManagerNative return; } try { - inm.cancelNotification("android", - R.string.heavy_weight_notification); + inm.cancelNotificationWithTag("android", null, + R.string.heavy_weight_notification, msg.arg1); } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Error canceling notification for service", e); @@ -1284,7 +1293,8 @@ public final class ActivityManagerService extends ActivityManagerNative catPw.println(); dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null); catPw.println(); - dumpServicesLocked(null, catPw, emptyArgs, 0, false, false, null); + mServices.dumpServicesLocked(null, catPw, emptyArgs, 0, + false, false, null); catPw.println(); dumpActivitiesLocked(null, catPw, emptyArgs, 0, false, false, null); } @@ -1303,6 +1313,18 @@ public final class ActivityManagerService extends ActivityManagerNative thread.start(); break; } + case REPORT_USER_SWITCH_MSG: { + dispatchUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2); + break; + } + case CONTINUE_USER_SWITCH_MSG: { + continueUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2); + break; + } + case USER_SWITCH_TIMEOUT_MSG: { + timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2); + break; + } } } }; @@ -1521,6 +1543,9 @@ public final class ActivityManagerService extends ActivityManagerNative mBroadcastQueues[0] = mFgBroadcastQueue; mBroadcastQueues[1] = mBgBroadcastQueue; + mServices = new ActiveServices(this); + mProviderMap = new ProviderMap(this); + File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); systemDir.mkdirs(); @@ -1536,11 +1561,16 @@ public final class ActivityManagerService extends ActivityManagerNative systemDir, "usagestats").toString()); mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0")); + // User 0 is the first and only user that runs at boot. + mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true)); + mUserLru.add(Integer.valueOf(0)); + GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED); mConfiguration.setToDefaults(); - mConfiguration.locale = Locale.getDefault(); + mConfiguration.setLocale(Locale.getDefault()); + mConfigurationSeq = mConfiguration.seq = 1; mProcessStats.init(); @@ -1764,7 +1794,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private final void updateLruProcessInternalLocked(ProcessRecord app, - boolean oomAdj, boolean updateActivityTime, int bestPos) { + boolean updateActivityTime, int bestPos) { // put it on the LRU to keep track of when it should be exited. int lrui = mLruProcesses.indexOf(app); if (lrui >= 0) mLruProcesses.remove(lrui); @@ -1796,7 +1826,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Also don't let it kick out the first few "real" hidden processes. skipTop = ProcessList.MIN_HIDDEN_APPS; } - + while (i >= 0) { ProcessRecord p = mLruProcesses.get(i); // If this app shouldn't be in front of the first N background @@ -1821,7 +1851,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (cr.binding != null && cr.binding.service != null && cr.binding.service.app != null && cr.binding.service.app.lruSeq != mLruSeq) { - updateLruProcessInternalLocked(cr.binding.service.app, false, + updateLruProcessInternalLocked(cr.binding.service.app, updateActivityTime, i+1); } } @@ -1829,21 +1859,21 @@ public final class ActivityManagerService extends ActivityManagerNative for (int j=app.conProviders.size()-1; j>=0; j--) { ContentProviderRecord cpr = app.conProviders.get(j).provider; if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) { - updateLruProcessInternalLocked(cpr.proc, false, + updateLruProcessInternalLocked(cpr.proc, updateActivityTime, i+1); } } - - //Slog.i(TAG, "Putting proc to front: " + app.processName); - if (oomAdj) { - updateOomAdjLocked(); - } } final void updateLruProcessLocked(ProcessRecord app, boolean oomAdj, boolean updateActivityTime) { mLruSeq++; - updateLruProcessInternalLocked(app, oomAdj, updateActivityTime, 0); + updateLruProcessInternalLocked(app, updateActivityTime, 0); + + //Slog.i(TAG, "Putting proc to front: " + app.processName); + if (oomAdj) { + updateOomAdjLocked(); + } } final ProcessRecord getProcessRecordLocked( @@ -1857,7 +1887,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (procs == null) return null; final int N = procs.size(); for (int i = 0; i < N; i++) { - if (UserId.isSameUser(procs.keyAt(i), uid)) return procs.valueAt(i); + if (UserHandle.isSameUser(procs.keyAt(i), uid)) return procs.valueAt(i); } } ProcessRecord proc = mProcessNames.get(processName, uid); @@ -1992,7 +2022,7 @@ public final class ActivityManagerService extends ActivityManagerNative mPidsSelfLocked.remove(app.pid); mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); } - app.pid = 0; + app.setPid(0); } if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG, @@ -2008,13 +2038,37 @@ public final class ActivityManagerService extends ActivityManagerNative int uid = app.uid; int[] gids = null; + int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; if (!app.isolated) { + int[] permGids = null; try { - gids = mContext.getPackageManager().getPackageGids( - app.info.packageName); + final PackageManager pm = mContext.getPackageManager(); + permGids = pm.getPackageGids(app.info.packageName); + + if (Environment.isExternalStorageEmulated()) { + if (pm.checkPermission( + android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, + app.info.packageName) == PERMISSION_GRANTED) { + mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL; + } else { + mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER; + } + } } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Unable to retrieve gids", e); } + + /* + * Add shared application GID so applications can share some + * resources like shared libraries + */ + if (permGids == null) { + gids = new int[1]; + } else { + gids = new int[permGids.length + 1]; + System.arraycopy(permGids, 0, gids, 1, permGids.length); + } + gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid)); } if (mFactoryTest != SystemServer.FACTORY_TEST_OFF) { if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL @@ -2053,7 +2107,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Start the process. It will either succeed and return a result containing // the PID of the new process, or else throw a RuntimeException. Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread", - app.processName, uid, uid, gids, debugFlags, + app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, null, null); BatteryStatsImpl bs = app.batteryStats.getBatteryStats(); @@ -2095,7 +2149,7 @@ public final class ActivityManagerService extends ActivityManagerNative } buf.append("}"); Slog.i(TAG, buf.toString()); - app.pid = startResult.pid; + app.setPid(startResult.pid); app.usingWrapper = startResult.usingWrapper; app.removed = false; synchronized (mPidsSelfLocked) { @@ -2107,7 +2161,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } catch (RuntimeException e) { // XXX do better error recovery. - app.pid = 0; + app.setPid(0); Slog.e(TAG, "Failure starting process " + app.processName, e); } } @@ -2143,8 +2197,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.addCategory(Intent.CATEGORY_HOME); } ActivityInfo aInfo = - intent.resolveActivityInfo(mContext.getPackageManager(), - STOCK_PM_FLAGS); + resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); if (aInfo != null) { intent.setComponent(new ComponentName( aInfo.applicationInfo.packageName, aInfo.name)); @@ -2164,6 +2217,29 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } + private ActivityInfo resolveActivityInfo(Intent intent, int flags, int userId) { + ActivityInfo ai = null; + ComponentName comp = intent.getComponent(); + try { + if (comp != null) { + ai = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId); + } else { + ResolveInfo info = AppGlobals.getPackageManager().resolveIntent( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags, userId); + + if (info != null) { + ai = info.activityInfo; + } + } + } catch (RemoteException e) { + // ignore + } + + return ai; + } + /** * Starts the "new version setup screen" if appropriate. */ @@ -2223,7 +2299,7 @@ public final class ActivityManagerService extends ActivityManagerNative } void enforceNotIsolatedCaller(String caller) { - if (UserId.isIsolated(Binder.getCallingUid())) { + if (UserHandle.isIsolated(Binder.getCallingUid())) { throw new SecurityException("Isolated process not allowed to call " + caller); } } @@ -2351,21 +2427,17 @@ public final class ActivityManagerService extends ActivityManagerNative Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) { + return startActivityAsUser(caller, intent, resolvedType, resultTo, resultWho, requestCode, + startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId()); + } + + public final int startActivityAsUser(IApplicationThread caller, + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, + String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) { enforceNotIsolatedCaller("startActivity"); - int userId = 0; - if (intent.getCategories() != null && intent.getCategories().contains(Intent.CATEGORY_HOME)) { - // Requesting home, set the identity to the current user - // HACK! - userId = mCurrentUserId; - } else { - // TODO: Fix this in a better way - calls coming from SystemUI should probably carry - // the current user's userId - if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) { - userId = 0; - } else { - userId = Binder.getOrigCallingUser(); - } - } + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), userId, + false, true, "startActivity", null); return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, null, null, options, userId); @@ -2374,24 +2446,27 @@ public final class ActivityManagerService extends ActivityManagerNative public final WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, - ParcelFileDescriptor profileFd, Bundle options) { + ParcelFileDescriptor profileFd, Bundle options, int userId) { enforceNotIsolatedCaller("startActivityAndWait"); + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), userId, + false, true, "startActivityAndWait", null); WaitResult res = new WaitResult(); - int userId = Binder.getOrigCallingUser(); mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, - res, null, options, userId); + res, null, options, UserHandle.getCallingUserId()); return res; } public final int startActivityWithConfig(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Configuration config, - Bundle options) { + Bundle options, int userId) { enforceNotIsolatedCaller("startActivityWithConfig"); + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), userId, + false, true, "startActivityWithConfig", null); int ret = mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, - null, null, null, config, options, Binder.getOrigCallingUser()); + null, null, null, config, options, userId); return ret; } @@ -2456,7 +2531,7 @@ public final class ActivityManagerService extends ActivityManagerNative AppGlobals.getPackageManager().queryIntentActivities( intent, r.resolvedType, PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS, - UserId.getCallingUserId()); + UserHandle.getCallingUserId()); // Look for the original activity in the list... final int N = resolves != null ? resolves.size() : 0; @@ -2522,18 +2597,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public final int startActivityInPackage(int uid, + final int startActivityInPackage(int uid, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, Bundle options) { - - // This is so super not safe, that only the system (or okay root) - // can do it. - int userId = Binder.getOrigCallingUser(); - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.myUid()) { - throw new SecurityException( - "startActivityInPackage only available to the system"); - } + String resultWho, int requestCode, int startFlags, Bundle options, int userId) { + + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), userId, + false, true, "startActivityInPackage", null); int ret = mMainStack.startActivityMayWait(null, uid, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, @@ -2545,23 +2614,18 @@ public final class ActivityManagerService extends ActivityManagerNative Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options) { enforceNotIsolatedCaller("startActivities"); int ret = mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo, - options, Binder.getOrigCallingUser()); + options, UserHandle.getCallingUserId()); return ret; } - public final int startActivitiesInPackage(int uid, + final int startActivitiesInPackage(int uid, Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle options) { + Bundle options, int userId) { - // This is so super not safe, that only the system (or okay root) - // can do it. - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.myUid()) { - throw new SecurityException( - "startActivityInPackage only available to the system"); - } + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), userId, + false, true, "startActivityInPackage", null); int ret = mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo, - options, UserId.getUserId(uid)); + options, userId); return ret; } @@ -2660,7 +2724,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final long origId = Binder.clearCallingIdentity(); boolean res = mMainStack.requestFinishActivityLocked(token, resultCode, - resultData, "app-request"); + resultData, "app-request", true); Binder.restoreCallingIdentity(origId); return res; } @@ -2690,13 +2754,14 @@ public final class ActivityManagerService extends ActivityManagerNative int index = mMainStack.indexOfTokenLocked(r.appToken); if (index >= 0) { mMainStack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, - null, "finish-heavy"); + null, "finish-heavy", true); } } } + mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG, + mHeavyWeightProcess.userId, 0)); mHeavyWeightProcess = null; - mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); } } @@ -3379,10 +3444,12 @@ public final class ActivityManagerService extends ActivityManagerNative } public boolean clearApplicationUserData(final String packageName, - final IPackageDataObserver observer, final int userId) { + final IPackageDataObserver observer, int userId) { enforceNotIsolatedCaller("clearApplicationUserData"); int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); + userId = handleIncomingUserLocked(pid, uid, + userId, false, true, "clearApplicationUserData", null); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); @@ -3424,7 +3491,7 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } - public void killBackgroundProcesses(final String packageName) { + public void killBackgroundProcesses(final String packageName, int userId) { if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) != PackageManager.PERMISSION_GRANTED && checkCallingPermission(android.Manifest.permission.RESTART_PACKAGES) @@ -3436,22 +3503,23 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - - int userId = UserId.getCallingUserId(); + + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), + userId, true, true, "killBackgroundProcesses", null); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); - int pkgUid = -1; synchronized(this) { + int appId = -1; try { - pkgUid = pm.getPackageUid(packageName, userId); + appId = UserHandle.getAppId(pm.getPackageUid(packageName, 0)); } catch (RemoteException e) { } - if (pkgUid == -1) { + if (appId == -1) { Slog.w(TAG, "Invalid packageName: " + packageName); return; } - killPackageProcessesLocked(packageName, pkgUid, + killPackageProcessesLocked(packageName, appId, userId, ProcessList.SERVICE_ADJ, false, true, true, false, "kill background"); } } finally { @@ -3501,7 +3569,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public void forceStopPackage(final String packageName) { + public void forceStopPackage(final String packageName, int userId) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: forceStopPackage() from pid=" @@ -3511,27 +3579,34 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - final int userId = UserId.getCallingUserId(); + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), + userId, true, true, "forceStopPackage", null); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); - int pkgUid = -1; synchronized(this) { - try { - pkgUid = pm.getPackageUid(packageName, userId); - } catch (RemoteException e) { - } - if (pkgUid == -1) { - Slog.w(TAG, "Invalid packageName: " + packageName); - return; - } - forceStopPackageLocked(packageName, pkgUid); - try { - pm.setPackageStoppedState(packageName, true, userId); - } catch (RemoteException e) { - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Failed trying to unstop package " - + packageName + ": " + e); + int[] users = userId == UserHandle.USER_ALL + ? getUsersLocked() : new int[] { userId }; + for (int user : users) { + int pkgUid = -1; + try { + pkgUid = pm.getPackageUid(packageName, user); + } catch (RemoteException e) { + } + if (pkgUid == -1) { + Slog.w(TAG, "Invalid packageName: " + packageName); + continue; + } + try { + pm.setPackageStoppedState(packageName, true, user); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + packageName + ": " + e); + } + if (isUserRunningLocked(user)) { + forceStopPackageLocked(packageName, pkgUid); + } } } } finally { @@ -3540,16 +3615,15 @@ public final class ActivityManagerService extends ActivityManagerNative } /* - * The pkg name and uid have to be specified. - * @see android.app.IActivityManager#killApplicationWithUid(java.lang.String, int) + * The pkg name and app id have to be specified. */ - public void killApplicationWithUid(String pkg, int uid) { + public void killApplicationWithAppId(String pkg, int appid) { if (pkg == null) { return; } // Make sure the uid is valid. - if (uid < 0) { - Slog.w(TAG, "Invalid uid specified for pkg : " + pkg); + if (appid < 0) { + Slog.w(TAG, "Invalid appid specified for pkg : " + pkg); return; } int callerUid = Binder.getCallingUid(); @@ -3557,7 +3631,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (callerUid == Process.SYSTEM_UID) { // Post an aysnc message to kill the application Message msg = mHandler.obtainMessage(KILL_APPLICATION_MSG); - msg.arg1 = uid; + msg.arg1 = appid; msg.arg2 = 0; msg.obj = pkg; mHandler.sendMessage(msg); @@ -3570,33 +3644,50 @@ public final class ActivityManagerService extends ActivityManagerNative public void closeSystemDialogs(String reason) { enforceNotIsolatedCaller("closeSystemDialogs"); + final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); - synchronized (this) { - closeSystemDialogsLocked(uid, reason); + try { + synchronized (this) { + // Only allow this from foreground processes, so that background + // applications can't abuse it to prevent system UI from being shown. + if (uid >= Process.FIRST_APPLICATION_UID) { + ProcessRecord proc; + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(pid); + } + if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { + Slog.w(TAG, "Ignoring closeSystemDialogs " + reason + + " from background process " + proc); + return; + } + } + closeSystemDialogsLocked(reason); + } + } finally { + Binder.restoreCallingIdentity(origId); } - Binder.restoreCallingIdentity(origId); } - void closeSystemDialogsLocked(int callingUid, String reason) { + void closeSystemDialogsLocked(String reason) { Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); if (reason != null) { intent.putExtra("reason", reason); } mWindowManager.closeSystemDialogs(reason); - + for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { r.stack.finishActivityLocked(r, i, - Activity.RESULT_CANCELED, null, "close-sys"); + Activity.RESULT_CANCELED, null, "close-sys", true); } } - + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, false, false, -1, - callingUid, 0 /* TODO: Verify */); + Process.SYSTEM_UID, UserHandle.USER_ALL); } public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) @@ -3647,7 +3738,8 @@ public final class ActivityManagerService extends ActivityManagerNative } private void forceStopPackageLocked(final String packageName, int uid) { - forceStopPackageLocked(packageName, uid, false, false, true, false, UserId.getUserId(uid)); + forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false, + false, true, false, UserHandle.getUserId(uid)); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, Uri.fromParts("package", packageName, null)); if (!mProcessesReady) { @@ -3657,18 +3749,29 @@ public final class ActivityManagerService extends ActivityManagerNative broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, false, false, - MY_PID, Process.SYSTEM_UID, UserId.getUserId(uid)); + MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid)); } - - private final boolean killPackageProcessesLocked(String packageName, int uid, - int minOomAdj, boolean callerWillRestart, boolean allowRestart, boolean doit, - boolean evenPersistent, String reason) { + + private void forceStopUserLocked(int userId) { + forceStopPackageLocked(null, -1, false, false, true, false, userId); + Intent intent = new Intent(Intent.ACTION_USER_STOPPED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, + false, false, + MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); + } + + private final boolean killPackageProcessesLocked(String packageName, int appId, + int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, + boolean doit, boolean evenPersistent, String reason) { ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); // Remove all processes this package may have touched: all with the // same UID (except for the system or root user), and all whose name // matches the package name. - final String procNamePrefix = packageName + ":"; + final String procNamePrefix = packageName != null ? (packageName + ":") : null; for (SparseArray<ProcessRecord> apps : mProcessNames.getMap().values()) { final int NA = apps.size(); for (int ia=0; ia<NA; ia++) { @@ -3681,20 +3784,41 @@ public final class ActivityManagerService extends ActivityManagerNative if (doit) { procs.add(app); } - // If uid is specified and the uid and process name match - // Or, the uid is not specified and the process name matches - } else if (((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid) - || ((app.processName.equals(packageName) - || app.processName.startsWith(procNamePrefix)) - && uid < 0))) { - if (app.setAdj >= minOomAdj) { - if (!doit) { - return true; - } - app.removed = true; - procs.add(app); + continue; + } + + // Skip process if it doesn't meet our oom adj requirement. + if (app.setAdj < minOomAdj) { + continue; + } + + // If no package is specified, we call all processes under the + // give user id. + if (packageName == null) { + if (app.userId != userId) { + continue; + } + // Package has been specified, we want to hit all processes + // that match it. We need to qualify this by the processes + // that are running under the specified app and user ID. + } else { + if (UserHandle.getAppId(app.uid) != appId) { + continue; + } + if (userId != UserHandle.USER_ALL && app.userId != userId) { + continue; + } + if (!app.pkgList.contains(packageName)) { + continue; } } + + // Process has passed all conditions, kill it! + if (!doit) { + return true; + } + app.removed = true; + procs.add(app); } } @@ -3705,39 +3829,71 @@ public final class ActivityManagerService extends ActivityManagerNative return N > 0; } - private final boolean forceStopPackageLocked(String name, int uid, + private final boolean forceStopPackageLocked(String name, int appId, boolean callerWillRestart, boolean purgeCache, boolean doit, boolean evenPersistent, int userId) { int i; int N; - if (uid < 0) { + if (userId == UserHandle.USER_ALL && name == null) { + Slog.w(TAG, "Can't force stop all processes of all users, that is insane!"); + } + + if (appId < 0 && name != null) { try { - uid = AppGlobals.getPackageManager().getPackageUid(name, userId); + appId = UserHandle.getAppId( + AppGlobals.getPackageManager().getPackageUid(name, 0)); } catch (RemoteException e) { } } if (doit) { - Slog.i(TAG, "Force stopping package " + name + " uid=" + uid); + if (name != null) { + Slog.i(TAG, "Force stopping package " + name + " appid=" + appId + + " user=" + userId); + } else { + Slog.i(TAG, "Force stopping user " + userId); + } Iterator<SparseArray<Long>> badApps = mProcessCrashTimes.getMap().values().iterator(); while (badApps.hasNext()) { SparseArray<Long> ba = badApps.next(); - if (ba.get(uid) != null) { + for (i=ba.size()-1; i>=0; i--) { + boolean remove = false; + final int entUid = ba.keyAt(i); + if (name != null) { + if (userId == UserHandle.USER_ALL) { + if (UserHandle.getAppId(entUid) == appId) { + remove = true; + } + } else { + if (entUid == UserHandle.getUid(userId, appId)) { + remove = true; + } + } + } else if (UserHandle.getUserId(entUid) == userId) { + remove = true; + } + if (remove) { + ba.removeAt(i); + } + } + if (ba.size() == 0) { badApps.remove(); } } } - - boolean didSomething = killPackageProcessesLocked(name, uid, -100, - callerWillRestart, false, doit, evenPersistent, "force stop"); + + boolean didSomething = killPackageProcessesLocked(name, appId, userId, + -100, callerWillRestart, false, doit, evenPersistent, + name == null ? ("force stop user " + userId) : ("force stop " + name)); TaskRecord lastTask = null; for (i=0; i<mMainStack.mHistory.size(); i++) { ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - final boolean samePackage = r.packageName.equals(name); - if (r.userId == userId + final boolean samePackage = r.packageName.equals(name) + || (name == null && r.userId == userId); + if ((userId == UserHandle.USER_ALL || r.userId == userId) && (samePackage || r.task == lastTask) && (r.app == null || evenPersistent || !r.app.persistent)) { if (!doit) { @@ -3764,48 +3920,79 @@ public final class ActivityManagerService extends ActivityManagerNative } } - ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord service : mServiceMap.getAllServices(userId)) { - if (service.packageName.equals(name) - && (service.app == null || evenPersistent || !service.app.persistent)) { - if (!doit) { - return true; - } - didSomething = true; - Slog.i(TAG, " Force stopping service " + service); - if (service.app != null) { - service.app.removed = true; - } - service.app = null; - service.isolatedProc = null; - services.add(service); + if (mServices.forceStopLocked(name, userId, evenPersistent, doit)) { + if (!doit) { + return true; } + didSomething = true; } - N = services.size(); - for (i=0; i<N; i++) { - bringDownServiceLocked(services.get(i), true); + if (name == null) { + // Remove all sticky broadcasts from this user. + mStickyBroadcasts.remove(userId); } ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); - for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(userId).values()) { - if (provider.info.packageName.equals(name) - && (provider.proc == null || evenPersistent || !provider.proc.persistent)) { - if (!doit) { - return true; - } - didSomething = true; - providers.add(provider); + if (mProviderMap.collectForceStopProviders(name, appId, doit, evenPersistent, + userId, providers)) { + if (!doit) { + return true; } + didSomething = true; } - N = providers.size(); for (i=0; i<N; i++) { removeDyingProviderLocked(null, providers.get(i), true); } + if (mIntentSenderRecords.size() > 0) { + Iterator<WeakReference<PendingIntentRecord>> it + = mIntentSenderRecords.values().iterator(); + while (it.hasNext()) { + WeakReference<PendingIntentRecord> wpir = it.next(); + if (wpir == null) { + it.remove(); + continue; + } + PendingIntentRecord pir = wpir.get(); + if (pir == null) { + it.remove(); + continue; + } + if (name == null) { + // Stopping user, remove all objects for the user. + if (pir.key.userId != userId) { + // Not the same user, skip it. + continue; + } + } else { + if (UserHandle.getAppId(pir.uid) != appId) { + // Different app id, skip it. + continue; + } + if (userId != UserHandle.USER_ALL && pir.key.userId != userId) { + // Different user, skip it. + continue; + } + if (!pir.key.packageName.equals(name)) { + // Different package, skip it. + continue; + } + } + if (!doit) { + return true; + } + didSomething = true; + it.remove(); + pir.canceled = true; + if (pir.key.activity != null) { + pir.key.activity.pendingResults.remove(pir.ref); + } + } + } + if (doit) { - if (purgeCache) { + if (purgeCache && name != null) { AttributeCache ac = AttributeCache.instance(); if (ac != null) { ac.removePackage(name); @@ -3831,8 +4018,9 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessNames.remove(name, uid); mIsolatedProcesses.remove(app.uid); if (mHeavyWeightProcess == app) { + mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG, + mHeavyWeightProcess.userId, 0)); mHeavyWeightProcess = null; - mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); } boolean needRestart = false; if (app.pid > 0 && app.pid != MY_PID) { @@ -3878,24 +4066,14 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessNames.remove(app.processName, app.uid); mIsolatedProcesses.remove(app.uid); if (mHeavyWeightProcess == app) { + mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG, + mHeavyWeightProcess.userId, 0)); mHeavyWeightProcess = null; - mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); } // Take care of any launching providers waiting for this process. checkAppInLaunchingProvidersLocked(app, true); // Take care of any services that are waiting for the process. - for (int i=0; i<mPendingServices.size(); i++) { - ServiceRecord sr = mPendingServices.get(i); - if ((app.uid == sr.appInfo.uid - && app.processName.equals(sr.processName)) - || sr.isolatedProc == app) { - Slog.w(TAG, "Forcing bringing down service: " + sr); - sr.isolatedProc = null; - mPendingServices.remove(i); - i--; - bringDownServiceLocked(sr, true); - } - } + mServices.processStartTimedOutLocked(app); EventLog.writeEvent(EventLogTags.AM_KILL, pid, app.processName, app.setAdj, "start timeout"); Process.killProcessQuiet(pid); @@ -4095,24 +4273,10 @@ public final class ActivityManagerService extends ActivityManagerNative } // Find any services that should be running in this process... - if (!badApp && mPendingServices.size() > 0) { - ServiceRecord sr = null; + if (!badApp) { try { - for (int i=0; i<mPendingServices.size(); i++) { - sr = mPendingServices.get(i); - if (app != sr.isolatedProc && (app.uid != sr.appInfo.uid - || !processName.equals(sr.processName))) { - continue; - } - - mPendingServices.remove(i); - i--; - realStartServiceLocked(sr, app); - didSomething = true; - } + didSomething |= mServices.attachApplicationLocked(app, processName); } catch (Exception e) { - Slog.w(TAG, "Exception in new application when starting service " - + sr.shortName, e); badApp = true; } } @@ -4235,15 +4399,6 @@ public final class ActivityManagerService extends ActivityManagerNative } }, pkgFilter); - IntentFilter userFilter = new IntentFilter(); - userFilter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - onUserRemoved(intent); - } - }, userFilter); - synchronized (this) { // Ensure that any processes we had put on hold are now started // up. @@ -4265,12 +4420,19 @@ public final class ActivityManagerService extends ActivityManagerNative // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); SystemProperties.set("dev.bootcomplete", "1"); - /* TODO: Send this to all users that are to be logged in on startup */ - broadcastIntentLocked(null, null, - new Intent(Intent.ACTION_BOOT_COMPLETED, null), - null, null, 0, null, null, - android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - false, false, MY_PID, Process.SYSTEM_UID, Binder.getOrigCallingUser()); + for (int i=0; i<mStartedUsers.size(); i++) { + UserStartedState uss = mStartedUsers.valueAt(i); + if (uss.mState == UserStartedState.STATE_BOOTING) { + uss.mState = UserStartedState.STATE_RUNNING; + final int userId = mStartedUsers.keyAt(i); + Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, + android.Manifest.permission.RECEIVE_BOOT_COMPLETED, + false, false, MY_PID, Process.SYSTEM_UID, userId); + } + } } } } @@ -4293,7 +4455,13 @@ public final class ActivityManagerService extends ActivityManagerNative enableScreenAfterBoot(); } } - + + public final void activityResumed(IBinder token) { + final long origId = Binder.clearCallingIdentity(); + mMainStack.activityResumed(token); + Binder.restoreCallingIdentity(origId); + } + public final void activityPaused(IBinder token) { final long origId = Binder.clearCallingIdentity(); mMainStack.activityPaused(token, false); @@ -4380,7 +4548,7 @@ public final class ActivityManagerService extends ActivityManagerNative public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, - int flags, Bundle options) { + int flags, Bundle options, int userId) { enforceNotIsolatedCaller("getIntentSender"); // Refuse possible leaked file descriptors if (intents != null) { @@ -4414,11 +4582,13 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { int callingUid = Binder.getCallingUid(); + userId = handleIncomingUserLocked(Binder.getCallingPid(), callingUid, userId, + false, true, "getIntentSender", null); try { if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { int uid = AppGlobals.getPackageManager() - .getPackageUid(packageName, UserId.getUserId(callingUid)); - if (!UserId.isSameApp(callingUid, uid)) { + .getPackageUid(packageName, UserHandle.getUserId(callingUid)); + if (!UserHandle.isSameApp(callingUid, uid)) { String msg = "Permission Denial: getIntentSender() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() @@ -4428,11 +4598,8 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } } - - if (DEBUG_MU) - Slog.i(TAG_MU, "Getting intent sender for origCallingUid=" - + Binder.getOrigCallingUid()); - return getIntentSenderLocked(type, packageName, Binder.getOrigCallingUid(), + + return getIntentSenderLocked(type, packageName, callingUid, userId, token, resultWho, requestCode, intents, resolvedTypes, flags, options); } catch (RemoteException e) { @@ -4441,8 +4608,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } - IIntentSender getIntentSenderLocked(int type, - String packageName, int callingUid, IBinder token, String resultWho, + IIntentSender getIntentSenderLocked(int type, String packageName, + int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) { if (DEBUG_MU) @@ -4466,7 +4633,7 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord.Key key = new PendingIntentRecord.Key( type, packageName, activity, resultWho, - requestCode, intents, resolvedTypes, flags, options); + requestCode, intents, resolvedTypes, flags, options, userId); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; @@ -4514,8 +4681,8 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord rec = (PendingIntentRecord)sender; try { int uid = AppGlobals.getPackageManager() - .getPackageUid(rec.key.packageName, UserId.getCallingUserId()); - if (!UserId.isSameApp(uid, Binder.getCallingUid())) { + .getPackageUid(rec.key.packageName, UserHandle.getCallingUserId()); + if (!UserHandle.isSameApp(uid, Binder.getCallingUid())) { String msg = "Permission Denial: cancelIntentSender() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() @@ -4734,7 +4901,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (permission == null) { return PackageManager.PERMISSION_DENIED; } - return checkComponentPermission(permission, pid, UserId.getAppId(uid), -1, true); + return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true); } /** @@ -4744,7 +4911,7 @@ public final class ActivityManagerService extends ActivityManagerNative int checkCallingPermission(String permission) { return checkPermission(permission, Binder.getCallingPid(), - UserId.getAppId(Binder.getCallingUid())); + UserHandle.getAppId(Binder.getCallingUid())); } /** @@ -4875,7 +5042,6 @@ public final class ActivityManagerService extends ActivityManagerNative pid = tlsIdentity.pid; } - uid = UserId.getAppId(uid); // Our own process gets to do everything. if (pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; @@ -4921,13 +5087,14 @@ public final class ActivityManagerService extends ActivityManagerNative String name = uri.getAuthority(); ProviderInfo pi = null; ContentProviderRecord cpr = mProviderMap.getProviderByName(name, - UserId.getUserId(callingUid)); + UserHandle.getUserId(callingUid)); if (cpr != null) { pi = cpr.info; } else { try { pi = pm.resolveContentProvider(name, - PackageManager.GET_URI_PERMISSION_PATTERNS, UserId.getUserId(callingUid)); + PackageManager.GET_URI_PERMISSION_PATTERNS, + UserHandle.getUserId(callingUid)); } catch (RemoteException ex) { } } @@ -4939,7 +5106,7 @@ public final class ActivityManagerService extends ActivityManagerNative int targetUid = lastTargetUid; if (targetUid < 0 && targetPkg != null) { try { - targetUid = pm.getPackageUid(targetPkg, UserId.getUserId(callingUid)); + targetUid = pm.getPackageUid(targetPkg, UserHandle.getUserId(callingUid)); if (targetUid < 0) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg); @@ -5231,7 +5398,7 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - int userId = UserId.getUserId(callingUid); + int userId = UserHandle.getUserId(callingUid); ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userId); if (cpr != null) { pi = cpr.info; @@ -5567,13 +5734,28 @@ public final class ActivityManagerService extends ActivityManagerNative } public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, - int flags) { + int flags, int userId) { final int callingUid = Binder.getCallingUid(); - // If it's the system uid asking, then use the current user id. - // TODO: Make sure that there aren't any other legitimate calls from the system uid that - // require the entire list. - final int callingUserId = callingUid == Process.SYSTEM_UID - ? mCurrentUserId : UserId.getUserId(callingUid); + if (userId != UserHandle.getCallingUserId()) { + // Check if the caller is holding permissions for cross-user requests. + if (checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Binder.getCallingPid(), callingUid, -1, true) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: " + + "Request to get recent tasks for user " + userId + + " but is calling from user " + UserHandle.getUserId(callingUid) + + "; this requires " + + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } else { + if (userId == UserHandle.USER_CURRENT) { + userId = mCurrentUserId; + } + } + } + synchronized (this) { enforceCallingPermission(android.Manifest.permission.GET_TASKS, "getRecentTasks()"); @@ -5582,7 +5764,7 @@ public final class ActivityManagerService extends ActivityManagerNative == PackageManager.PERMISSION_GRANTED; IPackageManager pm = AppGlobals.getPackageManager(); - + final int N = mRecentTasks.size(); ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<ActivityManager.RecentTaskInfo>( @@ -5590,7 +5772,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<N && maxNum > 0; i++) { TaskRecord tr = mRecentTasks.get(i); // Only add calling user's recent tasks - if (tr.userId != callingUserId) continue; + if (tr.userId != userId) continue; // Return the entry if desired by the caller. We always return // the first entry, because callers always expect this to be the // foreground app. We may filter others if the caller has @@ -5618,13 +5800,13 @@ public final class ActivityManagerService extends ActivityManagerNative // Check whether this activity is currently available. try { if (rti.origActivity != null) { - if (pm.getActivityInfo(rti.origActivity, 0, callingUserId) + if (pm.getActivityInfo(rti.origActivity, 0, userId) == null) { continue; } } else if (rti.baseIntent != null) { if (pm.queryIntentActivities(rti.baseIntent, - null, 0, callingUserId) == null) { + null, 0, userId) == null) { continue; } } @@ -5689,29 +5871,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // Find any running services associated with this app. - ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord sr : mServiceMap.getAllServices(tr.userId)) { - if (sr.packageName.equals(component.getPackageName())) { - services.add(sr); - } - } - - // Take care of any running services associated with the app. - for (int i=0; i<services.size(); i++) { - ServiceRecord sr = services.get(i); - if (sr.startRequested) { - if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) { - Slog.i(TAG, "Stopping service " + sr.shortName + ": remove task"); - stopServiceLocked(sr); - } else { - sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, - sr.makeNextStartId(), baseIntent, null)); - if (sr.app != null && sr.app.thread != null) { - sendServiceArgsLocked(sr, false); - } - } - } - } + mServices.cleanUpRemovedTaskLocked(tr, component, baseIntent); if (killProcesses) { // Find any running processes associated with this app. @@ -6055,15 +6215,26 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.uid); int userId = app.userId; if (providers != null) { - final int N = providers.size(); + int N = providers.size(); for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); + boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo, + cpi.name, cpi.flags); + if (singleton && UserHandle.getUserId(app.uid) != 0) { + // This is a singleton provider, but a user besides the + // default user is asking to initialize a process it runs + // in... well, no, it doesn't actually run in this process, + // it runs in the process of the default user. Get rid of it. + providers.remove(i); + N--; + continue; + } ComponentName comp = new ComponentName(cpi.packageName, cpi.name); ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId); if (cpr == null) { - cpr = new ContentProviderRecord(this, cpi, app.info, comp); + cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton); mProviderMap.putProviderByClass(comp, cpr); } if (DEBUG_MU) @@ -6203,7 +6374,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, - String name, IBinder token, boolean stable) { + String name, IBinder token, boolean stable, int userId) { ContentProviderRecord cpr; ContentProviderConnection conn = null; ProviderInfo cpi = null; @@ -6218,10 +6389,13 @@ public final class ActivityManagerService extends ActivityManagerNative + " (pid=" + Binder.getCallingPid() + ") when getting content provider " + name); } + if (r.userId != userId) { + throw new SecurityException("Calling requested user " + userId + + " but app is user " + r.userId); + } } // First check if this content provider has been published... - int userId = UserId.getUserId(r != null ? r.uid : Binder.getCallingUid()); cpr = mProviderMap.getProviderByName(name, userId); boolean providerRunning = cpr != null; if (providerRunning) { @@ -6296,6 +6470,7 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(origId); } + boolean singleton; if (!providerRunning) { try { cpi = AppGlobals.getPackageManager(). @@ -6306,7 +6481,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (cpi == null) { return null; } - if (isSingleton(cpi.processName, cpi.applicationInfo)) { + singleton = isSingleton(cpi.processName, cpi.applicationInfo, + cpi.name, cpi.flags); + if (singleton) { userId = 0; } cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId); @@ -6325,6 +6502,16 @@ public final class ActivityManagerService extends ActivityManagerNative "Attempt to launch content provider before system ready"); } + // Make sure that the user who owns this provider is started. If not, + // we don't want to allow it to run. + if (mStartedUsers.get(userId) == null) { + Slog.w(TAG, "Unable to launch app " + + cpi.applicationInfo.packageName + "/" + + cpi.applicationInfo.uid + " for provider " + + name + ": user " + userId + " is stopped"); + return null; + } + ComponentName comp = new ComponentName(cpi.packageName, cpi.name); cpr = mProviderMap.getProviderByClass(comp, userId); final boolean firstClass = cpr == null; @@ -6341,7 +6528,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } ai = getAppInfoForUser(ai, userId); - cpr = new ContentProviderRecord(this, cpi, ai, comp); + cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton); } catch (RemoteException ex) { // pm is in same process, this will never happen. } @@ -6463,17 +6650,19 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - return getContentProviderImpl(caller, name, null, stable); + return getContentProviderImpl(caller, name, null, stable, + UserHandle.getCallingUserId()); } public ContentProviderHolder getContentProviderExternal(String name, IBinder token) { enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY, "Do not have permission in call getContentProviderExternal()"); - return getContentProviderExternalUnchecked(name, token); + return getContentProviderExternalUnchecked(name, token, UserHandle.getCallingUserId()); } - private ContentProviderHolder getContentProviderExternalUnchecked(String name,IBinder token) { - return getContentProviderImpl(null, name, token, true); + private ContentProviderHolder getContentProviderExternalUnchecked(String name, + IBinder token, int userId) { + return getContentProviderImpl(null, name, token, true, userId); } /** @@ -6504,13 +6693,12 @@ public final class ActivityManagerService extends ActivityManagerNative public void removeContentProviderExternal(String name, IBinder token) { enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY, "Do not have permission in call removeContentProviderExternal()"); - removeContentProviderExternalUnchecked(name, token); + removeContentProviderExternalUnchecked(name, token, UserHandle.getCallingUserId()); } - private void removeContentProviderExternalUnchecked(String name, IBinder token) { + private void removeContentProviderExternalUnchecked(String name, IBinder token, int userId) { synchronized (this) { - ContentProviderRecord cpr = mProviderMap.getProviderByName(name, - Binder.getOrigCallingUser()); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId); if(cpr == null) { //remove from mProvidersByClass if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list"); @@ -6519,8 +6707,7 @@ public final class ActivityManagerService extends ActivityManagerNative //update content provider record entry info ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name); - ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, - Binder.getOrigCallingUser()); + ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId); if (localCpr.hasExternalProcessHandles()) { if (localCpr.removeExternalProcessHandleLocked(token)) { updateOomAdjLocked(); @@ -6731,14 +6918,16 @@ public final class ActivityManagerService extends ActivityManagerNative * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java */ - public String getProviderMimeType(Uri uri) { + public String getProviderMimeType(Uri uri, int userId) { enforceNotIsolatedCaller("getProviderMimeType"); + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, true, "getProviderMimeType", null); final String name = uri.getAuthority(); final long ident = Binder.clearCallingIdentity(); ContentProviderHolder holder = null; try { - holder = getContentProviderExternalUnchecked(name, null); + holder = getContentProviderExternalUnchecked(name, null, userId); if (holder != null) { return holder.provider.getType(uri); } @@ -6747,7 +6936,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } finally { if (holder != null) { - removeContentProviderExternalUnchecked(name, null); + removeContentProviderExternalUnchecked(name, null, userId); } Binder.restoreCallingIdentity(ident); } @@ -6766,7 +6955,7 @@ public final class ActivityManagerService extends ActivityManagerNative BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); int uid = info.uid; if (isolated) { - int userId = UserId.getUserId(uid); + int userId = UserHandle.getUserId(uid); int stepsLeft = Process.LAST_ISOLATED_UID - Process.FIRST_ISOLATED_UID + 1; uid = 0; while (true) { @@ -6774,7 +6963,7 @@ public final class ActivityManagerService extends ActivityManagerNative || mNextIsolatedProcessUid > Process.LAST_ISOLATED_UID) { mNextIsolatedProcessUid = Process.FIRST_ISOLATED_UID; } - uid = UserId.getUid(userId, mNextIsolatedProcessUid); + uid = UserHandle.getUid(userId, mNextIsolatedProcessUid); mNextIsolatedProcessUid++; if (mIsolatedProcesses.indexOfKey(uid) < 0) { // No process for this uid, use it. @@ -6812,7 +7001,7 @@ public final class ActivityManagerService extends ActivityManagerNative // This package really, really can not be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( - info.packageName, false, UserId.getUserId(app.uid)); + info.packageName, false, UserHandle.getUserId(app.uid)); } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " @@ -6843,7 +7032,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (count > 1) { final long origId = Binder.clearCallingIdentity(); mMainStack.finishActivityLocked((ActivityRecord)mMainStack.mHistory.get(count-1), - count-1, Activity.RESULT_CANCELED, null, "unhandled-back"); + count-1, Activity.RESULT_CANCELED, null, "unhandled-back", true); Binder.restoreCallingIdentity(origId); } } @@ -6851,8 +7040,9 @@ public final class ActivityManagerService extends ActivityManagerNative public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException { enforceNotIsolatedCaller("openContentUri"); + final int userId = UserHandle.getCallingUserId(); String name = uri.getAuthority(); - ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null); + ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null, userId); ParcelFileDescriptor pfd = null; if (cph != null) { // We record the binder invoker's uid in thread-local storage before @@ -6874,7 +7064,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // We've got the fd now, so we're done with the provider. - removeContentProviderExternalUnchecked(name, null); + removeContentProviderExternalUnchecked(name, null, userId); } else { Slog.d(TAG, "Failed to get provider for authority '" + name + "'"); } @@ -7085,7 +7275,8 @@ public final class ActivityManagerService extends ActivityManagerNative mDebugTransient = !persistent; if (packageName != null) { final long origId = Binder.clearCallingIdentity(); - forceStopPackageLocked(packageName, -1, false, false, true, true, 0); + forceStopPackageLocked(packageName, -1, false, false, true, true, + UserHandle.USER_ALL); Binder.restoreCallingIdentity(origId); } } @@ -7219,7 +7410,7 @@ public final class ActivityManagerService extends ActivityManagerNative lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; lp.width = WindowManager.LayoutParams.WRAP_CONTENT; lp.height = WindowManager.LayoutParams.WRAP_CONTENT; - lp.gravity = Gravity.BOTTOM | Gravity.LEFT; + lp.gravity = Gravity.BOTTOM | Gravity.START; lp.format = v.getBackground().getOpacity(); lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; @@ -7504,42 +7695,45 @@ public final class ActivityManagerService extends ActivityManagerNative } } + final int[] users = getUsersLocked(); for (int i=0; i<ris.size(); i++) { ActivityInfo ai = ris.get(i).activityInfo; ComponentName comp = new ComponentName(ai.packageName, ai.name); doneReceivers.add(comp); intent.setComponent(comp); - IIntentReceiver finisher = null; - if (i == ris.size()-1) { - finisher = new IIntentReceiver.Stub() { - public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean ordered, - boolean sticky) { - // The raw IIntentReceiver interface is called - // with the AM lock held, so redispatch to - // execute our code without the lock. - mHandler.post(new Runnable() { - public void run() { - synchronized (ActivityManagerService.this) { - mDidUpdate = true; + for (int j=0; j<users.length; j++) { + IIntentReceiver finisher = null; + if (i == ris.size()-1 && j == users.length-1) { + finisher = new IIntentReceiver.Stub() { + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, + boolean sticky, int sendingUser) { + // The raw IIntentReceiver interface is called + // with the AM lock held, so redispatch to + // execute our code without the lock. + mHandler.post(new Runnable() { + public void run() { + synchronized (ActivityManagerService.this) { + mDidUpdate = true; + } + writeLastDonePreBootReceivers(doneReceivers); + showBootMessage(mContext.getText( + R.string.android_upgrading_complete), + false); + systemReady(goingCallback); } - writeLastDonePreBootReceivers(doneReceivers); - showBootMessage(mContext.getText( - R.string.android_upgrading_complete), - false); - systemReady(goingCallback); - } - }); - } - }; - } - Slog.i(TAG, "Sending system update to: " + intent.getComponent()); - /* TODO: Send this to all users */ - broadcastIntentLocked(null, null, intent, null, finisher, - 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID, - 0 /* UserId zero */); - if (finisher != null) { - mWaitingUpdate = true; + }); + } + }; + } + Slog.i(TAG, "Sending system update to " + intent.getComponent() + + " for user " + users[j]); + broadcastIntentLocked(null, null, intent, null, finisher, + 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID, + users[j]); + if (finisher != null) { + mWaitingUpdate = true; + } } } } @@ -7661,7 +7855,19 @@ public final class ActivityManagerService extends ActivityManagerNative } catch (RemoteException e) { } + long ident = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent(Intent.ACTION_USER_STARTED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, + false, false, MY_PID, Process.SYSTEM_UID, mCurrentUserId); + } finally { + Binder.restoreCallingIdentity(ident); + } mMainStack.resumeTopActivityLocked(null); + sendUserSwitchBroadcastsLocked(-1, mCurrentUserId); } } @@ -7760,7 +7966,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (r.app == app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, null, "crashed"); + r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "crashed", false); } } if (!app.persistent) { @@ -7795,7 +8002,7 @@ public final class ActivityManagerService extends ActivityManagerNative + r.intent.getComponent().flattenToShortString()); int index = mMainStack.indexOfActivityLocked(r); r.stack.finishActivityLocked(r, index, - Activity.RESULT_CANCELED, null, "crashed"); + Activity.RESULT_CANCELED, null, "crashed", false); // Also terminate any activities below it that aren't yet // stopped, to avoid a situation where one will get // re-start our crashing activity once it gets resumed again. @@ -7809,7 +8016,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); r.stack.finishActivityLocked(r, index, - Activity.RESULT_CANCELED, null, "crashed"); + Activity.RESULT_CANCELED, null, "crashed", false); } } } @@ -8150,7 +8357,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (String pkg : process.pkgList) { sb.append("Package: ").append(pkg); try { - PackageInfo pi = pm.getPackageInfo(pkg, 0, 0); + PackageInfo pi = pm.getPackageInfo(pkg, 0, UserHandle.getCallingUserId()); if (pi != null) { sb.append(" v").append(pi.versionCode); if (pi.versionName != null) { @@ -8423,11 +8630,19 @@ public final class ActivityManagerService extends ActivityManagerNative // assume our apps are happy - lazy create the list List<ActivityManager.ProcessErrorStateInfo> errList = null; + final boolean allUsers = ActivityManager.checkUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED; + int userId = UserHandle.getUserId(Binder.getCallingUid()); + synchronized (this) { // iterate across all processes for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); + if (!allUsers && app.userId != userId) { + continue; + } if ((app.thread != null) && (app.crashing || app.notResponding)) { // This one's in trouble, so we'll generate a report for it // crashes are higher priority (in case there's a crash *and* an anr) @@ -8491,6 +8706,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.persistent) { outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT; } + if (app.hasActivities) { + outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES; + } outInfo.lastTrimLevel = app.trimMemoryLevel; int adj = app.curAdj; outInfo.importance = oomAdjToImportance(adj, outInfo); @@ -8501,10 +8719,17 @@ public final class ActivityManagerService extends ActivityManagerNative enforceNotIsolatedCaller("getRunningAppProcesses"); // Lazy instantiation of list List<ActivityManager.RunningAppProcessInfo> runList = null; + final boolean allUsers = ActivityManager.checkUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED; + int userId = UserHandle.getUserId(Binder.getCallingUid()); synchronized (this) { // Iterate across all processes for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); + if (!allUsers && app.userId != userId) { + continue; + } if ((app.thread != null) && (!app.crashing && !app.notResponding)) { // Generate process state info for running application ActivityManager.RunningAppProcessInfo currApp = @@ -8550,7 +8775,7 @@ public final class ActivityManagerService extends ActivityManagerNative IPackageManager pm = AppGlobals.getPackageManager(); for (String pkg : extList) { try { - ApplicationInfo info = pm.getApplicationInfo(pkg, 0, UserId.getCallingUserId()); + ApplicationInfo info = pm.getApplicationInfo(pkg, 0, UserHandle.getCallingUserId()); if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { retList.add(info); } @@ -8723,7 +8948,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); } - if (!dumpService(fd, pw, name, newArgs, 0, dumpAll)) { + if (!mServices.dumpService(fd, pw, name, newArgs, 0, dumpAll)) { pw.println("No services match: " + name); pw.println("Use -h for help."); } @@ -8744,7 +8969,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } else if ("services".equals(cmd) || "s".equals(cmd)) { synchronized (this) { - dumpServicesLocked(fd, pw, args, opti, true, dumpClient, null); + mServices.dumpServicesLocked(fd, pw, args, opti, true, dumpClient, null); } } else { // Dumping a single activity? @@ -8783,7 +9008,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - needSep = dumpServicesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); + needSep = mServices.dumpServicesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); if (needSep) { pw.println(" "); } @@ -9058,6 +9283,18 @@ public final class ActivityManagerService extends ActivityManagerNative } pw.println(); + pw.println(" mStartedUsers:"); + for (int i=0; i<mStartedUsers.size(); i++) { + UserStartedState uss = mStartedUsers.valueAt(i); + pw.print(" User #"); pw.print(uss.mHandle.getIdentifier()); + pw.print(": "); uss.dump("", pw); + } + pw.print(" mUserLru: ["); + for (int i=0; i<mUserLru.size(); i++) { + if (i > 0) pw.print(", "); + pw.print(mUserLru.get(i)); + } + pw.println("]"); pw.println(" mHomeProcess: " + mHomeProcess); pw.println(" mPreviousProcess: " + mPreviousProcess); if (dumpAll) { @@ -9134,7 +9371,9 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" mGoingToSleep=" + mMainStack.mGoingToSleep); pw.println(" mLaunchingActivity=" + mMainStack.mLaunchingActivity); pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq); - pw.println(" mNumServiceProcs=" + mNumServiceProcs + pw.println(" mNumNonHiddenProcs=" + mNumNonHiddenProcs + + " mNumHiddenProcs=" + mNumHiddenProcs + + " mNumServiceProcs=" + mNumServiceProcs + " mNewNumServiceProcs=" + mNewNumServiceProcs); } @@ -9214,120 +9453,6 @@ public final class ActivityManagerService extends ActivityManagerNative /** * There are three ways to call this: - * - no service specified: dump all the services - * - a flattened component name that matched an existing service was specified as the - * first arg: dump that one service - * - the first arg isn't the flattened component name of an existing service: - * dump all services whose component contains the first arg as a substring - */ - protected boolean dumpService(FileDescriptor fd, PrintWriter pw, String name, String[] args, - int opti, boolean dumpAll) { - ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - - if ("all".equals(name)) { - synchronized (this) { - try { - List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); - for (UserInfo user : users) { - for (ServiceRecord r1 : mServiceMap.getAllServices(user.id)) { - services.add(r1); - } - } - } catch (RemoteException re) { - } - } - } else { - ComponentName componentName = name != null - ? ComponentName.unflattenFromString(name) : null; - int objectId = 0; - if (componentName == null) { - // Not a '/' separated full component name; maybe an object ID? - try { - objectId = Integer.parseInt(name, 16); - name = null; - componentName = null; - } catch (RuntimeException e) { - } - } - - synchronized (this) { - try { - List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); - for (UserInfo user : users) { - for (ServiceRecord r1 : mServiceMap.getAllServices(user.id)) { - if (componentName != null) { - if (r1.name.equals(componentName)) { - services.add(r1); - } - } else if (name != null) { - if (r1.name.flattenToString().contains(name)) { - services.add(r1); - } - } else if (System.identityHashCode(r1) == objectId) { - services.add(r1); - } - } - } - } catch (RemoteException re) { - } - } - } - - if (services.size() <= 0) { - return false; - } - - boolean needSep = false; - for (int i=0; i<services.size(); i++) { - if (needSep) { - pw.println(); - } - needSep = true; - dumpService("", fd, pw, services.get(i), args, dumpAll); - } - return true; - } - - /** - * Invokes IApplicationThread.dumpService() on the thread of the specified service if - * there is a thread associated with the service. - */ - private void dumpService(String prefix, FileDescriptor fd, PrintWriter pw, - final ServiceRecord r, String[] args, boolean dumpAll) { - String innerPrefix = prefix + " "; - synchronized (this) { - pw.print(prefix); pw.print("SERVICE "); - pw.print(r.shortName); pw.print(" "); - pw.print(Integer.toHexString(System.identityHashCode(r))); - pw.print(" pid="); - if (r.app != null) pw.println(r.app.pid); - else pw.println("(not running)"); - if (dumpAll) { - r.dump(pw, innerPrefix); - } - } - if (r.app != null && r.app.thread != null) { - pw.print(prefix); pw.println(" Client:"); - pw.flush(); - try { - TransferPipe tp = new TransferPipe(); - try { - r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), r, args); - tp.setBufferPrefix(prefix + " "); - tp.go(fd); - } finally { - tp.kill(); - } - } catch (IOException e) { - pw.println(prefix + " Failure while dumping the service: " + e); - } catch (RemoteException e) { - pw.println(prefix + " Got a RemoteException while dumping the service"); - } - } - } - - /** - * There are three ways to call this: * - no provider specified: dump all the providers * - a flattened component name that matched an existing provider was specified as the * first arg: dump that one provider @@ -9527,9 +9652,15 @@ public final class ActivityManagerService extends ActivityManagerNative boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; - + boolean onlyHistory = false; + + if ("history".equals(dumpPackage)) { + onlyHistory = true; + dumpPackage = null; + } + pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)"); - if (dumpAll) { + if (!onlyHistory && dumpAll) { if (mRegisteredReceivers.size() > 0) { boolean printed = false; Iterator it = mRegisteredReceivers.values().iterator(); @@ -9562,39 +9693,41 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = true; - if (mStickyBroadcasts != null && dumpPackage == null) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Sticky broadcasts:"); - StringBuilder sb = new StringBuilder(128); - for (Map.Entry<String, ArrayList<Intent>> ent - : mStickyBroadcasts.entrySet()) { - pw.print(" * Sticky action "); pw.print(ent.getKey()); - if (dumpAll) { - pw.println(":"); - ArrayList<Intent> intents = ent.getValue(); - final int N = intents.size(); - for (int i=0; i<N; i++) { - sb.setLength(0); - sb.append(" Intent: "); - intents.get(i).toShortString(sb, false, true, false, false); - pw.println(sb.toString()); - Bundle bundle = intents.get(i).getExtras(); - if (bundle != null) { - pw.print(" "); - pw.println(bundle.toString()); + if (!onlyHistory && mStickyBroadcasts != null && dumpPackage == null) { + for (int user=0; user<mStickyBroadcasts.size(); user++) { + if (needSep) { + pw.println(); + } + needSep = true; + pw.print(" Sticky broadcasts for user "); + pw.print(mStickyBroadcasts.keyAt(user)); pw.println(":"); + StringBuilder sb = new StringBuilder(128); + for (Map.Entry<String, ArrayList<Intent>> ent + : mStickyBroadcasts.valueAt(user).entrySet()) { + pw.print(" * Sticky action "); pw.print(ent.getKey()); + if (dumpAll) { + pw.println(":"); + ArrayList<Intent> intents = ent.getValue(); + final int N = intents.size(); + for (int i=0; i<N; i++) { + sb.setLength(0); + sb.append(" Intent: "); + intents.get(i).toShortString(sb, false, true, false, false); + pw.println(sb.toString()); + Bundle bundle = intents.get(i).getExtras(); + if (bundle != null) { + pw.print(" "); + pw.println(bundle.toString()); + } } + } else { + pw.println(""); } - } else { - pw.println(""); } } - needSep = true; } - if (dumpAll) { + if (!onlyHistory && dumpAll) { pw.println(); for (BroadcastQueue queue : mBroadcastQueues) { pw.println(" mBroadcastsScheduled [" + queue.mQueueName + "]=" @@ -9608,199 +9741,6 @@ public final class ActivityManagerService extends ActivityManagerNative return needSep; } - /** - * Prints a list of ServiceRecords (dumpsys activity services) - */ - boolean dumpServicesLocked(FileDescriptor fd, PrintWriter pw, String[] args, - int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { - boolean needSep = false; - - ItemMatcher matcher = new ItemMatcher(); - matcher.build(args, opti); - - pw.println("ACTIVITY MANAGER SERVICES (dumpsys activity services)"); - try { - List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); - for (UserInfo user : users) { - if (mServiceMap.getAllServices(user.id).size() > 0) { - boolean printed = false; - long nowReal = SystemClock.elapsedRealtime(); - Iterator<ServiceRecord> it = mServiceMap.getAllServices( - user.id).iterator(); - needSep = false; - while (it.hasNext()) { - ServiceRecord r = it.next(); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { - continue; - } - if (!printed) { - pw.println(" Active services:"); - printed = true; - } - if (needSep) { - pw.println(); - } - pw.print(" * "); - pw.println(r); - if (dumpAll) { - r.dump(pw, " "); - needSep = true; - } else { - pw.print(" app="); - pw.println(r.app); - pw.print(" created="); - TimeUtils.formatDuration(r.createTime, nowReal, pw); - pw.print(" started="); - pw.print(r.startRequested); - pw.print(" connections="); - pw.println(r.connections.size()); - if (r.connections.size() > 0) { - pw.println(" Connections:"); - for (ArrayList<ConnectionRecord> clist : r.connections.values()) { - for (int i = 0; i < clist.size(); i++) { - ConnectionRecord conn = clist.get(i); - pw.print(" "); - pw.print(conn.binding.intent.intent.getIntent() - .toShortString(false, false, false, false)); - pw.print(" -> "); - ProcessRecord proc = conn.binding.client; - pw.println(proc != null ? proc.toShortString() : "null"); - } - } - } - } - if (dumpClient && r.app != null && r.app.thread != null) { - pw.println(" Client:"); - pw.flush(); - try { - TransferPipe tp = new TransferPipe(); - try { - r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), - r, args); - tp.setBufferPrefix(" "); - // Short timeout, since blocking here can - // deadlock with the application. - tp.go(fd, 2000); - } finally { - tp.kill(); - } - } catch (IOException e) { - pw.println(" Failure while dumping the service: " + e); - } catch (RemoteException e) { - pw.println(" Got a RemoteException while dumping the service"); - } - needSep = true; - } - } - needSep = printed; - } - } - } catch (RemoteException re) { - - } - - if (mPendingServices.size() > 0) { - boolean printed = false; - for (int i=0; i<mPendingServices.size(); i++) { - ServiceRecord r = mPendingServices.get(i); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Pending services:"); - printed = true; - } - pw.print(" * Pending "); pw.println(r); - r.dump(pw, " "); - } - needSep = true; - } - - if (mRestartingServices.size() > 0) { - boolean printed = false; - for (int i=0; i<mRestartingServices.size(); i++) { - ServiceRecord r = mRestartingServices.get(i); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Restarting services:"); - printed = true; - } - pw.print(" * Restarting "); pw.println(r); - r.dump(pw, " "); - } - needSep = true; - } - - if (mStoppingServices.size() > 0) { - boolean printed = false; - for (int i=0; i<mStoppingServices.size(); i++) { - ServiceRecord r = mStoppingServices.get(i); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Stopping services:"); - printed = true; - } - pw.print(" * Stopping "); pw.println(r); - r.dump(pw, " "); - } - needSep = true; - } - - if (dumpAll) { - if (mServiceConnections.size() > 0) { - boolean printed = false; - Iterator<ArrayList<ConnectionRecord>> it - = mServiceConnections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> r = it.next(); - for (int i=0; i<r.size(); i++) { - ConnectionRecord cr = r.get(i); - if (!matcher.match(cr.binding.service, cr.binding.service.name)) { - continue; - } - if (dumpPackage != null && (cr.binding.client == null - || !dumpPackage.equals(cr.binding.client.info.packageName))) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Connection bindings to services:"); - printed = true; - } - pw.print(" * "); pw.println(cr); - cr.dump(pw, " "); - } - } - needSep = true; - } - } - - return needSep; - } - boolean dumpProvidersLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = true; @@ -10106,6 +10046,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.print(" "); pw.print("oom: max="); pw.print(r.maxAdj); pw.print(" hidden="); pw.print(r.hiddenAdj); + pw.print(" empty="); pw.print(r.emptyAdj); pw.print(" curRaw="); pw.print(r.curRawAdj); pw.print(" setRaw="); pw.print(r.setRawAdj); pw.print(" cur="); pw.print(r.curAdj); @@ -10594,125 +10535,6 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } - private final void killServicesLocked(ProcessRecord app, - boolean allowRestart) { - // Report disconnected services. - if (false) { - // XXX we are letting the client link to the service for - // death notifications. - if (app.services.size() > 0) { - Iterator<ServiceRecord> it = app.services.iterator(); - while (it.hasNext()) { - ServiceRecord r = it.next(); - if (r.connections.size() > 0) { - Iterator<ArrayList<ConnectionRecord>> jt - = r.connections.values().iterator(); - while (jt.hasNext()) { - ArrayList<ConnectionRecord> cl = jt.next(); - for (int i=0; i<cl.size(); i++) { - ConnectionRecord c = cl.get(i); - if (c.binding.client != app) { - try { - //c.conn.connected(r.className, null); - } catch (Exception e) { - // todo: this should be asynchronous! - Slog.w(TAG, "Exception thrown disconnected servce " - + r.shortName - + " from app " + app.processName, e); - } - } - } - } - } - } - } - } - - // Clean up any connections this application has to other services. - if (app.connections.size() > 0) { - Iterator<ConnectionRecord> it = app.connections.iterator(); - while (it.hasNext()) { - ConnectionRecord r = it.next(); - removeConnectionLocked(r, app, null); - } - } - app.connections.clear(); - - if (app.services.size() != 0) { - // Any services running in the application need to be placed - // back in the pending list. - Iterator<ServiceRecord> it = app.services.iterator(); - while (it.hasNext()) { - ServiceRecord sr = it.next(); - synchronized (sr.stats.getBatteryStats()) { - sr.stats.stopLaunchedLocked(); - } - sr.app = null; - sr.isolatedProc = null; - sr.executeNesting = 0; - if (mStoppingServices.remove(sr)) { - if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove stopping " + sr); - } - - boolean hasClients = sr.bindings.size() > 0; - if (hasClients) { - Iterator<IntentBindRecord> bindings - = sr.bindings.values().iterator(); - while (bindings.hasNext()) { - IntentBindRecord b = bindings.next(); - if (DEBUG_SERVICE) Slog.v(TAG, "Killing binding " + b - + ": shouldUnbind=" + b.hasBound); - b.binder = null; - b.requested = b.received = b.hasBound = false; - } - } - - if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags - &ApplicationInfo.FLAG_PERSISTENT) == 0) { - Slog.w(TAG, "Service crashed " + sr.crashCount - + " times, stopping: " + sr); - EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, - sr.crashCount, sr.shortName, app.pid); - bringDownServiceLocked(sr, true); - } else if (!allowRestart) { - bringDownServiceLocked(sr, true); - } else { - boolean canceled = scheduleServiceRestartLocked(sr, true); - - // Should the service remain running? Note that in the - // extreme case of so many attempts to deliver a command - // that it failed we also will stop it here. - if (sr.startRequested && (sr.stopIfKilled || canceled)) { - if (sr.pendingStarts.size() == 0) { - sr.startRequested = false; - if (!hasClients) { - // Whoops, no reason to restart! - bringDownServiceLocked(sr, true); - } - } - } - } - } - - if (!allowRestart) { - app.services.clear(); - } - } - - // Make sure we have no more records on the stopping list. - int i = mStoppingServices.size(); - while (i > 0) { - i--; - ServiceRecord sr = mStoppingServices.get(i); - if (sr.app == app) { - mStoppingServices.remove(i); - if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove stopping " + sr); - } - } - - app.executingServices.clear(); - } - private final boolean removeDyingProviderLocked(ProcessRecord proc, ContentProviderRecord cpr, boolean always) { final boolean inLaunching = mLaunchingProviders.contains(cpr); @@ -10722,10 +10544,10 @@ public final class ActivityManagerService extends ActivityManagerNative cpr.launchingApp = null; cpr.notifyAll(); } - mProviderMap.removeProviderByClass(cpr.name, UserId.getUserId(cpr.uid)); + mProviderMap.removeProviderByClass(cpr.name, UserHandle.getUserId(cpr.uid)); String names[] = cpr.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - mProviderMap.removeProviderByName(names[j], UserId.getUserId(cpr.uid)); + mProviderMap.removeProviderByName(names[j], UserHandle.getUserId(cpr.uid)); } } @@ -10810,7 +10632,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.hasShownUi = false; app.hasAboveClient = false; - killServicesLocked(app, allowRestart); + mServices.killServicesLocked(app, allowRestart); boolean restart = false; @@ -10909,8 +10731,9 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessNames.remove(app.processName, app.uid); mIsolatedProcesses.remove(app.uid); if (mHeavyWeightProcess == app) { + mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG, + mHeavyWeightProcess.userId, 0)); mHeavyWeightProcess = null; - mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); } } else if (!app.removed) { // This app is persistent, so we need to keep its record around. @@ -10974,806 +10797,23 @@ public final class ActivityManagerService extends ActivityManagerNative // SERVICES // ========================================================= - ActivityManager.RunningServiceInfo makeRunningServiceInfoLocked(ServiceRecord r) { - ActivityManager.RunningServiceInfo info = - new ActivityManager.RunningServiceInfo(); - info.service = r.name; - if (r.app != null) { - info.pid = r.app.pid; - } - info.uid = r.appInfo.uid; - info.process = r.processName; - info.foreground = r.isForeground; - info.activeSince = r.createTime; - info.started = r.startRequested; - info.clientCount = r.connections.size(); - info.crashCount = r.crashCount; - info.lastActivityTime = r.lastActivity; - if (r.isForeground) { - info.flags |= ActivityManager.RunningServiceInfo.FLAG_FOREGROUND; - } - if (r.startRequested) { - info.flags |= ActivityManager.RunningServiceInfo.FLAG_STARTED; - } - if (r.app != null && r.app.pid == MY_PID) { - info.flags |= ActivityManager.RunningServiceInfo.FLAG_SYSTEM_PROCESS; - } - if (r.app != null && r.app.persistent) { - info.flags |= ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS; - } - - for (ArrayList<ConnectionRecord> connl : r.connections.values()) { - for (int i=0; i<connl.size(); i++) { - ConnectionRecord conn = connl.get(i); - if (conn.clientLabel != 0) { - info.clientPackage = conn.binding.client.info.packageName; - info.clientLabel = conn.clientLabel; - return info; - } - } - } - return info; - } - public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags) { enforceNotIsolatedCaller("getServices"); synchronized (this) { - ArrayList<ActivityManager.RunningServiceInfo> res - = new ArrayList<ActivityManager.RunningServiceInfo>(); - - int userId = UserId.getUserId(Binder.getCallingUid()); - if (mServiceMap.getAllServices(userId).size() > 0) { - Iterator<ServiceRecord> it - = mServiceMap.getAllServices(userId).iterator(); - while (it.hasNext() && res.size() < maxNum) { - res.add(makeRunningServiceInfoLocked(it.next())); - } - } - - for (int i=0; i<mRestartingServices.size() && res.size() < maxNum; i++) { - ServiceRecord r = mRestartingServices.get(i); - ActivityManager.RunningServiceInfo info = - makeRunningServiceInfoLocked(r); - info.restarting = r.nextRestartTime; - res.add(info); - } - - return res; + return mServices.getRunningServiceInfoLocked(maxNum, flags); } } public PendingIntent getRunningServiceControlPanel(ComponentName name) { enforceNotIsolatedCaller("getRunningServiceControlPanel"); synchronized (this) { - int userId = UserId.getUserId(Binder.getCallingUid()); - ServiceRecord r = mServiceMap.getServiceByName(name, userId); - if (r != null) { - for (ArrayList<ConnectionRecord> conn : r.connections.values()) { - for (int i=0; i<conn.size(); i++) { - if (conn.get(i).clientIntent != null) { - return conn.get(i).clientIntent; - } - } - } - } + return mServices.getRunningServiceControlPanelLocked(name); } - return null; } - private final ServiceRecord findServiceLocked(ComponentName name, - IBinder token) { - ServiceRecord r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); - return r == token ? r : null; - } - - private final class ServiceLookupResult { - final ServiceRecord record; - final String permission; - - ServiceLookupResult(ServiceRecord _record, String _permission) { - record = _record; - permission = _permission; - } - }; - - private ServiceLookupResult findServiceLocked(Intent service, - String resolvedType, int userId) { - ServiceRecord r = null; - if (service.getComponent() != null) { - r = mServiceMap.getServiceByName(service.getComponent(), userId); - } - if (r == null) { - Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServiceMap.getServiceByIntent(filter, userId); - } - - if (r == null) { - try { - ResolveInfo rInfo = - AppGlobals.getPackageManager().resolveService( - service, resolvedType, 0, userId); - ServiceInfo sInfo = - rInfo != null ? rInfo.serviceInfo : null; - if (sInfo == null) { - return null; - } - - ComponentName name = new ComponentName( - sInfo.applicationInfo.packageName, sInfo.name); - r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); - } catch (RemoteException ex) { - // pm is in same process, this will never happen. - } - } - if (r != null) { - int callingPid = Binder.getCallingPid(); - int callingUid = Binder.getCallingUid(); - if (checkComponentPermission(r.permission, - callingPid, callingUid, r.appInfo.uid, r.exported) - != PackageManager.PERMISSION_GRANTED) { - if (!r.exported) { - Slog.w(TAG, "Permission Denial: Accessing service " + r.name - + " from pid=" + callingPid - + ", uid=" + callingUid - + " that is not exported from uid " + r.appInfo.uid); - return new ServiceLookupResult(null, "not exported from uid " - + r.appInfo.uid); - } - Slog.w(TAG, "Permission Denial: Accessing service " + r.name - + " from pid=" + callingPid - + ", uid=" + callingUid - + " requires " + r.permission); - return new ServiceLookupResult(null, r.permission); - } - return new ServiceLookupResult(r, null); - } - return null; - } - - private class ServiceRestarter implements Runnable { - private ServiceRecord mService; - - void setService(ServiceRecord service) { - mService = service; - } - - public void run() { - synchronized(ActivityManagerService.this) { - performServiceRestartLocked(mService); - } - } - } - - private ServiceLookupResult retrieveServiceLocked(Intent service, - String resolvedType, int callingPid, int callingUid, int userId) { - ServiceRecord r = null; - if (DEBUG_SERVICE) - Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType - + " callingUid=" + callingUid); - - if (service.getComponent() != null) { - r = mServiceMap.getServiceByName(service.getComponent(), userId); - } - if (r == null) { - Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServiceMap.getServiceByIntent(filter, userId); - } - if (r == null) { - try { - ResolveInfo rInfo = - AppGlobals.getPackageManager().resolveService( - service, resolvedType, STOCK_PM_FLAGS, userId); - ServiceInfo sInfo = - rInfo != null ? rInfo.serviceInfo : null; - if (sInfo == null) { - Slog.w(TAG, "Unable to start service " + service + - ": not found"); - return null; - } - if (userId > 0) { - if (isSingleton(sInfo.processName, sInfo.applicationInfo)) { - userId = 0; - } - sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, userId); - } - ComponentName name = new ComponentName( - sInfo.applicationInfo.packageName, sInfo.name); - r = mServiceMap.getServiceByName(name, userId); - if (r == null) { - Intent.FilterComparison filter = new Intent.FilterComparison( - service.cloneFilter()); - ServiceRestarter res = new ServiceRestarter(); - BatteryStatsImpl.Uid.Pkg.Serv ss = null; - BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); - synchronized (stats) { - ss = stats.getServiceStatsLocked( - sInfo.applicationInfo.uid, sInfo.packageName, - sInfo.name); - } - r = new ServiceRecord(this, ss, name, filter, sInfo, res); - res.setService(r); - mServiceMap.putServiceByName(name, UserId.getUserId(r.appInfo.uid), r); - mServiceMap.putServiceByIntent(filter, UserId.getUserId(r.appInfo.uid), r); - - // Make sure this component isn't in the pending list. - int N = mPendingServices.size(); - for (int i=0; i<N; i++) { - ServiceRecord pr = mPendingServices.get(i); - if (pr.name.equals(name)) { - mPendingServices.remove(i); - i--; - N--; - } - } - } - } catch (RemoteException ex) { - // pm is in same process, this will never happen. - } - } - if (r != null) { - if (checkComponentPermission(r.permission, - callingPid, callingUid, r.appInfo.uid, r.exported) - != PackageManager.PERMISSION_GRANTED) { - if (!r.exported) { - Slog.w(TAG, "Permission Denial: Accessing service " + r.name - + " from pid=" + callingPid - + ", uid=" + callingUid - + " that is not exported from uid " + r.appInfo.uid); - return new ServiceLookupResult(null, "not exported from uid " - + r.appInfo.uid); - } - Slog.w(TAG, "Permission Denial: Accessing service " + r.name - + " from pid=" + callingPid - + ", uid=" + callingUid - + " requires " + r.permission); - return new ServiceLookupResult(null, r.permission); - } - return new ServiceLookupResult(r, null); - } - return null; - } - - private final void bumpServiceExecutingLocked(ServiceRecord r, String why) { - if (DEBUG_SERVICE) Log.v(TAG, ">>> EXECUTING " - + why + " of " + r + " in app " + r.app); - else if (DEBUG_SERVICE_EXECUTING) Log.v(TAG, ">>> EXECUTING " - + why + " of " + r.shortName); - long now = SystemClock.uptimeMillis(); - if (r.executeNesting == 0 && r.app != null) { - if (r.app.executingServices.size() == 0) { - Message msg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG); - msg.obj = r.app; - mHandler.sendMessageAtTime(msg, now+SERVICE_TIMEOUT); - } - r.app.executingServices.add(r); - } - r.executeNesting++; - r.executingStart = now; - } - - private final void sendServiceArgsLocked(ServiceRecord r, - boolean oomAdjusted) { - final int N = r.pendingStarts.size(); - if (N == 0) { - return; - } - - while (r.pendingStarts.size() > 0) { - try { - ServiceRecord.StartItem si = r.pendingStarts.remove(0); - if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to: " - + r + " " + r.intent + " args=" + si.intent); - if (si.intent == null && N > 1) { - // If somehow we got a dummy null intent in the middle, - // then skip it. DO NOT skip a null intent when it is - // the only one in the list -- this is to support the - // onStartCommand(null) case. - continue; - } - si.deliveredTime = SystemClock.uptimeMillis(); - r.deliveredStarts.add(si); - si.deliveryCount++; - if (si.neededGrants != null) { - grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, - si.getUriPermissionsLocked()); - } - bumpServiceExecutingLocked(r, "start"); - if (!oomAdjusted) { - oomAdjusted = true; - updateOomAdjLocked(r.app); - } - int flags = 0; - if (si.deliveryCount > 1) { - flags |= Service.START_FLAG_RETRY; - } - if (si.doneExecutingCount > 0) { - flags |= Service.START_FLAG_REDELIVERY; - } - r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); - } catch (RemoteException e) { - // Remote process gone... we'll let the normal cleanup take - // care of this. - if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r); - break; - } catch (Exception e) { - Slog.w(TAG, "Unexpected exception", e); - break; - } - } - } - - private final boolean requestServiceBindingLocked(ServiceRecord r, - IntentBindRecord i, boolean rebind) { - if (r.app == null || r.app.thread == null) { - // If service is not currently running, can't yet bind. - return false; - } - if ((!i.requested || rebind) && i.apps.size() > 0) { - try { - bumpServiceExecutingLocked(r, "bind"); - r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind); - if (!rebind) { - i.requested = true; - } - i.hasBound = true; - i.doRebind = false; - } catch (RemoteException e) { - if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while binding " + r); - return false; - } - } - return true; - } - - private final void requestServiceBindingsLocked(ServiceRecord r) { - Iterator<IntentBindRecord> bindings = r.bindings.values().iterator(); - while (bindings.hasNext()) { - IntentBindRecord i = bindings.next(); - if (!requestServiceBindingLocked(r, i, false)) { - break; - } - } - } - - private final void realStartServiceLocked(ServiceRecord r, - ProcessRecord app) throws RemoteException { - if (app.thread == null) { - throw new RemoteException(); - } - if (DEBUG_MU) - Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid - + ", ProcessRecord.uid = " + app.uid); - r.app = app; - r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); - - app.services.add(r); - bumpServiceExecutingLocked(r, "create"); - updateLruProcessLocked(app, true, true); - - boolean created = false; - try { - mStringBuilder.setLength(0); - r.intent.getIntent().toShortString(mStringBuilder, true, false, true, false); - EventLog.writeEvent(EventLogTags.AM_CREATE_SERVICE, - System.identityHashCode(r), r.shortName, - mStringBuilder.toString(), r.app.pid); - synchronized (r.stats.getBatteryStats()) { - r.stats.startLaunchedLocked(); - } - ensurePackageDexOpt(r.serviceInfo.packageName); - app.thread.scheduleCreateService(r, r.serviceInfo, - compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo)); - r.postNotification(); - created = true; - } finally { - if (!created) { - app.services.remove(r); - scheduleServiceRestartLocked(r, false); - } - } - - requestServiceBindingsLocked(r); - - // If the service is in the started state, and there are no - // pending arguments, then fake up one so its onStartCommand() will - // be called. - if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { - r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), - null, null)); - } - - sendServiceArgsLocked(r, true); - } - - private final boolean scheduleServiceRestartLocked(ServiceRecord r, - boolean allowCancel) { - boolean canceled = false; - - final long now = SystemClock.uptimeMillis(); - long minDuration = SERVICE_RESTART_DURATION; - long resetTime = SERVICE_RESET_RUN_DURATION; - - if ((r.serviceInfo.applicationInfo.flags - &ApplicationInfo.FLAG_PERSISTENT) != 0) { - minDuration /= 4; - } - - // Any delivered but not yet finished starts should be put back - // on the pending list. - final int N = r.deliveredStarts.size(); - if (N > 0) { - for (int i=N-1; i>=0; i--) { - ServiceRecord.StartItem si = r.deliveredStarts.get(i); - si.removeUriPermissionsLocked(); - if (si.intent == null) { - // We'll generate this again if needed. - } else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT - && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) { - r.pendingStarts.add(0, si); - long dur = SystemClock.uptimeMillis() - si.deliveredTime; - dur *= 2; - if (minDuration < dur) minDuration = dur; - if (resetTime < dur) resetTime = dur; - } else { - Slog.w(TAG, "Canceling start item " + si.intent + " in service " - + r.name); - canceled = true; - } - } - r.deliveredStarts.clear(); - } - - r.totalRestartCount++; - if (r.restartDelay == 0) { - r.restartCount++; - r.restartDelay = minDuration; - } else { - // If it has been a "reasonably long time" since the service - // was started, then reset our restart duration back to - // the beginning, so we don't infinitely increase the duration - // on a service that just occasionally gets killed (which is - // a normal case, due to process being killed to reclaim memory). - if (now > (r.restartTime+resetTime)) { - 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.nextRestartTime = now + r.restartDelay; - - // Make sure that we don't end up restarting a bunch of services - // all at the same time. - boolean repeat; - do { - repeat = false; - for (int i=mRestartingServices.size()-1; i>=0; i--) { - ServiceRecord r2 = mRestartingServices.get(i); - if (r2 != r && r.nextRestartTime - >= (r2.nextRestartTime-SERVICE_MIN_RESTART_TIME_BETWEEN) - && r.nextRestartTime - < (r2.nextRestartTime+SERVICE_MIN_RESTART_TIME_BETWEEN)) { - r.nextRestartTime = r2.nextRestartTime + SERVICE_MIN_RESTART_TIME_BETWEEN; - r.restartDelay = r.nextRestartTime - now; - repeat = true; - break; - } - } - } while (repeat); - - if (!mRestartingServices.contains(r)) { - mRestartingServices.add(r); - } - - r.cancelNotification(); - - mHandler.removeCallbacks(r.restarter); - mHandler.postAtTime(r.restarter, r.nextRestartTime); - r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay; - Slog.w(TAG, "Scheduling restart of crashed service " - + r.shortName + " in " + r.restartDelay + "ms"); - EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART, - r.shortName, r.restartDelay); - - return canceled; - } - - final void performServiceRestartLocked(ServiceRecord r) { - if (!mRestartingServices.contains(r)) { - return; - } - bringUpServiceLocked(r, r.intent.getIntent().getFlags(), true); - } - - private final boolean unscheduleServiceRestartLocked(ServiceRecord r) { - if (r.restartDelay == 0) { - return false; - } - r.resetRestartCounter(); - mRestartingServices.remove(r); - mHandler.removeCallbacks(r.restarter); - return true; - } - - private final boolean bringUpServiceLocked(ServiceRecord r, - int intentFlags, boolean whileRestarting) { - //Slog.i(TAG, "Bring up service:"); - //r.dump(" "); - - if (r.app != null && r.app.thread != null) { - sendServiceArgsLocked(r, false); - return true; - } - - if (!whileRestarting && r.restartDelay > 0) { - // If waiting for a restart, then do nothing. - return true; - } - - if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up " + r + " " + r.intent); - - // We are now bringing the service up, so no longer in the - // restarting state. - mRestartingServices.remove(r); - - // Service is now being launched, its package can't be stopped. - try { - AppGlobals.getPackageManager().setPackageStoppedState( - r.packageName, false, r.userId); - } catch (RemoteException e) { - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Failed trying to unstop package " - + r.packageName + ": " + e); - } - - final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; - final String appName = r.processName; - ProcessRecord app; - - if (!isolated) { - app = getProcessRecordLocked(appName, r.appInfo.uid); - if (DEBUG_MU) - Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app); - if (app != null && app.thread != null) { - try { - app.addPackage(r.appInfo.packageName); - realStartServiceLocked(r, app); - return true; - } catch (RemoteException e) { - Slog.w(TAG, "Exception when starting service " + r.shortName, e); - } - - // If a dead object exception was thrown -- fall through to - // restart the application. - } - } else { - // If this service runs in an isolated process, then each time - // we call startProcessLocked() we will get a new isolated - // process, starting another process if we are currently waiting - // for a previous process to come up. To deal with this, we store - // in the service any current isolated process it is running in or - // waiting to have come up. - app = r.isolatedProc; - } - - // Not running -- get it started, and enqueue this service record - // to be executed when the app comes up. - if (app == null) { - if ((app=startProcessLocked(appName, r.appInfo, true, intentFlags, - "service", r.name, false, isolated)) == null) { - Slog.w(TAG, "Unable to launch app " - + r.appInfo.packageName + "/" - + r.appInfo.uid + " for service " - + r.intent.getIntent() + ": process is bad"); - bringDownServiceLocked(r, true); - return false; - } - if (isolated) { - r.isolatedProc = app; - } - } - - if (!mPendingServices.contains(r)) { - mPendingServices.add(r); - } - - return true; - } - - private final void bringDownServiceLocked(ServiceRecord r, boolean force) { - //Slog.i(TAG, "Bring down service:"); - //r.dump(" "); - - // Does it still need to run? - if (!force && r.startRequested) { - return; - } - if (r.connections.size() > 0) { - if (!force) { - // XXX should probably keep a count of the number of auto-create - // connections directly in the service. - Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> cr = it.next(); - for (int i=0; i<cr.size(); i++) { - if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) { - return; - } - } - } - } - - // Report to all of the connections that the service is no longer - // available. - Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> c = it.next(); - for (int i=0; i<c.size(); i++) { - ConnectionRecord cr = c.get(i); - // There is still a connection to the service that is - // being brought down. Mark it as dead. - cr.serviceDead = true; - try { - cr.conn.connected(r.name, null); - } catch (Exception e) { - Slog.w(TAG, "Failure disconnecting service " + r.name + - " to connection " + c.get(i).conn.asBinder() + - " (in " + c.get(i).binding.client.processName + ")", e); - } - } - } - } - - // Tell the service that it has been unbound. - if (r.bindings.size() > 0 && r.app != null && r.app.thread != null) { - Iterator<IntentBindRecord> it = r.bindings.values().iterator(); - while (it.hasNext()) { - IntentBindRecord ibr = it.next(); - if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down binding " + ibr - + ": hasBound=" + ibr.hasBound); - if (r.app != null && r.app.thread != null && ibr.hasBound) { - try { - bumpServiceExecutingLocked(r, "bring down unbind"); - updateOomAdjLocked(r.app); - ibr.hasBound = false; - r.app.thread.scheduleUnbindService(r, - ibr.intent.getIntent()); - } catch (Exception e) { - Slog.w(TAG, "Exception when unbinding service " - + r.shortName, e); - serviceDoneExecutingLocked(r, true); - } - } - } - } - - if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down " + r + " " + r.intent); - EventLog.writeEvent(EventLogTags.AM_DESTROY_SERVICE, - System.identityHashCode(r), r.shortName, - (r.app != null) ? r.app.pid : -1); - - mServiceMap.removeServiceByName(r.name, r.userId); - mServiceMap.removeServiceByIntent(r.intent, r.userId); - r.totalRestartCount = 0; - unscheduleServiceRestartLocked(r); - - // Also make sure it is not on the pending list. - int N = mPendingServices.size(); - for (int i=0; i<N; i++) { - if (mPendingServices.get(i) == r) { - mPendingServices.remove(i); - if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r); - i--; - N--; - } - } - - r.cancelNotification(); - r.isForeground = false; - r.foregroundId = 0; - r.foregroundNoti = null; - - // Clear start entries. - r.clearDeliveredStartsLocked(); - r.pendingStarts.clear(); - - if (r.app != null) { - synchronized (r.stats.getBatteryStats()) { - r.stats.stopLaunchedLocked(); - } - r.app.services.remove(r); - if (r.app.thread != null) { - try { - bumpServiceExecutingLocked(r, "stop"); - mStoppingServices.add(r); - updateOomAdjLocked(r.app); - r.app.thread.scheduleStopService(r); - } catch (Exception e) { - Slog.w(TAG, "Exception when stopping service " - + r.shortName, e); - serviceDoneExecutingLocked(r, true); - } - updateServiceForegroundLocked(r.app, false); - } else { - if (DEBUG_SERVICE) Slog.v( - TAG, "Removed service that has no process: " + r); - } - } else { - if (DEBUG_SERVICE) Slog.v( - TAG, "Removed service that is not running: " + r); - } - - if (r.bindings.size() > 0) { - r.bindings.clear(); - } - - if (r.restarter instanceof ServiceRestarter) { - ((ServiceRestarter)r.restarter).setService(null); - } - } - - ComponentName startServiceLocked(IApplicationThread caller, - Intent service, String resolvedType, - int callingPid, int callingUid) { - synchronized(this) { - if (DEBUG_SERVICE) Slog.v(TAG, "startService: " + service - + " type=" + resolvedType + " args=" + service.getExtras()); - - if (caller != null) { - final ProcessRecord callerApp = getRecordForAppLocked(caller); - if (callerApp == null) { - throw new SecurityException( - "Unable to find app for caller " + caller - + " (pid=" + Binder.getCallingPid() - + ") when starting service " + service); - } - } - - ServiceLookupResult res = - retrieveServiceLocked(service, resolvedType, - callingPid, callingUid, UserId.getUserId(callingUid)); - if (res == null) { - return null; - } - if (res.record == null) { - return new ComponentName("!", res.permission != null - ? res.permission : "private to package"); - } - ServiceRecord r = res.record; - NeededUriGrants neededGrants = checkGrantUriPermissionFromIntentLocked( - callingUid, r.packageName, service, service.getFlags(), null); - if (unscheduleServiceRestartLocked(r)) { - if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); - } - r.startRequested = true; - r.callStart = false; - r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), - service, neededGrants)); - r.lastActivity = SystemClock.uptimeMillis(); - synchronized (r.stats.getBatteryStats()) { - r.stats.startRunningLocked(); - } - if (!bringUpServiceLocked(r, service.getFlags(), false)) { - return new ComponentName("!", "Service process is bad"); - } - return r.name; - } - } - public ComponentName startService(IApplicationThread caller, Intent service, - String resolvedType) { + String resolvedType, int userId) { enforceNotIsolatedCaller("startService"); // Refuse possible leaked file descriptors if (service != null && service.hasFileDescriptors() == true) { @@ -11785,74 +10825,41 @@ 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 = startServiceLocked(caller, service, - resolvedType, callingPid, callingUid); + ComponentName res = mServices.startServiceLocked(caller, service, + resolvedType, callingPid, callingUid, userId); Binder.restoreCallingIdentity(origId); return res; } } ComponentName startServiceInPackage(int uid, - Intent service, String resolvedType) { + Intent service, String resolvedType, int userId) { synchronized(this) { if (DEBUG_SERVICE) Slog.v(TAG, "startServiceInPackage: " + service + " type=" + resolvedType); final long origId = Binder.clearCallingIdentity(); - ComponentName res = startServiceLocked(null, service, - resolvedType, -1, uid); + ComponentName res = mServices.startServiceLocked(null, service, + resolvedType, -1, uid, userId); Binder.restoreCallingIdentity(origId); return res; } } - private void stopServiceLocked(ServiceRecord service) { - synchronized (service.stats.getBatteryStats()) { - service.stats.stopRunningLocked(); - } - service.startRequested = false; - service.callStart = false; - bringDownServiceLocked(service, false); - } - public int stopService(IApplicationThread caller, Intent service, - String resolvedType) { + String resolvedType, int userId) { enforceNotIsolatedCaller("stopService"); // Refuse possible leaked file descriptors if (service != null && service.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); } - synchronized(this) { - if (DEBUG_SERVICE) Slog.v(TAG, "stopService: " + service - + " type=" + resolvedType); - - final ProcessRecord callerApp = getRecordForAppLocked(caller); - if (caller != null && callerApp == null) { - throw new SecurityException( - "Unable to find app for caller " + caller - + " (pid=" + Binder.getCallingPid() - + ") when stopping service " + service); - } + checkValidCaller(Binder.getCallingUid(), userId); - // If this service is active, make sure it is stopped. - ServiceLookupResult r = findServiceLocked(service, resolvedType, - callerApp == null ? UserId.getCallingUserId() : callerApp.userId); - if (r != null) { - if (r.record != null) { - final long origId = Binder.clearCallingIdentity(); - try { - stopServiceLocked(r.record); - } finally { - Binder.restoreCallingIdentity(origId); - } - return 1; - } - return -1; - } + synchronized(this) { + return mServices.stopServiceLocked(caller, service, resolvedType, userId); } - - return 0; } public IBinder peekService(Intent service, String resolvedType) { @@ -11861,149 +10868,111 @@ public final class ActivityManagerService extends ActivityManagerNative if (service != null && service.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); } - - IBinder ret = null; - synchronized(this) { - ServiceLookupResult r = findServiceLocked(service, resolvedType, - UserId.getCallingUserId()); - - if (r != null) { - // r.record is null if findServiceLocked() failed the caller permission check - if (r.record == null) { - throw new SecurityException( - "Permission Denial: Accessing service " + r.record.name - + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() - + " requires " + r.permission); - } - IntentBindRecord ib = r.record.bindings.get(r.record.intent); - if (ib != null) { - ret = ib.binder; - } - } + return mServices.peekServiceLocked(service, resolvedType); } - - return ret; } public boolean stopServiceToken(ComponentName className, IBinder token, int startId) { synchronized(this) { - if (DEBUG_SERVICE) Slog.v(TAG, "stopServiceToken: " + className - + " " + token + " startId=" + startId); - ServiceRecord r = findServiceLocked(className, token); - if (r != null) { - if (startId >= 0) { - // Asked to only stop if done with all work. Note that - // to avoid leaks, we will take this as dropping all - // start items up to and including this one. - ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); - if (si != null) { - while (r.deliveredStarts.size() > 0) { - ServiceRecord.StartItem cur = r.deliveredStarts.remove(0); - cur.removeUriPermissionsLocked(); - if (cur == si) { - break; - } - } - } - - if (r.getLastStartId() != startId) { - return false; - } - - if (r.deliveredStarts.size() > 0) { - Slog.w(TAG, "stopServiceToken startId " + startId - + " is last, but have " + r.deliveredStarts.size() - + " remaining args"); - } - } - - synchronized (r.stats.getBatteryStats()) { - r.stats.stopRunningLocked(); - r.startRequested = false; - r.callStart = false; - } - final long origId = Binder.clearCallingIdentity(); - bringDownServiceLocked(r, false); - Binder.restoreCallingIdentity(origId); - return true; - } + return mServices.stopServiceTokenLocked(className, token, startId); } - return false; } public void setServiceForeground(ComponentName className, IBinder token, int id, Notification notification, boolean removeNotification) { - final long origId = Binder.clearCallingIdentity(); - try { synchronized(this) { - ServiceRecord r = findServiceLocked(className, token); - if (r != null) { - if (id != 0) { - if (notification == null) { - throw new IllegalArgumentException("null notification"); - } - if (r.foregroundId != id) { - r.cancelNotification(); - r.foregroundId = id; - } - notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; - r.foregroundNoti = notification; - r.isForeground = true; - r.postNotification(); - if (r.app != null) { - updateServiceForegroundLocked(r.app, true); - } - } else { - if (r.isForeground) { - r.isForeground = false; - if (r.app != null) { - updateLruProcessLocked(r.app, false, true); - updateServiceForegroundLocked(r.app, true); + mServices.setServiceForegroundLocked(className, token, id, notification, + removeNotification); + } + } + + public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, + boolean requireFull, String name, String callerPackage) { + synchronized(this) { + return handleIncomingUserLocked(callingPid, callingUid, userId, allowAll, + requireFull, name, callerPackage); + } + } + + int handleIncomingUserLocked(int callingPid, int callingUid, int userId, boolean allowAll, + boolean requireFull, String name, String callerPackage) { + final int callingUserId = UserHandle.getUserId(callingUid); + if (callingUserId != userId) { + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { + if ((requireFull || checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) + && checkComponentPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + callingPid, callingUid, -1, true) + != PackageManager.PERMISSION_GRANTED) { + if (userId == UserHandle.USER_CURRENT_OR_SELF) { + // In this case, they would like to just execute as their + // owner user instead of failing. + userId = callingUserId; + } else { + StringBuilder builder = new StringBuilder(128); + builder.append("Permission Denial: "); + builder.append(name); + if (callerPackage != null) { + builder.append(" from "); + builder.append(callerPackage); } - } - if (removeNotification) { - r.cancelNotification(); - r.foregroundId = 0; - r.foregroundNoti = null; + builder.append(" asks to run as user "); + builder.append(userId); + builder.append(" but is calling from user "); + builder.append(UserHandle.getUserId(callingUid)); + builder.append("; this requires "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + if (!requireFull) { + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); + } + String msg = builder.toString(); + Slog.w(TAG, msg); + throw new SecurityException(msg); } } } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - public void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) { - boolean anyForeground = false; - for (ServiceRecord sr : proc.services) { - if (sr.isForeground) { - anyForeground = true; - break; + if (userId == UserHandle.USER_CURRENT + || userId == UserHandle.USER_CURRENT_OR_SELF) { + userId = mCurrentUserId; } - } - if (anyForeground != proc.foregroundServices) { - proc.foregroundServices = anyForeground; - if (oomAdj) { - updateOomAdjLocked(); + if (!allowAll && userId < 0) { + throw new IllegalArgumentException( + "Call does not support special user #" + userId); } } + return userId; } - boolean isSingleton(String componentProcessName, ApplicationInfo aInfo) { + boolean isSingleton(String componentProcessName, ApplicationInfo aInfo, + String className, int flags) { boolean result = false; - if (UserId.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) { - result = false; + if (UserHandle.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) { + if ((flags&ServiceInfo.FLAG_SINGLE_USER) != 0) { + if (ActivityManager.checkUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + aInfo.uid) != PackageManager.PERMISSION_GRANTED) { + ComponentName comp = new ComponentName(aInfo.packageName, className); + String msg = "Permission Denial: Component " + comp.flattenToShortString() + + " requests FLAG_SINGLE_USER, but app does not hold " + + android.Manifest.permission.INTERACT_ACROSS_USERS; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + result = true; + } } else if (componentProcessName == aInfo.packageName) { result = (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0; } else if ("system".equals(componentProcessName)) { result = true; } if (DEBUG_MU) { - Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo + ") = " + result); + Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo + + ", " + className + ", 0x" + Integer.toHexString(flags) + ") = " + result); } return result; } @@ -12017,239 +10986,16 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Intent"); } - checkValidCaller(Binder.getCallingUid(), userId); - synchronized(this) { - if (DEBUG_SERVICE) Slog.v(TAG, "bindService: " + service - + " type=" + resolvedType + " conn=" + connection.asBinder() - + " flags=0x" + Integer.toHexString(flags)); - if (DEBUG_MU) - Slog.i(TAG_MU, "bindService uid=" + Binder.getCallingUid() + " origUid=" - + Binder.getOrigCallingUid()); - final ProcessRecord callerApp = getRecordForAppLocked(caller); - if (callerApp == null) { - throw new SecurityException( - "Unable to find app for caller " + caller - + " (pid=" + Binder.getCallingPid() - + ") when binding service " + service); - } - - ActivityRecord activity = null; - if (token != null) { - activity = mMainStack.isInStackLocked(token); - if (activity == null) { - Slog.w(TAG, "Binding with unknown activity: " + token); - return 0; - } - } - - int clientLabel = 0; - PendingIntent clientIntent = null; - - if (callerApp.info.uid == Process.SYSTEM_UID) { - // Hacky kind of thing -- allow system stuff to tell us - // what they are, so we can report this elsewhere for - // others to know why certain services are running. - try { - clientIntent = (PendingIntent)service.getParcelableExtra( - Intent.EXTRA_CLIENT_INTENT); - } catch (RuntimeException e) { - } - if (clientIntent != null) { - clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0); - if (clientLabel != 0) { - // There are no useful extras in the intent, trash them. - // System code calling with this stuff just needs to know - // this will happen. - service = service.cloneFilter(); - } - } - } - - ServiceLookupResult res = - retrieveServiceLocked(service, resolvedType, - Binder.getCallingPid(), Binder.getCallingUid(), userId); - if (res == null) { - return 0; - } - if (res.record == null) { - return -1; - } - if (isSingleton(res.record.processName, res.record.appInfo)) { - userId = 0; - res = retrieveServiceLocked(service, resolvedType, Binder.getCallingPid(), - Binder.getCallingUid(), 0); - } - ServiceRecord s = res.record; - - final long origId = Binder.clearCallingIdentity(); - - if (unscheduleServiceRestartLocked(s)) { - if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: " - + s); - } - - AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); - ConnectionRecord c = new ConnectionRecord(b, activity, - connection, flags, clientLabel, clientIntent); - - IBinder binder = connection.asBinder(); - ArrayList<ConnectionRecord> clist = s.connections.get(binder); - if (clist == null) { - clist = new ArrayList<ConnectionRecord>(); - s.connections.put(binder, clist); - } - clist.add(c); - b.connections.add(c); - if (activity != null) { - if (activity.connections == null) { - activity.connections = new HashSet<ConnectionRecord>(); - } - activity.connections.add(c); - } - b.client.connections.add(c); - if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { - b.client.hasAboveClient = true; - } - clist = mServiceConnections.get(binder); - if (clist == null) { - clist = new ArrayList<ConnectionRecord>(); - mServiceConnections.put(binder, clist); - } - clist.add(c); - - if ((flags&Context.BIND_AUTO_CREATE) != 0) { - s.lastActivity = SystemClock.uptimeMillis(); - if (!bringUpServiceLocked(s, service.getFlags(), false)) { - return 0; - } - } - - if (s.app != null) { - // This could have made the service more important. - updateOomAdjLocked(s.app); - } - - if (DEBUG_SERVICE) Slog.v(TAG, "Bind " + s + " with " + b - + ": received=" + b.intent.received - + " apps=" + b.intent.apps.size() - + " doRebind=" + b.intent.doRebind); - - if (s.app != null && b.intent.received) { - // Service is already running, so we can immediately - // publish the connection. - try { - c.conn.connected(s.name, b.intent.binder); - } catch (Exception e) { - Slog.w(TAG, "Failure sending service " + s.shortName - + " to connection " + c.conn.asBinder() - + " (in " + c.binding.client.processName + ")", e); - } - - // If this is the first app connected back to this binding, - // and the service had previously asked to be told when - // rebound, then do so. - if (b.intent.apps.size() == 1 && b.intent.doRebind) { - requestServiceBindingLocked(s, b.intent, true); - } - } else if (!b.intent.requested) { - requestServiceBindingLocked(s, b.intent, false); - } - - Binder.restoreCallingIdentity(origId); - } - - return 1; - } - - void removeConnectionLocked( - ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) { - IBinder binder = c.conn.asBinder(); - AppBindRecord b = c.binding; - ServiceRecord s = b.service; - ArrayList<ConnectionRecord> clist = s.connections.get(binder); - if (clist != null) { - clist.remove(c); - if (clist.size() == 0) { - s.connections.remove(binder); - } - } - b.connections.remove(c); - if (c.activity != null && c.activity != skipAct) { - if (c.activity.connections != null) { - c.activity.connections.remove(c); - } - } - if (b.client != skipApp) { - b.client.connections.remove(c); - if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { - b.client.updateHasAboveClientLocked(); - } - } - clist = mServiceConnections.get(binder); - if (clist != null) { - clist.remove(c); - if (clist.size() == 0) { - mServiceConnections.remove(binder); - } - } - - if (b.connections.size() == 0) { - b.intent.apps.remove(b.client); - } - - if (!c.serviceDead) { - if (DEBUG_SERVICE) Slog.v(TAG, "Disconnecting binding " + b.intent - + ": shouldUnbind=" + b.intent.hasBound); - if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0 - && b.intent.hasBound) { - try { - bumpServiceExecutingLocked(s, "unbind"); - updateOomAdjLocked(s.app); - b.intent.hasBound = false; - // Assume the client doesn't want to know about a rebind; - // we will deal with that later if it asks for one. - b.intent.doRebind = false; - s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent()); - } catch (Exception e) { - Slog.w(TAG, "Exception when unbinding service " + s.shortName, e); - serviceDoneExecutingLocked(s, true); - } - } - - if ((c.flags&Context.BIND_AUTO_CREATE) != 0) { - bringDownServiceLocked(s, false); - } + return mServices.bindServiceLocked(caller, token, service, resolvedType, + connection, flags, userId); } } public boolean unbindService(IServiceConnection connection) { synchronized (this) { - IBinder binder = connection.asBinder(); - if (DEBUG_SERVICE) Slog.v(TAG, "unbindService: conn=" + binder); - ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder); - if (clist == null) { - Slog.w(TAG, "Unbind failed: could not find connection for " - + connection.asBinder()); - return false; - } - - final long origId = Binder.clearCallingIdentity(); - - while (clist.size() > 0) { - ConnectionRecord r = clist.get(0); - removeConnectionLocked(r, null, null); - - if (r.binding.service.app != null) { - // This could have made the service less important. - updateOomAdjLocked(r.binding.service.app); - } - } - - Binder.restoreCallingIdentity(origId); + return mServices.unbindServiceLocked(connection); } - - return true; } public void publishService(IBinder token, Intent intent, IBinder service) { @@ -12262,53 +11008,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!(token instanceof ServiceRecord)) { throw new IllegalArgumentException("Invalid service token"); } - ServiceRecord r = (ServiceRecord)token; - - final long origId = Binder.clearCallingIdentity(); - - if (DEBUG_SERVICE) Slog.v(TAG, "PUBLISHING " + r - + " " + intent + ": " + service); - if (r != null) { - Intent.FilterComparison filter - = new Intent.FilterComparison(intent); - IntentBindRecord b = r.bindings.get(filter); - if (b != null && !b.received) { - b.binder = service; - b.requested = true; - b.received = true; - if (r.connections.size() > 0) { - Iterator<ArrayList<ConnectionRecord>> it - = r.connections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> clist = it.next(); - for (int i=0; i<clist.size(); i++) { - ConnectionRecord c = clist.get(i); - if (!filter.equals(c.binding.intent.intent)) { - if (DEBUG_SERVICE) Slog.v( - TAG, "Not publishing to: " + c); - if (DEBUG_SERVICE) Slog.v( - TAG, "Bound intent: " + c.binding.intent.intent); - if (DEBUG_SERVICE) Slog.v( - TAG, "Published intent: " + intent); - continue; - } - if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c); - try { - c.conn.connected(r.name, service); - } catch (Exception e) { - Slog.w(TAG, "Failure sending service " + r.name + - " to connection " + c.conn.asBinder() + - " (in " + c.binding.client.processName + ")", e); - } - } - } - } - } - - serviceDoneExecutingLocked(r, mStoppingServices.contains(r)); - - Binder.restoreCallingIdentity(origId); - } + mServices.publishServiceLocked((ServiceRecord)token, intent, service); } } @@ -12319,38 +11019,7 @@ public final class ActivityManagerService extends ActivityManagerNative } synchronized(this) { - if (!(token instanceof ServiceRecord)) { - throw new IllegalArgumentException("Invalid service token"); - } - ServiceRecord r = (ServiceRecord)token; - - final long origId = Binder.clearCallingIdentity(); - - if (r != null) { - Intent.FilterComparison filter - = new Intent.FilterComparison(intent); - IntentBindRecord b = r.bindings.get(filter); - if (DEBUG_SERVICE) Slog.v(TAG, "unbindFinished in " + r - + " at " + b + ": apps=" - + (b != null ? b.apps.size() : 0)); - - boolean inStopping = mStoppingServices.contains(r); - if (b != null) { - if (b.apps.size() > 0 && !inStopping) { - // Applications have already bound since the last - // unbind, so just rebind right here. - requestServiceBindingLocked(r, b, true); - } else { - // Note to tell the service the next time there is - // a new client. - b.doRebind = true; - } - } - - serviceDoneExecutingLocked(r, inStopping); - - Binder.restoreCallingIdentity(origId); - } + mServices.unbindFinishedLocked((ServiceRecord)token, intent, doRebind); } } @@ -12359,137 +11028,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!(token instanceof ServiceRecord)) { throw new IllegalArgumentException("Invalid service token"); } - ServiceRecord r = (ServiceRecord)token; - boolean inStopping = mStoppingServices.contains(token); - if (r != null) { - if (r != token) { - Slog.w(TAG, "Done executing service " + r.name - + " with incorrect token: given " + token - + ", expected " + r); - return; - } - - if (type == 1) { - // This is a call from a service start... take care of - // book-keeping. - r.callStart = true; - switch (res) { - case Service.START_STICKY_COMPATIBILITY: - case Service.START_STICKY: { - // We are done with the associated start arguments. - r.findDeliveredStart(startId, true); - // Don't stop if killed. - r.stopIfKilled = false; - break; - } - case Service.START_NOT_STICKY: { - // We are done with the associated start arguments. - r.findDeliveredStart(startId, true); - if (r.getLastStartId() == startId) { - // There is no more work, and this service - // doesn't want to hang around if killed. - r.stopIfKilled = true; - } - break; - } - case Service.START_REDELIVER_INTENT: { - // We'll keep this item until they explicitly - // call stop for it, but keep track of the fact - // that it was delivered. - ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); - if (si != null) { - si.deliveryCount = 0; - si.doneExecutingCount++; - // Don't stop if killed. - r.stopIfKilled = true; - } - break; - } - case Service.START_TASK_REMOVED_COMPLETE: { - // Special processing for onTaskRemoved(). Don't - // impact normal onStartCommand() processing. - r.findDeliveredStart(startId, true); - break; - } - default: - throw new IllegalArgumentException( - "Unknown service start result: " + res); - } - if (res == Service.START_STICKY_COMPATIBILITY) { - r.callStart = false; - } - } - if (DEBUG_MU) - Slog.v(TAG_MU, "before serviceDontExecutingLocked, uid=" - + Binder.getOrigCallingUid()); - final long origId = Binder.clearCallingIdentity(); - serviceDoneExecutingLocked(r, inStopping); - Binder.restoreCallingIdentity(origId); - } else { - Slog.w(TAG, "Done executing unknown service from pid " - + Binder.getCallingPid()); - } - } - } - - public void serviceDoneExecutingLocked(ServiceRecord r, boolean inStopping) { - if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r - + ": nesting=" + r.executeNesting - + ", inStopping=" + inStopping + ", app=" + r.app); - else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName); - r.executeNesting--; - if (r.executeNesting <= 0 && r.app != null) { - if (DEBUG_SERVICE) Slog.v(TAG, - "Nesting at 0 of " + r.shortName); - r.app.executingServices.remove(r); - if (r.app.executingServices.size() == 0) { - if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG, - "No more executingServices of " + r.shortName); - mHandler.removeMessages(SERVICE_TIMEOUT_MSG, r.app); - } - if (inStopping) { - if (DEBUG_SERVICE) Slog.v(TAG, - "doneExecuting remove stopping " + r); - mStoppingServices.remove(r); - r.bindings.clear(); - } - updateOomAdjLocked(r.app); - } - } - - void serviceTimeout(ProcessRecord proc) { - String anrMessage = null; - - synchronized(this) { - if (proc.executingServices.size() == 0 || proc.thread == null) { - return; - } - long maxTime = SystemClock.uptimeMillis() - SERVICE_TIMEOUT; - Iterator<ServiceRecord> it = proc.executingServices.iterator(); - ServiceRecord timeout = null; - long nextTime = 0; - while (it.hasNext()) { - ServiceRecord sr = it.next(); - if (sr.executingStart < maxTime) { - timeout = sr; - break; - } - if (sr.executingStart > nextTime) { - nextTime = sr.executingStart; - } - } - if (timeout != null && mLruProcesses.contains(proc)) { - Slog.w(TAG, "Timeout executing service: " + timeout); - anrMessage = "Executing service " + timeout.shortName; - } else { - Message msg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mHandler.sendMessageAtTime(msg, nextTime+SERVICE_TIMEOUT); - } - } - - if (anrMessage != null) { - appNotResponding(proc, null, null, anrMessage); + mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res); } } @@ -12515,7 +11054,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Backup agent is now in use, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( - app.packageName, false, UserId.getUserId(app.uid)); + app.packageName, false, UserHandle.getUserId(app.uid)); } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " @@ -12633,9 +11172,13 @@ public final class ActivityManagerService extends ActivityManagerNative // ========================================================= private final List getStickiesLocked(String action, IntentFilter filter, - List cur) { + List cur, int userId) { final ContentResolver resolver = mContext.getContentResolver(); - final ArrayList<Intent> list = mStickyBroadcasts.get(action); + HashMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); + if (stickies == null) { + return cur; + } + final ArrayList<Intent> list = stickies.get(action); if (list == null) { return cur; } @@ -12674,8 +11217,10 @@ public final class ActivityManagerService extends ActivityManagerNative } public Intent registerReceiver(IApplicationThread caller, String callerPackage, - IIntentReceiver receiver, IntentFilter filter, String permission) { + IIntentReceiver receiver, IntentFilter filter, String permission, int userId) { enforceNotIsolatedCaller("registerReceiver"); + int callingUid; + int callingPid; synchronized(this) { ProcessRecord callerApp = null; if (caller != null) { @@ -12691,10 +11236,17 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException("Given caller package " + callerPackage + " is not running in process " + callerApp); } + callingUid = callerApp.info.uid; + callingPid = callerApp.pid; } else { callerPackage = null; + callingUid = Binder.getCallingUid(); + callingPid = Binder.getCallingPid(); } + userId = this.handleIncomingUserLocked(callingPid, callingUid, userId, + true, true, "registerReceiver", callerPackage); + List allSticky = null; // Look for any matching sticky broadcasts... @@ -12702,10 +11254,16 @@ public final class ActivityManagerService extends ActivityManagerNative if (actions != null) { while (actions.hasNext()) { String action = (String)actions.next(); - allSticky = getStickiesLocked(action, filter, allSticky); + allSticky = getStickiesLocked(action, filter, allSticky, + UserHandle.USER_ALL); + allSticky = getStickiesLocked(action, filter, allSticky, + UserHandle.getUserId(callingUid)); } } else { - allSticky = getStickiesLocked(null, filter, allSticky); + allSticky = getStickiesLocked(null, filter, allSticky, + UserHandle.USER_ALL); + allSticky = getStickiesLocked(null, filter, allSticky, + UserHandle.getUserId(callingUid)); } // The first sticky in the list is returned directly back to @@ -12722,9 +11280,8 @@ public final class ActivityManagerService extends ActivityManagerNative ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); if (rl == null) { - rl = new ReceiverList(this, callerApp, - Binder.getCallingPid(), - Binder.getCallingUid(), receiver); + rl = new ReceiverList(this, callerApp, callingPid, callingUid, + userId, receiver); if (rl.app != null) { rl.app.receivers.add(rl); } else { @@ -12736,8 +11293,21 @@ public final class ActivityManagerService extends ActivityManagerNative rl.linkedToDeath = true; } mRegisteredReceivers.put(receiver.asBinder(), rl); + } else if (rl.uid != callingUid) { + throw new IllegalArgumentException( + "Receiver requested to register for uid " + callingUid + + " was previously registered for uid " + rl.uid); + } else if (rl.pid != callingPid) { + throw new IllegalArgumentException( + "Receiver requested to register for pid " + callingPid + + " was previously registered for pid " + rl.pid); + } else if (rl.userId != userId) { + throw new IllegalArgumentException( + "Receiver requested to register for user " + userId + + " was previously registered for user " + rl.userId); } - BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, permission); + BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, + permission, callingUid, userId); rl.add(bf); if (!bf.debugCheck()) { Slog.w(TAG, "==> For Dynamic broadast"); @@ -12756,7 +11326,7 @@ public final class ActivityManagerService extends ActivityManagerNative BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, null, null, -1, -1, null, receivers, null, 0, null, null, - false, true, true); + false, true, true, -1); queue.enqueueParallelBroadcastLocked(r); queue.scheduleBroadcastsLocked(); } @@ -12819,10 +11389,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private final void sendPackageBroadcastLocked(int cmd, String[] packages) { + private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); - if (r.thread != null) { + if (r.thread != null && (userId == UserHandle.USER_ALL || r.userId == userId)) { try { r.thread.dispatchPackageBroadcast(cmd, packages); } catch (RemoteException ex) { @@ -12830,7 +11400,67 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType, + int[] users) { + List<ResolveInfo> receivers = null; + try { + HashSet<ComponentName> singleUserReceivers = null; + boolean scannedFirstReceivers = false; + for (int user : users) { + List<ResolveInfo> newReceivers = AppGlobals.getPackageManager() + .queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user); + if (newReceivers != null && newReceivers.size() == 0) { + newReceivers = null; + } + if (receivers == null) { + receivers = newReceivers; + } else if (newReceivers != null) { + // We need to concatenate the additional receivers + // found with what we have do far. This would be easy, + // but we also need to de-dup any receivers that are + // singleUser. + if (!scannedFirstReceivers) { + // Collect any single user receivers we had already retrieved. + scannedFirstReceivers = true; + for (int i=0; i<receivers.size(); i++) { + ResolveInfo ri = receivers.get(i); + if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) { + ComponentName cn = new ComponentName( + ri.activityInfo.packageName, ri.activityInfo.name); + if (singleUserReceivers == null) { + singleUserReceivers = new HashSet<ComponentName>(); + } + singleUserReceivers.add(cn); + } + } + } + // Add the new results to the existing results, tracking + // and de-dupping single user receivers. + for (int i=0; i<newReceivers.size(); i++) { + ResolveInfo ri = newReceivers.get(i); + if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) { + ComponentName cn = new ComponentName( + ri.activityInfo.packageName, ri.activityInfo.name); + if (singleUserReceivers == null) { + singleUserReceivers = new HashSet<ComponentName>(); + } + if (!singleUserReceivers.contains(cn)) { + singleUserReceivers.add(cn); + receivers.add(ri); + } + } else { + receivers.add(ri); + } + } + } + } + } catch (RemoteException ex) { + // pm is in same process, this will never happen. + } + return receivers; + } + private final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, @@ -12848,7 +11478,45 @@ public final class ActivityManagerService extends ActivityManagerNative if ((resultTo != null) && !ordered) { Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!"); } - + + userId = handleIncomingUserLocked(callingPid, callingUid, userId, + true, false, "broadcast", callerPackage); + + // Make sure that the user who is receiving this broadcast is started + // If not, we will just skip it. + if (userId != UserHandle.USER_ALL && mStartedUsers.get(userId) == null) { + if (callingUid != Process.SYSTEM_UID || (intent.getFlags() + & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) { + Slog.w(TAG, "Skipping broadcast of " + intent + + ": user " + userId + " is stopped"); + return ActivityManager.BROADCAST_SUCCESS; + } + } + + /* + * Prevent non-system code (defined here to be non-persistent + * processes) from sending protected broadcasts. + */ + if (callingUid == Process.SYSTEM_UID || callingUid == Process.PHONE_UID + || callingUid == Process.SHELL_UID || callingUid == Process.BLUETOOTH_UID || + callingUid == 0) { + // Always okay. + } else if (callerApp == null || !callerApp.persistent) { + try { + if (AppGlobals.getPackageManager().isProtectedBroadcast( + intent.getAction())) { + String msg = "Permission Denial: not allowed to send broadcast " + + intent.getAction() + " from pid=" + + callingPid + ", uid=" + callingUid; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + } catch (RemoteException e) { + Slog.w(TAG, "Remote exception", e); + return ActivityManager.BROADCAST_SUCCESS; + } + } + // Handle special intents: if this broadcast is from the package // manager about a package being removed, we need to remove all of // its activities from the history stack. @@ -12873,7 +11541,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } else { - // If resources are unvailble just force stop all + // If resources are unavailable just force stop all // those packages and flush the attribute cache as well. if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())) { String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); @@ -12882,7 +11550,7 @@ public final class ActivityManagerService extends ActivityManagerNative forceStopPackageLocked(pkg, -1, false, true, true, false, userId); } sendPackageBroadcastLocked( - IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list); + IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list, userId); } } else { Uri data = intent.getData(); @@ -12895,7 +11563,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED, - new String[] {ssp}); + new String[] {ssp}, userId); } } } @@ -12939,29 +11607,6 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY, proxy)); } - /* - * Prevent non-system code (defined here to be non-persistent - * processes) from sending protected broadcasts. - */ - if (callingUid == Process.SYSTEM_UID || callingUid == Process.PHONE_UID - || callingUid == Process.SHELL_UID || callingUid == 0) { - // Always okay. - } else if (callerApp == null || !callerApp.persistent) { - try { - if (AppGlobals.getPackageManager().isProtectedBroadcast( - intent.getAction())) { - String msg = "Permission Denial: not allowed to send broadcast " - + intent.getAction() + " from pid=" - + callingPid + ", uid=" + callingUid; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - } catch (RemoteException e) { - Slog.w(TAG, "Remote exception", e); - return ActivityManager.BROADCAST_SUCCESS; - } - } - // Add to the sticky list if requested. if (sticky) { if (checkPermission(android.Manifest.permission.BROADCAST_STICKY, @@ -12982,10 +11627,38 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException( "Sticky broadcasts can't target a specific component"); } - ArrayList<Intent> list = mStickyBroadcasts.get(intent.getAction()); + // We use userId directly here, since the "all" target is maintained + // as a separate set of sticky broadcasts. + if (userId != UserHandle.USER_ALL) { + // But first, if this is not a broadcast to all users, then + // make sure it doesn't conflict with an existing broadcast to + // all users. + HashMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get( + UserHandle.USER_ALL); + if (stickies != null) { + ArrayList<Intent> list = stickies.get(intent.getAction()); + if (list != null) { + int N = list.size(); + int i; + for (i=0; i<N; i++) { + if (intent.filterEquals(list.get(i))) { + throw new IllegalArgumentException( + "Sticky broadcast " + intent + " for user " + + userId + " conflicts with existing global broadcast"); + } + } + } + } + } + HashMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); + if (stickies == null) { + stickies = new HashMap<String, ArrayList<Intent>>(); + mStickyBroadcasts.put(userId, stickies); + } + ArrayList<Intent> list = stickies.get(intent.getAction()); if (list == null) { list = new ArrayList<Intent>(); - mStickyBroadcasts.put(intent.getAction(), list); + stickies.put(intent.getAction(), list); } int N = list.size(); int i; @@ -13001,37 +11674,29 @@ public final class ActivityManagerService extends ActivityManagerNative } } + int[] users; + if (userId == UserHandle.USER_ALL) { + // Caller wants broadcast to go to all started users. + users = new int[mStartedUsers.size()]; + for (int i=0; i<mStartedUsers.size(); i++) { + users[i] = mStartedUsers.keyAt(i); + } + } else { + // Caller wants broadcast to go to one specific user. + users = new int[] {userId}; + } + // Figure out who all will receive this broadcast. List receivers = null; List<BroadcastFilter> registeredReceivers = null; - try { - if (intent.getComponent() != null) { - // Broadcast is going to one specific receiver class... - ActivityInfo ai = AppGlobals.getPackageManager(). - getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS, userId); - if (ai != null) { - receivers = new ArrayList(); - ResolveInfo ri = new ResolveInfo(); - if (isSingleton(ai.processName, ai.applicationInfo)) { - ri.activityInfo = getActivityInfoForUser(ai, 0); - } else { - ri.activityInfo = getActivityInfoForUser(ai, userId); - } - receivers.add(ri); - } - } else { - // Need to resolve the intent to interested receivers... - if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) - == 0) { - receivers = - AppGlobals.getPackageManager().queryIntentReceivers( - intent, resolvedType, STOCK_PM_FLAGS, userId); - } - registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, - userId); - } - } catch (RemoteException ex) { - // pm is in same process, this will never happen. + // Need to resolve the intent to interested receivers... + if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) + == 0) { + receivers = collectReceiverComponents(intent, resolvedType, users); + } + if (intent.getComponent() == null) { + registeredReceivers = mReceiverResolver.queryIntent(intent, + resolvedType, false, userId); } final boolean replacePending = @@ -13049,7 +11714,7 @@ public final class ActivityManagerService extends ActivityManagerNative BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, - ordered, sticky, false); + ordered, sticky, false, userId); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing parallel broadcast " + r); final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r); @@ -13139,7 +11804,7 @@ public final class ActivityManagerService extends ActivityManagerNative BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, receivers, resultTo, resultCode, resultData, map, ordered, - sticky, false); + sticky, false, userId); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r + ": prev had " + queue.mOrderedBroadcasts.size()); @@ -13224,13 +11889,15 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // TODO: Use the userId; maybe mStickyBroadcasts need to be tied to the user. public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); } + userId = handleIncomingUserLocked(Binder.getCallingPid(), + Binder.getCallingUid(), userId, true, false, "removeStickyBroadcast", null); + synchronized(this) { if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY) != PackageManager.PERMISSION_GRANTED) { @@ -13241,15 +11908,24 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - ArrayList<Intent> list = mStickyBroadcasts.get(intent.getAction()); - if (list != null) { - int N = list.size(); - int i; - for (i=0; i<N; i++) { - if (intent.filterEquals(list.get(i))) { - list.remove(i); - break; + HashMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); + if (stickies != null) { + ArrayList<Intent> list = stickies.get(intent.getAction()); + if (list != null) { + int N = list.size(); + int i; + for (i=0; i<N; i++) { + if (intent.filterEquals(list.get(i))) { + list.remove(i); + break; + } } + if (list.size() <= 0) { + stickies.remove(intent.getAction()); + } + } + if (stickies.size() <= 0) { + mStickyBroadcasts.remove(userId); } } } @@ -13305,8 +11981,10 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, - IInstrumentationWatcher watcher) { + IInstrumentationWatcher watcher, int userId) { enforceNotIsolatedCaller("startInstrumentation"); + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, true, "startInstrumentation", null); // Refuse possible leaked file descriptors if (arguments != null && arguments.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Bundle"); @@ -13318,9 +11996,10 @@ public final class ActivityManagerService extends ActivityManagerNative try { ii = mContext.getPackageManager().getInstrumentationInfo( className, STOCK_PM_FLAGS); - ai = mContext.getPackageManager().getApplicationInfo( - ii.targetPackage, STOCK_PM_FLAGS); + ai = AppGlobals.getPackageManager().getApplicationInfo( + ii.targetPackage, STOCK_PM_FLAGS, userId); } catch (PackageManager.NameNotFoundException e) { + } catch (RemoteException e) { } if (ii == null) { reportStartInstrumentationFailure(watcher, className, @@ -13347,7 +12026,6 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - int userId = UserId.getCallingUserId(); final long origId = Binder.clearCallingIdentity(); // Instrumentation can kill and relaunch even persistent processes forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId); @@ -13405,12 +12083,12 @@ public final class ActivityManagerService extends ActivityManagerNative app.instrumentationProfileFile = null; app.instrumentationArguments = null; - forceStopPackageLocked(app.processName, -1, false, false, true, true, app.userId); + forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId); } public void finishInstrumentation(IApplicationThread target, int resultCode, Bundle results) { - int userId = UserId.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); // Refuse possible leaked file descriptors if (results != null && results.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -13580,12 +12258,12 @@ public final class ActivityManagerService extends ActivityManagerNative intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, - null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); + null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) { broadcastIntentLocked(null, null, new Intent(Intent.ACTION_LOCALE_CHANGED), null, null, 0, null, null, - null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); + null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); } } } @@ -13706,7 +12384,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i = start; i > finishTo; i--) { ActivityRecord r = history.get(i); mMainStack.requestFinishActivityLocked(r.appToken, resultCode, resultData, - "navigate-up"); + "navigate-up", true); // Only return the supplied result for the first activity finished resultCode = Activity.RESULT_CANCELED; resultData = null; @@ -13723,7 +12401,7 @@ public final class ActivityManagerService extends ActivityManagerNative } else { try { ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( - destIntent.getComponent(), 0, UserId.getCallingUserId()); + destIntent.getComponent(), 0, UserHandle.getCallingUserId()); int res = mMainStack.startActivityLocked(srec.app.thread, destIntent, null, aInfo, parent.appToken, null, 0, -1, parent.launchedFromUid, 0, null, true, null); @@ -13732,7 +12410,7 @@ public final class ActivityManagerService extends ActivityManagerNative foundParentInTask = false; } mMainStack.requestFinishActivityLocked(parent.appToken, resultCode, - resultData, "navigate-up"); + resultData, "navigate-up", true); } } Binder.restoreCallingIdentity(origId); @@ -13775,14 +12453,15 @@ public final class ActivityManagerService extends ActivityManagerNative } private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj, - ProcessRecord TOP_APP, boolean recursed, boolean doingAll) { + int emptyAdj, ProcessRecord TOP_APP, boolean recursed, boolean doingAll) { if (mAdjSeq == app.adjSeq) { // This adjustment has already been computed. If we are calling // from the top, we may have already computed our adjustment with // an earlier hidden adjustment that isn't really for us... if // so, use the new hidden adjustment. if (!recursed && app.hidden) { - app.curAdj = app.curRawAdj = app.nonStoppingAdj = hiddenAdj; + app.curAdj = app.curRawAdj = app.nonStoppingAdj = + app.hasActivities ? hiddenAdj : emptyAdj; } return app.curRawAdj; } @@ -13790,7 +12469,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.thread == null) { app.adjSeq = mAdjSeq; app.curSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - return (app.curAdj=ProcessList.HIDDEN_APP_MAX_ADJ); + return (app.curAdj=app.curRawAdj=ProcessList.HIDDEN_APP_MAX_ADJ); } app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; @@ -13807,6 +12486,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "fixed"; app.adjSeq = mAdjSeq; app.curRawAdj = app.nonStoppingAdj = app.maxAdj; + app.hasActivities = false; app.foregroundActivities = false; app.keeping = true; app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; @@ -13817,12 +12497,15 @@ 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; - break; + } + if (r.app == app) { + app.hasActivities = true; } } } @@ -13831,6 +12514,7 @@ 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. @@ -13846,6 +12530,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "top-activity"; foregroundActivities = true; interesting = true; + app.hasActivities = true; } else if (app.instrumentationClass != null) { // Don't want to kill running instrumentation. adj = ProcessList.FOREGROUND_APP_ADJ; @@ -13867,21 +12552,13 @@ public final class ActivityManagerService extends ActivityManagerNative adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "exec-service"; - } else if (activitiesSize > 0) { - // This app is in the background with paused activities. - // We inspect activities to potentially upgrade adjustment further below. - adj = hiddenAdj; - schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - app.hidden = true; - app.adjType = "bg-activities"; } else { - // A very not-needed process. If this is lower in the lru list, - // we will push it in to the empty bucket. + // Assume process is hidden (has activities); we will correct + // later if this is not the case. adj = hiddenAdj; schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; app.hidden = true; - app.empty = true; - app.adjType = "bg-empty"; + app.adjType = "bg-activities"; } boolean hasStoppingActivities = false; @@ -13898,6 +12575,7 @@ public final class ActivityManagerService extends ActivityManagerNative } schedGroup = Process.THREAD_GROUP_DEFAULT; app.hidden = false; + app.hasActivities = true; foregroundActivities = true; break; } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { @@ -13915,9 +12593,20 @@ public final class ActivityManagerService extends ActivityManagerNative foregroundActivities = true; hasStoppingActivities = true; } + if (r.app == app) { + app.hasActivities = true; + } } } + if (adj == hiddenAdj && !app.hasActivities) { + // Whoops, this process is completely empty as far as we know + // at this point. + adj = emptyAdj; + app.empty = true; + app.adjType = "bg-empty"; + } + if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { if (app.foregroundServices) { // The user is aware of this app, so make it visible. @@ -14005,7 +12694,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "started-bg-ui-services"; } } else { - if (now < (s.lastActivity+MAX_SERVICE_INACTIVITY)) { + if (now < (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) { // This service has seen some activity within // recent memory, so we will keep its process ahead // of the background processes. @@ -14051,8 +12740,16 @@ public final class ActivityManagerService extends ActivityManagerNative myHiddenAdj = ProcessList.VISIBLE_APP_ADJ; } } - clientAdj = computeOomAdjLocked( - client, myHiddenAdj, TOP_APP, true, doingAll); + int myEmptyAdj = emptyAdj; + if (myEmptyAdj > client.emptyAdj) { + if (client.emptyAdj >= ProcessList.VISIBLE_APP_ADJ) { + myEmptyAdj = client.emptyAdj; + } else { + myEmptyAdj = ProcessList.VISIBLE_APP_ADJ; + } + } + clientAdj = computeOomAdjLocked(client, myHiddenAdj, + myEmptyAdj, TOP_APP, true, doingAll); String adjType = null; if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { // Not doing bind OOM management, so treat @@ -14068,7 +12765,8 @@ public final class ActivityManagerService extends ActivityManagerNative app.hidden = false; clientAdj = adj; } else { - if (now >= (s.lastActivity+MAX_SERVICE_INACTIVITY)) { + if (now >= (s.lastActivity + + ActiveServices.MAX_SERVICE_INACTIVITY)) { // This service has not seen activity within // recent memory, so allow it to drop to the // LRU list if there is no other reason to keep @@ -14190,8 +12888,16 @@ public final class ActivityManagerService extends ActivityManagerNative myHiddenAdj = ProcessList.FOREGROUND_APP_ADJ; } } - int clientAdj = computeOomAdjLocked( - client, myHiddenAdj, TOP_APP, true, doingAll); + int myEmptyAdj = emptyAdj; + if (myEmptyAdj > client.emptyAdj) { + if (client.emptyAdj > ProcessList.FOREGROUND_APP_ADJ) { + myEmptyAdj = client.emptyAdj; + } else { + myEmptyAdj = ProcessList.FOREGROUND_APP_ADJ; + } + } + int clientAdj = computeOomAdjLocked(client, myHiddenAdj, + myEmptyAdj, TOP_APP, true, doingAll); if (adj > clientAdj) { if (app.hasShownUi && app != mHomeProcess && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { @@ -14604,9 +13310,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private final boolean updateOomAdjLocked( - ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP, boolean doingAll) { + private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj, + int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) { app.hiddenAdj = hiddenAdj; + app.emptyAdj = emptyAdj; if (app.thread == null) { return false; @@ -14616,7 +13323,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean success = true; - computeOomAdjLocked(app, hiddenAdj, TOP_APP, false, doingAll); + computeOomAdjLocked(app, hiddenAdj, emptyAdj, TOP_APP, false, doingAll); if (app.curRawAdj != app.setRawAdj) { if (wasKeeping && !app.keeping) { @@ -14694,7 +13401,7 @@ public final class ActivityManagerService extends ActivityManagerNative return resumedActivity; } - private final boolean updateOomAdjLocked(ProcessRecord app) { + final boolean updateOomAdjLocked(ProcessRecord app) { final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; int curAdj = app.curAdj; @@ -14703,7 +13410,8 @@ public final class ActivityManagerService extends ActivityManagerNative mAdjSeq++; - boolean success = updateOomAdjLocked(app, app.hiddenAdj, TOP_APP, false); + boolean success = updateOomAdjLocked(app, app.hiddenAdj, app.emptyAdj, + TOP_APP, false); final boolean nowHidden = app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ && app.curAdj <= ProcessList.HIDDEN_APP_MAX_ADJ; if (nowHidden != wasHidden) { @@ -14731,34 +13439,53 @@ public final class ActivityManagerService extends ActivityManagerNative // how many slots we have for background processes; we may want // to put multiple processes in a slot of there are enough of // them. - int numSlots = ProcessList.HIDDEN_APP_MAX_ADJ - ProcessList.HIDDEN_APP_MIN_ADJ + 1; - int factor = (mLruProcesses.size()-4)/numSlots; - if (factor < 1) factor = 1; - int step = 0; + int numSlots = (ProcessList.HIDDEN_APP_MAX_ADJ + - ProcessList.HIDDEN_APP_MIN_ADJ + 1) / 2; + int emptyFactor = (mLruProcesses.size()-mNumNonHiddenProcs-mNumHiddenProcs)/numSlots; + if (emptyFactor < 1) emptyFactor = 1; + int hiddenFactor = (mNumHiddenProcs > 0 ? mNumHiddenProcs : 1)/numSlots; + if (hiddenFactor < 1) hiddenFactor = 1; + int stepHidden = 0; + int stepEmpty = 0; + final int emptyProcessLimit = mProcessLimit > 1 ? mProcessLimit / 2 : mProcessLimit; + final int hiddenProcessLimit = mProcessLimit > 1 ? mProcessLimit / 2 : mProcessLimit; int numHidden = 0; + int numEmpty = 0; int numTrimming = 0; - + + mNumNonHiddenProcs = 0; + mNumHiddenProcs = 0; + // First update the OOM adjustment for each of the // application processes based on their current state. int i = mLruProcesses.size(); int curHiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ; + int nextHiddenAdj = curHiddenAdj+1; + int curEmptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ; + int nextEmptyAdj = curEmptyAdj+2; while (i > 0) { i--; ProcessRecord app = mLruProcesses.get(i); //Slog.i(TAG, "OOM " + app + ": cur hidden=" + curHiddenAdj); - updateOomAdjLocked(app, curHiddenAdj, TOP_APP, true); - if (curHiddenAdj < ProcessList.HIDDEN_APP_MAX_ADJ - && app.curAdj == curHiddenAdj) { - step++; - if (step >= factor) { - step = 0; - curHiddenAdj++; - } - } + updateOomAdjLocked(app, curHiddenAdj, curEmptyAdj, TOP_APP, true); if (!app.killedBackground) { - if (app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { + if (app.curRawAdj == curHiddenAdj && app.hasActivities) { + // This process was assigned as a hidden process... step the + // hidden level. + mNumHiddenProcs++; + if (curHiddenAdj != nextHiddenAdj) { + stepHidden++; + if (stepHidden >= hiddenFactor) { + stepHidden = 0; + curHiddenAdj = nextHiddenAdj; + nextHiddenAdj += 2; + if (nextHiddenAdj > ProcessList.HIDDEN_APP_MAX_ADJ) { + nextHiddenAdj = ProcessList.HIDDEN_APP_MAX_ADJ; + } + } + } numHidden++; - if (numHidden > mProcessLimit) { + if (numHidden > hiddenProcessLimit) { Slog.i(TAG, "No longer want " + app.processName + " (pid " + app.pid + "): hidden #" + numHidden); EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, @@ -14766,8 +13493,37 @@ public final class ActivityManagerService extends ActivityManagerNative app.killedBackground = true; Process.killProcessQuiet(app.pid); } + } else { + if (app.curRawAdj == curEmptyAdj || app.curRawAdj == curHiddenAdj) { + // This process was assigned as an empty process... step the + // empty level. + if (curEmptyAdj != nextEmptyAdj) { + stepEmpty++; + if (stepEmpty >= emptyFactor) { + stepEmpty = 0; + curEmptyAdj = nextEmptyAdj; + nextEmptyAdj += 2; + if (nextEmptyAdj > ProcessList.HIDDEN_APP_MAX_ADJ) { + nextEmptyAdj = ProcessList.HIDDEN_APP_MAX_ADJ; + } + } + } + } else if (app.curRawAdj < ProcessList.HIDDEN_APP_MIN_ADJ) { + mNumNonHiddenProcs++; + } + if (app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { + numEmpty++; + if (numEmpty > emptyProcessLimit) { + Slog.i(TAG, "No longer want " + app.processName + + " (pid " + app.pid + "): empty #" + numEmpty); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "too many background"); + app.killedBackground = true; + Process.killProcessQuiet(app.pid); + } + } } - if (!app.killedBackground && app.isolated && app.services.size() <= 0) { + if (app.isolated && app.services.size() <= 0) { // If this is an isolated process, and there are no // services running in it, then the process is no longer // needed. We agressively kill these because we can by @@ -14797,18 +13553,20 @@ public final class ActivityManagerService extends ActivityManagerNative // are managing to keep around is less than half the maximum we desire; // if we are keeping a good number around, we'll let them use whatever // memory they want. - if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/2)) { + if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/4) + && numEmpty <= (ProcessList.MAX_HIDDEN_APPS/4)) { + final int numHiddenAndEmpty = numHidden + numEmpty; final int N = mLruProcesses.size(); - factor = numTrimming/3; + int factor = numTrimming/3; int minFactor = 2; if (mHomeProcess != null) minFactor++; if (mPreviousProcess != null) minFactor++; if (factor < minFactor) factor = minFactor; - step = 0; + int step = 0; int fgTrimLevel; - if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/5)) { + if (numHiddenAndEmpty <= (ProcessList.MAX_HIDDEN_APPS/5)) { fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; - } else if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/3)) { + } else if (numHiddenAndEmpty <= (ProcessList.MAX_HIDDEN_APPS/3)) { fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; } else { fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; @@ -15007,7 +13765,7 @@ public final class ActivityManagerService extends ActivityManagerNative mAutoStopProfiler = false; } - public boolean profileControl(String process, boolean start, + public boolean profileControl(String process, int userId, boolean start, String path, ParcelFileDescriptor fd, int profileType) throws RemoteException { try { @@ -15026,22 +13784,7 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord proc = null; if (process != null) { - try { - int pid = Integer.parseInt(process); - synchronized (mPidsSelfLocked) { - proc = mPidsSelfLocked.get(pid); - } - } catch (NumberFormatException e) { - } - - if (proc == null) { - HashMap<String, SparseArray<ProcessRecord>> all - = mProcessNames.getMap(); - SparseArray<ProcessRecord> procs = all.get(process); - if (procs != null && procs.size() > 0) { - proc = procs.valueAt(0); - } - } + proc = findProcessLocked(process, userId, "profileControl"); } if (start && (proc == null || proc.thread == null)) { @@ -15085,7 +13828,40 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public boolean dumpHeap(String process, boolean managed, + private ProcessRecord findProcessLocked(String process, int userId, String callName) { + userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), + userId, true, true, callName, null); + ProcessRecord proc = null; + try { + int pid = Integer.parseInt(process); + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(pid); + } + } catch (NumberFormatException e) { + } + + if (proc == null) { + HashMap<String, SparseArray<ProcessRecord>> all + = mProcessNames.getMap(); + SparseArray<ProcessRecord> procs = all.get(process); + if (procs != null && procs.size() > 0) { + proc = procs.valueAt(0); + if (userId != UserHandle.USER_ALL && proc.userId != userId) { + for (int i=1; i<procs.size(); i++) { + ProcessRecord thisProc = procs.valueAt(i); + if (thisProc.userId == userId) { + proc = thisProc; + break; + } + } + } + } + } + + return proc; + } + + public boolean dumpHeap(String process, int userId, boolean managed, String path, ParcelFileDescriptor fd) throws RemoteException { try { @@ -15102,24 +13878,7 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("null fd"); } - ProcessRecord proc = null; - try { - int pid = Integer.parseInt(process); - synchronized (mPidsSelfLocked) { - proc = mPidsSelfLocked.get(pid); - } - } catch (NumberFormatException e) { - } - - if (proc == null) { - HashMap<String, SparseArray<ProcessRecord>> all - = mProcessNames.getMap(); - SparseArray<ProcessRecord> procs = all.get(process); - if (procs != null && procs.size() > 0) { - proc = procs.valueAt(0); - } - } - + ProcessRecord proc = findProcessLocked(process, userId, "dumpHeap"); if (proc == null || proc.thread == null) { throw new IllegalArgumentException("Unknown process: " + process); } @@ -15167,101 +13926,392 @@ public final class ActivityManagerService extends ActivityManagerNative // Multi-user methods - private int mCurrentUserId; - private SparseIntArray mLoggedInUsers = new SparseIntArray(5); - + @Override public boolean switchUser(int userId) { - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.myUid()) { - Slog.e(TAG, "Trying to switch user from unauthorized app"); - return false; + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: switchUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); } - if (mCurrentUserId == userId) - return true; - synchronized (this) { - // Check if user is already logged in, otherwise check if user exists first before - // adding to the list of logged in users. - if (mLoggedInUsers.indexOfKey(userId) < 0) { - if (!userExists(userId)) { + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (this) { + final int oldUserId = mCurrentUserId; + if (oldUserId == userId) { + return true; + } + + final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId); + if (userInfo == null) { + Slog.w(TAG, "No user info for user #" + userId); return false; } - mLoggedInUsers.append(userId, userId); + + mWindowManager.startFreezingScreen(R.anim.screen_user_exit, + R.anim.screen_user_enter); + + // If the user we are switching to is not currently started, then + // we need to start it now. + if (mStartedUsers.get(userId) == null) { + mStartedUsers.put(userId, new UserStartedState(new UserHandle(userId), false)); + } + + mCurrentUserId = userId; + final Integer userIdInt = Integer.valueOf(userId); + mUserLru.remove(userIdInt); + mUserLru.add(userIdInt); + + final UserStartedState uss = mStartedUsers.get(userId); + + mHandler.removeMessages(REPORT_USER_SWITCH_MSG); + mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG); + mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG, + oldUserId, userId, uss)); + mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG, + oldUserId, userId, uss), USER_SWITCH_TIMEOUT); + Intent intent = new Intent(Intent.ACTION_USER_STARTED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, + false, false, MY_PID, Process.SYSTEM_UID, userId); + + if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) { + if (userId != 0) { + intent = new Intent(Intent.ACTION_USER_INITIALIZE); + broadcastIntentLocked(null, null, intent, null, + new IIntentReceiver.Stub() { + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, + boolean sticky, int sendingUser) { + synchronized (ActivityManagerService.this) { + getUserManagerLocked().makeInitialized(userInfo.id); + } + } + }, 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID, + userId); + } else { + getUserManagerLocked().makeInitialized(userInfo.id); + } + } + + boolean haveActivities = mMainStack.switchUserLocked(userId, uss); + if (!haveActivities) { + startHomeActivityLocked(userId); + } + + sendUserSwitchBroadcastsLocked(oldUserId, userId); } + } finally { + Binder.restoreCallingIdentity(ident); + } - mCurrentUserId = userId; - boolean haveActivities = mMainStack.switchUser(userId); - if (!haveActivities) { - startHomeActivityLocked(userId); + return true; + } + + void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) { + long ident = Binder.clearCallingIdentity(); + try { + Intent intent; + if (oldUserId >= 0) { + intent = new Intent(Intent.ACTION_USER_BACKGROUND); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(Intent.EXTRA_USER_HANDLE, oldUserId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, + false, false, MY_PID, Process.SYSTEM_UID, oldUserId); } + if (newUserId >= 0) { + intent = new Intent(Intent.ACTION_USER_FOREGROUND); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, null, + false, false, MY_PID, Process.SYSTEM_UID, newUserId); + intent = new Intent(Intent.ACTION_USER_SWITCHED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, + android.Manifest.permission.MANAGE_USERS, + false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + void dispatchUserSwitch(final UserStartedState uss, final int oldUserId, + final int newUserId) { + final int N = mUserSwitchObservers.beginBroadcast(); + if (N > 0) { + final IRemoteCallback callback = new IRemoteCallback.Stub() { + int mCount = 0; + @Override + public void sendResult(Bundle data) throws RemoteException { + synchronized (ActivityManagerService.this) { + if (mCurUserSwitchCallback == this) { + mCount++; + if (mCount == N) { + sendContinueUserSwitchLocked(uss, oldUserId, newUserId); + } + } + } + } + }; + synchronized (this) { + mCurUserSwitchCallback = callback; + } + for (int i=0; i<N; i++) { + try { + mUserSwitchObservers.getBroadcastItem(i).onUserSwitching( + newUserId, callback); + } catch (RemoteException e) { + } + } + } else { + synchronized (this) { + sendContinueUserSwitchLocked(uss, oldUserId, newUserId); + } } + mUserSwitchObservers.finishBroadcast(); + } - // Inform of user switch - Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED); - addedIntent.putExtra(Intent.EXTRA_USERID, userId); - mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS); + void timeoutUserSwitch(UserStartedState uss, int oldUserId, int newUserId) { + synchronized (this) { + Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId); + sendContinueUserSwitchLocked(uss, oldUserId, newUserId); + } + } - return true; + void sendContinueUserSwitchLocked(UserStartedState uss, int oldUserId, int newUserId) { + mCurUserSwitchCallback = null; + mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG); + mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG, + oldUserId, newUserId, uss)); } - @Override - public UserInfo getCurrentUser() throws RemoteException { - final int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.myUid()) { - Slog.e(TAG, "Trying to get user from unauthorized app"); - return null; + void continueUserSwitch(UserStartedState uss, int oldUserId, int newUserId) { + final int N = mUserSwitchObservers.beginBroadcast(); + for (int i=0; i<N; i++) { + try { + mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(newUserId); + } catch (RemoteException e) { + } + } + mUserSwitchObservers.finishBroadcast(); + synchronized (this) { + mWindowManager.stopFreezingScreen(); } - return AppGlobals.getPackageManager().getUser(mCurrentUserId); } - private void onUserRemoved(Intent intent) { - int extraUserId = intent.getIntExtra(Intent.EXTRA_USERID, -1); - if (extraUserId < 1) return; + void finishUserSwitch(UserStartedState uss) { + synchronized (this) { + if (uss.mState == UserStartedState.STATE_BOOTING + && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) { + uss.mState = UserStartedState.STATE_RUNNING; + final int userId = uss.mHandle.getIdentifier(); + Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + broadcastIntentLocked(null, null, intent, + null, null, 0, null, null, + android.Manifest.permission.RECEIVE_BOOT_COMPLETED, + false, false, MY_PID, Process.SYSTEM_UID, userId); + } + } + } - // Kill all the processes for the user - ArrayList<Pair<String, Integer>> pkgAndUids = new ArrayList<Pair<String,Integer>>(); + @Override + public int stopUser(final int userId, final IStopUserCallback callback) { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: switchUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + if (userId <= 0) { + throw new IllegalArgumentException("Can't stop primary user " + userId); + } synchronized (this) { - HashMap<String,SparseArray<ProcessRecord>> map = mProcessNames.getMap(); - for (Entry<String, SparseArray<ProcessRecord>> uidMap : map.entrySet()) { - SparseArray<ProcessRecord> uids = uidMap.getValue(); - for (int i = 0; i < uids.size(); i++) { - if (UserId.getUserId(uids.keyAt(i)) == extraUserId) { - pkgAndUids.add(new Pair<String,Integer>(uidMap.getKey(), uids.keyAt(i))); - } + if (mCurrentUserId == userId) { + return ActivityManager.USER_OP_IS_CURRENT; + } + + final UserStartedState uss = mStartedUsers.get(userId); + if (uss == null) { + // User is not started, nothing to do... but we do need to + // callback if requested. + if (callback != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + callback.userStopped(userId); + } catch (RemoteException e) { + } + } + }); } + return ActivityManager.USER_OP_SUCCESS; } - for (Pair<String,Integer> pkgAndUid : pkgAndUids) { - forceStopPackageLocked(pkgAndUid.first, pkgAndUid.second, - false, false, true, true, extraUserId); + if (callback != null) { + uss.mStopCallbacks.add(callback); + } + + if (uss.mState != UserStartedState.STATE_STOPPING) { + uss.mState = UserStartedState.STATE_STOPPING; + + long ident = Binder.clearCallingIdentity(); + try { + // Inform of user switch + Intent intent = new Intent(Intent.ACTION_SHUTDOWN); + final IIntentReceiver resultReceiver = new IIntentReceiver.Stub() { + @Override + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean ordered, boolean sticky, int sendingUser) { + finishUserStop(uss); + } + }; + broadcastIntentLocked(null, null, intent, + null, resultReceiver, 0, null, null, null, + true, false, MY_PID, Process.SYSTEM_UID, userId); + } finally { + Binder.restoreCallingIdentity(ident); + } } } + + return ActivityManager.USER_OP_SUCCESS; + } + + void finishUserStop(UserStartedState uss) { + final int userId = uss.mHandle.getIdentifier(); + boolean stopped; + ArrayList<IStopUserCallback> callbacks; + synchronized (this) { + callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks); + if (uss.mState != UserStartedState.STATE_STOPPING + || mStartedUsers.get(userId) != uss) { + stopped = false; + } else { + stopped = true; + // User can no longer run. + mStartedUsers.remove(userId); + + // Clean up all state and processes associated with the user. + // Kill all the processes for the user. + forceStopUserLocked(userId); + } + } + + for (int i=0; i<callbacks.size(); i++) { + try { + if (stopped) callbacks.get(i).userStopped(userId); + else callbacks.get(i).userStopAborted(userId); + } catch (RemoteException e) { + } + } + } + + @Override + public UserInfo getCurrentUser() { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: getCurrentUser() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + synchronized (this) { + return getUserManagerLocked().getUserInfo(mCurrentUserId); + } + } + + @Override + public boolean isUserRunning(int userId) { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: isUserRunning() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + synchronized (this) { + return isUserRunningLocked(userId); + } + } + + boolean isUserRunningLocked(int userId) { + UserStartedState state = mStartedUsers.get(userId); + return state != null && state.mState != UserStartedState.STATE_STOPPING; + } + + @Override + public void registerUserSwitchObserver(IUserSwitchObserver observer) { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: registerUserSwitchObserver() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + mUserSwitchObservers.register(observer); + } + + @Override + public void unregisterUserSwitchObserver(IUserSwitchObserver observer) { + mUserSwitchObservers.unregister(observer); } private boolean userExists(int userId) { - try { - UserInfo user = AppGlobals.getPackageManager().getUser(userId); - return user != null; - } catch (RemoteException re) { - // Won't happen, in same process + if (userId == 0) { + return true; } + UserManagerService ums = getUserManagerLocked(); + return ums != null ? (ums.getUserInfo(userId) != null) : false; + } - return false; + int[] getUsersLocked() { + UserManagerService ums = getUserManagerLocked(); + return ums != null ? ums.getUserIds() : new int[] { 0 }; + } + + UserManagerService getUserManagerLocked() { + if (mUserManager == null) { + IBinder b = ServiceManager.getService(Context.USER_SERVICE); + mUserManager = (UserManagerService)IUserManager.Stub.asInterface(b); + } + return mUserManager; } private void checkValidCaller(int uid, int userId) { - if (UserId.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0) return; + 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 UserId.getUid(userId, uid); + return UserHandle.getUid(userId, uid); } - private ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { + ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { if (info == null) return null; ApplicationInfo newInfo = new ApplicationInfo(info); newInfo.uid = applyUserId(info.uid, userId); @@ -15272,7 +14322,7 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) { if (aInfo == null - || (userId < 1 && aInfo.applicationInfo.uid < UserId.PER_USER_RANGE)) { + || (userId < 1 && aInfo.applicationInfo.uid < UserHandle.PER_USER_RANGE)) { return aInfo; } @@ -15280,86 +14330,4 @@ public final class ActivityManagerService extends ActivityManagerNative info.applicationInfo = getAppInfoForUser(info.applicationInfo, userId); return info; } - - static class ServiceMap { - - private final SparseArray<HashMap<ComponentName, ServiceRecord>> mServicesByNamePerUser - = new SparseArray<HashMap<ComponentName, ServiceRecord>>(); - private final SparseArray<HashMap<Intent.FilterComparison, ServiceRecord>> - mServicesByIntentPerUser = new SparseArray< - HashMap<Intent.FilterComparison, ServiceRecord>>(); - - ServiceRecord getServiceByName(ComponentName name, int callingUser) { - // TODO: Deal with global services - if (DEBUG_MU) - Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser); - return getServices(callingUser).get(name); - } - - ServiceRecord getServiceByName(ComponentName name) { - return getServiceByName(name, -1); - } - - ServiceRecord getServiceByIntent(Intent.FilterComparison filter, int callingUser) { - // TODO: Deal with global services - if (DEBUG_MU) - Slog.v(TAG_MU, "getServiceByIntent(" + filter + "), callingUser = " + callingUser); - return getServicesByIntent(callingUser).get(filter); - } - - ServiceRecord getServiceByIntent(Intent.FilterComparison filter) { - return getServiceByIntent(filter, -1); - } - - void putServiceByName(ComponentName name, int callingUser, ServiceRecord value) { - // TODO: Deal with global services - getServices(callingUser).put(name, value); - } - - void putServiceByIntent(Intent.FilterComparison filter, int callingUser, - ServiceRecord value) { - // TODO: Deal with global services - getServicesByIntent(callingUser).put(filter, value); - } - - void removeServiceByName(ComponentName name, int callingUser) { - // TODO: Deal with global services - ServiceRecord removed = getServices(callingUser).remove(name); - if (DEBUG_MU) - Slog.v(TAG, "removeServiceByName user=" + callingUser + " name=" + name - + " removed=" + removed); - } - - void removeServiceByIntent(Intent.FilterComparison filter, int callingUser) { - // TODO: Deal with global services - ServiceRecord removed = getServicesByIntent(callingUser).remove(filter); - if (DEBUG_MU) - Slog.v(TAG_MU, "removeServiceByIntent user=" + callingUser + " intent=" + filter - + " removed=" + removed); - } - - Collection<ServiceRecord> getAllServices(int callingUser) { - // TODO: Deal with global services - return getServices(callingUser).values(); - } - - private HashMap<ComponentName, ServiceRecord> getServices(int callingUser) { - HashMap map = mServicesByNamePerUser.get(callingUser); - if (map == null) { - map = new HashMap<ComponentName, ServiceRecord>(); - mServicesByNamePerUser.put(callingUser, map); - } - return map; - } - - private HashMap<Intent.FilterComparison, ServiceRecord> getServicesByIntent( - int callingUser) { - HashMap map = mServicesByIntentPerUser.get(callingUser); - if (map == null) { - map = new HashMap<Intent.FilterComparison, ServiceRecord>(); - mServicesByIntentPerUser.put(callingUser, map); - } - return map; - } - } } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index c40abb7..009fb5d 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -37,7 +37,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserId; +import android.os.UserHandle; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -321,7 +321,7 @@ final class ActivityRecord { appToken = new Token(this); info = aInfo; launchedFromUid = _launchedFromUid; - userId = UserId.getUserId(aInfo.applicationInfo.uid); + userId = UserHandle.getUserId(aInfo.applicationInfo.uid); intent = _intent; shortComponentName = _intent.getComponent().flattenToShortString(); resolvedType = _resolvedType; @@ -333,7 +333,6 @@ final class ActivityRecord { state = ActivityState.INITIALIZING; frontOfTask = false; launchFailed = false; - haveState = false; stopped = false; delayedResume = false; finishing = false; @@ -347,6 +346,11 @@ final class ActivityRecord { idle = false; hasBeenLaunched = false; + // This starts out true, since the initial state of an activity + // is that we have everything, and we shouldn't never consider it + // lacking in state to be removed if it dies. + haveState = true; + if (aInfo != null) { if (aInfo.targetActivity == null || aInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE @@ -614,14 +618,14 @@ final class ActivityRecord { pendingOptions.getStartY()+pendingOptions.getStartHeight())); } break; - case ActivityOptions.ANIM_THUMBNAIL: - case ActivityOptions.ANIM_THUMBNAIL_DELAYED: - boolean delayed = (animationType == ActivityOptions.ANIM_THUMBNAIL_DELAYED); + case ActivityOptions.ANIM_THUMBNAIL_SCALE_UP: + case ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN: + boolean scaleUp = (animationType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP); service.mWindowManager.overridePendingAppTransitionThumb( pendingOptions.getThumbnail(), pendingOptions.getStartX(), pendingOptions.getStartY(), pendingOptions.getOnAnimationStartListener(), - delayed); + scaleUp); if (intent.getSourceBounds() == null) { intent.setSourceBounds(new Rect(pendingOptions.getStartX(), pendingOptions.getStartY(), diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index b9e63b7..29ee0bc 100755 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -52,13 +52,13 @@ import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PowerManager; -import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserId; +import android.os.UserHandle; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.view.Display; import android.view.WindowManagerPolicy; import java.io.IOException; @@ -210,7 +210,10 @@ final class ActivityStack { */ final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible = new ArrayList<IActivityManager.WaitResult>(); - + + final ArrayList<UserStartedState> mStartingUsers + = new ArrayList<UserStartedState>(); + /** * Set when the system is going to sleep, until we have * successfully paused the current activity and released our wake lock. @@ -418,11 +421,10 @@ final class ActivityStack { } final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { - // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); - if (!r.finishing && r != notTop) { + if (!r.finishing && r != notTop && r.userId == mCurrentUser) { return r; } i--; @@ -431,11 +433,10 @@ final class ActivityStack { } final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { - // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); - if (!r.finishing && !r.delayedResume && r != notTop) { + if (!r.finishing && !r.delayedResume && r != notTop && r.userId == mCurrentUser) { return r; } i--; @@ -453,12 +454,12 @@ final class ActivityStack { * @return Returns the HistoryRecord of the next activity on the stack. */ final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { - // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); // Note: the taskId check depends on real taskId fields being non-zero - if (!r.finishing && (token != r.appToken) && (taskId != r.task.taskId)) { + if (!r.finishing && (token != r.appToken) && (taskId != r.task.taskId) + && r.userId == mCurrentUser) { return r; } i--; @@ -500,7 +501,7 @@ final class ActivityStack { TaskRecord cp = null; - final int userId = UserId.getUserId(info.applicationInfo.uid); + final int userId = UserHandle.getUserId(info.applicationInfo.uid); final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { ActivityRecord r = mHistory.get(i); @@ -544,7 +545,7 @@ final class ActivityStack { if (info.targetActivity != null) { cls = new ComponentName(info.packageName, info.targetActivity); } - final int userId = UserId.getUserId(info.applicationInfo.uid); + final int userId = UserHandle.getUserId(info.applicationInfo.uid); final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { @@ -573,37 +574,36 @@ final class ActivityStack { * Move the activities around in the stack to bring a user to the foreground. * @return whether there are any activities for the specified user. */ - final boolean switchUser(int userId) { - synchronized (mService) { - mCurrentUser = userId; + final boolean switchUserLocked(int userId, UserStartedState uss) { + mCurrentUser = userId; + mStartingUsers.add(uss); - // Only one activity? Nothing to do... - if (mHistory.size() < 2) - return false; + // Only one activity? Nothing to do... + if (mHistory.size() < 2) + return false; - boolean haveActivities = false; - // Check if the top activity is from the new user. - ActivityRecord top = mHistory.get(mHistory.size() - 1); - if (top.userId == userId) return true; - // Otherwise, move the user's activities to the top. - int N = mHistory.size(); - int i = 0; - while (i < N) { - ActivityRecord r = mHistory.get(i); - if (r.userId == userId) { - ActivityRecord moveToTop = mHistory.remove(i); - mHistory.add(moveToTop); - // No need to check the top one now - N--; - haveActivities = true; - } else { - i++; - } + boolean haveActivities = false; + // Check if the top activity is from the new user. + ActivityRecord top = mHistory.get(mHistory.size() - 1); + if (top.userId == userId) return true; + // Otherwise, move the user's activities to the top. + int N = mHistory.size(); + int i = 0; + while (i < N) { + ActivityRecord r = mHistory.get(i); + if (r.userId == userId) { + ActivityRecord moveToTop = mHistory.remove(i); + mHistory.add(moveToTop); + // No need to check the top one now + N--; + haveActivities = true; + } else { + i++; } - // Transition from the old top to the new top - resumeTopActivityLocked(top); - return haveActivities; } + // Transition from the old top to the new top + resumeTopActivityLocked(top); + return haveActivities; } final boolean realStartActivityLocked(ActivityRecord r, @@ -723,7 +723,7 @@ final class ActivityStack { + ", giving up", e); mService.appDiedLocked(app, app.pid, app.thread); requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, - "2nd-crash"); + "2nd-crash", false); return false; } @@ -754,8 +754,6 @@ final class ActivityStack { completeResumeLocked(r); checkReadyForSleepLocked(); if (DEBUG_SAVED_STATE) Slog.i(TAG, "Launch completed; removing icicle of " + r.icicle); - r.icicle = null; - r.haveState = false; } else { // This activity is not starting in the resumed state... which // should look like we asked it to pause+stop (but remain visible), @@ -902,7 +900,7 @@ final class ActivityStack { mService.notifyAll(); } } - + public final Bitmap screenshotActivities(ActivityRecord who) { if (who.noDisplay) { return null; @@ -919,7 +917,8 @@ final class ActivityStack { } if (w > 0) { - return mService.mWindowManager.screenshotApplications(who.appToken, w, h); + return mService.mWindowManager.screenshotApplications(who.appToken, + Display.DEFAULT_DISPLAY, w, h); } return null; } @@ -1008,7 +1007,21 @@ final class ActivityStack { resumeTopActivityLocked(null); } } - + + final void activityResumed(IBinder token) { + ActivityRecord r = null; + + synchronized (mService) { + int index = indexOfTokenLocked(token); + if (index >= 0) { + r = mHistory.get(index); + if (DEBUG_SAVED_STATE) Slog.i(TAG, "Resumed activity; dropping state of: " + r); + r.icicle = null; + r.haveState = false; + } + } + } + final void activityPaused(IBinder token, boolean timeout) { if (DEBUG_PAUSE) Slog.v( TAG, "Activity paused: token=" + token + ", timeout=" + timeout); @@ -1089,7 +1102,7 @@ final class ActivityStack { if (prev != null) { if (prev.finishing) { if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev); - prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE); + prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false); } else if (prev.app != null) { if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev); if (prev.waitingVisible) { @@ -1395,7 +1408,7 @@ final class ActivityStack { // Launcher... if (mMainStack) { ActivityOptions.abort(options); - return mService.startHomeActivityLocked(0); + return mService.startHomeActivityLocked(mCurrentUser); } } @@ -1425,7 +1438,16 @@ final class ActivityStack { ActivityOptions.abort(options); return false; } - + + // Make sure that the user who owns this activity is started. If not, + // we will just leave it as is because someone should be bringing + // another user's activities to the top of the stack. + if (mService.mStartedUsers.get(next.userId) == null) { + Slog.w(TAG, "Skipping resume of top activity " + next + + ": user " + next.userId + " is stopped"); + return false; + } + // The activity may be waiting for stop, but that is no longer // appropriate for it. mStoppingActivities.remove(next); @@ -1477,6 +1499,15 @@ final class ActivityStack { // can be resumed... if (mResumedActivity != null) { if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to start pausing"); + // At this point we want to put the upcoming activity's process + // at the top of the LRU list, since we know we will be needing it + // very soon and it would be a waste to let it get killed if it + // happens to be sitting towards the end. + 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); + } startPausingLocked(userLeaving, false); return true; } @@ -1492,7 +1523,7 @@ final class ActivityStack { Slog.d(TAG, "no-history finish of " + last + " on new resume"); } requestFinishActivityLocked(last.appToken, Activity.RESULT_CANCELED, null, - "no-history"); + "no-history", false); } } @@ -1714,14 +1745,9 @@ final class ActivityStack { // activity and try the next one. Slog.w(TAG, "Exception thrown during resume of " + next, e); requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED, null, - "resume-exception"); + "resume-exception", true); return true; } - - // Didn't need to use the icicle, and it is now out of date. - if (DEBUG_SAVED_STATE) Slog.i(TAG, "Resumed activity; didn't need icicle of: " + next); - next.icicle = null; - next.haveState = false; next.stopped = false; } else { @@ -2068,7 +2094,7 @@ final class ActivityStack { continue; } if (finishActivityLocked(p, srcPos, - Activity.RESULT_CANCELED, null, "reset")) { + Activity.RESULT_CANCELED, null, "reset", false)) { replyChainEnd--; srcPos--; } @@ -2131,7 +2157,7 @@ final class ActivityStack { continue; } if (finishActivityLocked(p, srcPos, - Activity.RESULT_CANCELED, null, "reset")) { + Activity.RESULT_CANCELED, null, "reset", false)) { taskTopI--; lastReparentPos--; replyChainEnd--; @@ -2188,7 +2214,7 @@ final class ActivityStack { } if (p.intent.getComponent().equals(target.intent.getComponent())) { if (finishActivityLocked(p, j, - Activity.RESULT_CANCELED, null, "replace")) { + Activity.RESULT_CANCELED, null, "replace", false)) { taskTopI--; lastReparentPos--; } @@ -2258,7 +2284,7 @@ final class ActivityStack { continue; } if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { + null, "clear", false)) { i--; } } @@ -2272,7 +2298,7 @@ final class ActivityStack { int index = indexOfTokenLocked(ret.appToken); if (index >= 0) { finishActivityLocked(ret, index, Activity.RESULT_CANCELED, - null, "clear"); + null, "clear", false); } return null; } @@ -2301,7 +2327,7 @@ final class ActivityStack { continue; } if (!finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { + null, "clear", false)) { i++; } } @@ -2404,7 +2430,7 @@ final class ActivityStack { } if (err == ActivityManager.START_SUCCESS) { - final int userId = aInfo != null ? UserId.getUserId(aInfo.applicationInfo.uid) : 0; + final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; Slog.i(TAG, "START {" + intent.toShortString(true, true, true, false) + " u=" + userId + "} from pid " + (callerApp != null ? callerApp.pid : callingPid)); } @@ -3013,7 +3039,8 @@ final class ActivityStack { // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, profileFile, profileFd, userId); - if (aInfo != null && mService.isSingleton(aInfo.processName, aInfo.applicationInfo)) { + if (aInfo != null && (aInfo.flags & ActivityInfo.FLAG_MULTIPROCESS) == 0 + && mService.isSingleton(aInfo.processName, aInfo.applicationInfo, null, 0)) { userId = 0; } aInfo = mService.getActivityInfoForUser(aInfo, userId); @@ -3062,7 +3089,7 @@ final class ActivityStack { IIntentSender target = mService.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, "android", - realCallingUid, null, null, 0, new Intent[] { intent }, + realCallingUid, userId, null, null, 0, new Intent[] { intent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); @@ -3306,7 +3333,7 @@ final class ActivityStack { Slog.d(TAG, "no-history finish of " + r); } requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, - "no-history"); + "no-history", false); } else { if (DEBUG_STATES) Slog.d(TAG, "Not finishing noHistory " + r + " on stop because we're just sleeping"); @@ -3412,6 +3439,7 @@ final class ActivityStack { ArrayList<ActivityRecord> stops = null; ArrayList<ActivityRecord> finishes = null; ArrayList<ActivityRecord> thumbnails = null; + ArrayList<UserStartedState> startingUsers = null; int NS = 0; int NF = 0; int NT = 0; @@ -3493,6 +3521,10 @@ final class ActivityStack { booting = mService.mBooting; mService.mBooting = false; } + if (mStartingUsers.size() > 0) { + startingUsers = new ArrayList<UserStartedState>(mStartingUsers); + mStartingUsers.clear(); + } } int i; @@ -3513,7 +3545,7 @@ final class ActivityStack { ActivityRecord r = (ActivityRecord)stops.get(i); synchronized (mService) { if (r.finishing) { - finishCurrentActivityLocked(r, FINISH_IMMEDIATELY); + finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false); } else { stopActivityLocked(r); } @@ -3537,6 +3569,10 @@ final class ActivityStack { if (booting) { mService.finishBooting(); + } else if (startingUsers != null) { + for (i=0; i<startingUsers.size(); i++) { + mService.finishUserSwitch(startingUsers.get(i)); + } } mService.trimApplications(); @@ -3559,7 +3595,7 @@ final class ActivityStack { * some reason it is being left as-is. */ final boolean requestFinishActivityLocked(IBinder token, int resultCode, - Intent resultData, String reason) { + Intent resultData, String reason, boolean oomAdj) { int index = indexOfTokenLocked(token); if (DEBUG_RESULTS || DEBUG_STATES) Slog.v( TAG, "Finishing activity @" + index + ": token=" + token @@ -3570,7 +3606,7 @@ final class ActivityStack { } ActivityRecord r = mHistory.get(index); - finishActivityLocked(r, index, resultCode, resultData, reason); + finishActivityLocked(r, index, resultCode, resultData, reason, oomAdj); return true; } @@ -3587,10 +3623,11 @@ final class ActivityStack { if ((r.resultWho == null && resultWho == null) || (r.resultWho != null && r.resultWho.equals(resultWho))) { finishActivityLocked(r, i, - Activity.RESULT_CANCELED, null, "request-sub"); + Activity.RESULT_CANCELED, null, "request-sub", false); } } } + mService.updateOomAdjLocked(); } final boolean finishActivityAffinityLocked(IBinder token) { @@ -3613,7 +3650,8 @@ final class ActivityStack { if (cur.taskAffinity != null && !cur.taskAffinity.equals(r.taskAffinity)) { break; } - finishActivityLocked(cur, index, Activity.RESULT_CANCELED, null, "request-affinity"); + finishActivityLocked(cur, index, Activity.RESULT_CANCELED, null, + "request-affinity", true); index--; } return true; @@ -3651,16 +3689,16 @@ final class ActivityStack { * list, or false if it is still in the list and will be removed later. */ final boolean finishActivityLocked(ActivityRecord r, int index, - int resultCode, Intent resultData, String reason) { - return finishActivityLocked(r, index, resultCode, resultData, reason, false); + int resultCode, Intent resultData, String reason, boolean oomAdj) { + return finishActivityLocked(r, index, resultCode, resultData, reason, false, oomAdj); } /** * @return Returns true if this activity has been removed from the history * list, or false if it is still in the list and will be removed later. */ - final boolean finishActivityLocked(ActivityRecord r, int index, - int resultCode, Intent resultData, String reason, boolean immediate) { + final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode, + Intent resultData, String reason, boolean immediate, boolean oomAdj) { if (r.finishing) { Slog.w(TAG, "Duplicate finish request for " + r); return false; @@ -3704,7 +3742,7 @@ final class ActivityStack { if (immediate) { return finishCurrentActivityLocked(r, index, - FINISH_IMMEDIATELY) == null; + FINISH_IMMEDIATELY, oomAdj) == null; } else if (mResumedActivity == r) { boolean endTask = index <= 0 || (mHistory.get(index-1)).task != r.task; @@ -3728,7 +3766,7 @@ final class ActivityStack { // it is done pausing; else we can just directly finish it here. if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r); return finishCurrentActivityLocked(r, index, - FINISH_AFTER_PAUSE) == null; + FINISH_AFTER_PAUSE, oomAdj) == null; } else { if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r); } @@ -3741,17 +3779,17 @@ final class ActivityStack { private static final int FINISH_AFTER_VISIBLE = 2; private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, - int mode) { + int mode, boolean oomAdj) { final int index = indexOfActivityLocked(r); if (index < 0) { return null; } - return finishCurrentActivityLocked(r, index, mode); + return finishCurrentActivityLocked(r, index, mode, oomAdj); } private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, - int index, int mode) { + int index, int mode, boolean oomAdj) { // First things first: if this activity is currently visible, // and the resumed activity is not yet visible, then hold off on // finishing until the resumed one becomes visible. @@ -3770,7 +3808,9 @@ final class ActivityStack { if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r + " (finish requested)"); r.state = ActivityState.STOPPING; - mService.updateOomAdjLocked(); + if (oomAdj) { + mService.updateOomAdjLocked(); + } return r; } @@ -3790,7 +3830,8 @@ final class ActivityStack { || prevState == ActivityState.INITIALIZING) { // If this activity is already stopped, we can just finish // it right now. - boolean activityRemoved = destroyActivityLocked(r, true, true, "finish-imm"); + boolean activityRemoved = destroyActivityLocked(r, true, + oomAdj, "finish-imm"); if (activityRemoved) { resumeTopActivityLocked(null); } @@ -3901,7 +3942,7 @@ final class ActivityStack { Iterator<ConnectionRecord> it = r.connections.iterator(); while (it.hasNext()) { ConnectionRecord c = it.next(); - mService.removeConnectionLocked(c, null, r); + mService.mServices.removeConnectionLocked(c, null, r); } r.connections = null; } @@ -3982,9 +4023,8 @@ final class ActivityStack { ActivityManagerService.CANCEL_HEAVY_NOTIFICATION_MSG); } if (r.app.activities.size() == 0) { - // No longer have activities, so update location in - // LRU list. - mService.updateLruProcessLocked(r.app, oomAdj, false); + // No longer have activities, so update oom adj. + mService.updateOomAdjLocked(); } } diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 8f797ec..ab20208 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -346,18 +346,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub { mStats.noteFullWifiLockReleasedLocked(uid); } } - - public void noteScanWifiLockAcquired(int uid) { + + public void noteWifiScanStarted(int uid) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteScanWifiLockAcquiredLocked(uid); + mStats.noteWifiScanStartedLocked(uid); } } - - public void noteScanWifiLockReleased(int uid) { + + public void noteWifiScanStopped(int uid) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteScanWifiLockReleasedLocked(uid); + mStats.noteWifiScanStoppedLocked(uid); } } @@ -389,17 +389,17 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } - public void noteScanWifiLockAcquiredFromSource(WorkSource ws) { + public void noteWifiScanStartedFromSource(WorkSource ws) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteScanWifiLockAcquiredFromSourceLocked(ws); + mStats.noteWifiScanStartedFromSourceLocked(ws); } } - public void noteScanWifiLockReleasedFromSource(WorkSource ws) { + public void noteWifiScanStoppedFromSource(WorkSource ws) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteScanWifiLockReleasedFromSourceLocked(ws); + mStats.noteWifiScanStoppedFromSourceLocked(ws); } } diff --git a/services/java/com/android/server/am/BroadcastFilter.java b/services/java/com/android/server/am/BroadcastFilter.java index b49bc22..07440b5 100644 --- a/services/java/com/android/server/am/BroadcastFilter.java +++ b/services/java/com/android/server/am/BroadcastFilter.java @@ -27,13 +27,17 @@ class BroadcastFilter extends IntentFilter { final ReceiverList receiverList; final String packageName; final String requiredPermission; + final int owningUid; + final int owningUserId; BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList, - String _packageName, String _requiredPermission) { + String _packageName, String _requiredPermission, int _owningUid, int _userId) { super(_filter); receiverList = _receiverList; packageName = _packageName; requiredPermission = _requiredPermission; + owningUid = _owningUid; + owningUserId = _userId; } public void dump(PrintWriter pw, String prefix) { diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index 47b8c0a..b0af081 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -20,12 +20,15 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import android.app.ActivityManager; import android.app.AppGlobals; import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -33,7 +36,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserId; +import android.os.UserHandle; import android.util.EventLog; import android.util.Slog; @@ -219,7 +222,7 @@ public class BroadcastQueue { mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver, mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo), - r.resultCode, r.resultData, r.resultExtras, r.ordered); + r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId); if (DEBUG_BROADCAST) Slog.v(TAG, "Process cur broadcast " + r + " DELIVERED for app " + app); started = true; @@ -249,7 +252,7 @@ public class BroadcastQueue { finishReceiverLocked(br, br.resultCode, br.resultData, br.resultExtras, br.resultAbort, true); scheduleBroadcastsLocked(); - // We need to reset the state if we fails to start the receiver. + // We need to reset the state if we failed to start the receiver. br.state = BroadcastRecord.IDLE; throw new RuntimeException(e.getMessage()); } @@ -354,15 +357,16 @@ public class BroadcastQueue { private static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, Intent intent, int resultCode, String data, Bundle extras, - boolean ordered, boolean sticky) throws RemoteException { + boolean ordered, boolean sticky, int sendingUser) throws RemoteException { // Send the intent to the receiver asynchronously using one-way binder calls. if (app != null && app.thread != null) { // If we have an app thread, do the call through that so it is // correctly ordered with other one-way calls. app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, - data, extras, ordered, sticky); + data, extras, ordered, sticky, sendingUser); } else { - receiver.performReceive(intent, resultCode, data, extras, ordered, sticky); + receiver.performReceive(intent, resultCode, data, extras, ordered, + sticky, sendingUser); } } @@ -382,7 +386,7 @@ public class BroadcastQueue { skip = true; } } - if (r.requiredPermission != null) { + if (!skip && r.requiredPermission != null) { int perm = mService.checkComponentPermission(r.requiredPermission, filter.receiverList.pid, filter.receiverList.uid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { @@ -425,8 +429,8 @@ public class BroadcastQueue { + " (seq=" + seq + "): " + r); } performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, - new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, r.ordered, r.initialSticky); + new Intent(r.intent), r.resultCode, r.resultData, + r.resultExtras, r.ordered, r.initialSticky, r.userId); if (ordered) { r.state = BroadcastRecord.CALL_DONE_RECEIVE; } @@ -576,7 +580,7 @@ public class BroadcastQueue { } performReceiveLocked(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, false, false); + r.resultData, r.resultExtras, false, false, r.userId); // Set this to null so that the reference // (local and remote) isnt kept in the mBroadcastHistory. r.resultTo = null; @@ -649,6 +653,9 @@ public class BroadcastQueue { ResolveInfo info = (ResolveInfo)nextReceiver; + ComponentName component = new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); boolean skip = false; int perm = mService.checkComponentPermission(info.activityInfo.permission, @@ -661,16 +668,14 @@ public class BroadcastQueue { + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ")" + " is not exported from uid " + info.activityInfo.applicationInfo.uid - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); + + " due to receiver " + component.flattenToShortString()); } else { Slog.w(TAG, "Permission Denial: broadcasting " + r.intent.toString() + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ")" + " requires " + info.activityInfo.permission - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); + + " due to receiver " + component.flattenToShortString()); } skip = true; } @@ -686,13 +691,33 @@ public class BroadcastQueue { if (perm != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Permission Denial: receiving " + r.intent + " to " - + info.activityInfo.applicationInfo.packageName + + component.flattenToShortString() + " requires " + r.requiredPermission + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")"); skip = true; } } + boolean isSingleton = false; + try { + isSingleton = mService.isSingleton(info.activityInfo.processName, + info.activityInfo.applicationInfo, + info.activityInfo.name, info.activityInfo.flags); + } catch (SecurityException e) { + Slog.w(TAG, e.getMessage()); + skip = true; + } + if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) { + if (ActivityManager.checkUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + info.activityInfo.applicationInfo.uid) + != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString() + + " requests FLAG_SINGLE_USER, but app does not hold " + + android.Manifest.permission.INTERACT_ACROSS_USERS); + skip = true; + } + } if (r.curApp != null && r.curApp.crashing) { // If the target process is crashing, just skip it. if (DEBUG_BROADCAST) Slog.v(TAG, @@ -715,17 +740,12 @@ public class BroadcastQueue { r.state = BroadcastRecord.APP_RECEIVE; String targetProcess = info.activityInfo.processName; - r.curComponent = new ComponentName( - info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - if (r.callingUid != Process.SYSTEM_UID) { - boolean isSingleton = mService.isSingleton(info.activityInfo.processName, - info.activityInfo.applicationInfo); - int targetUserId = isSingleton ? 0 : UserId.getUserId(r.callingUid); - info.activityInfo = mService.getActivityInfoForUser(info.activityInfo,targetUserId); + r.curComponent = component; + if (r.callingUid != Process.SYSTEM_UID && isSingleton) { + info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0); } r.curReceiver = info.activityInfo; - if (DEBUG_MU && r.callingUid > UserId.PER_USER_RANGE) { + if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) { Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, " + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = " + info.activityInfo.applicationInfo.uid); @@ -734,7 +754,7 @@ public class BroadcastQueue { // Broadcast is being executed, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( - r.curComponent.getPackageName(), false, UserId.getUserId(r.callingUid)); + r.curComponent.getPackageName(), false, UserHandle.getUserId(r.callingUid)); } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " @@ -945,12 +965,12 @@ public class BroadcastQueue { if (!printed) { if (needSep) { pw.println(); - needSep = false; } + needSep = true; printed = true; pw.println(" Active broadcasts [" + mQueueName + "]:"); } - pw.println(" Broadcast #" + i + ":"); + pw.println(" Active Broadcast " + mQueueName + " #" + i + ":"); br.dump(pw, " "); } printed = false; @@ -965,9 +985,10 @@ public class BroadcastQueue { pw.println(); } needSep = true; + printed = true; pw.println(" Active ordered broadcasts [" + mQueueName + "]:"); } - pw.println(" Ordered Broadcast #" + i + ":"); + pw.println(" Active Ordered Broadcast " + mQueueName + " #" + i + ":"); mOrderedBroadcasts.get(i).dump(pw, " "); } if (dumpPackage == null || (mPendingBroadcast != null @@ -1003,7 +1024,8 @@ public class BroadcastQueue { printed = true; } if (dumpAll) { - pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); + pw.print(" Historical Broadcast " + mQueueName + " #"); + pw.print(i); pw.println(":"); r.dump(pw, " "); } else { if (i >= 50) { diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index dd560fc..ca6d5f7 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -44,6 +44,7 @@ class BroadcastRecord extends Binder { final boolean ordered; // serialize the send to receivers? final boolean sticky; // originated from existing sticky data? final boolean initialSticky; // initial broadcast from register to sticky? + final int userId; // user id this broadcast was for final String requiredPermission; // a permission the caller has required final List receivers; // contains BroadcastFilter and ResolveInfo IIntentReceiver resultTo; // who receives final result if non-null @@ -79,7 +80,7 @@ class BroadcastRecord extends Binder { void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); - pw.print(prefix); pw.println(this); + pw.print(prefix); pw.print(this); pw.print(" to user "); pw.println(userId); pw.print(prefix); pw.println(intent); if (sticky) { Bundle bundle = intent.getExtras(); @@ -140,14 +141,15 @@ class BroadcastRecord extends Binder { pw.println(curReceiver.applicationInfo.sourceDir); } } - String stateStr = " (?)"; - switch (state) { - case IDLE: stateStr=" (IDLE)"; break; - case APP_RECEIVE: stateStr=" (APP_RECEIVE)"; break; - case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break; - case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break; + if (state != IDLE) { + String stateStr = " (?)"; + switch (state) { + case APP_RECEIVE: stateStr=" (APP_RECEIVE)"; break; + case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break; + case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break; + } + pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr); } - pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr); final int N = receivers != null ? receivers.size() : 0; String p2 = prefix + " "; PrintWriterPrinter printer = new PrintWriterPrinter(pw); @@ -167,7 +169,8 @@ class BroadcastRecord extends Binder { int _callingPid, int _callingUid, String _requiredPermission, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, - boolean _sticky, boolean _initialSticky) { + boolean _sticky, boolean _initialSticky, + int _userId) { queue = _queue; intent = _intent; callerApp = _callerApp; @@ -183,6 +186,7 @@ class BroadcastRecord extends Binder { ordered = _serialized; sticky = _sticky; initialSticky = _initialSticky; + userId = _userId; nextReceiver = 0; state = IDLE; } diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java index 3ba3fbb..3a6492e 100644 --- a/services/java/com/android/server/am/CompatModePackages.java +++ b/services/java/com/android/server/am/CompatModePackages.java @@ -4,7 +4,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -12,7 +11,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import com.android.internal.os.AtomicFile; import com.android.internal.util.FastXmlSerializer; import android.app.ActivityManager; @@ -24,6 +22,7 @@ import android.content.res.CompatibilityInfo; import android.os.Handler; import android.os.Message; import android.os.RemoteException; +import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java index 0106114..5b3ff8d 100644 --- a/services/java/com/android/server/am/ConnectionRecord.java +++ b/services/java/com/android/server/am/ConnectionRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import android.app.IServiceConnection; import android.app.PendingIntent; +import android.content.Context; import java.io.PrintWriter; @@ -62,6 +63,33 @@ class ConnectionRecord { sb.append("ConnectionRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); + if ((flags&Context.BIND_AUTO_CREATE) != 0) { + sb.append("CR "); + } + if ((flags&Context.BIND_DEBUG_UNBIND) != 0) { + sb.append("DBG "); + } + if ((flags&Context.BIND_NOT_FOREGROUND) != 0) { + sb.append("NOTFG "); + } + if ((flags&Context.BIND_ABOVE_CLIENT) != 0) { + sb.append("ABCLT "); + } + if ((flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { + sb.append("OOM "); + } + if ((flags&Context.BIND_WAIVE_PRIORITY) != 0) { + sb.append("WPRI "); + } + if ((flags&Context.BIND_IMPORTANT) != 0) { + sb.append("IMP "); + } + if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { + sb.append("ACT "); + } + if ((flags&Context.BIND_NOT_VISIBLE) != 0) { + sb.append("NOTVIS "); + } if (serviceDead) { sb.append("DEAD "); } diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java index fb21b06..de306b5 100644 --- a/services/java/com/android/server/am/ContentProviderRecord.java +++ b/services/java/com/android/server/am/ContentProviderRecord.java @@ -38,6 +38,7 @@ class ContentProviderRecord { final int uid; final ApplicationInfo appInfo; final ComponentName name; + final boolean singleton; public IContentProvider provider; public boolean noReleaseNeeded; // All attached clients @@ -54,12 +55,13 @@ class ContentProviderRecord { String shortStringName; public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info, - ApplicationInfo ai, ComponentName _name) { + ApplicationInfo ai, ComponentName _name, boolean _singleton) { service = _service; info = _info; uid = ai.uid; appInfo = ai; name = _name; + singleton = _singleton; noReleaseNeeded = uid == 0 || uid == Process.SYSTEM_UID; } @@ -69,6 +71,7 @@ class ContentProviderRecord { uid = cpr.uid; appInfo = cpr.appInfo; name = cpr.name; + singleton = cpr.singleton; noReleaseNeeded = cpr.noReleaseNeeded; } @@ -82,7 +85,7 @@ class ContentProviderRecord { public boolean canRunHere(ProcessRecord app) { return (info.multiprocess || info.processName.equals(app.processName)) - && (uid == Process.SYSTEM_UID || uid == app.info.uid); + && uid == app.info.uid; } public void addExternalProcessHandleLocked(IBinder token) { @@ -150,6 +153,9 @@ class ContentProviderRecord { pw.print(prefix); pw.print("uid="); pw.print(uid); pw.print(" provider="); pw.println(provider); } + if (singleton) { + pw.print(prefix); pw.print("singleton="); pw.println(singleton); + } pw.print(prefix); pw.print("authority="); pw.println(info.authority); if (full) { if (info.isSyncable || info.multiprocess || info.initOrder != 0) { diff --git a/services/java/com/android/server/am/IntentBindRecord.java b/services/java/com/android/server/am/IntentBindRecord.java index c94f714..0a92964 100644 --- a/services/java/com/android/server/am/IntentBindRecord.java +++ b/services/java/com/android/server/am/IntentBindRecord.java @@ -16,6 +16,7 @@ package com.android.server.am; +import android.content.Context; import android.content.Intent; import android.os.IBinder; @@ -78,6 +79,20 @@ class IntentBindRecord { intent = _intent; } + int collectFlags() { + int flags = 0; + if (apps.size() > 0) { + for (AppBindRecord app : apps.values()) { + if (app.connections.size() > 0) { + for (ConnectionRecord conn : app.connections) { + flags |= conn.flags; + } + } + } + } + return flags; + } + public String toString() { if (stringName != null) { return stringName; @@ -86,6 +101,9 @@ class IntentBindRecord { sb.append("IntentBindRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); + if ((collectFlags()&Context.BIND_AUTO_CREATE) != 0) { + sb.append("CR "); + } sb.append(service.shortName); sb.append(':'); if (intent != null) { diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index ad15da1..c61f13c 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -25,7 +25,6 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.os.UserId; import android.util.Slog; import java.io.PrintWriter; @@ -54,11 +53,12 @@ class PendingIntentRecord extends IIntentSender.Stub { String[] allResolvedTypes; final int flags; final int hashCode; + final int userId; private static final int ODD_PRIME_NUMBER = 37; Key(int _t, String _p, ActivityRecord _a, String _w, - int _r, Intent[] _i, String[] _it, int _f, Bundle _o) { + int _r, Intent[] _i, String[] _it, int _f, Bundle _o, int _userId) { type = _t; packageName = _p; activity = _a; @@ -70,10 +70,12 @@ class PendingIntentRecord extends IIntentSender.Stub { allResolvedTypes = _it; flags = _f; options = _o; - + userId = _userId; + int hash = 23; hash = (ODD_PRIME_NUMBER*hash) + _f; hash = (ODD_PRIME_NUMBER*hash) + _r; + hash = (ODD_PRIME_NUMBER*hash) + _userId; if (_w != null) { hash = (ODD_PRIME_NUMBER*hash) + _w.hashCode(); } @@ -102,6 +104,9 @@ class PendingIntentRecord extends IIntentSender.Stub { if (type != other.type) { return false; } + if (userId != other.userId){ + return false; + } if (!packageName.equals(other.packageName)) { return false; } @@ -156,7 +161,7 @@ class PendingIntentRecord extends IIntentSender.Stub { + " intent=" + (requestIntent != null ? requestIntent.toShortString(false, true, false, false) : "<null>") - + " flags=0x" + Integer.toHexString(flags) + "}"; + + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}"; } String typeName() { @@ -237,11 +242,10 @@ class PendingIntentRecord extends IIntentSender.Stub { allIntents[allIntents.length-1] = finalIntent; allResolvedTypes[allResolvedTypes.length-1] = resolvedType; owner.startActivitiesInPackage(uid, allIntents, - allResolvedTypes, resultTo, options); + allResolvedTypes, resultTo, options, key.userId); } else { - owner.startActivityInPackage(uid, - finalIntent, resolvedType, - resultTo, resultWho, requestCode, 0, options); + owner.startActivityInPackage(uid, finalIntent, resolvedType, + resultTo, resultWho, requestCode, 0, options, key.userId); } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, @@ -259,8 +263,7 @@ class PendingIntentRecord extends IIntentSender.Stub { owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType, finishedReceiver, code, null, null, - requiredPermission, (finishedReceiver != null), false, UserId - .getUserId(uid)); + requiredPermission, (finishedReceiver != null), false, key.userId); sendFinish = false; } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, @@ -270,7 +273,7 @@ class PendingIntentRecord extends IIntentSender.Stub { case ActivityManager.INTENT_SENDER_SERVICE: try { owner.startServiceInPackage(uid, - finalIntent, resolvedType); + finalIntent, resolvedType, key.userId); } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startService intent", e); @@ -281,7 +284,7 @@ class PendingIntentRecord extends IIntentSender.Stub { if (sendFinish) { try { finishedReceiver.performReceive(new Intent(finalIntent), 0, - null, null, false, false); + null, null, false, false, key.userId); } catch (RemoteException e) { } } diff --git a/services/java/com/android/server/am/ProcessList.java b/services/java/com/android/server/am/ProcessList.java index af7b314..afc060e 100644 --- a/services/java/com/android/server/am/ProcessList.java +++ b/services/java/com/android/server/am/ProcessList.java @@ -24,6 +24,7 @@ import com.android.server.wm.WindowManagerService; import android.graphics.Point; import android.util.Slog; +import android.view.Display; /** * Activity manager code dealing with processes. @@ -146,7 +147,7 @@ class ProcessList { void applyDisplaySize(WindowManagerService wm) { if (!mHaveDisplaySize) { Point p = new Point(); - wm.getInitialDisplaySize(p); + wm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, p); if (p.x != 0 && p.y != 0) { updateOomLevels(p.x, p.y, true); mHaveDisplaySize = true; diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index cba9480..d372422 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -30,7 +30,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Process; import android.os.SystemClock; -import android.os.UserId; +import android.os.UserHandle; import android.util.PrintWriterPrinter; import android.util.TimeUtils; @@ -61,6 +61,7 @@ class ProcessRecord { long lruWeight; // Weight for ordering in LRU list int maxAdj; // Maximum OOM adjustment for this process int hiddenAdj; // If hidden, this is the adjustment to use + int emptyAdj; // If empty, this is the adjustment to use int curRawAdj; // Current OOM unlimited adjustment for this process int setRawAdj; // Last set OOM unlimited adjustment for this process int nonStoppingAdj; // Adjustment not counting any stopping activities @@ -73,6 +74,7 @@ class ProcessRecord { boolean serviceb; // Process currently is on the service B list boolean keeping; // Actively running code so don't kill due to that? boolean setIsForeground; // Running foreground UI when last set? + boolean hasActivities; // Are there any activities running in this process? boolean foregroundServices; // Running any services that are foreground? boolean foregroundActivities; // Running any activities that are foreground? boolean systemNoUi; // This is a system process, but not currently showing UI. @@ -199,6 +201,7 @@ class ProcessRecord { pw.print(" empty="); pw.println(empty); pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj); pw.print(" hidden="); pw.print(hiddenAdj); + pw.print(" empty="); pw.print(emptyAdj); pw.print(" curRaw="); pw.print(curRawAdj); pw.print(" setRaw="); pw.print(setRawAdj); pw.print(" nonStopping="); pw.print(nonStoppingAdj); @@ -215,7 +218,9 @@ class ProcessRecord { pw.print(" foregroundServices="); pw.print(foregroundServices); pw.print(" forcingToForeground="); pw.println(forcingToForeground); pw.print(prefix); pw.print("persistent="); pw.print(persistent); - pw.print(" removed="); pw.println(removed); + pw.print(" removed="); pw.print(removed); + pw.print(" hasActivities="); pw.print(hasActivities); + pw.print(" foregroundActivities="); pw.println(foregroundActivities); pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq); pw.print(" lruSeq="); pw.println(lruSeq); if (!keeping) { @@ -308,12 +313,12 @@ class ProcessRecord { info = _info; isolated = _info.uid != _uid; uid = _uid; - userId = UserId.getUserId(_uid); + userId = UserHandle.getUserId(_uid); processName = _processName; pkgList.add(_info.packageName); thread = _thread; maxAdj = ProcessList.HIDDEN_APP_MAX_ADJ; - hiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ; + hiddenAdj = emptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ; curRawAdj = setRawAdj = -100; curAdj = setAdj = -100; persistent = false; @@ -391,7 +396,7 @@ class ProcessRecord { sb.append(info.uid%Process.FIRST_APPLICATION_UID); if (uid != info.uid) { sb.append('i'); - sb.append(UserId.getAppId(uid) - Process.FIRST_ISOLATED_UID); + sb.append(UserHandle.getAppId(uid) - Process.FIRST_ISOLATED_UID); } } } diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java index e4608a2..9dbf5f5 100644 --- a/services/java/com/android/server/am/ProviderMap.java +++ b/services/java/com/android/server/am/ProviderMap.java @@ -18,9 +18,8 @@ package com.android.server.am; import android.content.ComponentName; import android.os.Binder; -import android.os.Process; import android.os.RemoteException; -import android.os.UserId; +import android.os.UserHandle; import android.util.Slog; import android.util.SparseArray; @@ -31,8 +30,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; /** * Keeps track of content providers by authority (name) and class. It separates the mapping by @@ -44,9 +41,11 @@ public class ProviderMap { private static final boolean DBG = false; - private final HashMap<String, ContentProviderRecord> mGlobalByName + private final ActivityManagerService mAm; + + private final HashMap<String, ContentProviderRecord> mSingletonByName = new HashMap<String, ContentProviderRecord>(); - private final HashMap<ComponentName, ContentProviderRecord> mGlobalByClass + private final HashMap<ComponentName, ContentProviderRecord> mSingletonByClass = new HashMap<ComponentName, ContentProviderRecord>(); private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser @@ -54,6 +53,10 @@ public class ProviderMap { private final SparseArray<HashMap<ComponentName, ContentProviderRecord>> mProvidersByClassPerUser = new SparseArray<HashMap<ComponentName, ContentProviderRecord>>(); + ProviderMap(ActivityManagerService am) { + mAm = am; + } + ContentProviderRecord getProviderByName(String name) { return getProviderByName(name, -1); } @@ -63,7 +66,7 @@ public class ProviderMap { Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid()); } // Try to find it in the global list - ContentProviderRecord record = mGlobalByName.get(name); + ContentProviderRecord record = mSingletonByName.get(name); if (record != null) { return record; } @@ -81,7 +84,7 @@ public class ProviderMap { Slog.i(TAG, "getProviderByClass: " + name + ", callingUid = " + Binder.getCallingUid()); } // Try to find it in the global list - ContentProviderRecord record = mGlobalByClass.get(name); + ContentProviderRecord record = mSingletonByClass.get(name); if (record != null) { return record; } @@ -95,10 +98,10 @@ public class ProviderMap { Slog.i(TAG, "putProviderByName: " + name + " , callingUid = " + Binder.getCallingUid() + ", record uid = " + record.appInfo.uid); } - if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) { - mGlobalByName.put(name, record); + if (record.singleton) { + mSingletonByName.put(name, record); } else { - final int userId = UserId.getUserId(record.appInfo.uid); + final int userId = UserHandle.getUserId(record.appInfo.uid); getProvidersByName(userId).put(name, record); } } @@ -108,56 +111,54 @@ public class ProviderMap { Slog.i(TAG, "putProviderByClass: " + name + " , callingUid = " + Binder.getCallingUid() + ", record uid = " + record.appInfo.uid); } - if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) { - mGlobalByClass.put(name, record); + if (record.singleton) { + mSingletonByClass.put(name, record); } else { - final int userId = UserId.getUserId(record.appInfo.uid); + final int userId = UserHandle.getUserId(record.appInfo.uid); getProvidersByClass(userId).put(name, record); } } - void removeProviderByName(String name, int optionalUserId) { - if (mGlobalByName.containsKey(name)) { + void removeProviderByName(String name, int userId) { + if (mSingletonByName.containsKey(name)) { if (DBG) Slog.i(TAG, "Removing from globalByName name=" + name); - mGlobalByName.remove(name); + mSingletonByName.remove(name); } else { - // TODO: Verify this works, i.e., the caller happens to be from the correct user + if (userId < 0) throw new IllegalArgumentException("Bad user " + userId); if (DBG) Slog.i(TAG, - "Removing from providersByName name=" + name + " user=" - + (optionalUserId == -1 ? Binder.getOrigCallingUser() : optionalUserId)); - HashMap<String, ContentProviderRecord> map = getProvidersByName(optionalUserId); + "Removing from providersByName name=" + name + " user=" + userId); + HashMap<String, ContentProviderRecord> map = getProvidersByName(userId); // map returned by getProvidersByName wouldn't be null map.remove(name); if (map.size() == 0) { - mProvidersByNamePerUser.remove(optionalUserId); + mProvidersByNamePerUser.remove(userId); } } } - void removeProviderByClass(ComponentName name, int optionalUserId) { - if (mGlobalByClass.containsKey(name)) { + void removeProviderByClass(ComponentName name, int userId) { + if (mSingletonByClass.containsKey(name)) { if (DBG) Slog.i(TAG, "Removing from globalByClass name=" + name); - mGlobalByClass.remove(name); + mSingletonByClass.remove(name); } else { + if (userId < 0) throw new IllegalArgumentException("Bad user " + userId); if (DBG) Slog.i(TAG, - "Removing from providersByClass name=" + name + " user=" - + (optionalUserId == -1 ? Binder.getOrigCallingUser() : optionalUserId)); - HashMap<ComponentName, ContentProviderRecord> map = getProvidersByClass(optionalUserId); + "Removing from providersByClass name=" + name + " user=" + userId); + HashMap<ComponentName, ContentProviderRecord> map = getProvidersByClass(userId); // map returned by getProvidersByClass wouldn't be null map.remove(name); if (map.size() == 0) { - mProvidersByClassPerUser.remove(optionalUserId); + mProvidersByClassPerUser.remove(userId); } } } - private HashMap<String, ContentProviderRecord> getProvidersByName(int optionalUserId) { - final int userId = optionalUserId >= 0 - ? optionalUserId : Binder.getOrigCallingUser(); + private HashMap<String, ContentProviderRecord> getProvidersByName(int userId) { + if (userId < 0) throw new IllegalArgumentException("Bad user " + userId); final HashMap<String, ContentProviderRecord> map = mProvidersByNamePerUser.get(userId); if (map == null) { HashMap<String, ContentProviderRecord> newMap = new HashMap<String, ContentProviderRecord>(); @@ -168,12 +169,13 @@ public class ProviderMap { } } - HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int optionalUserId) { - final int userId = optionalUserId >= 0 - ? optionalUserId : Binder.getOrigCallingUser(); - final HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.get(userId); + HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int userId) { + if (userId < 0) throw new IllegalArgumentException("Bad user " + userId); + final HashMap<ComponentName, ContentProviderRecord> map + = mProvidersByClassPerUser.get(userId); if (map == null) { - HashMap<ComponentName, ContentProviderRecord> newMap = new HashMap<ComponentName, ContentProviderRecord>(); + HashMap<ComponentName, ContentProviderRecord> newMap + = new HashMap<ComponentName, ContentProviderRecord>(); mProvidersByClassPerUser.put(userId, newMap); return newMap; } else { @@ -181,6 +183,53 @@ public class ProviderMap { } } + private boolean collectForceStopProvidersLocked(String name, int appId, + boolean doit, boolean evenPersistent, int userId, + HashMap<ComponentName, ContentProviderRecord> providers, + ArrayList<ContentProviderRecord> result) { + boolean didSomething = false; + for (ContentProviderRecord provider : providers.values()) { + if ((name == null || provider.info.packageName.equals(name)) + && (provider.proc == null || evenPersistent || !provider.proc.persistent)) { + if (!doit) { + return true; + } + didSomething = true; + result.add(provider); + } + } + return didSomething; + } + + boolean collectForceStopProviders(String name, int appId, + boolean doit, boolean evenPersistent, int userId, + ArrayList<ContentProviderRecord> result) { + boolean didSomething = collectForceStopProvidersLocked(name, appId, doit, + evenPersistent, userId, mSingletonByClass, result); + if (!doit && didSomething) { + return true; + } + if (userId == UserHandle.USER_ALL) { + for (int i=0; i<mProvidersByClassPerUser.size(); i++) { + if (collectForceStopProvidersLocked(name, appId, doit, evenPersistent, + userId, mProvidersByClassPerUser.valueAt(i), result)) { + if (!doit) { + return true; + } + didSomething = true; + } + } + } else { + HashMap<ComponentName, ContentProviderRecord> items + = getProvidersByClass(userId); + if (items != null) { + didSomething |= collectForceStopProvidersLocked(name, appId, doit, + evenPersistent, userId, items, result); + } + } + return didSomething; + } + private void dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll, HashMap<ComponentName, ContentProviderRecord> map) { Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it = map.entrySet().iterator(); @@ -207,37 +256,29 @@ public class ProviderMap { } void dumpProvidersLocked(PrintWriter pw, boolean dumpAll) { - boolean needSep = false; - if (mGlobalByClass.size() > 0) { - if (needSep) - pw.println(" "); - pw.println(" Published content providers (by class):"); - dumpProvidersByClassLocked(pw, dumpAll, mGlobalByClass); + if (mSingletonByClass.size() > 0) { + pw.println(" Published single-user content providers (by class):"); + dumpProvidersByClassLocked(pw, dumpAll, mSingletonByClass); } - if (mProvidersByClassPerUser.size() > 1) { + pw.println(""); + for (int i = 0; i < mProvidersByClassPerUser.size(); i++) { + HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i); pw.println(""); - for (int i = 0; i < mProvidersByClassPerUser.size(); i++) { - HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i); - pw.println(" User " + mProvidersByClassPerUser.keyAt(i) + ":"); - dumpProvidersByClassLocked(pw, dumpAll, map); - pw.println(" "); - } - } else if (mProvidersByClassPerUser.size() == 1) { - HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(0); + pw.println(" Published user " + mProvidersByClassPerUser.keyAt(i) + + " content providers (by class):"); dumpProvidersByClassLocked(pw, dumpAll, map); } - needSep = true; if (dumpAll) { - pw.println(" "); - pw.println(" Authority to provider mappings:"); - dumpProvidersByNameLocked(pw, mGlobalByName); + pw.println(""); + pw.println(" Single-user authority to provider mappings:"); + dumpProvidersByNameLocked(pw, mSingletonByName); for (int i = 0; i < mProvidersByNamePerUser.size(); i++) { - if (i > 0) { - pw.println(" User " + mProvidersByNamePerUser.keyAt(i) + ":"); - } + pw.println(""); + pw.println(" User " + mProvidersByNamePerUser.keyAt(i) + + " authority to provider mappings:"); dumpProvidersByNameLocked(pw, mProvidersByNamePerUser.valueAt(i)); } } @@ -245,30 +286,33 @@ public class ProviderMap { protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll) { + ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>(); ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); - if ("all".equals(name)) { - synchronized (this) { - for (ContentProviderRecord r1 : getProvidersByClass(-1).values()) { - providers.add(r1); - } + synchronized (mAm) { + allProviders.addAll(mSingletonByClass.values()); + for (int i=0; i<mProvidersByClassPerUser.size(); i++) { + allProviders.addAll(mProvidersByClassPerUser.valueAt(i).values()); } - } else { - ComponentName componentName = name != null - ? ComponentName.unflattenFromString(name) : null; - int objectId = 0; - if (componentName == null) { - // Not a '/' separated full component name; maybe an object ID? - try { - objectId = Integer.parseInt(name, 16); - name = null; - componentName = null; - } catch (RuntimeException e) { + + if ("all".equals(name)) { + providers.addAll(allProviders); + } else { + ComponentName componentName = name != null + ? ComponentName.unflattenFromString(name) : null; + int objectId = 0; + if (componentName == null) { + // Not a '/' separated full component name; maybe an object ID? + try { + objectId = Integer.parseInt(name, 16); + name = null; + componentName = null; + } catch (RuntimeException e) { + } } - } - synchronized (this) { - for (ContentProviderRecord r1 : getProvidersByClass(-1).values()) { + for (int i=0; i<allProviders.size(); i++) { + ContentProviderRecord r1 = allProviders.get(i); if (componentName != null) { if (r1.name.equals(componentName)) { providers.add(r1); @@ -306,7 +350,7 @@ public class ProviderMap { private void dumpProvider(String prefix, FileDescriptor fd, PrintWriter pw, final ContentProviderRecord r, String[] args, boolean dumpAll) { String innerPrefix = prefix + " "; - synchronized (this) { + synchronized (mAm) { pw.print(prefix); pw.print("PROVIDER "); pw.print(r); pw.print(" pid="); @@ -338,6 +382,4 @@ public class ProviderMap { } } } - - } diff --git a/services/java/com/android/server/am/ReceiverList.java b/services/java/com/android/server/am/ReceiverList.java index 32c24c6..9b6701e 100644 --- a/services/java/com/android/server/am/ReceiverList.java +++ b/services/java/com/android/server/am/ReceiverList.java @@ -39,18 +39,20 @@ class ReceiverList extends ArrayList<BroadcastFilter> public final ProcessRecord app; public final int pid; public final int uid; + public final int userId; BroadcastRecord curBroadcast = null; boolean linkedToDeath = false; String stringName; ReceiverList(ActivityManagerService _owner, ProcessRecord _app, - int _pid, int _uid, IIntentReceiver _receiver) { + int _pid, int _uid, int _userId, IIntentReceiver _receiver) { owner = _owner; receiver = _receiver; app = _app; pid = _pid; uid = _uid; + userId = _userId; } // Want object identity, not the array identity we are inheriting. @@ -67,8 +69,9 @@ class ReceiverList extends ArrayList<BroadcastFilter> } void dumpLocal(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("app="); pw.print(app); - pw.print(" pid="); pw.print(pid); pw.print(" uid="); pw.println(uid); + pw.print(prefix); pw.print("app="); pw.print(app.toShortString()); + pw.print(" pid="); pw.print(pid); pw.print(" uid="); pw.print(uid); + pw.print(" user="); pw.println(userId); if (curBroadcast != null || linkedToDeath) { pw.print(prefix); pw.print("curBroadcast="); pw.print(curBroadcast); pw.print(" linkedToDeath="); pw.println(linkedToDeath); @@ -103,6 +106,8 @@ class ReceiverList extends ArrayList<BroadcastFilter> sb.append((app != null ? app.processName : "(unknown name)")); sb.append('/'); sb.append(uid); + sb.append("/u"); + sb.append(userId); sb.append((receiver.asBinder() instanceof Binder) ? " local:" : " remote:"); sb.append(Integer.toHexString(System.identityHashCode(receiver.asBinder()))); sb.append('}'); diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 828eef7..7055fdc 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -23,6 +23,7 @@ import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationManager; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -31,7 +32,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; -import android.os.UserId; +import android.os.UserHandle; import android.util.Slog; import android.util.TimeUtils; @@ -260,6 +261,9 @@ class ServiceRecord extends Binder { IntentBindRecord b = it.next(); pw.print(prefix); pw.print("* IntentBindRecord{"); pw.print(Integer.toHexString(System.identityHashCode(b))); + if ((b.collectFlags()&Context.BIND_AUTO_CREATE) != 0) { + pw.append(" CREATE"); + } pw.println("}:"); b.dumpInService(pw, prefix + " "); } @@ -296,7 +300,7 @@ class ServiceRecord extends Binder { this.restarter = restarter; createTime = SystemClock.elapsedRealtime(); lastActivity = SystemClock.uptimeMillis(); - userId = UserId.getUserId(appInfo.uid); + userId = UserHandle.getUserId(appInfo.uid); } public AppBindRecord retrieveAppBindingLocked(Intent intent, @@ -366,7 +370,7 @@ class ServiceRecord extends Binder { try { int[] outId = new int[1]; nm.enqueueNotificationInternal(localPackageName, appUid, appPid, - null, localForegroundId, localForegroundNoti, outId); + null, localForegroundId, localForegroundNoti, outId, userId); } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Error showing notification for service", e); @@ -395,7 +399,8 @@ class ServiceRecord extends Binder { return; } try { - inm.cancelNotification(localPackageName, localForegroundId); + inm.cancelNotificationWithTag(localPackageName, null, + localForegroundId, userId); } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Error canceling notification for service", e); diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index 3a767c2..1bae9ca 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -19,7 +19,7 @@ package com.android.server.am; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.os.UserId; +import android.os.UserHandle; import android.util.Slog; import java.io.PrintWriter; @@ -101,7 +101,7 @@ class TaskRecord extends ThumbnailHolder { } if (info.applicationInfo != null) { - userId = UserId.getUserId(info.applicationInfo.uid); + userId = UserHandle.getUserId(info.applicationInfo.uid); } } diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index ba65f39..7059674 100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -27,12 +27,12 @@ import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; import android.os.SystemClock; +import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; import com.android.internal.app.IUsageStats; import com.android.internal.content.PackageMonitor; -import com.android.internal.os.AtomicFile; import com.android.internal.os.PkgUsageStats; import com.android.internal.util.FastXmlSerializer; diff --git a/services/java/com/android/server/am/UserStartedState.java b/services/java/com/android/server/am/UserStartedState.java new file mode 100644 index 0000000..3f3ed85 --- /dev/null +++ b/services/java/com/android/server/am/UserStartedState.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import java.io.PrintWriter; +import java.util.ArrayList; + +import android.app.IStopUserCallback; +import android.os.UserHandle; + +public class UserStartedState { + public final static int STATE_BOOTING = 0; + public final static int STATE_RUNNING = 1; + public final static int STATE_STOPPING = 2; + + public final UserHandle mHandle; + public final ArrayList<IStopUserCallback> mStopCallbacks + = new ArrayList<IStopUserCallback>(); + + public int mState = STATE_BOOTING; + + public UserStartedState(UserHandle handle, boolean initial) { + mHandle = handle; + } + + void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mState="); pw.println(mState); + } +} diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 682ecf8..79fb458 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -43,6 +43,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -319,6 +320,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void limitReached(String limitName, String iface) {} + public void interfaceClassDataActivityChanged(String label, boolean active) {} + public int tether(String iface) { if (DBG) Log.d(TAG, "Tethering " + iface); TetherInterfaceSM sm = null; @@ -415,7 +418,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, activeList); broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER, erroredList); - mContext.sendStickyBroadcast(broadcast); + mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL); if (DBG) { Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " + activeList.size() + ", " + erroredList.size()); @@ -501,8 +504,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mUsbTetherRequested = false; } } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); + NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra( + ConnectivityManager.EXTRA_NETWORK_INFO); + if (networkInfo != null && + networkInfo.getDetailedState() != NetworkInfo.DetailedState.FAILED) { + if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); + mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); + } } } } @@ -1135,7 +1143,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private State mStopTetheringErrorState; private State mSetDnsForwardersErrorState; - private ArrayList mNotifyList; + private ArrayList<TetherInterfaceSM> mNotifyList; private int mCurrentConnectionSequence; private int mMobileApnReserved = ConnectivityManager.TYPE_NONE; @@ -1165,7 +1173,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mSetDnsForwardersErrorState = new SetDnsForwardersErrorState(); addState(mSetDnsForwardersErrorState); - mNotifyList = new ArrayList(); + mNotifyList = new ArrayList<TetherInterfaceSM>(); setInitialState(mInitialState); } @@ -1362,8 +1370,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { if (DBG) Log.d(TAG, "notifying tethered with iface =" + ifaceName); mUpstreamIfaceName = ifaceName; - for (Object o : mNotifyList) { - TetherInterfaceSM sm = (TetherInterfaceSM)o; + for (TetherInterfaceSM sm : mNotifyList) { sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, ifaceName); } @@ -1381,13 +1388,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { switch (message.what) { case CMD_TETHER_MODE_REQUESTED: TetherInterfaceSM who = (TetherInterfaceSM)message.obj; - if (VDBG) Log.d(TAG, "Tether Mode requested by " + who.toString()); + if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); mNotifyList.add(who); transitionTo(mTetherModeAliveState); break; case CMD_TETHER_MODE_UNREQUESTED: who = (TetherInterfaceSM)message.obj; - if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who.toString()); + if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); int index = mNotifyList.indexOf(who); if (index != -1) { mNotifyList.remove(who); @@ -1424,18 +1431,29 @@ public class Tethering extends INetworkManagementEventObserver.Stub { switch (message.what) { case CMD_TETHER_MODE_REQUESTED: TetherInterfaceSM who = (TetherInterfaceSM)message.obj; + if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); mNotifyList.add(who); who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, mUpstreamIfaceName); break; case CMD_TETHER_MODE_UNREQUESTED: who = (TetherInterfaceSM)message.obj; + if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); int index = mNotifyList.indexOf(who); if (index != -1) { + if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who); mNotifyList.remove(index); if (mNotifyList.isEmpty()) { turnOffMasterTetherSettings(); // transitions appropriately + } else { + if (DBG) { + Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() + + " live requests:"); + for (Object o : mNotifyList) Log.d(TAG, " " + o); + } } + } else { + Log.e(TAG, "TetherModeAliveState UNREQUESTED has unknown who: " + who); } break; case CMD_UPSTREAM_CHANGED: @@ -1562,7 +1580,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { pw.println(); pw.println("Tether state:"); for (Object o : mIfaces.values()) { - pw.println(" "+o.toString()); + pw.println(" " + o); } } pw.println(); diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index 4b82037..b3cbb84 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -16,8 +16,11 @@ package com.android.server.connectivity; +import static android.Manifest.permission.BIND_VPN_SERVICE; + import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -25,55 +28,116 @@ import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; +import android.net.BaseNetworkStateTracker; +import android.net.ConnectivityManager; import android.net.INetworkManagementEventObserver; +import android.net.LinkProperties; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.net.NetworkInfo; +import android.net.RouteInfo; +import android.net.NetworkInfo.DetailedState; import android.os.Binder; import android.os.FileUtils; import android.os.IBinder; +import android.os.INetworkManagementService; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; +import android.os.SystemService; +import android.security.Credentials; +import android.security.KeyStore; import android.util.Log; +import android.widget.Toast; import com.android.internal.R; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.internal.util.Preconditions; import com.android.server.ConnectivityService.VpnCallback; +import com.android.server.net.BaseNetworkObserver; import java.io.File; import java.io.InputStream; import java.io.OutputStream; +import java.net.Inet4Address; +import java.net.InetAddress; import java.nio.charset.Charsets; import java.util.Arrays; +import libcore.io.IoUtils; + /** * @hide */ -public class Vpn extends INetworkManagementEventObserver.Stub { - - private final static String TAG = "Vpn"; - - private final static String BIND_VPN_SERVICE = - android.Manifest.permission.BIND_VPN_SERVICE; +public class Vpn extends BaseNetworkStateTracker { + private static final String TAG = "Vpn"; + private static final boolean LOGD = true; + + // TODO: create separate trackers for each unique VPN to support + // automated reconnection - private final Context mContext; private final VpnCallback mCallback; private String mPackage = VpnConfig.LEGACY_VPN; private String mInterface; private Connection mConnection; private LegacyVpnRunner mLegacyVpnRunner; + private PendingIntent mStatusIntent; + private boolean mEnableNotif = true; - public Vpn(Context context, VpnCallback callback) { + public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) { + // TODO: create dedicated TYPE_VPN network type + super(ConnectivityManager.TYPE_DUMMY); mContext = context; mCallback = callback; + + try { + netService.registerObserver(mObserver); + } catch (RemoteException e) { + Log.wtf(TAG, "Problem registering observer", e); + } + } + + public void setEnableNotifications(boolean enableNotif) { + mEnableNotif = enableNotif; + } + + @Override + protected void startMonitoringInternal() { + // Ignored; events are sent through callbacks for now + } + + @Override + public boolean teardown() { + // TODO: finish migration to unique tracker for each VPN + throw new UnsupportedOperationException(); + } + + @Override + public boolean reconnect() { + // TODO: finish migration to unique tracker for each VPN + throw new UnsupportedOperationException(); + } + + @Override + public String getTcpBufferSizesPropName() { + return PROP_TCP_BUFFER_UNKNOWN; + } + + /** + * Update current state, dispaching event to listeners. + */ + private void updateState(DetailedState detailedState, String reason) { + if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason); + mNetworkInfo.setDetailedState(detailedState, reason, null); + mCallback.onStateChanged(new NetworkInfo(mNetworkInfo)); } /** @@ -112,10 +176,13 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Reset the interface and hide the notification. if (mInterface != null) { jniReset(mInterface); - long identity = Binder.clearCallingIdentity(); - mCallback.restore(); - hideNotification(); - Binder.restoreCallingIdentity(identity); + final long token = Binder.clearCallingIdentity(); + try { + mCallback.restore(); + hideNotification(); + } finally { + Binder.restoreCallingIdentity(token); + } mInterface = null; } @@ -136,6 +203,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; + updateState(DetailedState.IDLE, "prepare"); return true; } @@ -144,7 +212,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { * interface. The socket is NOT closed by this method. * * @param socket The socket to be bound. - * @param name The name of the interface. + * @param interfaze The name of the interface. */ public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception { PackageManager pm = mContext.getPackageManager(); @@ -208,6 +276,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Configure the interface. Abort if any of these steps fails. ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); try { + updateState(DetailedState.CONNECTING, "establish"); String interfaze = jniGetName(tun.getFd()); if (jniSetAddresses(interfaze, config.addresses) < 1) { throw new IllegalArgumentException("At least one address must be specified"); @@ -228,11 +297,8 @@ public class Vpn extends INetworkManagementEventObserver.Stub { mConnection = connection; mInterface = interfaze; } catch (RuntimeException e) { - try { - tun.close(); - } catch (Exception ex) { - // ignore - } + updateState(DetailedState.FAILED, "establish"); + IoUtils.closeQuietly(tun); throw e; } Log.i(TAG, "Established by " + config.user + " on " + mInterface); @@ -242,54 +308,61 @@ public class Vpn extends INetworkManagementEventObserver.Stub { config.interfaze = mInterface; // Override DNS servers and show the notification. - long identity = Binder.clearCallingIdentity(); - mCallback.override(config.dnsServers, config.searchDomains); - showNotification(config, label, bitmap); - Binder.restoreCallingIdentity(identity); + final long token = Binder.clearCallingIdentity(); + try { + mCallback.override(config.dnsServers, config.searchDomains); + showNotification(config, label, bitmap); + } finally { + Binder.restoreCallingIdentity(token); + } + // TODO: ensure that contract class eventually marks as connected + updateState(DetailedState.AUTHENTICATING, "establish"); return tun; } - // INetworkManagementEventObserver.Stub - @Override - public void interfaceAdded(String interfaze) { - } - - // INetworkManagementEventObserver.Stub - @Override - public synchronized void interfaceStatusChanged(String interfaze, boolean up) { - if (!up && mLegacyVpnRunner != null) { - mLegacyVpnRunner.check(interfaze); + @Deprecated + public synchronized void interfaceStatusChanged(String iface, boolean up) { + try { + mObserver.interfaceStatusChanged(iface, up); + } catch (RemoteException e) { + // ignored; target is local } } - // INetworkManagementEventObserver.Stub - @Override - public void interfaceLinkStateChanged(String interfaze, boolean up) { - } - - // INetworkManagementEventObserver.Stub - @Override - public synchronized void interfaceRemoved(String interfaze) { - if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { - long identity = Binder.clearCallingIdentity(); - mCallback.restore(); - hideNotification(); - Binder.restoreCallingIdentity(identity); - mInterface = null; - if (mConnection != null) { - mContext.unbindService(mConnection); - mConnection = null; - } else if (mLegacyVpnRunner != null) { - mLegacyVpnRunner.exit(); - mLegacyVpnRunner = null; + private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() { + @Override + public void interfaceStatusChanged(String interfaze, boolean up) { + synchronized (Vpn.this) { + if (!up && mLegacyVpnRunner != null) { + mLegacyVpnRunner.check(interfaze); + } } } - } - // INetworkManagementEventObserver.Stub - @Override - public void limitReached(String limit, String interfaze) { - } + @Override + public void interfaceRemoved(String interfaze) { + synchronized (Vpn.this) { + if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { + final long token = Binder.clearCallingIdentity(); + try { + mCallback.restore(); + hideNotification(); + } finally { + Binder.restoreCallingIdentity(token); + } + mInterface = null; + if (mConnection != null) { + mContext.unbindService(mConnection); + mConnection = null; + updateState(DetailedState.DISCONNECTED, "interfaceRemoved"); + } else if (mLegacyVpnRunner != null) { + mLegacyVpnRunner.exit(); + mLegacyVpnRunner = null; + } + } + } + } + }; private void enforceControlPermission() { // System user is allowed to control VPN. @@ -326,6 +399,9 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } private void showNotification(VpnConfig config, String label, Bitmap icon) { + if (!mEnableNotif) return; + mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config); + NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -341,15 +417,18 @@ public class Vpn extends INetworkManagementEventObserver.Stub { .setLargeIcon(icon) .setContentTitle(title) .setContentText(text) - .setContentIntent(VpnConfig.getIntentForStatusPanel(mContext, config)) + .setContentIntent(mStatusIntent) .setDefaults(0) .setOngoing(true) - .getNotification(); + .build(); nm.notify(R.drawable.vpn_connected, notification); } } private void hideNotification() { + if (!mEnableNotif) return; + mStatusIntent = null; + NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -366,31 +445,172 @@ public class Vpn extends INetworkManagementEventObserver.Stub { private native int jniCheck(String interfaze); private native void jniProtect(int socket, String interfaze); + private static String findLegacyVpnGateway(LinkProperties prop) { + for (RouteInfo route : prop.getRoutes()) { + // Currently legacy VPN only works on IPv4. + if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) { + return route.getGateway().getHostAddress(); + } + } + + throw new IllegalStateException("Unable to find suitable gateway"); + } + /** - * Start legacy VPN. This method stops the daemons and restart them - * if arguments are not null. Heavy things are offloaded to another - * thread, so callers will not be blocked for a long time. - * - * @param config The parameters to configure the network. - * @param raoocn The arguments to be passed to racoon. - * @param mtpd The arguments to be passed to mtpd. + * Start legacy VPN, controlling native daemons as needed. Creates a + * secondary thread to perform connection work, returning quickly. */ - public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { + public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) { + if (keyStore.state() != KeyStore.State.UNLOCKED) { + throw new IllegalStateException("KeyStore isn't unlocked"); + } + + final String iface = egress.getInterfaceName(); + final String gateway = findLegacyVpnGateway(egress); + + // Load certificates. + String privateKey = ""; + String userCert = ""; + String caCert = ""; + String serverCert = ""; + if (!profile.ipsecUserCert.isEmpty()) { + privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert; + byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert); + userCert = (value == null) ? null : new String(value, Charsets.UTF_8); + } + if (!profile.ipsecCaCert.isEmpty()) { + byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert); + caCert = (value == null) ? null : new String(value, Charsets.UTF_8); + } + if (!profile.ipsecServerCert.isEmpty()) { + byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert); + serverCert = (value == null) ? null : new String(value, Charsets.UTF_8); + } + if (privateKey == null || userCert == null || caCert == null || serverCert == null) { + throw new IllegalStateException("Cannot load credentials"); + } + + // Prepare arguments for racoon. + String[] racoon = null; + switch (profile.type) { + case VpnProfile.TYPE_L2TP_IPSEC_PSK: + racoon = new String[] { + iface, profile.server, "udppsk", profile.ipsecIdentifier, + profile.ipsecSecret, "1701", + }; + break; + case VpnProfile.TYPE_L2TP_IPSEC_RSA: + racoon = new String[] { + iface, profile.server, "udprsa", privateKey, userCert, + caCert, serverCert, "1701", + }; + break; + case VpnProfile.TYPE_IPSEC_XAUTH_PSK: + racoon = new String[] { + iface, profile.server, "xauthpsk", profile.ipsecIdentifier, + profile.ipsecSecret, profile.username, profile.password, "", gateway, + }; + break; + case VpnProfile.TYPE_IPSEC_XAUTH_RSA: + racoon = new String[] { + iface, profile.server, "xauthrsa", privateKey, userCert, + caCert, serverCert, profile.username, profile.password, "", gateway, + }; + break; + case VpnProfile.TYPE_IPSEC_HYBRID_RSA: + racoon = new String[] { + iface, profile.server, "hybridrsa", + caCert, serverCert, profile.username, profile.password, "", gateway, + }; + break; + } + + // Prepare arguments for mtpd. + String[] mtpd = null; + switch (profile.type) { + case VpnProfile.TYPE_PPTP: + mtpd = new String[] { + iface, "pptp", profile.server, "1723", + "name", profile.username, "password", profile.password, + "linkname", "vpn", "refuse-eap", "nodefaultroute", + "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400", + (profile.mppe ? "+mppe" : "nomppe"), + }; + break; + case VpnProfile.TYPE_L2TP_IPSEC_PSK: + case VpnProfile.TYPE_L2TP_IPSEC_RSA: + mtpd = new String[] { + iface, "l2tp", profile.server, "1701", profile.l2tpSecret, + "name", profile.username, "password", profile.password, + "linkname", "vpn", "refuse-eap", "nodefaultroute", + "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400", + }; + break; + } + + VpnConfig config = new VpnConfig(); + config.legacy = true; + config.user = profile.key; + config.interfaze = iface; + config.session = profile.name; + config.routes = profile.routes; + if (!profile.dnsServers.isEmpty()) { + config.dnsServers = Arrays.asList(profile.dnsServers.split(" +")); + } + if (!profile.searchDomains.isEmpty()) { + config.searchDomains = Arrays.asList(profile.searchDomains.split(" +")); + } + + startLegacyVpn(config, racoon, mtpd); + } + + private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) { + stopLegacyVpn(); + // Prepare for the new request. This also checks the caller. prepare(null, VpnConfig.LEGACY_VPN); + updateState(DetailedState.CONNECTING, "startLegacyVpn"); // Start a new LegacyVpnRunner and we are done! mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd); mLegacyVpnRunner.start(); } + public synchronized void stopLegacyVpn() { + if (mLegacyVpnRunner != null) { + mLegacyVpnRunner.exit(); + mLegacyVpnRunner = null; + + synchronized (LegacyVpnRunner.TAG) { + // wait for old thread to completely finish before spinning up + // new instance, otherwise state updates can be out of order. + } + } + } + /** * Return the information of the current ongoing legacy VPN. */ public synchronized LegacyVpnInfo getLegacyVpnInfo() { // Check if the caller is authorized. enforceControlPermission(); - return (mLegacyVpnRunner == null) ? null : mLegacyVpnRunner.getInfo(); + if (mLegacyVpnRunner == null) return null; + + final LegacyVpnInfo info = new LegacyVpnInfo(); + info.key = mLegacyVpnRunner.mConfig.user; + info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo); + if (mNetworkInfo.isConnected()) { + info.intent = mStatusIntent; + } + return info; + } + + public VpnConfig getLegacyVpnConfig() { + if (mLegacyVpnRunner != null) { + return mLegacyVpnRunner.mConfig; + } else { + return null; + } } /** @@ -407,8 +627,6 @@ public class Vpn extends INetworkManagementEventObserver.Stub { private final String[] mDaemons; private final String[][] mArguments; private final LocalSocket[] mSockets; - private final String mOuterInterface; - private final LegacyVpnInfo mInfo; private long mTimer = -1; @@ -416,20 +634,13 @@ public class Vpn extends INetworkManagementEventObserver.Stub { super(TAG); mConfig = config; mDaemons = new String[] {"racoon", "mtpd"}; + // TODO: clear arguments from memory once launched mArguments = new String[][] {racoon, mtpd}; mSockets = new LocalSocket[mDaemons.length]; - mInfo = new LegacyVpnInfo(); - - // This is the interface which VPN is running on. - mOuterInterface = mConfig.interfaze; - - // Legacy VPN is not a real package, so we use it to carry the key. - mInfo.key = mConfig.user; - mConfig.user = VpnConfig.LEGACY_VPN; } public void check(String interfaze) { - if (interfaze.equals(mOuterInterface)) { + if (interfaze.equals(mConfig.interfaze)) { Log.i(TAG, "Legacy VPN is going down with " + interfaze); exit(); } @@ -439,21 +650,9 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // We assume that everything is reset after stopping the daemons. interrupt(); for (LocalSocket socket : mSockets) { - try { - socket.close(); - } catch (Exception e) { - // ignore - } - } - } - - public LegacyVpnInfo getInfo() { - // Update the info when VPN is disconnected. - if (mInfo.state == LegacyVpnInfo.STATE_CONNECTED && mInterface == null) { - mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED; - mInfo.intent = null; + IoUtils.closeQuietly(socket); } - return mInfo; + updateState(DetailedState.DISCONNECTED, "exit"); } @Override @@ -463,6 +662,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { synchronized (TAG) { Log.v(TAG, "Executing"); execute(); + monitorDaemons(); } } @@ -474,22 +674,21 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } else if (now - mTimer <= 60000) { Thread.sleep(yield ? 200 : 1); } else { - mInfo.state = LegacyVpnInfo.STATE_TIMEOUT; + updateState(DetailedState.FAILED, "checkpoint"); throw new IllegalStateException("Time is up"); } } private void execute() { // Catch all exceptions so we can clean up few things. + boolean initFinished = false; try { // Initialize the timer. checkpoint(false); - mInfo.state = LegacyVpnInfo.STATE_INITIALIZING; // Wait for the daemons to stop. for (String daemon : mDaemons) { - String key = "init.svc." + daemon; - while (!"stopped".equals(SystemProperties.get(key, "stopped"))) { + while (!SystemService.isStopped(daemon)) { checkpoint(true); } } @@ -501,6 +700,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { throw new IllegalStateException("Cannot delete the state"); } new File("/data/misc/vpn/abort").delete(); + initFinished = true; // Check if we need to restart any of the daemons. boolean restart = false; @@ -508,10 +708,10 @@ public class Vpn extends INetworkManagementEventObserver.Stub { restart = restart || (arguments != null); } if (!restart) { - mInfo.state = LegacyVpnInfo.STATE_DISCONNECTED; + updateState(DetailedState.DISCONNECTED, "execute"); return; } - mInfo.state = LegacyVpnInfo.STATE_CONNECTING; + updateState(DetailedState.CONNECTING, "execute"); // Start the daemon with arguments. for (int i = 0; i < mDaemons.length; ++i) { @@ -522,11 +722,10 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Start the daemon. String daemon = mDaemons[i]; - SystemProperties.set("ctl.start", daemon); + SystemService.start(daemon); // Wait for the daemon to start. - String key = "init.svc." + daemon; - while (!"running".equals(SystemProperties.get(key))) { + while (!SystemService.isRunning(daemon)) { checkpoint(true); } @@ -582,8 +781,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Check if a running daemon is dead. for (int i = 0; i < mDaemons.length; ++i) { String daemon = mDaemons[i]; - if (mArguments[i] != null && !"running".equals( - SystemProperties.get("init.svc." + daemon))) { + if (mArguments[i] != null && !SystemService.isRunning(daemon)) { throw new IllegalStateException(daemon + " is dead"); } } @@ -640,26 +838,53 @@ public class Vpn extends INetworkManagementEventObserver.Stub { showNotification(mConfig, null, null); Log.i(TAG, "Connected!"); - mInfo.state = LegacyVpnInfo.STATE_CONNECTED; - mInfo.intent = VpnConfig.getIntentForStatusPanel(mContext, null); + updateState(DetailedState.CONNECTED, "execute"); } } catch (Exception e) { Log.i(TAG, "Aborting", e); exit(); } finally { // Kill the daemons if they fail to stop. - if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING) { + if (!initFinished) { for (String daemon : mDaemons) { - SystemProperties.set("ctl.stop", daemon); + SystemService.stop(daemon); } } // Do not leave an unstable state. - if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING || - mInfo.state == LegacyVpnInfo.STATE_CONNECTING) { - mInfo.state = LegacyVpnInfo.STATE_FAILED; + if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) { + updateState(DetailedState.FAILED, "execute"); } } } + + /** + * Monitor the daemons we started, moving to disconnected state if the + * underlying services fail. + */ + private void monitorDaemons() { + if (!mNetworkInfo.isConnected()) { + return; + } + + try { + while (true) { + Thread.sleep(2000); + for (int i = 0; i < mDaemons.length; i++) { + if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) { + return; + } + } + } + } catch (InterruptedException e) { + Log.d(TAG, "interrupted during monitorDaemons(); stopping services"); + } finally { + for (String daemon : mDaemons) { + SystemService.stop(daemon); + } + + updateState(DetailedState.DISCONNECTED, "babysit"); + } + } } } diff --git a/services/java/com/android/server/display/DisplayAdapter.java b/services/java/com/android/server/display/DisplayAdapter.java new file mode 100644 index 0000000..abc1d32 --- /dev/null +++ b/services/java/com/android/server/display/DisplayAdapter.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.content.Context; +import android.os.Handler; + +import java.io.PrintWriter; + +/** + * A display adapter makes zero or more display devices available to the system + * and provides facilities for discovering when displays are connected or disconnected. + * <p> + * For now, all display adapters are registered in the system server but + * in principle it could be done from other processes. + * </p><p> + * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. + * </p> + */ +abstract class DisplayAdapter { + private final DisplayManagerService.SyncRoot mSyncRoot; + private final Context mContext; + private final Handler mHandler; + private final Listener mListener; + private final String mName; + + public static final int DISPLAY_DEVICE_EVENT_ADDED = 1; + public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2; + public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3; + + public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot, + Context context, Handler handler, Listener listener, String name) { + mSyncRoot = syncRoot; + mContext = context; + mHandler = handler; + mListener = listener; + mName = name; + } + + /** + * Gets the object that the display adapter should synchronize on when handling + * calls that come in from outside of the display manager service. + */ + public final DisplayManagerService.SyncRoot getSyncRoot() { + return mSyncRoot; + } + + /** + * Gets the display adapter's context. + */ + public final Context getContext() { + return mContext; + } + + /** + * Gets a handler that the display adapter may use to post asynchronous messages. + */ + public final Handler getHandler() { + return mHandler; + } + + /** + * Gets the display adapter name for debugging purposes. + */ + public final String getName() { + return mName; + } + + /** + * Registers the display adapter with the display manager. + * + * The display adapter should register any built-in display devices as soon as possible. + * The boot process will wait for the default display to be registered. + * Other display devices can be registered dynamically later. + */ + public void registerLocked() { + } + + /** + * Dumps the local state of the display adapter. + */ + public void dumpLocked(PrintWriter pw) { + } + + /** + * Sends a display device event to the display adapter listener asynchronously. + */ + protected final void sendDisplayDeviceEventLocked( + final DisplayDevice device, final int event) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayDeviceEvent(device, event); + } + }); + } + + /** + * Sends a request to perform traversals. + */ + protected final void sendTraversalRequestLocked() { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onTraversalRequested(); + } + }); + } + + public interface Listener { + public void onDisplayDeviceEvent(DisplayDevice device, int event); + public void onTraversalRequested(); + } +} diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java new file mode 100644 index 0000000..f5aa3d4 --- /dev/null +++ b/services/java/com/android/server/display/DisplayDevice.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.graphics.Rect; +import android.os.IBinder; +import android.view.Surface; + +import java.io.PrintWriter; + +/** + * Represents a physical display device such as the built-in display + * an external monitor, or a WiFi display. + * <p> + * Display devices are guarded by the {@link DisplayManagerService.SyncRoot} lock. + * </p> + */ +abstract class DisplayDevice { + private final DisplayAdapter mDisplayAdapter; + private final IBinder mDisplayToken; + + // The display device does not manage these properties itself, they are set by + // the display manager service. The display device shouldn't really be looking at these. + private int mCurrentLayerStack = -1; + private int mCurrentOrientation = -1; + private Rect mCurrentLayerStackRect; + private Rect mCurrentDisplayRect; + + // The display device owns its surface, but it should only set it + // within a transaction from performTraversalInTransactionLocked. + private Surface mCurrentSurface; + + public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) { + mDisplayAdapter = displayAdapter; + mDisplayToken = displayToken; + } + + /** + * Gets the display adapter that owns the display device. + * + * @return The display adapter. + */ + public final DisplayAdapter getAdapterLocked() { + return mDisplayAdapter; + } + + /** + * Gets the Surface Flinger display token for this display. + * + * @return The display token, or null if the display is not being managed + * by Surface Flinger. + */ + public final IBinder getDisplayTokenLocked() { + return mDisplayToken; + } + + /** + * Gets the name of the display device. + * + * @return The display device name. + */ + public final String getNameLocked() { + return getDisplayDeviceInfoLocked().name; + } + + /** + * Gets information about the display device. + * + * The information returned should not change between calls unless the display + * adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event and + * {@link #applyPendingDisplayDeviceInfoChangesLocked()} has been called to apply + * the pending changes. + * + * @return The display device info, which should be treated as immutable by the caller. + * The display device should allocate a new display device info object whenever + * the data changes. + */ + public abstract DisplayDeviceInfo getDisplayDeviceInfoLocked(); + + /** + * Applies any pending changes to the observable state of the display device + * if the display adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event. + */ + public void applyPendingDisplayDeviceInfoChangesLocked() { + } + + /** + * Gives the display device a chance to update its properties while in a transaction. + */ + public void performTraversalInTransactionLocked() { + } + + /** + * Sets the display layer stack while in a transaction. + */ + public final void setLayerStackInTransactionLocked(int layerStack) { + if (mCurrentLayerStack != layerStack) { + mCurrentLayerStack = layerStack; + Surface.setDisplayLayerStack(mDisplayToken, layerStack); + } + } + + /** + * Sets the display projection while in a transaction. + * + * @param orientation defines the display's orientation + * @param layerStackRect defines which area of the window manager coordinate + * space will be used + * @param displayRect defines where on the display will layerStackRect be + * mapped to. displayRect is specified post-orientation, that is + * it uses the orientation seen by the end-user + */ + public final void setProjectionInTransactionLocked(int orientation, + Rect layerStackRect, Rect displayRect) { + if (mCurrentOrientation != orientation + || mCurrentLayerStackRect == null + || !mCurrentLayerStackRect.equals(layerStackRect) + || mCurrentDisplayRect == null + || !mCurrentDisplayRect.equals(displayRect)) { + mCurrentOrientation = orientation; + + if (mCurrentLayerStackRect == null) { + mCurrentLayerStackRect = new Rect(); + } + mCurrentLayerStackRect.set(layerStackRect); + + if (mCurrentDisplayRect == null) { + mCurrentDisplayRect = new Rect(); + } + mCurrentDisplayRect.set(displayRect); + + Surface.setDisplayProjection(mDisplayToken, + orientation, layerStackRect, displayRect); + } + } + + /** + * Sets the display surface while in a transaction. + */ + public final void setSurfaceInTransactionLocked(Surface surface) { + if (mCurrentSurface != surface) { + mCurrentSurface = surface; + Surface.setDisplaySurface(mDisplayToken, surface); + } + } + + /** + * Populates the specified viewport object with orientation, + * physical and logical rects based on the display's current projection. + */ + public final void populateViewportLocked(DisplayViewport viewport) { + viewport.orientation = mCurrentOrientation; + + if (mCurrentLayerStackRect != null) { + viewport.logicalFrame.set(mCurrentLayerStackRect); + } else { + viewport.logicalFrame.setEmpty(); + } + + if (mCurrentDisplayRect != null) { + viewport.physicalFrame.set(mCurrentDisplayRect); + } else { + viewport.physicalFrame.setEmpty(); + } + + boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90 + || mCurrentOrientation == Surface.ROTATION_270); + DisplayDeviceInfo info = getDisplayDeviceInfoLocked(); + viewport.deviceWidth = isRotated ? info.height : info.width; + viewport.deviceHeight = isRotated ? info.width : info.height; + } + + /** + * Dumps the local state of the display device. + * Does not need to dump the display device info because that is already dumped elsewhere. + */ + public void dumpLocked(PrintWriter pw) { + pw.println("mAdapter=" + mDisplayAdapter.getName()); + pw.println("mDisplayToken=" + mDisplayToken); + pw.println("mCurrentLayerStack=" + mCurrentLayerStack); + pw.println("mCurrentOrientation=" + mCurrentOrientation); + pw.println("mCurrentLayerStackRect=" + mCurrentLayerStackRect); + pw.println("mCurrentDisplayRect=" + mCurrentDisplayRect); + pw.println("mCurrentSurface=" + mCurrentSurface); + } +} diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java new file mode 100644 index 0000000..f0cd0f5 --- /dev/null +++ b/services/java/com/android/server/display/DisplayDeviceInfo.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.util.DisplayMetrics; + +import libcore.util.Objects; + +/** + * Describes the characteristics of a physical display device. + */ +final class DisplayDeviceInfo { + /** + * Flag: Indicates that this display device should be considered the default display + * device of the system. + */ + public static final int FLAG_DEFAULT_DISPLAY = 1 << 0; + + /** + * Flag: Indicates that this display device can rotate to show contents in a + * different orientation. Otherwise the rotation is assumed to be fixed in the + * natural orientation and the display manager should transform the content to fit. + */ + public static final int FLAG_SUPPORTS_ROTATION = 1 << 1; + + /** + * Flag: Indicates that this display device can show secure surfaces. + */ + public static final int FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT = 1 << 2; + + /** + * Touch attachment: Display does not receive touch. + */ + public static final int TOUCH_NONE = 0; + + /** + * Touch attachment: Touch input is via the internal interface. + */ + public static final int TOUCH_INTERNAL = 1; + + /** + * Touch attachment: Touch input is via an external interface, such as USB. + */ + public static final int TOUCH_EXTERNAL = 2; + + /** + * Gets the name of the display device, which may be derived from + * EDID or other sources. The name may be displayed to the user. + */ + public String name; + + /** + * The width of the display in its natural orientation, in pixels. + * This value is not affected by display rotation. + */ + public int width; + + /** + * The height of the display in its natural orientation, in pixels. + * This value is not affected by display rotation. + */ + public int height; + + /** + * The refresh rate of the display. + */ + public float refreshRate; + + /** + * The nominal apparent density of the display in DPI used for layout calculations. + * This density is sensitive to the viewing distance. A big TV and a tablet may have + * the same apparent density even though the pixels on the TV are much bigger than + * those on the tablet. + */ + public int densityDpi; + + /** + * The physical density of the display in DPI in the X direction. + * This density should specify the physical size of each pixel. + */ + public float xDpi; + + /** + * The physical density of the display in DPI in the X direction. + * This density should specify the physical size of each pixel. + */ + public float yDpi; + + /** + * Display flags. + */ + public int flags; + + /** + * The touch attachment, per {@link DisplayViewport#touch}. + */ + public int touch; + + public void setAssumedDensityForExternalDisplay(int width, int height) { + densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080; + // Technically, these values should be smaller than the apparent density + // but we don't know the physical size of the display. + xDpi = densityDpi; + yDpi = densityDpi; + } + + @Override + public boolean equals(Object o) { + return o instanceof DisplayDeviceInfo && equals((DisplayDeviceInfo)o); + } + + public boolean equals(DisplayDeviceInfo other) { + return other != null + && Objects.equal(name, other.name) + && width == other.width + && height == other.height + && refreshRate == other.refreshRate + && densityDpi == other.densityDpi + && xDpi == other.xDpi + && yDpi == other.yDpi + && flags == other.flags + && touch == other.touch; + } + + @Override + public int hashCode() { + return 0; // don't care + } + + public void copyFrom(DisplayDeviceInfo other) { + name = other.name; + width = other.width; + height = other.height; + refreshRate = other.refreshRate; + densityDpi = other.densityDpi; + xDpi = other.xDpi; + yDpi = other.yDpi; + flags = other.flags; + touch = other.touch; + } + + // For debugging purposes + @Override + public String toString() { + return "DisplayDeviceInfo{\"" + name + "\": " + width + " x " + height + ", " + refreshRate + " fps, " + + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi" + + ", touch " + touchToString(touch) + flagsToString(flags) + "}"; + } + + private static String touchToString(int touch) { + switch (touch) { + case TOUCH_NONE: + return "NONE"; + case TOUCH_INTERNAL: + return "INTERNAL"; + case TOUCH_EXTERNAL: + return "EXTERNAL"; + default: + return Integer.toString(touch); + } + } + + private static String flagsToString(int flags) { + StringBuilder msg = new StringBuilder(); + if ((flags & FLAG_DEFAULT_DISPLAY) != 0) { + msg.append(", FLAG_DEFAULT_DISPLAY"); + } + if ((flags & FLAG_SUPPORTS_ROTATION) != 0) { + msg.append(", FLAG_SUPPORTS_ROTATION"); + } + if ((flags & FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT) != 0) { + msg.append(", FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT"); + } + return msg.toString(); + } +} diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java new file mode 100644 index 0000000..39f2418 --- /dev/null +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import com.android.internal.util.IndentingPrintWriter; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.display.DisplayManagerGlobal; +import android.hardware.display.IDisplayManager; +import android.hardware.display.IDisplayManagerCallback; +import android.hardware.display.WifiDisplayStatus; +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.os.SystemProperties; +import android.util.Slog; +import android.util.SparseArray; +import android.view.Display; +import android.view.DisplayInfo; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Manages attached displays. + * <p> + * The {@link DisplayManagerService} manages the global lifecycle of displays, + * decides how to configure logical displays based on the physical display devices currently + * attached, sends notifications to the system and to applications when the state + * changes, and so on. + * </p><p> + * The display manager service relies on a collection of {@link DisplayAdapter} components, + * for discovering and configuring physical display devices attached to the system. + * There are separate display adapters for each manner that devices are attached: + * one display adapter for built-in local displays, one for simulated non-functional + * displays when the system is headless, one for simulated overlay displays used for + * development, one for wifi displays, etc. + * </p><p> + * Display adapters are only weakly coupled to the display manager service. + * Display adapters communicate changes in display device state to the display manager + * service asynchronously via a {@link DisplayAdapter.Listener} registered + * by the display manager service. This separation of concerns is important for + * two main reasons. First, it neatly encapsulates the responsibilities of these + * two classes: display adapters handle individual display devices whereas + * the display manager service handles the global state. Second, it eliminates + * the potential for deadlocks resulting from asynchronous display device discovery. + * </p> + * + * <h3>Synchronization</h3> + * <p> + * Because the display manager may be accessed by multiple threads, the synchronization + * story gets a little complicated. In particular, the window manager may call into + * the display manager while holding a surface transaction with the expectation that + * it can apply changes immediately. Unfortunately, that means we can't just do + * everything asynchronously (*grump*). + * </p><p> + * To make this work, all of the objects that belong to the display manager must + * use the same lock. We call this lock the synchronization root and it has a unique + * type {@link DisplayManagerService.SyncRoot}. Methods that require this lock are + * named with the "Locked" suffix. + * </p><p> + * Where things get tricky is that the display manager is not allowed to make + * any potentially reentrant calls, especially into the window manager. We generally + * avoid this by making all potentially reentrant out-calls asynchronous. + * </p> + */ +public final class DisplayManagerService extends IDisplayManager.Stub { + private static final String TAG = "DisplayManagerService"; + private static final boolean DEBUG = false; + + private static final String SYSTEM_HEADLESS = "ro.config.headless"; + private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000; + + private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER = 1; + private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2; + private static final int MSG_DELIVER_DISPLAY_EVENT = 3; + private static final int MSG_REQUEST_TRAVERSAL = 4; + private static final int MSG_UPDATE_VIEWPORT = 5; + + private final Context mContext; + private final boolean mHeadless; + private final DisplayManagerHandler mHandler; + private final Handler mUiHandler; + private final DisplayAdapterListener mDisplayAdapterListener; + private WindowManagerFuncs mWindowManagerFuncs; + private InputManagerFuncs mInputManagerFuncs; + + // The synchronization root for the display manager. + // This lock guards most of the display manager's state. + private final SyncRoot mSyncRoot = new SyncRoot(); + + // True if in safe mode. + // This option may disable certain display adapters. + public boolean mSafeMode; + + // True if we are in a special boot mode where only core applications and + // services should be started. This option may disable certain display adapters. + public boolean mOnlyCore; + + // All callback records indexed by calling process id. + public final SparseArray<CallbackRecord> mCallbacks = + new SparseArray<CallbackRecord>(); + + // List of all currently registered display adapters. + private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>(); + + // List of all currently connected display devices. + private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>(); + + // List of all removed display devices. + private final ArrayList<DisplayDevice> mRemovedDisplayDevices = new ArrayList<DisplayDevice>(); + + // List of all logical displays indexed by logical display id. + private final SparseArray<LogicalDisplay> mLogicalDisplays = + new SparseArray<LogicalDisplay>(); + private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1; + + // Set to true when there are pending display changes that have yet to be applied + // to the surface flinger state. + private boolean mPendingTraversal; + + // The Wifi display adapter, or null if not registered. + private WifiDisplayAdapter mWifiDisplayAdapter; + + // Viewports of the default display and the display that should receive touch + // input from an external source. Used by the input system. + private final DisplayViewport mDefaultViewport = new DisplayViewport(); + private final DisplayViewport mExternalTouchViewport = new DisplayViewport(); + + // Temporary callback list, used when sending display events to applications. + // May be used outside of the lock but only on the handler thread. + private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>(); + + // Temporary display info, used for comparing display configurations. + private final DisplayInfo mTempDisplayInfo = new DisplayInfo(); + + // Temporary viewports, used when sending new viewport information to the + // input system. May be used outside of the lock but only on the handler thread. + private final DisplayViewport mTempDefaultViewport = new DisplayViewport(); + private final DisplayViewport mTempExternalTouchViewport = new DisplayViewport(); + + public DisplayManagerService(Context context, Handler mainHandler, Handler uiHandler) { + mContext = context; + mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1"); + + mHandler = new DisplayManagerHandler(mainHandler.getLooper()); + mUiHandler = uiHandler; + mDisplayAdapterListener = new DisplayAdapterListener(); + + mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER); + } + + /** + * Pauses the boot process to wait for the first display to be initialized. + */ + public boolean waitForDefaultDisplay() { + synchronized (mSyncRoot) { + long timeout = SystemClock.uptimeMillis() + WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT; + while (mLogicalDisplays.get(Display.DEFAULT_DISPLAY) == null) { + long delay = timeout - SystemClock.uptimeMillis(); + if (delay <= 0) { + return false; + } + if (DEBUG) { + Slog.d(TAG, "waitForDefaultDisplay: waiting, timeout=" + delay); + } + try { + mSyncRoot.wait(delay); + } catch (InterruptedException ex) { + } + } + } + return true; + } + + /** + * Called during initialization to associate the display manager with the + * window manager. + */ + public void setWindowManager(WindowManagerFuncs windowManagerFuncs) { + synchronized (mSyncRoot) { + mWindowManagerFuncs = windowManagerFuncs; + scheduleTraversalLocked(); + } + } + + /** + * Called during initialization to associate the display manager with the + * input manager. + */ + public void setInputManager(InputManagerFuncs inputManagerFuncs) { + synchronized (mSyncRoot) { + mInputManagerFuncs = inputManagerFuncs; + scheduleTraversalLocked(); + } + } + + /** + * Called when the system is ready to go. + */ + public void systemReady(boolean safeMode, boolean onlyCore) { + synchronized (mSyncRoot) { + mSafeMode = safeMode; + mOnlyCore = onlyCore; + } + + mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS); + } + + /** + * Returns true if the device is headless. + * + * @return True if the device is headless. + */ + public boolean isHeadless() { + return mHeadless; + } + + /** + * Overrides the display information of a particular logical display. + * This is used by the window manager to control the size and characteristics + * of the default display. It is expected to apply the requested change + * to the display information synchronously so that applications will immediately + * observe the new state. + * + * @param displayId The logical display id. + * @param info The new data to be stored. + */ + public void setDisplayInfoOverrideFromWindowManager( + int displayId, DisplayInfo info) { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); + display.setDisplayInfoOverrideFromWindowManagerLocked(info); + if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) { + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + scheduleTraversalLocked(); + } + } + } + } + + /** + * Called by the window manager to perform traversals while holding a + * surface flinger transaction. + */ + public void performTraversalInTransactionFromWindowManager() { + synchronized (mSyncRoot) { + if (!mPendingTraversal) { + return; + } + mPendingTraversal = false; + + performTraversalInTransactionLocked(); + } + } + + /** + * Returns information about the specified logical display. + * + * @param displayId The logical display id. + * @return The logical display info, or null if the display does not exist. The + * returned object must be treated as immutable. + */ + @Override // Binder call + public DisplayInfo getDisplayInfo(int displayId) { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + return display.getDisplayInfoLocked(); + } + return null; + } + } + + /** + * Returns the list of all display ids. + */ + @Override // Binder call + public int[] getDisplayIds() { + synchronized (mSyncRoot) { + final int count = mLogicalDisplays.size(); + int[] displayIds = new int[count]; + for (int i = 0; i < count; i++) { + displayIds[i] = mLogicalDisplays.keyAt(i); + } + return displayIds; + } + } + + @Override // Binder call + public void registerCallback(IDisplayManagerCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mSyncRoot) { + int callingPid = Binder.getCallingPid(); + if (mCallbacks.get(callingPid) != null) { + throw new SecurityException("The calling process has already " + + "registered an IDisplayManagerCallback."); + } + + CallbackRecord record = new CallbackRecord(callingPid, callback); + try { + IBinder binder = callback.asBinder(); + binder.linkToDeath(record, 0); + } catch (RemoteException ex) { + // give up + throw new RuntimeException(ex); + } + + mCallbacks.put(callingPid, record); + } + } + + private void onCallbackDied(int pid) { + synchronized (mSyncRoot) { + mCallbacks.remove(pid); + } + } + + @Override // Binder call + public void scanWifiDisplays() { + if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestScanLocked(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void connectWifiDisplay(String address) { + if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission"); + } + if (address == null) { + throw new IllegalArgumentException("address must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestConnectLocked(address); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void disconnectWifiDisplay() { + if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestDisconnectLocked(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public WifiDisplayStatus getWifiDisplayStatus() { + if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + return mWifiDisplayAdapter.getWifiDisplayStatusLocked(); + } else { + return new WifiDisplayStatus(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void registerDefaultDisplayAdapter() { + // Register default display adapter. + synchronized (mSyncRoot) { + if (mHeadless) { + registerDisplayAdapterLocked(new HeadlessDisplayAdapter( + mSyncRoot, mContext, mHandler, mDisplayAdapterListener)); + } else { + registerDisplayAdapterLocked(new LocalDisplayAdapter( + mSyncRoot, mContext, mHandler, mDisplayAdapterListener)); + } + } + } + + private void registerAdditionalDisplayAdapters() { + synchronized (mSyncRoot) { + if (shouldRegisterNonEssentialDisplayAdaptersLocked()) { + registerDisplayAdapterLocked(new OverlayDisplayAdapter( + mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler)); + mWifiDisplayAdapter = new WifiDisplayAdapter( + mSyncRoot, mContext, mHandler, mDisplayAdapterListener); + registerDisplayAdapterLocked(mWifiDisplayAdapter); + } + } + } + + private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() { + // In safe mode, we disable non-essential display adapters to give the user + // an opportunity to fix broken settings or other problems that might affect + // system stability. + // In only-core mode, we disable non-essential display adapters to minimize + // the number of dependencies that are started while in this mode and to + // prevent problems that might occur due to the device being encrypted. + return !mSafeMode && !mOnlyCore; + } + + private void registerDisplayAdapterLocked(DisplayAdapter adapter) { + mDisplayAdapters.add(adapter); + adapter.registerLocked(); + } + + private void handleDisplayDeviceAdded(DisplayDevice device) { + synchronized (mSyncRoot) { + if (mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to add already added display device: " + + device.getDisplayDeviceInfoLocked()); + return; + } + + mDisplayDevices.add(device); + addLogicalDisplayLocked(device); + scheduleTraversalLocked(); + } + } + + private void handleDisplayDeviceChanged(DisplayDevice device) { + synchronized (mSyncRoot) { + if (!mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to change non-existent display device: " + + device.getDisplayDeviceInfoLocked()); + return; + } + + device.applyPendingDisplayDeviceInfoChangesLocked(); + if (updateLogicalDisplaysLocked()) { + scheduleTraversalLocked(); + } + } + } + + private void handleDisplayDeviceRemoved(DisplayDevice device) { + synchronized (mSyncRoot) { + if (!mDisplayDevices.remove(device)) { + Slog.w(TAG, "Attempted to remove non-existent display device: " + + device.getDisplayDeviceInfoLocked()); + return; + } + + mRemovedDisplayDevices.add(device); + updateLogicalDisplaysLocked(); + scheduleTraversalLocked(); + } + } + + // Adds a new logical display based on the given display device. + // Sends notifications if needed. + private void addLogicalDisplayLocked(DisplayDevice device) { + DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked(); + boolean isDefault = (deviceInfo.flags + & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0; + if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) { + Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo); + isDefault = false; + } + + final int displayId = assignDisplayIdLocked(isDefault); + final int layerStack = assignLayerStackLocked(displayId); + + LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); + display.updateLocked(mDisplayDevices); + if (!display.isValidLocked()) { + // This should never happen currently. + Slog.w(TAG, "Ignoring display device because the logical display " + + "created from it was not considered valid: " + deviceInfo); + return; + } + + mLogicalDisplays.put(displayId, display); + + // Wake up waitForDefaultDisplay. + if (isDefault) { + mSyncRoot.notifyAll(); + } + + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED); + } + + private int assignDisplayIdLocked(boolean isDefault) { + return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++; + } + + private int assignLayerStackLocked(int displayId) { + // Currently layer stacks and display ids are the same. + // This need not be the case. + return displayId; + } + + // Updates all existing logical displays given the current set of display devices. + // Removes invalid logical displays. + // Sends notifications if needed. + private boolean updateLogicalDisplaysLocked() { + boolean changed = false; + for (int i = mLogicalDisplays.size(); i-- > 0; ) { + final int displayId = mLogicalDisplays.keyAt(i); + LogicalDisplay display = mLogicalDisplays.valueAt(i); + + mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); + display.updateLocked(mDisplayDevices); + if (!display.isValidLocked()) { + mLogicalDisplays.removeAt(i); + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + changed = true; + } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) { + sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); + changed = true; + } + } + return changed; + } + + private void performTraversalInTransactionLocked() { + // Perform one last traversal for each removed display device. + final int removedCount = mRemovedDisplayDevices.size(); + for (int i = 0; i < removedCount; i++) { + DisplayDevice device = mRemovedDisplayDevices.get(i); + device.performTraversalInTransactionLocked(); + } + mRemovedDisplayDevices.clear(); + + // Clear all viewports before configuring displays so that we can keep + // track of which ones we have configured. + clearViewportsLocked(); + + // Configure each display device. + final int count = mDisplayDevices.size(); + for (int i = 0; i < count; i++) { + DisplayDevice device = mDisplayDevices.get(i); + configureDisplayInTransactionLocked(device); + device.performTraversalInTransactionLocked(); + } + + // Tell the input system about these new viewports. + if (mInputManagerFuncs != null) { + mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT); + } + } + + /** + * Tells the display manager whether there is interesting unique content on the + * specified logical display. This is used to control automatic mirroring. + * <p> + * If the display has unique content, then the display manager arranges for it + * to be presented on a physical display if appropriate. Otherwise, the display manager + * may choose to make the physical display mirror some other logical display. + * </p> + * + * @param displayId The logical display id to update. + * @param hasContent True if the logical display has content. + */ + public void setDisplayHasContent(int displayId, boolean hasContent) { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null && display.hasContentLocked() != hasContent) { + display.setHasContentLocked(hasContent); + scheduleTraversalLocked(); + } + } + } + + private void clearViewportsLocked() { + mDefaultViewport.valid = false; + mExternalTouchViewport.valid = false; + } + + private void configureDisplayInTransactionLocked(DisplayDevice device) { + // Find the logical display that the display device is showing. + LogicalDisplay display = findLogicalDisplayForDeviceLocked(device); + if (display != null && !display.hasContentLocked()) { + display = null; + } + if (display == null) { + display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); + } + + // Apply the logical display configuration to the display device. + if (display == null) { + // TODO: no logical display for the device, blank it + Slog.w(TAG, "Missing logical display to use for physical display device: " + + device.getDisplayDeviceInfoLocked()); + return; + } else { + display.configureDisplayInTransactionLocked(device); + } + + // Update the viewports if needed. + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + if (!mDefaultViewport.valid + && (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) { + setViewportLocked(mDefaultViewport, display, device); + } + if (!mExternalTouchViewport.valid + && info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) { + setViewportLocked(mExternalTouchViewport, display, device); + } + } + + private static void setViewportLocked(DisplayViewport viewport, + LogicalDisplay display, DisplayDevice device) { + viewport.valid = true; + viewport.displayId = display.getDisplayIdLocked(); + device.populateViewportLocked(viewport); + } + + private LogicalDisplay findLogicalDisplayForDeviceLocked(DisplayDevice device) { + final int count = mLogicalDisplays.size(); + for (int i = 0; i < count; i++) { + LogicalDisplay display = mLogicalDisplays.valueAt(i); + if (display.getPrimaryDisplayDeviceLocked() == device) { + return display; + } + } + return null; + } + + private void sendDisplayEventLocked(int displayId, int event) { + Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); + mHandler.sendMessage(msg); + } + + // Requests that performTraversalsInTransactionFromWindowManager be called at a + // later time to apply changes to surfaces and displays. + private void scheduleTraversalLocked() { + if (!mPendingTraversal && mWindowManagerFuncs != null) { + mPendingTraversal = true; + mHandler.sendEmptyMessage(MSG_REQUEST_TRAVERSAL); + } + } + + // Runs on Handler thread. + // Delivers display event notifications to callbacks. + private void deliverDisplayEvent(int displayId, int event) { + if (DEBUG) { + Slog.d(TAG, "Delivering display event: displayId=" + + displayId + ", event=" + event); + } + + // Grab the lock and copy the callbacks. + final int count; + synchronized (mSyncRoot) { + count = mCallbacks.size(); + mTempCallbacks.clear(); + for (int i = 0; i < count; i++) { + mTempCallbacks.add(mCallbacks.valueAt(i)); + } + } + + // After releasing the lock, send the notifications out. + for (int i = 0; i < count; i++) { + mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event); + } + mTempCallbacks.clear(); + } + + @Override // Binder call + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + if (mContext == null + || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump DisplayManager from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("DISPLAY MANAGER (dumpsys display)"); + pw.println(" mHeadless=" + mHeadless); + + synchronized (mSyncRoot) { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.increaseIndent(); + + pw.println(); + pw.println("Display Adapters: size=" + mDisplayAdapters.size()); + for (DisplayAdapter adapter : mDisplayAdapters) { + pw.println(" " + adapter.getName()); + adapter.dumpLocked(ipw); + } + + pw.println(); + pw.println("Display Devices: size=" + mDisplayDevices.size()); + for (DisplayDevice device : mDisplayDevices) { + pw.println(" " + device.getDisplayDeviceInfoLocked()); + device.dumpLocked(ipw); + } + + final int logicalDisplayCount = mLogicalDisplays.size(); + pw.println(); + pw.println("Logical Displays: size=" + logicalDisplayCount); + for (int i = 0; i < logicalDisplayCount; i++) { + int displayId = mLogicalDisplays.keyAt(i); + LogicalDisplay display = mLogicalDisplays.valueAt(i); + pw.println(" Display " + displayId + ":"); + display.dumpLocked(ipw); + } + + pw.println(); + pw.println("Default viewport: " + mDefaultViewport); + pw.println("External touch viewport: " + mExternalTouchViewport); + } + } + + /** + * This is the object that everything in the display manager locks on. + * We make it an inner class within the {@link DisplayManagerService} to so that it is + * clear that the object belongs to the display manager service and that it is + * a unique object with a special purpose. + */ + public static final class SyncRoot { + } + + /** + * Private interface to the window manager. + */ + public interface WindowManagerFuncs { + /** + * Request that the window manager call + * {@link #performTraversalInTransactionFromWindowManager} within a surface + * transaction at a later time. + */ + void requestTraversal(); + } + + /** + * Private interface to the input manager. + */ + public interface InputManagerFuncs { + /** + * Sets information about the displays as needed by the input system. + * The input system should copy this information if required. + */ + void setDisplayViewports(DisplayViewport defaultViewport, + DisplayViewport externalTouchViewport); + } + + private final class DisplayManagerHandler extends Handler { + public DisplayManagerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER: + registerDefaultDisplayAdapter(); + break; + + case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS: + registerAdditionalDisplayAdapters(); + break; + + case MSG_DELIVER_DISPLAY_EVENT: + deliverDisplayEvent(msg.arg1, msg.arg2); + break; + + case MSG_REQUEST_TRAVERSAL: + mWindowManagerFuncs.requestTraversal(); + break; + + case MSG_UPDATE_VIEWPORT: { + synchronized (mSyncRoot) { + mTempDefaultViewport.copyFrom(mDefaultViewport); + mTempExternalTouchViewport.copyFrom(mExternalTouchViewport); + } + mInputManagerFuncs.setDisplayViewports( + mTempDefaultViewport, mTempExternalTouchViewport); + break; + } + } + } + } + + private final class DisplayAdapterListener implements DisplayAdapter.Listener { + @Override + public void onDisplayDeviceEvent(DisplayDevice device, int event) { + switch (event) { + case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED: + handleDisplayDeviceAdded(device); + break; + + case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED: + handleDisplayDeviceChanged(device); + break; + + case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED: + handleDisplayDeviceRemoved(device); + break; + } + } + + @Override + public void onTraversalRequested() { + synchronized (mSyncRoot) { + scheduleTraversalLocked(); + } + } + } + + private final class CallbackRecord implements DeathRecipient { + private final int mPid; + private final IDisplayManagerCallback mCallback; + + public CallbackRecord(int pid, IDisplayManagerCallback callback) { + mPid = pid; + mCallback = callback; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Display listener for pid " + mPid + " died."); + } + onCallbackDied(mPid); + } + + public void notifyDisplayEventAsync(int displayId, int event) { + try { + mCallback.onDisplayEvent(displayId, event); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + + mPid + " that displays changed, assuming it died.", ex); + binderDied(); + } + } + } +} diff --git a/services/java/com/android/server/display/DisplayViewport.java b/services/java/com/android/server/display/DisplayViewport.java new file mode 100644 index 0000000..5080556 --- /dev/null +++ b/services/java/com/android/server/display/DisplayViewport.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.graphics.Rect; + +/** + * Describes how the pixels of physical display device reflects the content of + * a logical display. + * <p> + * This information is used by the input system to translate touch input from + * physical display coordinates into logical display coordinates. + * </p> + */ +public final class DisplayViewport { + // True if this viewport is valid. + public boolean valid; + + // The logical display id. + public int displayId; + + // The rotation applied to the physical coordinate system. + public int orientation; + + // The portion of the logical display that are presented on this physical display. + public final Rect logicalFrame = new Rect(); + + // The portion of the (rotated) physical display that shows the logical display contents. + // The relation between logical and physical frame defines how the coordinate system + // should be scaled or translated after rotation. + public final Rect physicalFrame = new Rect(); + + // The full width and height of the display device, rotated in the same + // manner as physicalFrame. This expresses the full native size of the display device. + // The physical frame should usually fit within this area. + public int deviceWidth; + public int deviceHeight; + + public void copyFrom(DisplayViewport viewport) { + valid = viewport.valid; + displayId = viewport.displayId; + orientation = viewport.orientation; + logicalFrame.set(viewport.logicalFrame); + physicalFrame.set(viewport.physicalFrame); + deviceWidth = viewport.deviceWidth; + deviceHeight = viewport.deviceHeight; + } + + // For debugging purposes. + @Override + public String toString() { + return "DisplayViewport{valid=" + valid + + ", displayId=" + displayId + + ", orientation=" + orientation + + ", logicalFrame=" + logicalFrame + + ", physicalFrame=" + physicalFrame + + ", deviceWidth=" + deviceWidth + + ", deviceHeight=" + deviceHeight + + "}"; + } +} diff --git a/services/java/com/android/server/display/HeadlessDisplayAdapter.java b/services/java/com/android/server/display/HeadlessDisplayAdapter.java new file mode 100644 index 0000000..f3bec1d --- /dev/null +++ b/services/java/com/android/server/display/HeadlessDisplayAdapter.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.content.Context; +import android.os.Handler; +import android.util.DisplayMetrics; + +/** + * Provides a fake default display for headless systems. + * <p> + * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. + * </p> + */ +final class HeadlessDisplayAdapter extends DisplayAdapter { + private static final String TAG = "HeadlessDisplayAdapter"; + + public HeadlessDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, + Context context, Handler handler, Listener listener) { + super(syncRoot, context, handler, listener, TAG); + } + + @Override + public void registerLocked() { + super.registerLocked(); + sendDisplayDeviceEventLocked(new HeadlessDisplayDevice(), DISPLAY_DEVICE_EVENT_ADDED); + } + + private final class HeadlessDisplayDevice extends DisplayDevice { + private DisplayDeviceInfo mInfo; + + public HeadlessDisplayDevice() { + super(HeadlessDisplayAdapter.this, null); + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + if (mInfo == null) { + mInfo = new DisplayDeviceInfo(); + mInfo.name = getContext().getResources().getString( + com.android.internal.R.string.display_manager_built_in_display_name); + mInfo.width = 640; + mInfo.height = 480; + mInfo.refreshRate = 60; + mInfo.densityDpi = DisplayMetrics.DENSITY_DEFAULT; + mInfo.xDpi = 160; + mInfo.yDpi = 160; + mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY + | DisplayDeviceInfo.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT; + mInfo.touch = DisplayDeviceInfo.TOUCH_NONE; + } + return mInfo; + } + } +} diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java new file mode 100644 index 0000000..eab4c9a --- /dev/null +++ b/services/java/com/android/server/display/LocalDisplayAdapter.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.content.Context; +import android.os.Handler; +import android.os.IBinder; +import android.util.SparseArray; +import android.view.Surface; +import android.view.Surface.PhysicalDisplayInfo; + +import java.io.PrintWriter; + +/** + * A display adapter for the local displays managed by Surface Flinger. + * <p> + * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. + * </p> + */ +final class LocalDisplayAdapter extends DisplayAdapter { + private static final String TAG = "LocalDisplayAdapter"; + + private static final int[] BUILT_IN_DISPLAY_IDS_TO_SCAN = new int[] { + Surface.BUILT_IN_DISPLAY_ID_MAIN, + Surface.BUILT_IN_DISPLAY_ID_HDMI, + }; + + private final SparseArray<LocalDisplayDevice> mDevices = + new SparseArray<LocalDisplayDevice>(); + + private final PhysicalDisplayInfo mTempPhys = new PhysicalDisplayInfo(); + + public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, + Context context, Handler handler, Listener listener) { + super(syncRoot, context, handler, listener, TAG); + } + + @Override + public void registerLocked() { + // TODO: listen for notifications from Surface Flinger about + // built-in displays being added or removed and rescan as needed. + super.registerLocked(); + scanDisplaysLocked(); + } + + private void scanDisplaysLocked() { + for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) { + IBinder displayToken = Surface.getBuiltInDisplay(builtInDisplayId); + if (displayToken != null && Surface.getDisplayInfo(displayToken, mTempPhys)) { + LocalDisplayDevice device = mDevices.get(builtInDisplayId); + if (device == null) { + // Display was added. + device = new LocalDisplayDevice(displayToken, builtInDisplayId, mTempPhys); + mDevices.put(builtInDisplayId, device); + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED); + } else if (device.updatePhysicalDisplayInfoLocked(mTempPhys)) { + // Display properties changed. + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED); + } + } else { + LocalDisplayDevice device = mDevices.get(builtInDisplayId); + if (device != null) { + // Display was removed. + mDevices.remove(builtInDisplayId); + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED); + } + } + } + } + + private final class LocalDisplayDevice extends DisplayDevice { + private final int mBuiltInDisplayId; + private final PhysicalDisplayInfo mPhys; + + private DisplayDeviceInfo mInfo; + private boolean mHavePendingChanges; + + public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId, + PhysicalDisplayInfo phys) { + super(LocalDisplayAdapter.this, displayToken); + mBuiltInDisplayId = builtInDisplayId; + mPhys = new PhysicalDisplayInfo(phys); + } + + public boolean updatePhysicalDisplayInfoLocked(PhysicalDisplayInfo phys) { + if (!mPhys.equals(phys)) { + mPhys.copyFrom(phys); + mHavePendingChanges = true; + return true; + } + return false; + } + + @Override + public void applyPendingDisplayDeviceInfoChangesLocked() { + if (mHavePendingChanges) { + mInfo = null; + mHavePendingChanges = false; + } + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + if (mInfo == null) { + mInfo = new DisplayDeviceInfo(); + mInfo.width = mPhys.width; + mInfo.height = mPhys.height; + mInfo.refreshRate = mPhys.refreshRate; + if (mBuiltInDisplayId == Surface.BUILT_IN_DISPLAY_ID_MAIN) { + mInfo.name = getContext().getResources().getString( + com.android.internal.R.string.display_manager_built_in_display_name); + mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY + | DisplayDeviceInfo.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT + | DisplayDeviceInfo.FLAG_SUPPORTS_ROTATION; + mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f); + mInfo.xDpi = mPhys.xDpi; + mInfo.yDpi = mPhys.yDpi; + mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; + } else { + mInfo.name = getContext().getResources().getString( + com.android.internal.R.string.display_manager_hdmi_display_name); + mInfo.flags = DisplayDeviceInfo.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT; + mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; + mInfo.setAssumedDensityForExternalDisplay(mPhys.width, mPhys.height); + } + } + return mInfo; + } + + @Override + public void dumpLocked(PrintWriter pw) { + super.dumpLocked(pw); + pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId); + pw.println("mPhys=" + mPhys); + } + } +} diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java new file mode 100644 index 0000000..3607de1 --- /dev/null +++ b/services/java/com/android/server/display/LogicalDisplay.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.graphics.Rect; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.Surface; + +import java.io.PrintWriter; +import java.util.List; + +import libcore.util.Objects; + +/** + * Describes how a logical display is configured. + * <p> + * At this time, we only support logical displays that are coupled to a particular + * primary display device from which the logical display derives its basic properties + * such as its size, density and refresh rate. + * </p><p> + * A logical display may be mirrored onto multiple display devices in addition to its + * primary display device. Note that the contents of a logical display may not + * always be visible, even on its primary display device, such as in the case where + * the primary display device is currently mirroring content from a different + * logical display. + * </p><p> + * This object is designed to encapsulate as much of the policy of logical + * displays as possible. The idea is to make it easy to implement new kinds of + * logical displays mostly by making local changes to this class. + * </p><p> + * Note: The display manager architecture does not actually require logical displays + * to be associated with any individual display device. Logical displays and + * display devices are orthogonal concepts. Some mapping will exist between + * logical displays and display devices but it can be many-to-many and + * and some might have no relation at all. + * </p><p> + * Logical displays are guarded by the {@link DisplayManagerService.SyncRoot} lock. + * </p> + */ +final class LogicalDisplay { + private final DisplayInfo mBaseDisplayInfo = new DisplayInfo(); + + private final int mDisplayId; + private final int mLayerStack; + private DisplayInfo mOverrideDisplayInfo; // set by the window manager + private DisplayInfo mInfo; + + // The display device that this logical display is based on and which + // determines the base metrics that it uses. + private DisplayDevice mPrimaryDisplayDevice; + private DisplayDeviceInfo mPrimaryDisplayDeviceInfo; + + // True if the logical display has unique content. + private boolean mHasContent; + + // Temporary rectangle used when needed. + private final Rect mTempLayerStackRect = new Rect(); + private final Rect mTempDisplayRect = new Rect(); + + public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { + mDisplayId = displayId; + mLayerStack = layerStack; + mPrimaryDisplayDevice = primaryDisplayDevice; + } + + /** + * Gets the logical display id of this logical display. + * + * @return The logical display id. + */ + public int getDisplayIdLocked() { + return mDisplayId; + } + + /** + * Gets the primary display device associated with this logical display. + * + * @return The primary display device. + */ + public DisplayDevice getPrimaryDisplayDeviceLocked() { + return mPrimaryDisplayDevice; + } + + /** + * Gets information about the logical display. + * + * @return The device info, which should be treated as immutable by the caller. + * The logical display should allocate a new display info object whenever + * the data changes. + */ + public DisplayInfo getDisplayInfoLocked() { + if (mInfo == null) { + mInfo = new DisplayInfo(); + if (mOverrideDisplayInfo != null) { + mInfo.copyFrom(mOverrideDisplayInfo); + mInfo.layerStack = mBaseDisplayInfo.layerStack; + mInfo.name = mBaseDisplayInfo.name; + } else { + mInfo.copyFrom(mBaseDisplayInfo); + } + } + return mInfo; + } + + /** + * Sets overridden logical display information from the window manager. + * This method can be used to adjust application insets, rotation, and other + * properties that the window manager takes care of. + * + * @param info The logical display information, may be null. + */ + public void setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) { + if (info != null) { + if (mOverrideDisplayInfo == null) { + mOverrideDisplayInfo = new DisplayInfo(info); + mInfo = null; + } else if (!mOverrideDisplayInfo.equals(info)) { + mOverrideDisplayInfo.copyFrom(info); + mInfo = null; + } + } else if (mOverrideDisplayInfo != null) { + mOverrideDisplayInfo = null; + mInfo = null; + } + } + + /** + * Returns true if the logical display is in a valid state. + * This method should be checked after calling {@link #updateLocked} to handle the + * case where a logical display should be removed because all of its associated + * display devices are gone or if it is otherwise no longer needed. + * + * @return True if the logical display is still valid. + */ + public boolean isValidLocked() { + return mPrimaryDisplayDevice != null; + } + + /** + * Updates the state of the logical display based on the available display devices. + * The logical display might become invalid if it is attached to a display device + * that no longer exists. + * + * @param devices The list of all connected display devices. + */ + public void updateLocked(List<DisplayDevice> devices) { + // Nothing to update if already invalid. + if (mPrimaryDisplayDevice == null) { + return; + } + + // Check whether logical display has become invalid. + if (!devices.contains(mPrimaryDisplayDevice)) { + mPrimaryDisplayDevice = null; + return; + } + + // Bootstrap the logical display using its associated primary physical display. + // We might use more elaborate configurations later. It's possible that the + // configuration of several physical displays might be used to determine the + // logical display that they are sharing. (eg. Adjust size for pixel-perfect + // mirroring over HDMI.) + DisplayDeviceInfo deviceInfo = mPrimaryDisplayDevice.getDisplayDeviceInfoLocked(); + if (!Objects.equal(mPrimaryDisplayDeviceInfo, deviceInfo)) { + mBaseDisplayInfo.layerStack = mLayerStack; + mBaseDisplayInfo.flags = 0; + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT) != 0) { + mBaseDisplayInfo.flags |= Display.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT; + } + mBaseDisplayInfo.name = deviceInfo.name; + mBaseDisplayInfo.appWidth = deviceInfo.width; + mBaseDisplayInfo.appHeight = deviceInfo.height; + mBaseDisplayInfo.logicalWidth = deviceInfo.width; + mBaseDisplayInfo.logicalHeight = deviceInfo.height; + mBaseDisplayInfo.rotation = Surface.ROTATION_0; + mBaseDisplayInfo.refreshRate = deviceInfo.refreshRate; + mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi; + mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi; + mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi; + mBaseDisplayInfo.smallestNominalAppWidth = deviceInfo.width; + mBaseDisplayInfo.smallestNominalAppHeight = deviceInfo.height; + mBaseDisplayInfo.largestNominalAppWidth = deviceInfo.width; + mBaseDisplayInfo.largestNominalAppHeight = deviceInfo.height; + + mPrimaryDisplayDeviceInfo = deviceInfo; + mInfo = null; + } + } + + /** + * Applies the layer stack and transformation to the given display device + * so that it shows the contents of this logical display. + * + * We know that the given display device is only ever showing the contents of + * a single logical display, so this method is expected to blow away all of its + * transformation properties to make it happen regardless of what the + * display device was previously showing. + * + * The caller must have an open Surface transaction. + * + * The display device may not be the primary display device, in the case + * where the display is being mirrored. + * + * @param device The display device to modify. + */ + public void configureDisplayInTransactionLocked(DisplayDevice device) { + final DisplayInfo displayInfo = getDisplayInfoLocked(); + final DisplayDeviceInfo displayDeviceInfo = device.getDisplayDeviceInfoLocked(); + + // Set the layer stack. + device.setLayerStackInTransactionLocked(mLayerStack); + + // Set the viewport. + // This is the area of the logical display that we intend to show on the + // display device. For now, it is always the full size of the logical display. + mTempLayerStackRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); + + // Set the orientation. + // The orientation specifies how the physical coordinate system of the display + // is rotated when the contents of the logical display are rendered. + int orientation = Surface.ROTATION_0; + if (device == mPrimaryDisplayDevice + && (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_SUPPORTS_ROTATION) != 0) { + orientation = displayInfo.rotation; + } + + // Set the frame. + // The frame specifies the rotated physical coordinates into which the viewport + // is mapped. We need to take care to preserve the aspect ratio of the viewport. + // Currently we maximize the area to fill the display, but we could try to be + // more clever and match resolutions. + boolean rotated = (orientation == Surface.ROTATION_90 + || orientation == Surface.ROTATION_270); + int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width; + int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height; + + // Determine whether the width or height is more constrained to be scaled. + // physWidth / displayInfo.logicalWidth => letter box + // or physHeight / displayInfo.logicalHeight => pillar box + // + // We avoid a division (and possible floating point imprecision) here by + // multiplying the fractions by the product of their denominators before + // comparing them. + int displayRectWidth, displayRectHeight; + if (physWidth * displayInfo.logicalHeight + < physHeight * displayInfo.logicalWidth) { + // Letter box. + displayRectWidth = physWidth; + displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth; + } else { + // Pillar box. + displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight; + displayRectHeight = physHeight; + } + int displayRectTop = (physHeight - displayRectHeight) / 2; + int displayRectLeft = (physWidth - displayRectWidth) / 2; + mTempDisplayRect.set(displayRectLeft, displayRectTop, + displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight); + + device.setProjectionInTransactionLocked(orientation, mTempLayerStackRect, mTempDisplayRect); + } + + /** + * Returns true if the logical display has unique content. + * <p> + * If the display has unique content then we will try to ensure that it is + * visible on at least its primary display device. Otherwise we will ignore the + * logical display and perhaps show mirrored content on the primary display device. + * </p> + * + * @return True if the display has unique content. + */ + public boolean hasContentLocked() { + return mHasContent; + } + + /** + * Sets whether the logical display has unique content. + * + * @param hasContent True if the display has unique content. + */ + public void setHasContentLocked(boolean hasContent) { + mHasContent = hasContent; + } + + public void dumpLocked(PrintWriter pw) { + pw.println("mLayerStack=" + mLayerStack); + pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ? + mPrimaryDisplayDevice.getNameLocked() : "null")); + pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo); + pw.println("mOverrideDisplayInfo=" + mOverrideDisplayInfo); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java new file mode 100644 index 0000000..75ddd24 --- /dev/null +++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.IBinder; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Slog; +import android.view.Gravity; +import android.view.Surface; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A display adapter that uses overlay windows to simulate secondary displays + * for development purposes. Use Development Settings to enable one or more + * overlay displays. + * <p> + * This object has two different handlers (which may be the same) which must not + * get confused. The main handler is used to posting messages to the display manager + * service as usual. The UI handler is only used by the {@link OverlayDisplayWindow}. + * </p><p> + * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. + * </p> + */ +final class OverlayDisplayAdapter extends DisplayAdapter { + static final String TAG = "OverlayDisplayAdapter"; + static final boolean DEBUG = false; + + private static final int MIN_WIDTH = 100; + private static final int MIN_HEIGHT = 100; + private static final int MAX_WIDTH = 4096; + private static final int MAX_HEIGHT = 4096; + + private static final Pattern SETTING_PATTERN = + Pattern.compile("(\\d+)x(\\d+)/(\\d+)"); + + private final Handler mUiHandler; + private final ArrayList<OverlayDisplayHandle> mOverlays = + new ArrayList<OverlayDisplayHandle>(); + private String mCurrentOverlaySetting = ""; + + public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, + Context context, Handler handler, Listener listener, Handler uiHandler) { + super(syncRoot, context, handler, listener, TAG); + mUiHandler = uiHandler; + } + + @Override + public void dumpLocked(PrintWriter pw) { + super.dumpLocked(pw); + + pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting); + pw.println("mOverlays: size=" + mOverlays.size()); + for (OverlayDisplayHandle overlay : mOverlays) { + overlay.dumpLocked(pw); + } + } + + @Override + public void registerLocked() { + super.registerLocked(); + + getHandler().post(new Runnable() { + @Override + public void run() { + getContext().getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), + true, new SettingsObserver(getHandler())); + + synchronized (getSyncRoot()) { + updateOverlayDisplayDevicesLocked(); + } + } + }); + } + + private void updateOverlayDisplayDevicesLocked() { + String value = Settings.System.getString(getContext().getContentResolver(), + Settings.Secure.OVERLAY_DISPLAY_DEVICES); + if (value == null) { + value = ""; + } + + if (value.equals(mCurrentOverlaySetting)) { + return; + } + mCurrentOverlaySetting = value; + + if (!mOverlays.isEmpty()) { + Slog.i(TAG, "Dismissing all overlay display devices."); + for (OverlayDisplayHandle overlay : mOverlays) { + overlay.dismissLocked(); + } + mOverlays.clear(); + } + + int count = 0; + for (String part : value.split(";")) { + Matcher matcher = SETTING_PATTERN.matcher(part); + if (matcher.matches()) { + if (count >= 4) { + Slog.w(TAG, "Too many overlay display devices specified: " + value); + break; + } + try { + int width = Integer.parseInt(matcher.group(1), 10); + int height = Integer.parseInt(matcher.group(2), 10); + int densityDpi = Integer.parseInt(matcher.group(3), 10); + if (width >= MIN_WIDTH && width <= MAX_WIDTH + && height >= MIN_HEIGHT && height <= MAX_HEIGHT + && densityDpi >= DisplayMetrics.DENSITY_LOW + && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) { + int number = ++count; + String name = getContext().getResources().getString( + com.android.internal.R.string.display_manager_overlay_display_name, + number); + int gravity = chooseOverlayGravity(number); + + Slog.i(TAG, "Showing overlay display device #" + number + + ": name=" + name + ", width=" + width + ", height=" + height + + ", densityDpi=" + densityDpi); + + mOverlays.add(new OverlayDisplayHandle(name, + width, height, densityDpi, gravity)); + continue; + } + } catch (NumberFormatException ex) { + } + } else if (part.isEmpty()) { + continue; + } + Slog.w(TAG, "Malformed overlay display devices setting: " + value); + } + } + + private static int chooseOverlayGravity(int overlayNumber) { + switch (overlayNumber) { + case 1: + return Gravity.TOP | Gravity.LEFT; + case 2: + return Gravity.BOTTOM | Gravity.RIGHT; + case 3: + return Gravity.TOP | Gravity.RIGHT; + case 4: + default: + return Gravity.BOTTOM | Gravity.LEFT; + } + } + + private final class SettingsObserver extends ContentObserver { + public SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + synchronized (getSyncRoot()) { + updateOverlayDisplayDevicesLocked(); + } + } + } + + private final class OverlayDisplayDevice extends DisplayDevice { + private final String mName; + private final int mWidth; + private final int mHeight; + private final float mRefreshRate; + private final int mDensityDpi; + + private Surface mSurface; + private DisplayDeviceInfo mInfo; + + public OverlayDisplayDevice(IBinder displayToken, String name, + int width, int height, float refreshRate, int densityDpi, + Surface surface) { + super(OverlayDisplayAdapter.this, displayToken); + mName = name; + mWidth = width; + mHeight = height; + mRefreshRate = refreshRate; + mDensityDpi = densityDpi; + mSurface = surface; + } + + public void clearSurfaceLocked() { + mSurface = null; + sendTraversalRequestLocked(); + } + + @Override + public void performTraversalInTransactionLocked() { + setSurfaceInTransactionLocked(mSurface); + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + if (mInfo == null) { + mInfo = new DisplayDeviceInfo(); + mInfo.name = mName; + mInfo.width = mWidth; + mInfo.height = mHeight; + mInfo.refreshRate = mRefreshRate; + mInfo.densityDpi = mDensityDpi; + mInfo.xDpi = mDensityDpi; + mInfo.yDpi = mDensityDpi; + mInfo.flags = DisplayDeviceInfo.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT; + mInfo.touch = DisplayDeviceInfo.TOUCH_NONE; + } + return mInfo; + } + } + + /** + * Functions as a handle for overlay display devices which are created and + * destroyed asynchronously. + * + * Guarded by the {@link DisplayManagerService.SyncRoot} lock. + */ + private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener { + private final String mName; + private final int mWidth; + private final int mHeight; + private final int mDensityDpi; + private final int mGravity; + + private OverlayDisplayWindow mWindow; + private OverlayDisplayDevice mDevice; + + public OverlayDisplayHandle(String name, + int width, int height, int densityDpi, int gravity) { + mName = name; + mWidth = width; + mHeight = height; + mDensityDpi = densityDpi; + mGravity = gravity; + + mUiHandler.post(mShowRunnable); + } + + public void dismissLocked() { + mUiHandler.removeCallbacks(mShowRunnable); + mUiHandler.post(mDismissRunnable); + } + + // Called on the UI thread. + @Override + public void onWindowCreated(Surface surface, float refreshRate) { + synchronized (getSyncRoot()) { + IBinder displayToken = Surface.createDisplay(mName); + mDevice = new OverlayDisplayDevice(displayToken, mName, + mWidth, mHeight, refreshRate, mDensityDpi, surface); + + sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED); + } + } + + // Called on the UI thread. + @Override + public void onWindowDestroyed() { + synchronized (getSyncRoot()) { + if (mDevice != null) { + mDevice.clearSurfaceLocked(); + sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED); + } + } + } + + public void dumpLocked(PrintWriter pw) { + pw.println(" " + mName + ":"); + pw.println(" mWidth=" + mWidth); + pw.println(" mHeight=" + mHeight); + pw.println(" mDensityDpi=" + mDensityDpi); + pw.println(" mGravity=" + mGravity); + + // Try to dump the window state. + if (mWindow != null) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.increaseIndent(); + DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, 200); + } + } + + // Runs on the UI thread. + private final Runnable mShowRunnable = new Runnable() { + @Override + public void run() { + OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(), + mName, mWidth, mHeight, mDensityDpi, mGravity, + OverlayDisplayHandle.this); + window.show(); + + synchronized (getSyncRoot()) { + mWindow = window; + } + } + }; + + // Runs on the UI thread. + private final Runnable mDismissRunnable = new Runnable() { + @Override + public void run() { + OverlayDisplayWindow window; + synchronized (getSyncRoot()) { + window = mWindow; + mWindow = null; + } + + if (window != null) { + window.dismiss(); + } + } + }; + } +} diff --git a/services/java/com/android/server/display/OverlayDisplayWindow.java b/services/java/com/android/server/display/OverlayDisplayWindow.java new file mode 100644 index 0000000..d08f65f --- /dev/null +++ b/services/java/com/android/server/display/OverlayDisplayWindow.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import com.android.internal.util.DumpUtils; + +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.hardware.display.DisplayManager; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.Surface; +import android.view.TextureView; +import android.view.View; +import android.view.WindowManager; +import android.view.TextureView.SurfaceTextureListener; +import android.widget.TextView; + +import java.io.PrintWriter; + +/** + * Manages an overlay window on behalf of {@link OverlayDisplayAdapter}. + * <p> + * This object must only be accessed on the UI thread. + * No locks are held by this object and locks must not be held while making called into it. + * </p> + */ +final class OverlayDisplayWindow implements DumpUtils.Dump { + private static final String TAG = "OverlayDisplayWindow"; + private static final boolean DEBUG = false; + + private final float INITIAL_SCALE = 0.5f; + private final float MIN_SCALE = 0.3f; + private final float MAX_SCALE = 1.0f; + private final float WINDOW_ALPHA = 0.8f; + + // When true, disables support for moving and resizing the overlay. + // The window is made non-touchable, which makes it possible to + // directly interact with the content underneath. + private final boolean DISABLE_MOVE_AND_RESIZE = false; + + private final Context mContext; + private final String mName; + private final int mWidth; + private final int mHeight; + private final int mDensityDpi; + private final int mGravity; + private final Listener mListener; + private final String mTitle; + + private final DisplayManager mDisplayManager; + private final WindowManager mWindowManager; + + + private final Display mDefaultDisplay; + private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); + + private View mWindowContent; + private WindowManager.LayoutParams mWindowParams; + private TextureView mTextureView; + private TextView mTitleTextView; + + private GestureDetector mGestureDetector; + private ScaleGestureDetector mScaleGestureDetector; + + private boolean mWindowVisible; + private int mWindowX; + private int mWindowY; + private float mWindowScale; + + private float mLiveTranslationX; + private float mLiveTranslationY; + private float mLiveScale = 1.0f; + + public OverlayDisplayWindow(Context context, String name, + int width, int height, int densityDpi, int gravity, Listener listener) { + mContext = context; + mName = name; + mWidth = width; + mHeight = height; + mDensityDpi = densityDpi; + mGravity = gravity; + mListener = listener; + mTitle = context.getResources().getString( + com.android.internal.R.string.display_manager_overlay_display_title, + mName, mWidth, mHeight, mDensityDpi); + + mDisplayManager = (DisplayManager)context.getSystemService( + Context.DISPLAY_SERVICE); + mWindowManager = (WindowManager)context.getSystemService( + Context.WINDOW_SERVICE); + + mDefaultDisplay = mWindowManager.getDefaultDisplay(); + updateDefaultDisplayInfo(); + + createWindow(); + } + + public void show() { + if (!mWindowVisible) { + mDisplayManager.registerDisplayListener(mDisplayListener, null); + if (!updateDefaultDisplayInfo()) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + return; + } + + clearLiveState(); + updateWindowParams(); + mWindowManager.addView(mWindowContent, mWindowParams); + mWindowVisible = true; + } + } + + public void dismiss() { + if (mWindowVisible) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + mWindowManager.removeView(mWindowContent); + mWindowVisible = false; + } + } + + public void relayout() { + if (mWindowVisible) { + updateWindowParams(); + mWindowManager.updateViewLayout(mWindowContent, mWindowParams); + } + } + + public void dump(PrintWriter pw) { + pw.println("mWindowVisible=" + mWindowVisible); + pw.println("mWindowX=" + mWindowX); + pw.println("mWindowY=" + mWindowY); + pw.println("mWindowScale=" + mWindowScale); + pw.println("mWindowParams=" + mWindowParams); + if (mTextureView != null) { + pw.println("mTextureView.getScaleX()=" + mTextureView.getScaleX()); + pw.println("mTextureView.getScaleY()=" + mTextureView.getScaleY()); + } + pw.println("mLiveTranslationX=" + mLiveTranslationX); + pw.println("mLiveTranslationY=" + mLiveTranslationY); + pw.println("mLiveScale=" + mLiveScale); + } + + private boolean updateDefaultDisplayInfo() { + if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { + Slog.w(TAG, "Cannot show overlay display because there is no " + + "default display upon which to show it."); + return false; + } + return true; + } + + private void createWindow() { + LayoutInflater inflater = LayoutInflater.from(mContext); + + mWindowContent = inflater.inflate( + com.android.internal.R.layout.overlay_display_window, null); + mWindowContent.setOnTouchListener(mOnTouchListener); + + mTextureView = (TextureView)mWindowContent.findViewById( + com.android.internal.R.id.overlay_display_window_texture); + mTextureView.setPivotX(0); + mTextureView.setPivotY(0); + mTextureView.getLayoutParams().width = mWidth; + mTextureView.getLayoutParams().height = mHeight; + mTextureView.setOpaque(false); + mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); + + mTitleTextView = (TextView)mWindowContent.findViewById( + com.android.internal.R.id.overlay_display_window_title); + mTitleTextView.setText(mTitle); + + mWindowParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY); + mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + if (DISABLE_MOVE_AND_RESIZE) { + mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + } + mWindowParams.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; + mWindowParams.alpha = WINDOW_ALPHA; + mWindowParams.gravity = Gravity.TOP | Gravity.LEFT; + mWindowParams.setTitle(mTitle); + + mGestureDetector = new GestureDetector(mContext, mOnGestureListener); + mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener); + + // Set the initial position and scale. + // The position and scale will be clamped when the display is first shown. + mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ? + 0 : mDefaultDisplayInfo.logicalWidth; + mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ? + 0 : mDefaultDisplayInfo.logicalHeight; + mWindowScale = INITIAL_SCALE; + } + + private void updateWindowParams() { + float scale = mWindowScale * mLiveScale; + scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalWidth / mWidth); + scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalHeight / mHeight); + scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale)); + + float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f; + int width = (int)(mWidth * scale); + int height = (int)(mHeight * scale); + int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale); + int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale); + x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width)); + y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height)); + + if (DEBUG) { + Slog.d(TAG, "updateWindowParams: scale=" + scale + + ", offsetScale=" + offsetScale + + ", x=" + x + ", y=" + y + + ", width=" + width + ", height=" + height); + } + + mTextureView.setScaleX(scale); + mTextureView.setScaleY(scale); + + mWindowParams.x = x; + mWindowParams.y = y; + mWindowParams.width = width; + mWindowParams.height = height; + } + + private void saveWindowParams() { + mWindowX = mWindowParams.x; + mWindowY = mWindowParams.y; + mWindowScale = mTextureView.getScaleX(); + clearLiveState(); + } + + private void clearLiveState() { + mLiveTranslationX = 0f; + mLiveTranslationY = 0f; + mLiveScale = 1.0f; + } + + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == mDefaultDisplay.getDisplayId()) { + if (updateDefaultDisplayInfo()) { + relayout(); + } else { + dismiss(); + } + } + } + + @Override + public void onDisplayRemoved(int displayId) { + if (displayId == mDefaultDisplay.getDisplayId()) { + dismiss(); + } + } + }; + + private final SurfaceTextureListener mSurfaceTextureListener = + new SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, + int width, int height) { + mListener.onWindowCreated(new Surface(surfaceTexture), + mDefaultDisplayInfo.refreshRate); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + mListener.onWindowDestroyed(); + return true; + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, + int width, int height) { + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + } + }; + + private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + // Work in screen coordinates. + final float oldX = event.getX(); + final float oldY = event.getY(); + event.setLocation(event.getRawX(), event.getRawY()); + + mGestureDetector.onTouchEvent(event); + mScaleGestureDetector.onTouchEvent(event); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + saveWindowParams(); + break; + } + + // Revert to window coordinates. + event.setLocation(oldX, oldY); + return true; + } + }; + + private final GestureDetector.OnGestureListener mOnGestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + mLiveTranslationX -= distanceX; + mLiveTranslationY -= distanceY; + relayout(); + return true; + } + }; + + private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener = + new ScaleGestureDetector.SimpleOnScaleGestureListener() { + @Override + public boolean onScale(ScaleGestureDetector detector) { + mLiveScale *= detector.getScaleFactor(); + relayout(); + return true; + } + }; + + /** + * Watches for significant changes in the overlay display window lifecycle. + */ + public interface Listener { + public void onWindowCreated(Surface surface, float refreshRate); + public void onWindowDestroyed(); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java new file mode 100644 index 0000000..b57d3dc --- /dev/null +++ b/services/java/com/android/server/display/WifiDisplayAdapter.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; + +import android.content.Context; +import android.content.Intent; +import android.hardware.display.DisplayManager; +import android.hardware.display.WifiDisplay; +import android.hardware.display.WifiDisplayStatus; +import android.media.RemoteDisplay; +import android.os.Handler; +import android.os.IBinder; +import android.view.Surface; + +import java.io.PrintWriter; +import java.util.Arrays; + +/** + * Connects to Wifi displays that implement the Miracast protocol. + * <p> + * The Wifi display protocol relies on Wifi direct for discovering and pairing + * with the display. Once connected, the Media Server opens an RTSP socket and accepts + * a connection from the display. After session negotiation, the Media Server + * streams encoded buffers to the display. + * </p><p> + * This class is responsible for connecting to Wifi displays and mediating + * the interactions between Media Server, Surface Flinger and the Display Manager Service. + * </p><p> + * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. + * </p> + */ +final class WifiDisplayAdapter extends DisplayAdapter { + private static final String TAG = "WifiDisplayAdapter"; + + private WifiDisplayController mDisplayController; + private WifiDisplayDevice mDisplayDevice; + + private WifiDisplayStatus mCurrentStatus; + private boolean mEnabled; + private int mScanState; + private int mActiveDisplayState; + private WifiDisplay mActiveDisplay; + private WifiDisplay[] mKnownDisplays = WifiDisplay.EMPTY_ARRAY; + + private boolean mPendingStatusChangeBroadcast; + + public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, + Context context, Handler handler, Listener listener) { + super(syncRoot, context, handler, listener, TAG); + } + + @Override + public void dumpLocked(PrintWriter pw) { + super.dumpLocked(pw); + + pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked()); + pw.println("mEnabled=" + mEnabled); + pw.println("mScanState=" + mScanState); + pw.println("mActiveDisplayState=" + mActiveDisplayState); + pw.println("mActiveDisplay=" + mActiveDisplay); + pw.println("mKnownDisplays=" + Arrays.toString(mKnownDisplays)); + pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast); + + // Try to dump the controller state. + if (mDisplayController == null) { + pw.println("mDisplayController=null"); + } else { + pw.println("mDisplayController:"); + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.increaseIndent(); + DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200); + } + } + + @Override + public void registerLocked() { + super.registerLocked(); + + getHandler().post(new Runnable() { + @Override + public void run() { + mDisplayController = new WifiDisplayController( + getContext(), getHandler(), mWifiDisplayListener); + } + }); + } + + public void requestScanLocked() { + getHandler().post(new Runnable() { + @Override + public void run() { + if (mDisplayController != null) { + mDisplayController.requestScan(); + } + } + }); + } + + public void requestConnectLocked(final String address) { + getHandler().post(new Runnable() { + @Override + public void run() { + if (mDisplayController != null) { + mDisplayController.requestConnect(address); + } + } + }); + } + + public void requestDisconnectLocked() { + getHandler().post(new Runnable() { + @Override + public void run() { + if (mDisplayController != null) { + mDisplayController.requestDisconnect(); + } + } + }); + } + + public WifiDisplayStatus getWifiDisplayStatusLocked() { + if (mCurrentStatus == null) { + mCurrentStatus = new WifiDisplayStatus(mEnabled, mScanState, mActiveDisplayState, + mActiveDisplay, mKnownDisplays); + } + return mCurrentStatus; + } + + private void handleConnectLocked(WifiDisplay display, + Surface surface, int width, int height, int flags) { + handleDisconnectLocked(); + + int deviceFlags = 0; + if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) { + deviceFlags |= DisplayDeviceInfo.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT; + } + + float refreshRate = 60.0f; // TODO: get this for real + + String name = display.getDeviceName(); + IBinder displayToken = Surface.createDisplay(name); + mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height, + refreshRate, deviceFlags, surface); + sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED); + } + + private void handleDisconnectLocked() { + if (mDisplayDevice != null) { + mDisplayDevice.clearSurfaceLocked(); + sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED); + mDisplayDevice = null; + } + } + + private void scheduleStatusChangedBroadcastLocked() { + if (!mPendingStatusChangeBroadcast) { + mPendingStatusChangeBroadcast = true; + getHandler().post(mStatusChangeBroadcast); + } + } + + private final Runnable mStatusChangeBroadcast = new Runnable() { + @Override + public void run() { + final Intent intent; + synchronized (getSyncRoot()) { + if (!mPendingStatusChangeBroadcast) { + return; + } + + mPendingStatusChangeBroadcast = false; + intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS, + getWifiDisplayStatusLocked()); + } + + // Send protected broadcast about wifi display status to receivers that + // have the required permission. + getContext().sendBroadcast(intent, + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY); + } + }; + + private final WifiDisplayController.Listener mWifiDisplayListener = + new WifiDisplayController.Listener() { + @Override + public void onEnablementChanged(boolean enabled) { + synchronized (getSyncRoot()) { + if (mEnabled != enabled) { + mCurrentStatus = null; + mEnabled = enabled; + scheduleStatusChangedBroadcastLocked(); + } + } + } + + @Override + public void onScanStarted() { + synchronized (getSyncRoot()) { + if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) { + mCurrentStatus = null; + mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING; + scheduleStatusChangedBroadcastLocked(); + } + } + } + + public void onScanFinished(WifiDisplay[] knownDisplays) { + synchronized (getSyncRoot()) { + if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING + || !Arrays.equals(mKnownDisplays, knownDisplays)) { + mCurrentStatus = null; + mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING; + mKnownDisplays = knownDisplays; + scheduleStatusChangedBroadcastLocked(); + } + } + } + + @Override + public void onDisplayConnecting(WifiDisplay display) { + synchronized (getSyncRoot()) { + if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING + || mActiveDisplay == null + || !mActiveDisplay.equals(display)) { + mCurrentStatus = null; + mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING; + mActiveDisplay = display; + scheduleStatusChangedBroadcastLocked(); + } + } + } + + @Override + public void onDisplayConnectionFailed() { + synchronized (getSyncRoot()) { + if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED + || mActiveDisplay != null) { + mCurrentStatus = null; + mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; + mActiveDisplay = null; + scheduleStatusChangedBroadcastLocked(); + } + } + } + + @Override + public void onDisplayConnected(WifiDisplay display, Surface surface, + int width, int height, int flags) { + synchronized (getSyncRoot()) { + handleConnectLocked(display, surface, width, height, flags); + + if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED + || mActiveDisplay == null + || !mActiveDisplay.equals(display)) { + mCurrentStatus = null; + mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED; + mActiveDisplay = display; + scheduleStatusChangedBroadcastLocked(); + } + } + } + + @Override + public void onDisplayDisconnected() { + // Stop listening. + synchronized (getSyncRoot()) { + handleDisconnectLocked(); + + if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED + || mActiveDisplay != null) { + mCurrentStatus = null; + mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; + mActiveDisplay = null; + scheduleStatusChangedBroadcastLocked(); + } + } + } + }; + + private final class WifiDisplayDevice extends DisplayDevice { + private final String mName; + private final int mWidth; + private final int mHeight; + private final float mRefreshRate; + private final int mFlags; + + private Surface mSurface; + private DisplayDeviceInfo mInfo; + + public WifiDisplayDevice(IBinder displayToken, String name, + int width, int height, float refreshRate, int flags, + Surface surface) { + super(WifiDisplayAdapter.this, displayToken); + mName = name; + mWidth = width; + mHeight = height; + mRefreshRate = refreshRate; + mFlags = flags; + mSurface = surface; + } + + public void clearSurfaceLocked() { + mSurface = null; + sendTraversalRequestLocked(); + } + + @Override + public void performTraversalInTransactionLocked() { + setSurfaceInTransactionLocked(mSurface); + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + if (mInfo == null) { + mInfo = new DisplayDeviceInfo(); + mInfo.name = mName; + mInfo.width = mWidth; + mInfo.height = mHeight; + mInfo.refreshRate = mRefreshRate; + mInfo.flags = mFlags; + mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; + mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight); + } + return mInfo; + } + } +} diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java new file mode 100644 index 0000000..87e11e6 --- /dev/null +++ b/services/java/com/android/server/display/WifiDisplayController.java @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import com.android.internal.util.DumpUtils; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.display.WifiDisplay; +import android.media.AudioManager; +import android.media.RemoteDisplay; +import android.net.NetworkInfo; +import android.net.wifi.p2p.WifiP2pConfig; +import android.net.wifi.p2p.WifiP2pDevice; +import android.net.wifi.p2p.WifiP2pDeviceList; +import android.net.wifi.p2p.WifiP2pGroup; +import android.net.wifi.p2p.WifiP2pManager; +import android.net.wifi.p2p.WifiP2pWfdInfo; +import android.net.wifi.p2p.WifiP2pManager.ActionListener; +import android.net.wifi.p2p.WifiP2pManager.Channel; +import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener; +import android.net.wifi.p2p.WifiP2pManager.PeerListListener; +import android.os.Handler; +import android.util.Slog; +import android.view.Surface; + +import java.io.PrintWriter; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Enumeration; + +/** + * Manages all of the various asynchronous interactions with the {@link WifiP2pManager} + * on behalf of {@link WifiDisplayAdapter}. + * <p> + * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid + * accidentally introducing any deadlocks due to the display manager calling + * outside of itself while holding its lock. It's also way easier to write this + * asynchronous code if we can assume that it is single-threaded. + * </p><p> + * The controller must be instantiated on the handler thread. + * </p> + */ +final class WifiDisplayController implements DumpUtils.Dump { + private static final String TAG = "WifiDisplayController"; + private static final boolean DEBUG = false; + + private static final int DEFAULT_CONTROL_PORT = 7236; + private static final int MAX_THROUGHPUT = 50; + private static final int CONNECTION_TIMEOUT_SECONDS = 30; + private static final int RTSP_TIMEOUT_SECONDS = 15; + + private static final int DISCOVER_PEERS_MAX_RETRIES = 10; + private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500; + + private static final int CONNECT_MAX_RETRIES = 3; + private static final int CONNECT_RETRY_DELAY_MILLIS = 500; + + // A unique token to identify the remote submix that is managed by Wifi display. + // It must match what the media server uses when it starts recording the submix + // for transmission. We use 0 although the actual value is currently ignored. + private static final int REMOTE_SUBMIX_ADDRESS = 0; + + private final Context mContext; + private final Handler mHandler; + private final Listener mListener; + + private final WifiP2pManager mWifiP2pManager; + private final Channel mWifiP2pChannel; + + private final AudioManager mAudioManager; + + private boolean mWifiP2pEnabled; + private boolean mWfdEnabled; + private boolean mWfdEnabling; + private NetworkInfo mNetworkInfo; + + private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers = + new ArrayList<WifiP2pDevice>(); + + // True if there is a call to discoverPeers in progress. + private boolean mDiscoverPeersInProgress; + + // Number of discover peers retries remaining. + private int mDiscoverPeersRetriesLeft; + + // The device to which we want to connect, or null if we want to be disconnected. + private WifiP2pDevice mDesiredDevice; + + // The device to which we are currently connecting, or null if we have already connected + // or are not trying to connect. + private WifiP2pDevice mConnectingDevice; + + // The device to which we are currently connected, which means we have an active P2P group. + private WifiP2pDevice mConnectedDevice; + + // The group info obtained after connecting. + private WifiP2pGroup mConnectedDeviceGroupInfo; + + // Number of connection retries remaining. + private int mConnectionRetriesLeft; + + // The remote display that is listening on the connection. + // Created after the Wifi P2P network is connected. + private RemoteDisplay mRemoteDisplay; + + // The remote display interface. + private String mRemoteDisplayInterface; + + // True if RTSP has connected. + private boolean mRemoteDisplayConnected; + + // True if the remote submix is enabled. + private boolean mRemoteSubmixOn; + + public WifiDisplayController(Context context, Handler handler, Listener listener) { + mContext = context; + mHandler = handler; + mListener = listener; + + mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE); + mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null); + + mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); + intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); + intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); + context.registerReceiver(mWifiP2pReceiver, intentFilter); + } + + public void dump(PrintWriter pw) { + pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled); + pw.println("mWfdEnabled=" + mWfdEnabled); + pw.println("mWfdEnabling=" + mWfdEnabling); + pw.println("mNetworkInfo=" + mNetworkInfo); + pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress); + pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft); + pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice)); + pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice)); + pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice)); + pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft); + pw.println("mRemoteDisplay=" + mRemoteDisplay); + pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface); + pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected); + pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn); + + pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size()); + for (WifiP2pDevice device : mKnownWifiDisplayPeers) { + pw.println(" " + describeWifiP2pDevice(device)); + } + } + + public void requestScan() { + discoverPeers(); + } + + public void requestConnect(String address) { + for (WifiP2pDevice device : mKnownWifiDisplayPeers) { + if (device.deviceAddress.equals(address)) { + connect(device); + } + } + } + + public void requestDisconnect() { + disconnect(); + } + + private void enableWfd() { + if (!mWfdEnabled && !mWfdEnabling) { + mWfdEnabling = true; + + WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); + wfdInfo.setWfdEnabled(true); + wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE); + wfdInfo.setSessionAvailable(true); + wfdInfo.setControlPort(DEFAULT_CONTROL_PORT); + wfdInfo.setMaxThroughput(MAX_THROUGHPUT); + mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { + @Override + public void onSuccess() { + if (DEBUG) { + Slog.d(TAG, "Successfully set WFD info."); + } + if (mWfdEnabling) { + mWfdEnabling = false; + setWfdEnabled(true); + } + } + + @Override + public void onFailure(int reason) { + if (DEBUG) { + Slog.d(TAG, "Failed to set WFD info with reason " + reason + "."); + } + mWfdEnabling = false; + } + }); + } + } + + private void setWfdEnabled(final boolean enabled) { + if (mWfdEnabled != enabled) { + mWfdEnabled = enabled; + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onEnablementChanged(enabled); + } + }); + } + } + + private void discoverPeers() { + if (!mDiscoverPeersInProgress) { + mDiscoverPeersInProgress = true; + mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES; + handleScanStarted(); + tryDiscoverPeers(); + } + } + + private void tryDiscoverPeers() { + mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() { + @Override + public void onSuccess() { + if (DEBUG) { + Slog.d(TAG, "Discover peers succeeded. Requesting peers now."); + } + + mDiscoverPeersInProgress = false; + requestPeers(); + } + + @Override + public void onFailure(int reason) { + if (DEBUG) { + Slog.d(TAG, "Discover peers failed with reason " + reason + "."); + } + + if (mDiscoverPeersInProgress) { + if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (mDiscoverPeersInProgress) { + if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) { + mDiscoverPeersRetriesLeft -= 1; + if (DEBUG) { + Slog.d(TAG, "Retrying discovery. Retries left: " + + mDiscoverPeersRetriesLeft); + } + tryDiscoverPeers(); + } else { + handleScanFinished(); + mDiscoverPeersInProgress = false; + } + } + } + }, DISCOVER_PEERS_RETRY_DELAY_MILLIS); + } else { + handleScanFinished(); + mDiscoverPeersInProgress = false; + } + } + } + }); + } + + private void requestPeers() { + mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() { + @Override + public void onPeersAvailable(WifiP2pDeviceList peers) { + if (DEBUG) { + Slog.d(TAG, "Received list of peers."); + } + + mKnownWifiDisplayPeers.clear(); + for (WifiP2pDevice device : peers.getDeviceList()) { + if (DEBUG) { + Slog.d(TAG, " " + describeWifiP2pDevice(device)); + } + + if (isWifiDisplay(device)) { + mKnownWifiDisplayPeers.add(device); + } + } + + handleScanFinished(); + } + }); + } + + private void handleScanStarted() { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onScanStarted(); + } + }); + } + + private void handleScanFinished() { + final int count = mKnownWifiDisplayPeers.size(); + final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count); + for (int i = 0; i < count; i++) { + displays[i] = createWifiDisplay(mKnownWifiDisplayPeers.get(i)); + } + + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onScanFinished(displays); + } + }); + } + + private void connect(final WifiP2pDevice device) { + if (mDesiredDevice != null + && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) { + if (DEBUG) { + Slog.d(TAG, "connect: nothing to do, already connecting to " + + describeWifiP2pDevice(device)); + } + return; + } + + if (mConnectedDevice != null + && !mConnectedDevice.deviceAddress.equals(device.deviceAddress) + && mDesiredDevice == null) { + if (DEBUG) { + Slog.d(TAG, "connect: nothing to do, already connected to " + + describeWifiP2pDevice(device) + " and not part way through " + + "connecting to a different device."); + } + return; + } + + mDesiredDevice = device; + mConnectionRetriesLeft = CONNECT_MAX_RETRIES; + updateConnection(); + } + + private void disconnect() { + mDesiredDevice = null; + updateConnection(); + } + + private void retryConnection() { + if (mDesiredDevice != null && mConnectedDevice != mDesiredDevice + && mConnectionRetriesLeft > 0) { + mConnectionRetriesLeft -= 1; + Slog.i(TAG, "Retrying Wifi display connection. Retries left: " + + mConnectionRetriesLeft); + + // Cheap hack. Make a new instance of the device object so that we + // can distinguish it from the previous connection attempt. + // This will cause us to tear everything down before we try again. + mDesiredDevice = new WifiP2pDevice(mDesiredDevice); + updateConnection(); + } + } + + /** + * This function is called repeatedly after each asynchronous operation + * until all preconditions for the connection have been satisfied and the + * connection is established (or not). + */ + private void updateConnection() { + // Step 1. Before we try to connect to a new device, tell the system we + // have disconnected from the old one. + if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) { + Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface + + " from Wifi display: " + mConnectedDevice.deviceName); + + mRemoteDisplay.dispose(); + mRemoteDisplay = null; + mRemoteDisplayInterface = null; + mRemoteDisplayConnected = false; + mHandler.removeCallbacks(mRtspTimeout); + + setRemoteSubmixOn(false); + + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayDisconnected(); + } + }); + + // continue to next step + } + + // Step 2. Before we try to connect to a new device, disconnect from the old one. + if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) { + Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName); + + final WifiP2pDevice oldDevice = mConnectedDevice; + mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() { + @Override + public void onSuccess() { + Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName); + next(); + } + + @Override + public void onFailure(int reason) { + Slog.i(TAG, "Failed to disconnect from Wifi display: " + + oldDevice.deviceName + ", reason=" + reason); + next(); + } + + private void next() { + if (mConnectedDevice == oldDevice) { + mConnectedDevice = null; + updateConnection(); + } + } + }); + return; // wait for asynchronous callback + } + + // Step 3. Before we try to connect to a new device, stop trying to connect + // to the old one. + if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) { + Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName); + + mHandler.removeCallbacks(mConnectionTimeout); + + final WifiP2pDevice oldDevice = mConnectingDevice; + mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() { + @Override + public void onSuccess() { + Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName); + next(); + } + + @Override + public void onFailure(int reason) { + Slog.i(TAG, "Failed to cancel connection to Wifi display: " + + oldDevice.deviceName + ", reason=" + reason); + next(); + } + + private void next() { + if (mConnectingDevice == oldDevice) { + mConnectingDevice = null; + updateConnection(); + } + } + }); + return; // wait for asynchronous callback + } + + // Step 4. If we wanted to disconnect, then mission accomplished. + if (mDesiredDevice == null) { + return; // done + } + + // Step 5. Try to connect. + if (mConnectedDevice == null && mConnectingDevice == null) { + Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName); + + mConnectingDevice = mDesiredDevice; + WifiP2pConfig config = new WifiP2pConfig(); + config.deviceAddress = mConnectingDevice.deviceAddress; + + final WifiDisplay display = createWifiDisplay(mConnectingDevice); + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayConnecting(display); + } + }); + + final WifiP2pDevice newDevice = mDesiredDevice; + mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() { + @Override + public void onSuccess() { + // The connection may not yet be established. We still need to wait + // for WIFI_P2P_CONNECTION_CHANGED_ACTION. However, we might never + // get that broadcast, so we register a timeout. + Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName); + + mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000); + } + + @Override + public void onFailure(int reason) { + if (mConnectingDevice == newDevice) { + Slog.i(TAG, "Failed to initiate connection to Wifi display: " + + newDevice.deviceName + ", reason=" + reason); + mConnectingDevice = null; + handleConnectionFailure(false); + } + } + }); + return; // wait for asynchronous callback + } + + // Step 6. Listen for incoming connections. + if (mConnectedDevice != null && mRemoteDisplay == null) { + Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo); + if (addr == null) { + Slog.i(TAG, "Failed to get local interface address for communicating " + + "with Wifi display: " + mConnectedDevice.deviceName); + handleConnectionFailure(false); + return; // done + } + + setRemoteSubmixOn(true); + + final WifiP2pDevice oldDevice = mConnectedDevice; + final int port = getPortNumber(mConnectedDevice); + final String iface = addr.getHostAddress() + ":" + port; + mRemoteDisplayInterface = iface; + + Slog.i(TAG, "Listening for RTSP connection on " + iface + + " from Wifi display: " + mConnectedDevice.deviceName); + + mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() { + @Override + public void onDisplayConnected(final Surface surface, + final int width, final int height, final int flags) { + if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { + Slog.i(TAG, "Opened RTSP connection with Wifi display: " + + mConnectedDevice.deviceName); + mRemoteDisplayConnected = true; + mHandler.removeCallbacks(mRtspTimeout); + + final WifiDisplay display = createWifiDisplay(mConnectedDevice); + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayConnected(display, + surface, width, height, flags); + } + }); + } + } + + @Override + public void onDisplayDisconnected() { + if (mConnectedDevice == oldDevice) { + Slog.i(TAG, "Closed RTSP connection with Wifi display: " + + mConnectedDevice.deviceName); + mHandler.removeCallbacks(mRtspTimeout); + disconnect(); + } + } + + @Override + public void onDisplayError(int error) { + if (mConnectedDevice == oldDevice) { + Slog.i(TAG, "Lost RTSP connection with Wifi display due to error " + + error + ": " + mConnectedDevice.deviceName); + mHandler.removeCallbacks(mRtspTimeout); + handleConnectionFailure(false); + } + } + }, mHandler); + + mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000); + } + } + + private void setRemoteSubmixOn(boolean on) { + if (mRemoteSubmixOn != on) { + mRemoteSubmixOn = on; + mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS); + } + } + + private void handleStateChanged(boolean enabled) { + if (mWifiP2pEnabled != enabled) { + mWifiP2pEnabled = enabled; + if (enabled) { + if (!mWfdEnabled) { + enableWfd(); + } + } else { + setWfdEnabled(false); + disconnect(); + } + } + } + + private void handlePeersChanged() { + if (mWifiP2pEnabled) { + if (mWfdEnabled) { + requestPeers(); + } else { + enableWfd(); + } + } + } + + private void handleConnectionChanged(NetworkInfo networkInfo) { + mNetworkInfo = networkInfo; + if (mWfdEnabled && networkInfo.isConnected()) { + if (mDesiredDevice != null) { + mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() { + @Override + public void onGroupInfoAvailable(WifiP2pGroup info) { + if (DEBUG) { + Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info)); + } + + if (mConnectingDevice != null && !info.contains(mConnectingDevice)) { + Slog.i(TAG, "Aborting connection to Wifi display because " + + "the current P2P group does not contain the device " + + "we expected to find: " + mConnectingDevice.deviceName); + handleConnectionFailure(false); + return; + } + + if (mDesiredDevice != null && !info.contains(mDesiredDevice)) { + disconnect(); + return; + } + + if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { + Slog.i(TAG, "Connected to Wifi display: " + + mConnectingDevice.deviceName); + + mHandler.removeCallbacks(mConnectionTimeout); + mConnectedDeviceGroupInfo = info; + mConnectedDevice = mConnectingDevice; + mConnectingDevice = null; + updateConnection(); + } + } + }); + } + } else { + disconnect(); + + // After disconnection for a group, for some reason we have a tendency + // to get a peer change notification with an empty list of peers. + // Perform a fresh scan. + if (mWfdEnabled) { + requestPeers(); + } + } + } + + private final Runnable mConnectionTimeout = new Runnable() { + @Override + public void run() { + if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { + Slog.i(TAG, "Timed out waiting for Wifi display connection after " + + CONNECTION_TIMEOUT_SECONDS + " seconds: " + + mConnectingDevice.deviceName); + handleConnectionFailure(true); + } + } + }; + + private final Runnable mRtspTimeout = new Runnable() { + @Override + public void run() { + if (mConnectedDevice != null + && mRemoteDisplay != null && !mRemoteDisplayConnected) { + Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after " + + RTSP_TIMEOUT_SECONDS + " seconds: " + + mConnectedDevice.deviceName); + handleConnectionFailure(true); + } + } + }; + + private void handleConnectionFailure(boolean timeoutOccurred) { + Slog.i(TAG, "Wifi display connection failed!"); + + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onDisplayConnectionFailed(); + } + }); + + if (mDesiredDevice != null) { + if (mConnectionRetriesLeft > 0) { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + retryConnection(); + } + }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS); + } else { + disconnect(); + } + } + } + + private static Inet4Address getInterfaceAddress(WifiP2pGroup info) { + NetworkInterface iface; + try { + iface = NetworkInterface.getByName(info.getInterface()); + } catch (SocketException ex) { + Slog.w(TAG, "Could not obtain address of network interface " + + info.getInterface(), ex); + return null; + } + + Enumeration<InetAddress> addrs = iface.getInetAddresses(); + while (addrs.hasMoreElements()) { + InetAddress addr = addrs.nextElement(); + if (addr instanceof Inet4Address) { + return (Inet4Address)addr; + } + } + + Slog.w(TAG, "Could not obtain address of network interface " + + info.getInterface() + " because it had no IPv4 addresses."); + return null; + } + + private static int getPortNumber(WifiP2pDevice device) { + if (device.deviceName.startsWith("DIRECT-") + && device.deviceName.endsWith("Broadcom")) { + // These dongles ignore the port we broadcast in our WFD IE. + return 8554; + } + return DEFAULT_CONTROL_PORT; + } + + private static boolean isWifiDisplay(WifiP2pDevice device) { + return device.wfdInfo != null + && device.wfdInfo.isWfdEnabled() + && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType()); + } + + private static boolean isPrimarySinkDeviceType(int deviceType) { + return deviceType == WifiP2pWfdInfo.PRIMARY_SINK + || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK; + } + + private static String describeWifiP2pDevice(WifiP2pDevice device) { + return device != null ? device.toString().replace('\n', ',') : "null"; + } + + private static String describeWifiP2pGroup(WifiP2pGroup group) { + return group != null ? group.toString().replace('\n', ',') : "null"; + } + + private static WifiDisplay createWifiDisplay(WifiP2pDevice device) { + return new WifiDisplay(device.deviceAddress, device.deviceName); + } + + private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { + boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, + WifiP2pManager.WIFI_P2P_STATE_DISABLED)) == + WifiP2pManager.WIFI_P2P_STATE_ENABLED; + if (DEBUG) { + Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled=" + + enabled); + } + + handleStateChanged(enabled); + } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) { + if (DEBUG) { + Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION."); + } + + handlePeersChanged(); + } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { + NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra( + WifiP2pManager.EXTRA_NETWORK_INFO); + if (DEBUG) { + Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo=" + + networkInfo); + } + + handleConnectionChanged(networkInfo); + } + } + }; + + /** + * Called on the handler thread when displays are connected or disconnected. + */ + public interface Listener { + void onEnablementChanged(boolean enabled); + + void onScanStarted(); + void onScanFinished(WifiDisplay[] knownDisplays); + + void onDisplayConnecting(WifiDisplay display); + void onDisplayConnectionFailed(); + void onDisplayConnected(WifiDisplay display, + Surface surface, int width, int height, int flags); + void onDisplayDisconnected(); + } +} diff --git a/services/java/com/android/server/input/InputFilter.java b/services/java/com/android/server/input/InputFilter.java deleted file mode 100644 index 2ce0a02..0000000 --- a/services/java/com/android/server/input/InputFilter.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.input; - -import com.android.server.wm.WindowManagerService; - -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.view.InputEvent; -import android.view.InputEventConsistencyVerifier; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.WindowManagerPolicy; - -/** - * Filters input events before they are dispatched to the system. - * <p> - * At most one input filter can be installed by calling - * {@link WindowManagerService#setInputFilter}. When an input filter is installed, the - * system's behavior changes as follows: - * <ul> - * <li>Input events are first delivered to the {@link WindowManagerPolicy} - * interception methods before queuing as usual. This critical step takes care of managing - * the power state of the device and handling wake keys.</li> - * <li>Input events are then asynchronously delivered to the input filter's - * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to - * applications as usual. The input filter only receives input events that were - * generated by input device; the input filter will not receive input events that were - * injected into the system by other means, such as by instrumentation.</li> - * <li>The input filter processes and optionally transforms the stream of events. For example, - * it may transform a sequence of motion events representing an accessibility gesture into - * a different sequence of motion events, key presses or other system-level interactions. - * The input filter can send events to be dispatched by calling - * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the - * input event.</li> - * </ul> - * </p> - * <h3>The importance of input event consistency</h3> - * <p> - * The input filter mechanism is very low-level. At a minimum, it needs to ensure that it - * sends an internally consistent stream of input events to the dispatcher. There are - * very important invariants to be maintained. - * </p><p> - * For example, if a key down is sent, a corresponding key up should also be sent eventually. - * Likewise, for touch events, each pointer must individually go down with - * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then - * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP} - * and the sequence of pointer ids used must be consistent throughout the gesture. - * </p><p> - * Sometimes a filter may wish to cancel a previously dispatched key or motion. It should - * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly. - * </p><p> - * The input filter must take into account the fact that the input events coming from different - * devices or even different sources all consist of distinct streams of input. - * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify - * the source of the event and its semantics. There are be multiple sources of keys, - * touches and other input: they must be kept separate. - * </p> - * <h3>Policy flags</h3> - * <p> - * Input events received from the dispatcher and sent to the dispatcher have policy flags - * associated with them. Policy flags control some functions of the dispatcher. - * </p><p> - * The early policy interception decides whether an input event should be delivered - * to applications or dropped. The policy indicates its decision by setting the - * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may - * sometimes receive events that do not have this flag set. It should take note of - * the fact that the policy intends to drop the event, clean up its state, and - * then send appropriate cancellation events to the dispatcher if needed. - * </p><p> - * For example, suppose the input filter is processing a gesture and one of the touch events - * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set. - * The input filter should clear its internal state about the gesture and then send key or - * motion events to the dispatcher to cancel any keys or pointers that are down. - * </p><p> - * Corollary: Events that set sent to the dispatcher should usually include the - * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! - * </p><p> - * It may be prudent to disable automatic key repeating for synthetic key events - * by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag. - * </p> - */ -public abstract class InputFilter { - private static final int MSG_INSTALL = 1; - private static final int MSG_UNINSTALL = 2; - private static final int MSG_INPUT_EVENT = 3; - - private final H mH; - private Host mHost; - - // Consistency verifiers for debugging purposes. - private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier = - InputEventConsistencyVerifier.isInstrumentationEnabled() ? - new InputEventConsistencyVerifier(this, - InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT, - "InputFilter#InboundInputEventConsistencyVerifier") : null; - private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier = - InputEventConsistencyVerifier.isInstrumentationEnabled() ? - new InputEventConsistencyVerifier(this, - InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT, - "InputFilter#OutboundInputEventConsistencyVerifier") : null; - - /** - * Creates the input filter. - * - * @param looper The looper to run callbacks on. - */ - public InputFilter(Looper looper) { - mH = new H(looper); - } - - /** - * Called when the input filter is installed. - * This method is guaranteed to be non-reentrant. - * - * @param host The input filter host environment. - */ - final void install(Host host) { - mH.obtainMessage(MSG_INSTALL, host).sendToTarget(); - } - - /** - * Called when the input filter is uninstalled. - * This method is guaranteed to be non-reentrant. - */ - final void uninstall() { - mH.obtainMessage(MSG_UNINSTALL).sendToTarget(); - } - - /** - * Called to enqueue the input event for filtering. - * The event will be recycled after the input filter processes it. - * This method is guaranteed to be non-reentrant. - * - * @param event The input event to enqueue. - */ - final void filterInputEvent(InputEvent event, int policyFlags) { - mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget(); - } - - /** - * Sends an input event to the dispatcher. - * - * @param event The input event to publish. - * @param policyFlags The input event policy flags. - */ - public void sendInputEvent(InputEvent event, int policyFlags) { - if (event == null) { - throw new IllegalArgumentException("event must not be null"); - } - if (mHost == null) { - throw new IllegalStateException("Cannot send input event because the input filter " + - "is not installed."); - } - if (mOutboundInputEventConsistencyVerifier != null) { - mOutboundInputEventConsistencyVerifier.onInputEvent(event, 0); - } - mHost.sendInputEvent(event, policyFlags); - } - - /** - * Called when an input event has been received from the dispatcher. - * <p> - * The default implementation sends the input event back to the dispatcher, unchanged. - * </p><p> - * The event will be recycled when this method returns. If you want to keep it around, - * make a copy! - * </p> - * - * @param event The input event that was received. - * @param policyFlags The input event policy flags. - */ - public void onInputEvent(InputEvent event, int policyFlags) { - sendInputEvent(event, policyFlags); - } - - /** - * Called when the filter is installed into the dispatch pipeline. - * <p> - * This method is called before the input filter receives any input events. - * The input filter should take this opportunity to prepare itself. - * </p> - */ - public void onInstalled() { - } - - /** - * Called when the filter is uninstalled from the dispatch pipeline. - * <p> - * This method is called after the input filter receives its last input event. - * The input filter should take this opportunity to clean up. - * </p> - */ - public void onUninstalled() { - } - - private final class H extends Handler { - public H(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_INSTALL: - mHost = (Host)msg.obj; - if (mInboundInputEventConsistencyVerifier != null) { - mInboundInputEventConsistencyVerifier.reset(); - } - if (mOutboundInputEventConsistencyVerifier != null) { - mOutboundInputEventConsistencyVerifier.reset(); - } - onInstalled(); - break; - - case MSG_UNINSTALL: - try { - onUninstalled(); - } finally { - mHost = null; - } - break; - - case MSG_INPUT_EVENT: { - final InputEvent event = (InputEvent)msg.obj; - try { - if (mInboundInputEventConsistencyVerifier != null) { - mInboundInputEventConsistencyVerifier.onInputEvent(event, 0); - } - onInputEvent(event, msg.arg1); - } finally { - event.recycle(); - } - break; - } - } - } - } - - interface Host { - public void sendInputEvent(InputEvent event, int policyFlags); - } -} diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java index bdd0aa4..805818a 100644 --- a/services/java/com/android/server/input/InputManagerService.java +++ b/services/java/com/android/server/input/InputManagerService.java @@ -19,6 +19,8 @@ package com.android.server.input; import com.android.internal.R; import com.android.internal.util.XmlUtils; import com.android.server.Watchdog; +import com.android.server.display.DisplayManagerService; +import com.android.server.display.DisplayViewport; import org.xmlpull.v1.XmlPullParser; @@ -42,8 +44,8 @@ import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.ContentObserver; -import android.hardware.input.IInputManager; import android.hardware.input.IInputDevicesChangedListener; +import android.hardware.input.IInputManager; import android.hardware.input.InputManager; import android.hardware.input.KeyboardLayout; import android.os.Binder; @@ -57,11 +59,12 @@ import android.os.Process; import android.os.RemoteException; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; -import android.server.BluetoothService; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; +import android.view.IInputFilter; +import android.view.IInputFilterHost; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; @@ -89,7 +92,8 @@ import libcore.util.Objects; /* * Wraps the C++ InputManager and provides its callbacks. */ -public class InputManagerService extends IInputManager.Stub implements Watchdog.Monitor { +public class InputManagerService extends IInputManager.Stub + implements Watchdog.Monitor, DisplayManagerService.InputManagerFuncs { static final String TAG = "InputManager"; static final boolean DEBUG = false; @@ -108,7 +112,6 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. private final Callbacks mCallbacks; private final InputManagerHandler mHandler; private boolean mSystemReady; - private BluetoothService mBluetoothService; private NotificationManager mNotificationManager; // Persistent data store. Must be locked each time during use. @@ -137,17 +140,18 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. // State for the currently installed input filter. final Object mInputFilterLock = new Object(); - InputFilter mInputFilter; // guarded by mInputFilterLock + IInputFilter mInputFilter; // guarded by mInputFilterLock InputFilterHost mInputFilterHost; // guarded by mInputFilterLock private static native int nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); private static native void nativeStart(int ptr); - private static native void nativeSetDisplaySize(int ptr, int displayId, - int width, int height, int externalWidth, int externalHeight); - private static native void nativeSetDisplayOrientation(int ptr, int displayId, - int rotation, int externalRotation); - + private static native void nativeSetDisplayViewport(int ptr, boolean external, + int displayId, int rotation, + int logicalLeft, int logicalTop, int logicalRight, int logicalBottom, + int physicalLeft, int physicalTop, int physicalRight, int physicalBottom, + int deviceWidth, int deviceHeight); + private static native int nativeGetScanCodeState(int ptr, int deviceId, int sourceMask, int scanCode); private static native int nativeGetKeyCodeState(int ptr, @@ -236,11 +240,11 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. updateShowTouchesFromSettings(); } - public void systemReady(BluetoothService bluetoothService) { + // TODO(BT) Pass in paramter for bluetooth system + public void systemReady() { if (DEBUG) { Slog.d(TAG, "System ready."); } - mBluetoothService = bluetoothService; mNotificationManager = (NotificationManager)mContext.getSystemService( Context.NOTIFICATION_SERVICE); mSystemReady = true; @@ -282,29 +286,28 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. nativeReloadDeviceAliases(mPtr); } - public void setDisplaySize(int displayId, int width, int height, - int externalWidth, int externalHeight) { - if (width <= 0 || height <= 0 || externalWidth <= 0 || externalHeight <= 0) { - throw new IllegalArgumentException("Invalid display id or dimensions."); + @Override + public void setDisplayViewports(DisplayViewport defaultViewport, + DisplayViewport externalTouchViewport) { + if (defaultViewport.valid) { + setDisplayViewport(false, defaultViewport); } - - if (DEBUG) { - Slog.d(TAG, "Setting display #" + displayId + " size to " + width + "x" + height - + " external size " + externalWidth + "x" + externalHeight); + + if (externalTouchViewport.valid) { + setDisplayViewport(true, externalTouchViewport); + } else if (defaultViewport.valid) { + setDisplayViewport(true, defaultViewport); } - nativeSetDisplaySize(mPtr, displayId, width, height, externalWidth, externalHeight); } - - public void setDisplayOrientation(int displayId, int rotation, int externalRotation) { - if (rotation < Surface.ROTATION_0 || rotation > Surface.ROTATION_270) { - throw new IllegalArgumentException("Invalid rotation."); - } - - if (DEBUG) { - Slog.d(TAG, "Setting display #" + displayId + " orientation to rotation " + rotation - + " external rotation " + externalRotation); - } - nativeSetDisplayOrientation(mPtr, displayId, rotation, externalRotation); + + private void setDisplayViewport(boolean external, DisplayViewport viewport) { + nativeSetDisplayViewport(mPtr, external, + viewport.displayId, viewport.orientation, + viewport.logicalFrame.left, viewport.logicalFrame.top, + viewport.logicalFrame.right, viewport.logicalFrame.bottom, + viewport.physicalFrame.left, viewport.physicalFrame.top, + viewport.physicalFrame.right, viewport.physicalFrame.bottom, + viewport.deviceWidth, viewport.deviceHeight); } /** @@ -425,9 +428,9 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. * * @param filter The input filter, or null to remove the current filter. */ - public void setInputFilter(InputFilter filter) { + public void setInputFilter(IInputFilter filter) { synchronized (mInputFilterLock) { - final InputFilter oldFilter = mInputFilter; + final IInputFilter oldFilter = mInputFilter; if (oldFilter == filter) { return; // nothing to do } @@ -436,13 +439,21 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. mInputFilter = null; mInputFilterHost.disconnectLocked(); mInputFilterHost = null; - oldFilter.uninstall(); + try { + oldFilter.uninstall(); + } catch (RemoteException re) { + /* ignore */ + } } if (filter != null) { mInputFilter = filter; mInputFilterHost = new InputFilterHost(); - filter.install(mInputFilterHost); + try { + filter.install(mInputFilterHost); + } catch (RemoteException re) { + /* ignore */ + } } nativeSetInputFilterEnabled(mPtr, filter != null); @@ -1210,8 +1221,12 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. } // Native callback. - private void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { - mCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen); + private void notifySwitch(long whenNanos, int switchCode, int switchValue) { + switch (switchCode) { + case SW_LID: + mCallbacks.notifyLidSwitchChanged(whenNanos, switchValue == 0); + break; + } } // Native callback. @@ -1229,7 +1244,11 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. final boolean filterInputEvent(InputEvent event, int policyFlags) { synchronized (mInputFilterLock) { if (mInputFilter != null) { - mInputFilter.filterInputEvent(event, policyFlags); + try { + mInputFilter.filterInputEvent(event, policyFlags); + } catch (RemoteException e) { + /* ignore */ + } return false; } } @@ -1384,9 +1403,9 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. // Native callback. private String getDeviceAlias(String uniqueId) { - if (mBluetoothService != null && - BluetoothAdapter.checkBluetoothAddress(uniqueId)) { - return mBluetoothService.getRemoteAlias(uniqueId); + if (BluetoothAdapter.checkBluetoothAddress(uniqueId)) { + // TODO(BT) mBluetoothService.getRemoteAlias(uniqueId) + return null; } return null; } @@ -1422,6 +1441,10 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. * Private handler for the input manager. */ private final class InputManagerHandler extends Handler { + public InputManagerHandler() { + super(true /*async*/); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -1447,7 +1470,7 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog. /** * Hosting interface for input filters to call back into the input manager. */ - private final class InputFilterHost implements InputFilter.Host { + private final class InputFilterHost extends IInputFilterHost.Stub { private boolean mDisconnected; public void disconnectLocked() { diff --git a/services/java/com/android/server/input/InputWindowHandle.java b/services/java/com/android/server/input/InputWindowHandle.java index 03d66af..ad4fdd1 100644 --- a/services/java/com/android/server/input/InputWindowHandle.java +++ b/services/java/com/android/server/input/InputWindowHandle.java @@ -87,12 +87,16 @@ public final class InputWindowHandle { // Window input features. public int inputFeatures; + // Display this input is on. + public final int displayId; + private native void nativeDispose(); public InputWindowHandle(InputApplicationHandle inputApplicationHandle, - Object windowState) { + Object windowState, int displayId) { this.inputApplicationHandle = inputApplicationHandle; this.windowState = windowState; + this.displayId = displayId; } @Override diff --git a/services/java/com/android/server/input/PersistentDataStore.java b/services/java/com/android/server/input/PersistentDataStore.java index fbe3e8b..71de776 100644 --- a/services/java/com/android/server/input/PersistentDataStore.java +++ b/services/java/com/android/server/input/PersistentDataStore.java @@ -16,7 +16,6 @@ package com.android.server.input; -import com.android.internal.os.AtomicFile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; @@ -25,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java index 1026a0d..354858b 100755 --- a/services/java/com/android/server/location/ComprehensiveCountryDetector.java +++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java @@ -384,8 +384,8 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { } protected boolean isAirplaneModeOff() { - return Settings.System.getInt( - mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0; + return Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 0; } /** diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java index 07f3125..7d030e9 100644 --- a/services/java/com/android/server/location/GeocoderProxy.java +++ b/services/java/com/android/server/location/GeocoderProxy.java @@ -16,92 +16,64 @@ package com.android.server.location; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; import android.location.Address; import android.location.GeocoderParams; import android.location.IGeocodeProvider; -import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; import android.util.Log; +import com.android.server.ServiceWatcher; import java.util.List; /** - * A class for proxying IGeocodeProvider implementations. - * - * {@hide} + * Proxy for IGeocodeProvider implementations. */ public class GeocoderProxy { - private static final String TAG = "GeocoderProxy"; - public static final String SERVICE_ACTION = - "com.android.location.service.GeocodeProvider"; + private static final String SERVICE_ACTION = "com.android.location.service.GeocodeProvider"; private final Context mContext; - private final Intent mIntent; - private final Object mMutex = new Object(); // synchronizes access to mServiceConnection - private Connection mServiceConnection; // never null after ctor - - public GeocoderProxy(Context context, String packageName) { - mContext = context; - mIntent = new Intent(SERVICE_ACTION); - reconnect(packageName); - } - - /** Bind to service. Will reconnect if already connected */ - public void reconnect(String packageName) { - synchronized (mMutex) { - if (mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - } - mServiceConnection = new Connection(); - mIntent.setPackage(packageName); - mContext.bindService(mIntent, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND - | Context.BIND_ALLOW_OOM_MANAGEMENT); + private final ServiceWatcher mServiceWatcher; + + public static GeocoderProxy createAndBind(Context context, + List<String> initialPackageNames) { + GeocoderProxy proxy = new GeocoderProxy(context, initialPackageNames); + if (proxy.bind()) { + return proxy; + } else { + return null; } } - private class Connection implements ServiceConnection { + public GeocoderProxy(Context context, List<String> initialPackageNames) { + mContext = context; - private IGeocodeProvider mProvider; + mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, initialPackageNames, + null, null); + } - public void onServiceConnected(ComponentName className, IBinder service) { - synchronized (this) { - mProvider = IGeocodeProvider.Stub.asInterface(service); - } - } + private boolean bind () { + return mServiceWatcher.start(); + } - public void onServiceDisconnected(ComponentName className) { - synchronized (this) { - mProvider = null; - } - } + private IGeocodeProvider getService() { + return IGeocodeProvider.Stub.asInterface(mServiceWatcher.getBinder()); + } - public IGeocodeProvider getProvider() { - synchronized (this) { - return mProvider; - } - } + public String getConnectedPackageName() { + return mServiceWatcher.getBestPackageName(); } public String getFromLocation(double latitude, double longitude, int maxResults, GeocoderParams params, List<Address> addrs) { - IGeocodeProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } + IGeocodeProvider provider = getService(); if (provider != null) { try { - return provider.getFromLocation(latitude, longitude, maxResults, - params, addrs); + return provider.getFromLocation(latitude, longitude, maxResults, params, addrs); } catch (RemoteException e) { - Log.e(TAG, "getFromLocation failed", e); + Log.w(TAG, e); } } return "Service not Available"; @@ -111,19 +83,17 @@ public class GeocoderProxy { double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, int maxResults, GeocoderParams params, List<Address> addrs) { - IGeocodeProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } + IGeocodeProvider provider = getService(); if (provider != null) { try { return provider.getFromLocationName(locationName, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, maxResults, params, addrs); } catch (RemoteException e) { - Log.e(TAG, "getFromLocationName failed", e); + Log.w(TAG, e); } } return "Service not Available"; } + } diff --git a/services/java/com/android/server/location/GeofenceManager.java b/services/java/com/android/server/location/GeofenceManager.java new file mode 100644 index 0000000..26d9c15 --- /dev/null +++ b/services/java/com/android/server/location/GeofenceManager.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 20012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location; + +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import android.Manifest.permission; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.location.Geofence; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationRequest; +import android.os.Bundle; +import android.os.Looper; +import android.os.PowerManager; +import android.os.SystemClock; +import android.util.Log; + +import com.android.server.LocationManagerService; + +public class GeofenceManager implements LocationListener, PendingIntent.OnFinished { + private static final String TAG = "GeofenceManager"; + private static final boolean D = LocationManagerService.D; + + /** + * Assume a maximum land speed, as a heuristic to throttle location updates. + * (Air travel should result in an airplane mode toggle which will + * force a new location update anyway). + */ + private static final int MAX_SPEED_M_S = 100; // 360 km/hr (high speed train) + + private final Context mContext; + private final LocationManager mLocationManager; + private final PowerManager.WakeLock mWakeLock; + private final Looper mLooper; // looper thread to take location updates on + private final LocationBlacklist mBlacklist; + + private Object mLock = new Object(); + + // access to members below is synchronized on mLock + private Location mLastLocation; + private List<GeofenceState> mFences = new LinkedList<GeofenceState>(); + + public GeofenceManager(Context context, LocationBlacklist blacklist) { + mContext = context; + mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); + PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mLooper = Looper.myLooper(); + mBlacklist = blacklist; + + LocationRequest request = new LocationRequest() + .setQuality(LocationRequest.POWER_NONE) + .setFastestInterval(0); + mLocationManager.requestLocationUpdates(request, this, Looper.myLooper()); + } + + public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent, int uid, + String packageName) { + GeofenceState state = new GeofenceState(geofence, mLastLocation, + request.getExpireAt(), packageName, intent); + + synchronized (mLock) { + // first make sure it doesn't already exist + for (int i = mFences.size() - 1; i >= 0; i--) { + GeofenceState w = mFences.get(i); + if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) { + // already exists, remove the old one + mFences.remove(i); + break; + } + } + mFences.add(state); + updateProviderRequirementsLocked(); + } + } + + public void removeFence(Geofence fence, PendingIntent intent) { + synchronized (mLock) { + Iterator<GeofenceState> iter = mFences.iterator(); + while (iter.hasNext()) { + GeofenceState state = iter.next(); + if (state.mIntent.equals(intent)) { + + if (fence == null) { + // alwaus remove + iter.remove(); + } else { + // just remove matching fences + if (fence.equals(state.mFence)) { + iter.remove(); + } + } + } + } + updateProviderRequirementsLocked(); + } + } + + public void removeFence(String packageName) { + synchronized (mLock) { + Iterator<GeofenceState> iter = mFences.iterator(); + while (iter.hasNext()) { + GeofenceState state = iter.next(); + if (state.mPackageName.equals(packageName)) { + iter.remove(); + } + } + updateProviderRequirementsLocked(); + } + } + + private void removeExpiredFencesLocked() { + long time = SystemClock.elapsedRealtime(); + Iterator<GeofenceState> iter = mFences.iterator(); + while (iter.hasNext()) { + GeofenceState state = iter.next(); + if (state.mExpireAt < time) { + iter.remove(); + } + } + } + + private void processLocation(Location location) { + List<PendingIntent> enterIntents = new LinkedList<PendingIntent>(); + List<PendingIntent> exitIntents = new LinkedList<PendingIntent>(); + + synchronized (mLock) { + mLastLocation = location; + + removeExpiredFencesLocked(); + + for (GeofenceState state : mFences) { + if (mBlacklist.isBlacklisted(state.mPackageName)) { + if (D) Log.d(TAG, "skipping geofence processing for blacklisted app: " + + state.mPackageName); + continue; + } + + int event = state.processLocation(location); + if ((event & GeofenceState.FLAG_ENTER) != 0) { + enterIntents.add(state.mIntent); + } + if ((event & GeofenceState.FLAG_EXIT) != 0) { + exitIntents.add(state.mIntent); + } + } + updateProviderRequirementsLocked(); + } + + // release lock before sending intents + for (PendingIntent intent : exitIntents) { + sendIntentExit(intent); + } + for (PendingIntent intent : enterIntents) { + sendIntentEnter(intent); + } + } + + private void sendIntentEnter(PendingIntent pendingIntent) { + Intent intent = new Intent(); + intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true); + sendIntent(pendingIntent, intent); + } + + private void sendIntentExit(PendingIntent pendingIntent) { + Intent intent = new Intent(); + intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false); + sendIntent(pendingIntent, intent); + } + + private void sendIntent(PendingIntent pendingIntent, Intent intent) { + try { + mWakeLock.acquire(); + pendingIntent.send(mContext, 0, intent, this, null, permission.ACCESS_FINE_LOCATION); + } catch (PendingIntent.CanceledException e) { + removeFence(null, pendingIntent); + mWakeLock.release(); + } + } + + private void updateProviderRequirementsLocked() { + double minDistance = Double.MAX_VALUE; + for (GeofenceState state : mFences) { + if (state.getDistance() < minDistance) { + minDistance = state.getDistance(); + } + } + + if (minDistance == Double.MAX_VALUE) { + disableLocationLocked(); + } else { + int intervalMs = (int)(minDistance * 1000) / MAX_SPEED_M_S; + requestLocationLocked(intervalMs); + } + } + + private void requestLocationLocked(int intervalMs) { + mLocationManager.requestLocationUpdates(new LocationRequest().setInterval(intervalMs), this, + mLooper); + } + + private void disableLocationLocked() { + mLocationManager.removeUpdates(this); + } + + @Override + public void onLocationChanged(Location location) { + processLocation(location); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { } + + @Override + public void onProviderEnabled(String provider) { } + + @Override + public void onProviderDisabled(String provider) { } + + @Override + public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, + String resultData, Bundle resultExtras) { + mWakeLock.release(); + } + + public void dump(PrintWriter pw) { + pw.println(" Geofences:"); + + for (GeofenceState state : mFences) { + pw.append(" "); + pw.append(state.mPackageName); + pw.append(" "); + pw.append(state.mFence.toString()); + pw.append("\n"); + } + } +} diff --git a/services/java/com/android/server/location/GeofenceState.java b/services/java/com/android/server/location/GeofenceState.java new file mode 100644 index 0000000..1fd737f --- /dev/null +++ b/services/java/com/android/server/location/GeofenceState.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.server.location; + +import android.app.PendingIntent; +import android.location.Geofence; +import android.location.Location; + +/** + * Represents state associated with a geofence + */ +public class GeofenceState { + public final static int FLAG_ENTER = 0x01; + public final static int FLAG_EXIT = 0x02; + + private static final int STATE_UNKNOWN = 0; + private static final int STATE_INSIDE = 1; + private static final int STATE_OUTSIDE = 2; + + public final Geofence mFence; + private final Location mLocation; + public final long mExpireAt; + public final String mPackageName; + public final PendingIntent mIntent; + + int mState; // current state + double mDistance; // current distance to center of fence + + public GeofenceState(Geofence fence, Location prevLocation, long expireAt, + String packageName, PendingIntent intent) { + mState = STATE_UNKNOWN; + + mFence = fence; + mExpireAt = expireAt; + mPackageName = packageName; + mIntent = intent; + + mLocation = new Location(""); + mLocation.setLatitude(fence.getLatitude()); + mLocation.setLongitude(fence.getLongitude()); + + if (prevLocation != null) { + processLocation(prevLocation); + } + } + + /** + * Process a new location. + * @return FLAG_ENTER or FLAG_EXIT if the fence was crossed, 0 otherwise + */ + public int processLocation(Location location) { + mDistance = mLocation.distanceTo(location); + + int prevState = mState; + //TODO: inside/outside detection could be made more rigorous + boolean inside = mDistance <= Math.max(mFence.getRadius(), location.getAccuracy()); + if (inside) { + mState = STATE_INSIDE; + } else { + mState = STATE_OUTSIDE; + } + + if (prevState != 0 && mState != prevState) { + if (mState == STATE_INSIDE) return FLAG_ENTER; + if (mState == STATE_OUTSIDE) return FLAG_EXIT; + } + return 0; + } + + public double getDistance() { + return mDistance; + } + + @Override + public String toString() { + String state; + switch (mState) { + case STATE_INSIDE: + state = "IN"; + break; + case STATE_OUTSIDE: + state = "OUT"; + break; + default: + state = "?"; + } + return String.format("%s d=%.0f %s", mFence.toString(), mDistance, state); + } +} diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index 4ad6140..bb11fe7 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -29,11 +29,13 @@ import android.location.IGpsStatusProvider; import android.location.ILocationManager; import android.location.INetInitiatedListener; import android.location.Location; +import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; +import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -45,6 +47,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.provider.Telephony.Carriers; @@ -54,17 +57,19 @@ import android.telephony.TelephonyManager; import android.telephony.gsm.GsmCellLocation; import android.util.Log; import android.util.NtpTrustedTime; -import android.util.SparseIntArray; - import com.android.internal.app.IBatteryStats; import com.android.internal.location.GpsNetInitiatedHandler; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; +import java.io.PrintWriter; import java.io.StringReader; import java.util.ArrayList; import java.util.Date; @@ -81,8 +86,12 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final String TAG = "GpsLocationProvider"; - private static final boolean DEBUG = false; - private static final boolean VERBOSE = false; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); + + private static final ProviderProperties PROPERTIES = new ProviderProperties( + true, true, false, false, true, true, true, + Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); // these need to match GpsPositionMode enum in gps.h private static final int GPS_POSITION_MODE_STANDALONE = 0; @@ -150,14 +159,15 @@ public class GpsLocationProvider implements LocationProviderInterface { // Handler messages private static final int CHECK_LOCATION = 1; private static final int ENABLE = 2; - private static final int ENABLE_TRACKING = 3; + private static final int SET_REQUEST = 3; private static final int UPDATE_NETWORK_STATE = 4; private static final int INJECT_NTP_TIME = 5; private static final int DOWNLOAD_XTRA_DATA = 6; private static final int UPDATE_LOCATION = 7; private static final int ADD_LISTENER = 8; private static final int REMOVE_LISTENER = 9; - private static final int REQUEST_SINGLE_SHOT = 10; + private static final int INJECT_NTP_TIME_FINISHED = 10; + private static final int DOWNLOAD_XTRA_DATA_FINISHED = 11; // Request setid private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1; @@ -179,6 +189,18 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final String PROPERTIES_FILE = "/etc/gps.conf"; + /** simpler wrapper for ProviderRequest + Worksource */ + private static class GpsRequest { + public ProviderRequest request; + public WorkSource source; + public GpsRequest(ProviderRequest request, WorkSource source) { + this.request = request; + this.source = source; + } + } + + private Object mLock = new Object(); + private int mLocationFlags = LOCATION_INVALID; // current status @@ -198,16 +220,28 @@ public class GpsLocationProvider implements LocationProviderInterface { // Typical hot TTTF is ~5 seconds, so 10 seconds seems sane. private static final int GPS_POLLING_THRESHOLD_INTERVAL = 10 * 1000; - // true if we are enabled - private volatile boolean mEnabled; - + // how often to request NTP time, in milliseconds + // current setting 24 hours + private static final long NTP_INTERVAL = 24*60*60*1000; + // how long to wait if we have a network error in NTP or XTRA downloading + // current setting - 5 minutes + private static final long RETRY_INTERVAL = 5*60*1000; + + // true if we are enabled, protected by this + private boolean mEnabled; + // true if we have network connectivity private boolean mNetworkAvailable; + // states for injecting ntp and downloading xtra data + private static final int STATE_PENDING_NETWORK = 0; + private static final int STATE_DOWNLOADING = 1; + private static final int STATE_IDLE = 2; + // flags to trigger NTP or XTRA data download when network becomes available // initialized to true so we do NTP and XTRA when the network comes up after booting - private boolean mInjectNtpTimePending = true; - private boolean mDownloadXtraDataPending = true; + private int mInjectNtpTimePending = STATE_PENDING_NETWORK; + private int mDownloadXtraDataPending = STATE_PENDING_NETWORK; // set to true if the GPS engine does not do on-demand NTP time requests private boolean mPeriodicTimeInjection; @@ -217,16 +251,13 @@ public class GpsLocationProvider implements LocationProviderInterface { // true if GPS engine is on private boolean mEngineOn; - + // requested frequency of fixes, in milliseconds private int mFixInterval = 1000; // true if we started navigation private boolean mStarted; - // true if single shot request is in progress - private boolean mSingleShot; - // capabilities of the GPS engine private int mEngineCapabilities; @@ -236,7 +267,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // for calculating time to first fix private long mFixRequestTime = 0; // time to first fix for most recent session - private int mTTFF = 0; + private int mTimeToFirstFix = 0; // time we received our last fix private long mLastFixTime; @@ -251,7 +282,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private final Context mContext; private final NtpTrustedTime mNtpTime; - private final ILocationManager mLocationManager; + private final ILocationManager mILocationManager; private Location mLocation = new Location(LocationManager.GPS_PROVIDER); private Bundle mLocationExtras = new Bundle(); private ArrayList<Listener> mListeners = new ArrayList<Listener>(); @@ -267,17 +298,11 @@ public class GpsLocationProvider implements LocationProviderInterface { private int mAGpsDataConnectionState; private int mAGpsDataConnectionIpAddr; private final ConnectivityManager mConnMgr; - private final GpsNetInitiatedHandler mNIHandler; + private final GpsNetInitiatedHandler mNIHandler; // Wakelocks private final static String WAKELOCK_KEY = "GpsLocationProvider"; private final PowerManager.WakeLock mWakeLock; - // bitfield of pending messages to our Handler - // used only for messages that cannot have multiple instances queued - private int mPendingMessageBits; - // separate counter for ADD_LISTENER and REMOVE_LISTENER messages, - // which might have multiple instances queued - private int mPendingListenerMessages; // Alarms private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP"; @@ -287,22 +312,18 @@ public class GpsLocationProvider implements LocationProviderInterface { private final PendingIntent mTimeoutIntent; private final IBatteryStats mBatteryStats; - private final SparseIntArray mClientUids = new SparseIntArray(); - // how often to request NTP time, in milliseconds - // current setting 24 hours - private static final long NTP_INTERVAL = 24*60*60*1000; - // how long to wait if we have a network error in NTP or XTRA downloading - // current setting - 5 minutes - private static final long RETRY_INTERVAL = 5*60*1000; + // only modified on handler thread + private int[] mClientUids = new int[0]; private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() { + @Override public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException { if (listener == null) { throw new NullPointerException("listener is null in addGpsStatusListener"); } - synchronized(mListeners) { + synchronized (mListeners) { IBinder binder = listener.asBinder(); int size = mListeners.size(); for (int i = 0; i < size; i++) { @@ -319,12 +340,13 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + @Override public void removeGpsStatusListener(IGpsStatusListener listener) { if (listener == null) { throw new NullPointerException("listener is null in addGpsStatusListener"); } - synchronized(mListeners) { + synchronized (mListeners) { IBinder binder = listener.asBinder(); Listener l = null; int size = mListeners.size(); @@ -353,7 +375,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (action.equals(ALARM_WAKEUP)) { if (DEBUG) Log.d(TAG, "ALARM_WAKEUP"); - startNavigating(false); + startNavigating(); } else if (action.equals(ALARM_TIMEOUT)) { if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); hibernate(); @@ -361,6 +383,22 @@ public class GpsLocationProvider implements LocationProviderInterface { checkSmsSuplInit(intent); } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) { checkWapSuplInit(intent); + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + int networkState; + if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) { + networkState = LocationProvider.TEMPORARILY_UNAVAILABLE; + } else { + networkState = LocationProvider.AVAILABLE; + } + + // retrieve NetworkInfo result for this UID + NetworkInfo info = + intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); + ConnectivityManager connManager = (ConnectivityManager) + mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + info = connManager.getNetworkInfo(info.getType()); + + updateNetworkState(networkState, info); } } }; @@ -382,10 +420,10 @@ public class GpsLocationProvider implements LocationProviderInterface { return native_is_supported(); } - public GpsLocationProvider(Context context, ILocationManager locationManager) { + public GpsLocationProvider(Context context, ILocationManager ilocationManager) { mContext = context; mNtpTime = NtpTrustedTime.getInstance(context); - mLocationManager = locationManager; + mILocationManager = ilocationManager; mNIHandler = new GpsNetInitiatedHandler(context); mLocation.setExtras(mLocationExtras); @@ -393,7 +431,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // Create a wake lock PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); - mWakeLock.setReferenceCounted(false); + mWakeLock.setReferenceCounted(true); mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0); @@ -467,22 +505,21 @@ public class GpsLocationProvider implements LocationProviderInterface { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ALARM_WAKEUP); intentFilter.addAction(ALARM_TIMEOUT); + intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); mContext.registerReceiver(mBroadcastReciever, intentFilter); } /** * Returns the name of this provider. */ + @Override public String getName() { return LocationManager.GPS_PROVIDER; } - /** - * Returns true if the provider requires access to a - * data network (e.g., the Internet), false otherwise. - */ - public boolean requiresNetwork() { - return true; + @Override + public ProviderProperties getProperties() { + return PROPERTIES; } public void updateNetworkState(int state, NetworkInfo info) { @@ -516,7 +553,7 @@ public class GpsLocationProvider implements LocationProviderInterface { String apnName = info.getExtraInfo(); if (mNetworkAvailable) { if (apnName == null) { - /* Assign a dummy value in the case of C2K as otherwise we will have a runtime + /* Assign a dummy value in the case of C2K as otherwise we will have a runtime exception in the following call to native_agps_data_conn_open*/ apnName = "dummy-apn"; } @@ -541,88 +578,111 @@ public class GpsLocationProvider implements LocationProviderInterface { } if (mNetworkAvailable) { - if (mInjectNtpTimePending) { + if (mInjectNtpTimePending == STATE_PENDING_NETWORK) { sendMessage(INJECT_NTP_TIME, 0, null); } - if (mDownloadXtraDataPending) { + if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) { sendMessage(DOWNLOAD_XTRA_DATA, 0, null); } } } private void handleInjectNtpTime() { + if (mInjectNtpTimePending == STATE_DOWNLOADING) { + // already downloading data + return; + } if (!mNetworkAvailable) { // try again when network is up - mInjectNtpTimePending = true; + mInjectNtpTimePending = STATE_PENDING_NETWORK; return; } - mInjectNtpTimePending = false; + mInjectNtpTimePending = STATE_DOWNLOADING; - long delay; + // hold wake lock while task runs + mWakeLock.acquire(); + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + long delay; - // force refresh NTP cache when outdated - if (mNtpTime.getCacheAge() >= NTP_INTERVAL) { - mNtpTime.forceRefresh(); - } + // force refresh NTP cache when outdated + if (mNtpTime.getCacheAge() >= NTP_INTERVAL) { + mNtpTime.forceRefresh(); + } - // only update when NTP time is fresh - if (mNtpTime.getCacheAge() < NTP_INTERVAL) { - long time = mNtpTime.getCachedNtpTime(); - long timeReference = mNtpTime.getCachedNtpTimeReference(); - long certainty = mNtpTime.getCacheCertainty(); - long now = System.currentTimeMillis(); - - Log.d(TAG, "NTP server returned: " - + time + " (" + new Date(time) - + ") reference: " + timeReference - + " certainty: " + certainty - + " system time offset: " + (time - now)); - - native_inject_time(time, timeReference, (int) certainty); - delay = NTP_INTERVAL; - } else { - if (DEBUG) Log.d(TAG, "requestTime failed"); - delay = RETRY_INTERVAL; - } + // only update when NTP time is fresh + if (mNtpTime.getCacheAge() < NTP_INTERVAL) { + long time = mNtpTime.getCachedNtpTime(); + long timeReference = mNtpTime.getCachedNtpTimeReference(); + long certainty = mNtpTime.getCacheCertainty(); + long now = System.currentTimeMillis(); + + Log.d(TAG, "NTP server returned: " + + time + " (" + new Date(time) + + ") reference: " + timeReference + + " certainty: " + certainty + + " system time offset: " + (time - now)); + + native_inject_time(time, timeReference, (int) certainty); + delay = NTP_INTERVAL; + } else { + if (DEBUG) Log.d(TAG, "requestTime failed"); + delay = RETRY_INTERVAL; + } - if (mPeriodicTimeInjection) { - // send delayed message for next NTP injection - // since this is delayed and not urgent we do not hold a wake lock here - mHandler.removeMessages(INJECT_NTP_TIME); - mHandler.sendMessageDelayed(Message.obtain(mHandler, INJECT_NTP_TIME), delay); - } + sendMessage(INJECT_NTP_TIME_FINISHED, 0, null); + + if (mPeriodicTimeInjection) { + // send delayed message for next NTP injection + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.sendEmptyMessageDelayed(INJECT_NTP_TIME, delay); + } + + // release wake lock held by task + mWakeLock.release(); + } + }); } private void handleDownloadXtraData() { + if (mDownloadXtraDataPending == STATE_DOWNLOADING) { + // already downloading data + return; + } if (!mNetworkAvailable) { // try again when network is up - mDownloadXtraDataPending = true; + mDownloadXtraDataPending = STATE_PENDING_NETWORK; return; } - mDownloadXtraDataPending = false; + mDownloadXtraDataPending = STATE_DOWNLOADING; + + // hold wake lock while task runs + mWakeLock.acquire(); + AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties); + byte[] data = xtraDownloader.downloadXtraData(); + if (data != null) { + if (DEBUG) { + Log.d(TAG, "calling native_inject_xtra_data"); + } + native_inject_xtra_data(data, data.length); + } + sendMessage(DOWNLOAD_XTRA_DATA_FINISHED, 0, null); - GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties); - byte[] data = xtraDownloader.downloadXtraData(); - if (data != null) { - if (DEBUG) { - Log.d(TAG, "calling native_inject_xtra_data"); - } - native_inject_xtra_data(data, data.length); - } else { - // try again later - // since this is delayed and not urgent we do not hold a wake lock here - mHandler.removeMessages(DOWNLOAD_XTRA_DATA); - mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), RETRY_INTERVAL); - } - } + if (data == null) { + // try again later + // since this is delayed and not urgent we do not hold a wake lock here + mHandler.sendEmptyMessageDelayed(DOWNLOAD_XTRA_DATA, RETRY_INTERVAL); + } - /** - * This is called to inform us when another location provider returns a location. - * Someday we might use this for network location injection to aid the GPS - */ - public void updateLocation(Location location) { - sendMessage(UPDATE_LOCATION, 0, location); + // release wake lock held by task + mWakeLock.release(); + } + }); } private void handleUpdateLocation(Location location) { @@ -633,107 +693,26 @@ public class GpsLocationProvider implements LocationProviderInterface { } /** - * Returns true if the provider requires access to a - * satellite-based positioning system (e.g., GPS), false - * otherwise. - */ - public boolean requiresSatellite() { - return true; - } - - /** - * Returns true if the provider requires access to an appropriate - * cellular network (e.g., to make use of cell tower IDs), false - * otherwise. - */ - public boolean requiresCell() { - return false; - } - - /** - * Returns true if the use of this provider may result in a - * monetary charge to the user, false if use is free. It is up to - * each provider to give accurate information. - */ - public boolean hasMonetaryCost() { - return false; - } - - /** - * Returns true if the provider is able to provide altitude - * information, false otherwise. A provider that reports altitude - * under most circumstances but may occassionally not report it - * should return true. - */ - public boolean supportsAltitude() { - return true; - } - - /** - * Returns true if the provider is able to provide speed - * information, false otherwise. A provider that reports speed - * under most circumstances but may occassionally not report it - * should return true. - */ - public boolean supportsSpeed() { - return true; - } - - /** - * Returns true if the provider is able to provide bearing - * information, false otherwise. A provider that reports bearing - * under most circumstances but may occassionally not report it - * should return true. - */ - public boolean supportsBearing() { - return true; - } - - /** - * Returns the power requirement for this provider. - * - * @return the power requirement for this provider, as one of the - * constants Criteria.POWER_REQUIREMENT_*. - */ - public int getPowerRequirement() { - return Criteria.POWER_HIGH; - } - - /** - * Returns true if this provider meets the given criteria, - * false otherwise. - */ - public boolean meetsCriteria(Criteria criteria) { - return (criteria.getPowerRequirement() != Criteria.POWER_LOW); - } - - /** - * Returns the horizontal accuracy of this provider - * - * @return the accuracy of location from this provider, as one - * of the constants Criteria.ACCURACY_*. - */ - public int getAccuracy() { - return Criteria.ACCURACY_FINE; - } - - /** * Enables this provider. When enabled, calls to getStatus() * must be handled. Hardware may be started up * when the provider is enabled. */ + @Override public void enable() { - synchronized (mHandler) { - sendMessage(ENABLE, 1, null); - } + sendMessage(ENABLE, 1, null); } private void handleEnable() { if (DEBUG) Log.d(TAG, "handleEnable"); - if (mEnabled) return; - mEnabled = native_init(); - if (mEnabled) { + synchronized (mLock) { + if (mEnabled) return; + mEnabled = true; + } + + boolean enabled = native_init(); + + if (enabled) { mSupportsXtra = native_supports_xtra(); if (mSuplServerHost != null) { native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); @@ -742,6 +721,9 @@ public class GpsLocationProvider implements LocationProviderInterface { native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); } } else { + synchronized (mLock) { + mEnabled = false; + } Log.w(TAG, "Failed to enable location provider"); } } @@ -751,27 +733,35 @@ public class GpsLocationProvider implements LocationProviderInterface { * need not be handled. Hardware may be shut * down while the provider is disabled. */ + @Override public void disable() { - synchronized (mHandler) { - sendMessage(ENABLE, 0, null); - } + sendMessage(ENABLE, 0, null); } private void handleDisable() { if (DEBUG) Log.d(TAG, "handleDisable"); - if (!mEnabled) return; - mEnabled = false; + synchronized (mLock) { + if (!mEnabled) return; + mEnabled = false; + } + stopNavigating(); + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); // do this before releasing wakelock native_cleanup(); } + @Override public boolean isEnabled() { - return mEnabled; + synchronized (mLock) { + return mEnabled; + } } + @Override public int getStatus(Bundle extras) { if (extras != null) { extras.putInt("satellites", mSvCount); @@ -788,93 +778,69 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + @Override public long getStatusUpdateTime() { return mStatusUpdateTime; } - public void enableLocationTracking(boolean enable) { - // FIXME - should set a flag here to avoid race conditions with single shot request - synchronized (mHandler) { - sendMessage(ENABLE_TRACKING, (enable ? 1 : 0), null); - } + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + sendMessage(SET_REQUEST, 0, new GpsRequest(request, source)); } - private void handleEnableLocationTracking(boolean enable) { - if (enable) { - mTTFF = 0; - mLastFixTime = 0; - startNavigating(false); - } else { - if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) { - mAlarmManager.cancel(mWakeupIntent); - mAlarmManager.cancel(mTimeoutIntent); - } - stopNavigating(); - } - } + private void handleSetRequest(ProviderRequest request, WorkSource source) { + if (DEBUG) Log.d(TAG, "setRequest " + request); - public boolean requestSingleShotFix() { - if (mStarted) { - // cannot do single shot if already navigating - return false; - } - synchronized (mHandler) { - mHandler.removeMessages(REQUEST_SINGLE_SHOT); - Message m = Message.obtain(mHandler, REQUEST_SINGLE_SHOT); - mHandler.sendMessage(m); - } - return true; - } - private void handleRequestSingleShot() { - mTTFF = 0; - mLastFixTime = 0; - startNavigating(true); - } - public void setMinTime(long minTime, WorkSource ws) { - if (DEBUG) Log.d(TAG, "setMinTime " + minTime); - - if (minTime >= 0) { - mFixInterval = (int)minTime; + if (request.reportLocation) { + // update client uids + int[] uids = new int[source.size()]; + for (int i=0; i < source.size(); i++) { + uids[i] = source.get(i); + } + updateClientUids(uids); + + mFixInterval = (int) request.interval; + // check for overflow + if (mFixInterval != request.interval) { + Log.w(TAG, "interval overflow: " + request.interval); + mFixInterval = Integer.MAX_VALUE; + } + + // apply request to GPS engine if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) { + // change period if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC, mFixInterval, 0, 0)) { Log.e(TAG, "set_position_mode failed in setMinTime()"); } + } else if (!mStarted) { + // start GPS + startNavigating(); } - } - } - - public String getInternalState() { - StringBuilder s = new StringBuilder(); - s.append(" mFixInterval=").append(mFixInterval).append("\n"); - s.append(" mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities)).append(" ("); - if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHED "); - if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB "); - if (hasCapability(GPS_CAPABILITY_MSA)) s.append("MSA "); - if (hasCapability(GPS_CAPABILITY_SINGLE_SHOT)) s.append("SINGLE_SHOT "); - if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) s.append("ON_DEMAND_TIME "); - s.append(")\n"); + } else { + updateClientUids(new int[0]); - s.append(native_get_internal_state()); - return s.toString(); + stopNavigating(); + mAlarmManager.cancel(mWakeupIntent); + mAlarmManager.cancel(mTimeoutIntent); + } } private final class Listener implements IBinder.DeathRecipient { final IGpsStatusListener mListener; - - int mSensors = 0; - + Listener(IGpsStatusListener listener) { mListener = listener; } - + + @Override public void binderDied() { if (DEBUG) Log.d(TAG, "GPS status listener died"); - synchronized(mListeners) { + synchronized (mListeners) { mListeners.remove(this); } if (mListener != null) { @@ -883,64 +849,50 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - public void addListener(int uid) { - synchronized (mWakeLock) { - mPendingListenerMessages++; - mWakeLock.acquire(); - Message m = Message.obtain(mHandler, ADD_LISTENER); - m.arg1 = uid; - mHandler.sendMessage(m); - } - } - - private void handleAddListener(int uid) { - synchronized(mListeners) { - if (mClientUids.indexOfKey(uid) >= 0) { - // Shouldn't be here -- already have this uid. - Log.w(TAG, "Duplicate add listener for uid " + uid); - return; + private void updateClientUids(int[] uids) { + // Find uid's that were not previously tracked + for (int uid1 : uids) { + boolean newUid = true; + for (int uid2 : mClientUids) { + if (uid1 == uid2) { + newUid = false; + break; + } } - mClientUids.put(uid, 0); - if (mNavigating) { + if (newUid) { try { - mBatteryStats.noteStartGps(uid); + mBatteryStats.noteStartGps(uid1); } catch (RemoteException e) { - Log.w(TAG, "RemoteException in addListener"); + Log.w(TAG, "RemoteException", e); } } } - } - - public void removeListener(int uid) { - synchronized (mWakeLock) { - mPendingListenerMessages++; - mWakeLock.acquire(); - Message m = Message.obtain(mHandler, REMOVE_LISTENER); - m.arg1 = uid; - mHandler.sendMessage(m); - } - } - private void handleRemoveListener(int uid) { - synchronized(mListeners) { - if (mClientUids.indexOfKey(uid) < 0) { - // Shouldn't be here -- don't have this uid. - Log.w(TAG, "Unneeded remove listener for uid " + uid); - return; + // Find uid'd that were tracked but have now disappeared + for (int uid1 : mClientUids) { + boolean oldUid = true; + for (int uid2 : uids) { + if (uid1 == uid2) { + oldUid = false; + break; + } } - mClientUids.delete(uid); - if (mNavigating) { + if (oldUid) { try { - mBatteryStats.noteStopGps(uid); + mBatteryStats.noteStopGps(uid1); } catch (RemoteException e) { - Log.w(TAG, "RemoteException in removeListener"); + Log.w(TAG, "RemoteException", e); } } } + + // save current uids + mClientUids = uids; } + @Override public boolean sendExtraCommand(String command, Bundle extras) { - + long identity = Binder.clearCallingIdentity(); boolean result = false; @@ -957,7 +909,7 @@ public class GpsLocationProvider implements LocationProviderInterface { } else { Log.w(TAG, "sendExtraCommand: unknown command " + command); } - + Binder.restoreCallingIdentity(identity); return result; } @@ -992,18 +944,17 @@ public class GpsLocationProvider implements LocationProviderInterface { return false; } - private void startNavigating(boolean singleShot) { + private void startNavigating() { if (!mStarted) { if (DEBUG) Log.d(TAG, "startNavigating"); + mTimeToFirstFix = 0; + mLastFixTime = 0; mStarted = true; - mSingleShot = singleShot; mPositionMode = GPS_POSITION_MODE_STANDALONE; if (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { - if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) { - mPositionMode = GPS_POSITION_MODE_MS_ASSISTED; - } else if (hasCapability(GPS_CAPABILITY_MSB)) { + if (hasCapability(GPS_CAPABILITY_MSB)) { mPositionMode = GPS_POSITION_MODE_MS_BASED; } } @@ -1039,9 +990,8 @@ public class GpsLocationProvider implements LocationProviderInterface { if (DEBUG) Log.d(TAG, "stopNavigating"); if (mStarted) { mStarted = false; - mSingleShot = false; native_stop(); - mTTFF = 0; + mTimeToFirstFix = 0; mLastFixTime = 0; mLocationFlags = LOCATION_INVALID; @@ -1056,8 +1006,7 @@ public class GpsLocationProvider implements LocationProviderInterface { mAlarmManager.cancel(mTimeoutIntent); mAlarmManager.cancel(mWakeupIntent); long now = SystemClock.elapsedRealtime(); - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + mFixInterval, mWakeupIntent); + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, mWakeupIntent); } private boolean hasCapability(int capability) { @@ -1078,6 +1027,9 @@ public class GpsLocationProvider implements LocationProviderInterface { mLocation.setLatitude(latitude); mLocation.setLongitude(longitude); mLocation.setTime(timestamp); + // It would be nice to push the elapsed real-time timestamp + // further down the stack, but this is still useful + mLocation.setElapsedRealtimeNano(SystemClock.elapsedRealtimeNano()); } if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) { mLocation.setAltitude(altitude); @@ -1102,7 +1054,7 @@ public class GpsLocationProvider implements LocationProviderInterface { mLocation.setExtras(mLocationExtras); try { - mLocationManager.reportLocation(mLocation, false); + mILocationManager.reportLocation(mLocation, false); } catch (RemoteException e) { Log.e(TAG, "RemoteException calling reportLocation"); } @@ -1110,17 +1062,17 @@ public class GpsLocationProvider implements LocationProviderInterface { mLastFixTime = System.currentTimeMillis(); // report time to first fix - if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { - mTTFF = (int)(mLastFixTime - mFixRequestTime); - if (DEBUG) Log.d(TAG, "TTFF: " + mTTFF); + if (mTimeToFirstFix == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + mTimeToFirstFix = (int)(mLastFixTime - mFixRequestTime); + if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix); // notify status listeners - synchronized(mListeners) { + synchronized (mListeners) { int size = mListeners.size(); for (int i = 0; i < size; i++) { Listener listener = mListeners.get(i); try { - listener.mListener.onFirstFix(mTTFF); + listener.mListener.onFirstFix(mTimeToFirstFix); } catch (RemoteException e) { Log.w(TAG, "RemoteException in stopNavigating"); mListeners.remove(listener); @@ -1131,9 +1083,6 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - if (mSingleShot) { - stopNavigating(); - } if (mStarted && mStatus != LocationProvider.AVAILABLE) { // we want to time out if we do not receive a fix // within the time out and we are requesting infrequent fixes @@ -1144,7 +1093,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // send an intent to notify that the GPS is receiving fixes. Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION); intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); updateStatus(LocationProvider.AVAILABLE, mSvCount); } @@ -1161,7 +1110,7 @@ public class GpsLocationProvider implements LocationProviderInterface { private void reportStatus(int status) { if (DEBUG) Log.v(TAG, "reportStatus status: " + status); - synchronized(mListeners) { + synchronized (mListeners) { boolean wasNavigating = mNavigating; switch (status) { @@ -1199,24 +1148,10 @@ public class GpsLocationProvider implements LocationProviderInterface { } } - try { - // update battery stats - for (int i=mClientUids.size() - 1; i >= 0; i--) { - int uid = mClientUids.keyAt(i); - if (mNavigating) { - mBatteryStats.noteStartGps(uid); - } else { - mBatteryStats.noteStopGps(uid); - } - } - } catch (RemoteException e) { - Log.w(TAG, "RemoteException in reportStatus"); - } - // send an intent to notify that the GPS has been enabled or disabled. Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION); intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } } } @@ -1227,15 +1162,15 @@ public class GpsLocationProvider implements LocationProviderInterface { private void reportSvStatus() { int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks); - - synchronized(mListeners) { + + synchronized (mListeners) { int size = mListeners.size(); for (int i = 0; i < size; i++) { Listener listener = mListeners.get(i); try { - listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, - mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], - mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); + listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, + mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], + mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); } catch (RemoteException e) { Log.w(TAG, "RemoteException in reportSvInfo"); mListeners.remove(listener); @@ -1251,7 +1186,7 @@ public class GpsLocationProvider implements LocationProviderInterface { " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK])); for (int i = 0; i < svCount; i++) { Log.v(TAG, "sv: " + mSvs[i] + - " snr: " + (float)mSnrs[i]/10 + + " snr: " + mSnrs[i]/10 + " elev: " + mSvElevations[i] + " azimuth: " + mSvAzimuths[i] + ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " E") + @@ -1268,7 +1203,7 @@ public class GpsLocationProvider implements LocationProviderInterface { // send an intent to notify that the GPS is no longer receiving fixes. Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION); intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount); } } @@ -1339,7 +1274,7 @@ public class GpsLocationProvider implements LocationProviderInterface { * called from native code to report NMEA data received */ private void reportNmea(long timestamp) { - synchronized(mListeners) { + synchronized (mListeners) { int size = mListeners.size(); if (size > 0) { // don't bother creating the String if we have no listeners @@ -1386,19 +1321,18 @@ public class GpsLocationProvider implements LocationProviderInterface { //============================================================= private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { // Sends a response for an NI reqeust to HAL. + @Override public boolean sendNiResponse(int notificationId, int userResponse) { // TODO Add Permission check - StringBuilder extrasBuf = new StringBuilder(); - if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId + ", response: " + userResponse); native_send_ni_response(notificationId, userResponse); return true; } }; - + public INetInitiatedListener getNetInitiatedListener() { return mNetInitiatedListener; } @@ -1547,19 +1481,18 @@ public class GpsLocationProvider implements LocationProviderInterface { } private void sendMessage(int message, int arg, Object obj) { - // hold a wake lock while messages are pending - synchronized (mWakeLock) { - mPendingMessageBits |= (1 << message); - mWakeLock.acquire(); - mHandler.removeMessages(message); - Message m = Message.obtain(mHandler, message); - m.arg1 = arg; - m.obj = obj; - mHandler.sendMessage(m); - } + // hold a wake lock until this message is delivered + // note that this assumes the message will not be removed from the queue before + // it is handled (otherwise the wake lock would be leaked). + mWakeLock.acquire(); + mHandler.obtainMessage(message, arg, 1, obj).sendToTarget(); } private final class ProviderHandler extends Handler { + public ProviderHandler() { + super(true /*async*/); + } + @Override public void handleMessage(Message msg) { int message = msg.what; @@ -1571,11 +1504,9 @@ public class GpsLocationProvider implements LocationProviderInterface { handleDisable(); } break; - case ENABLE_TRACKING: - handleEnableLocationTracking(msg.arg1 == 1); - break; - case REQUEST_SINGLE_SHOT: - handleRequestSingleShot(); + case SET_REQUEST: + GpsRequest gpsRequest = (GpsRequest) msg.obj; + handleSetRequest(gpsRequest.request, gpsRequest.source); break; case UPDATE_NETWORK_STATE: handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj); @@ -1588,25 +1519,19 @@ public class GpsLocationProvider implements LocationProviderInterface { handleDownloadXtraData(); } break; - case UPDATE_LOCATION: - handleUpdateLocation((Location)msg.obj); + case INJECT_NTP_TIME_FINISHED: + mInjectNtpTimePending = STATE_IDLE; break; - case ADD_LISTENER: - handleAddListener(msg.arg1); + case DOWNLOAD_XTRA_DATA_FINISHED: + mDownloadXtraDataPending = STATE_IDLE; break; - case REMOVE_LISTENER: - handleRemoveListener(msg.arg1); + case UPDATE_LOCATION: + handleUpdateLocation((Location)msg.obj); break; } - // release wake lock if no messages are pending - synchronized (mWakeLock) { - mPendingMessageBits &= ~(1 << message); - if (message == ADD_LISTENER || message == REMOVE_LISTENER) { - mPendingListenerMessages--; - } - if (mPendingMessageBits == 0 && mPendingListenerMessages == 0) { - mWakeLock.release(); - } + if (msg.arg2 == 1) { + // wakelock was taken for this message, release it + mWakeLock.release(); } } }; @@ -1617,17 +1542,39 @@ public class GpsLocationProvider implements LocationProviderInterface { super("GpsLocationProvider"); } + @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); initialize(); Looper.prepare(); + + LocationManager locManager = + (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); mHandler = new ProviderHandler(); // signal when we are initialized and ready to go mInitializedLatch.countDown(); + locManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, + 0, 0, new NetworkLocationListener(), Looper.myLooper()); Looper.loop(); } } + private final class NetworkLocationListener implements LocationListener { + @Override + public void onLocationChanged(Location location) { + // this callback happens on mHandler looper + if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) { + handleUpdateLocation(location); + } + } + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { } + @Override + public void onProviderEnabled(String provider) { } + @Override + public void onProviderDisabled(String provider) { } + } + private String getSelectedApn() { Uri uri = Uri.parse("content://telephony/carriers/preferapn"); String apn = null; @@ -1647,6 +1594,22 @@ public class GpsLocationProvider implements LocationProviderInterface { return apn; } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + StringBuilder s = new StringBuilder(); + s.append(" mFixInterval=").append(mFixInterval).append("\n"); + s.append(" mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities)).append(" ("); + if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHED "); + if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB "); + if (hasCapability(GPS_CAPABILITY_MSA)) s.append("MSA "); + if (hasCapability(GPS_CAPABILITY_SINGLE_SHOT)) s.append("SINGLE_SHOT "); + if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) s.append("ON_DEMAND_TIME "); + s.append(")\n"); + + s.append(native_get_internal_state()); + pw.append(s); + } + // for GPS SV statistics private static final int MAX_SVS = 32; private static final int EPHEMERIS_MASK = 0; diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java index d4fb8ee..38871d7 100755 --- a/services/java/com/android/server/location/LocationBasedCountryDetector.java +++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java @@ -114,7 +114,9 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { for (String provider : providers) { Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider); if (lastKnownLocation != null) { - if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) { + if (bestLocation == null || + bestLocation.getElapsedRealtimeNano() < + lastKnownLocation.getElapsedRealtimeNano()) { bestLocation = lastKnownLocation; } } diff --git a/services/java/com/android/server/location/LocationBlacklist.java b/services/java/com/android/server/location/LocationBlacklist.java new file mode 100644 index 0000000..71fa9f9 --- /dev/null +++ b/services/java/com/android/server/location/LocationBlacklist.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.server.location; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import com.android.server.LocationManagerService; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Allows applications to be blacklisted from location updates at run-time. + * + * This is a silent blacklist. Applications can still call Location Manager + * API's, but they just won't receive any locations. + */ +public final class LocationBlacklist extends ContentObserver { + private static final String TAG = "LocationBlacklist"; + private static final boolean D = LocationManagerService.D; + private static final String BLACKLIST_CONFIG_NAME = "locationPackagePrefixBlacklist"; + private static final String WHITELIST_CONFIG_NAME = "locationPackagePrefixWhitelist"; + + private final Context mContext; + private final Object mLock = new Object(); + + // all fields below synchronized on mLock + private String[] mWhitelist = new String[0]; + private String[] mBlacklist = new String[0]; + + public LocationBlacklist(Context context, Handler handler) { + super(handler); + mContext = context; + } + + public void init() { + mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + BLACKLIST_CONFIG_NAME), false, this); +// mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( +// WHITELIST_CONFIG_NAME), false, this); + reloadBlacklist(); + } + + private void reloadBlacklist() { + String blacklist[] = getStringArray(BLACKLIST_CONFIG_NAME); + String whitelist[] = getStringArray(WHITELIST_CONFIG_NAME); + synchronized (mLock) { + mWhitelist = whitelist; + Slog.i(TAG, "whitelist: " + Arrays.toString(mWhitelist)); + mBlacklist = blacklist; + Slog.i(TAG, "blacklist: " + Arrays.toString(mBlacklist)); + } + } + + /** + * Return true if in blacklist + * (package name matches blacklist, and does not match whitelist) + */ + public boolean isBlacklisted(String packageName) { + synchronized (mLock) { + for (String black : mBlacklist) { + if (packageName.startsWith(black)) { + if (inWhitelist(packageName)) { + continue; + } else { + if (D) Log.d(TAG, "dropping location (blacklisted): " + + packageName + " matches " + black); + return true; + } + } + } + } + return false; + } + + /** + * Return true if any of packages are in whitelist + */ + private boolean inWhitelist(String pkg) { + synchronized (mLock) { + for (String white : mWhitelist) { + if (pkg.startsWith(white)) return true; + } + } + return false; + } + + @Override + public void onChange(boolean selfChange) { + reloadBlacklist(); + } + + private String[] getStringArray(String key) { + String flatString = Settings.Secure.getString(mContext.getContentResolver(), key); + if (flatString == null) { + return new String[0]; + } + String[] splitStrings = flatString.split(","); + ArrayList<String> result = new ArrayList<String>(); + for (String pkg : splitStrings) { + pkg = pkg.trim(); + if (pkg.isEmpty()) { + continue; + } + result.add(pkg); + } + return result.toArray(new String[result.size()]); + } + + public void dump(PrintWriter pw) { + pw.println("mWhitelist=" + Arrays.toString(mWhitelist) + " mBlacklist=" + + Arrays.toString(mBlacklist)); + } +} diff --git a/services/java/com/android/server/location/LocationFudger.java b/services/java/com/android/server/location/LocationFudger.java new file mode 100644 index 0000000..84fd255 --- /dev/null +++ b/services/java/com/android/server/location/LocationFudger.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.security.SecureRandom; +import android.content.Context; +import android.database.ContentObserver; +import android.location.Location; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Log; + + +/** + * Contains the logic to obfuscate (fudge) locations for coarse applications. + * + * <p>The goal is just to prevent applications with only + * the coarse location permission from receiving a fine location. + */ +public class LocationFudger { + private static final boolean D = false; + private static final String TAG = "LocationFudge"; + + private static final String EXTRA_COARSE_LOCATION = "coarseLocation"; + + /** + * Default coarse accuracy in meters. + */ + private static final float DEFAULT_ACCURACY_IN_METERS = 2000.0f; + + /** + * Minimum coarse accuracy in meters. + */ + private static final float MINIMUM_ACCURACY_IN_METERS = 200.0f; + + /** + * Secure settings key for coarse accuracy. + */ + private static final String COARSE_ACCURACY_CONFIG_NAME = "locationCoarseAccuracy"; + + /** + * This is the fastest interval that applications can receive coarse + * locations. + */ + public static final long FASTEST_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes + + /** + * The duration until we change the random offset. + */ + private static final long CHANGE_INTERVAL_MS = 60 * 60 * 1000; // 1 hour + + /** + * The percentage that we change the random offset at every interval. + * + * <p>0.0 indicates the random offset doesn't change. 1.0 + * indicates the random offset is completely replaced every interval. + */ + private static final double CHANGE_PER_INTERVAL = 0.03; // 3% change + + // Pre-calculated weights used to move the random offset. + // + // The goal is to iterate on the previous offset, but keep + // the resulting standard deviation the same. The variance of + // two gaussian distributions summed together is equal to the + // sum of the variance of each distribution. So some quick + // algebra results in the following sqrt calculation to + // weigh in a new offset while keeping the final standard + // deviation unchanged. + private static final double NEW_WEIGHT = CHANGE_PER_INTERVAL; + private static final double PREVIOUS_WEIGHT = Math.sqrt(1 - NEW_WEIGHT * NEW_WEIGHT); + + /** + * This number actually varies because the earth is not round, but + * 111,000 meters is considered generally acceptable. + */ + private static final int APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111000; + + /** + * Maximum latitude. + * + * <p>We pick a value 1 meter away from 90.0 degrees in order + * to keep cosine(MAX_LATITUDE) to a non-zero value, so that we avoid + * divide by zero fails. + */ + private static final double MAX_LATITUDE = 90.0 - + (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR); + + private final Object mLock = new Object(); + private final SecureRandom mRandom = new SecureRandom(); + + /** + * Used to monitor coarse accuracy secure setting for changes. + */ + private final ContentObserver mSettingsObserver; + + /** + * Used to resolve coarse accuracy setting. + */ + private final Context mContext; + + // all fields below protected by mLock + private double mOffsetLatitudeMeters; + private double mOffsetLongitudeMeters; + private long mNextInterval; + + /** + * Best location accuracy allowed for coarse applications. + * This value should only be set by {@link #setAccuracyInMetersLocked(float)}. + */ + private float mAccuracyInMeters; + + /** + * The distance between grids for snap-to-grid. See {@link #createCoarse}. + * This value should only be set by {@link #setAccuracyInMetersLocked(float)}. + */ + private double mGridSizeInMeters; + + /** + * Standard deviation of the (normally distributed) random offset applied + * to coarse locations. It does not need to be as large as + * {@link #COARSE_ACCURACY_METERS} because snap-to-grid is the primary obfuscation + * method. See further details in the implementation. + * This value should only be set by {@link #setAccuracyInMetersLocked(float)}. + */ + private double mStandardDeviationInMeters; + + public LocationFudger(Context context, Handler handler) { + mContext = context; + mSettingsObserver = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange) { + setAccuracyInMeters(loadCoarseAccuracy()); + } + }; + mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + COARSE_ACCURACY_CONFIG_NAME), false, mSettingsObserver); + + float accuracy = loadCoarseAccuracy(); + synchronized (mLock) { + setAccuracyInMetersLocked(accuracy); + mOffsetLatitudeMeters = nextOffsetLocked(); + mOffsetLongitudeMeters = nextOffsetLocked(); + mNextInterval = SystemClock.elapsedRealtime() + CHANGE_INTERVAL_MS; + } + } + + /** + * Get the cached coarse location, or generate a new one and cache it. + */ + public Location getOrCreate(Location location) { + synchronized (mLock) { + Bundle extras = location.getExtras(); + if (extras == null) { + return addCoarseLocationExtraLocked(location); + } + Parcelable parcel = extras.getParcelable(EXTRA_COARSE_LOCATION); + if (parcel == null) { + return addCoarseLocationExtraLocked(location); + } + if (!(parcel instanceof Location)) { + return addCoarseLocationExtraLocked(location); + } + Location coarse = (Location) parcel; + if (coarse.getAccuracy() < mAccuracyInMeters) { + return addCoarseLocationExtraLocked(location); + } + return coarse; + } + } + + private Location addCoarseLocationExtraLocked(Location location) { + Bundle extras = location.getExtras(); + if (extras == null) extras = new Bundle(); + Location coarse = createCoarseLocked(location); + extras.putParcelable(EXTRA_COARSE_LOCATION, coarse); + location.setExtras(extras); + return coarse; + } + + /** + * Create a coarse location. + * + * <p>Two techniques are used: random offsets and snap-to-grid. + * + * <p>First we add a random offset. This mitigates against detecting + * grid transitions. Without a random offset it is possible to detect + * a users position very accurately when they cross a grid boundary. + * The random offset changes very slowly over time, to mitigate against + * taking many location samples and averaging them out. + * + * <p>Second we snap-to-grid (quantize). This has the nice property of + * producing stable results, and mitigating against taking many samples + * to average out a random offset. + */ + private Location createCoarseLocked(Location fine) { + Location coarse = new Location(fine); + + // clean all the optional information off the location, because + // this can leak detailed location information + coarse.removeBearing(); + coarse.removeSpeed(); + coarse.removeAltitude(); + coarse.setExtras(null); + + double lat = coarse.getLatitude(); + double lon = coarse.getLongitude(); + + // wrap + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + // Step 1) apply a random offset + // + // The goal of the random offset is to prevent the application + // from determining that the device is on a grid boundary + // when it crosses from one grid to the next. + // + // We apply the offset even if the location already claims to be + // inaccurate, because it may be more accurate than claimed. + updateRandomOffsetLocked(); + // perform lon first whilst lat is still within bounds + lon += metersToDegreesLongitude(mOffsetLongitudeMeters, lat); + lat += metersToDegreesLatitude(mOffsetLatitudeMeters); + if (D) Log.d(TAG, String.format("applied offset of %.0f, %.0f (meters)", + mOffsetLongitudeMeters, mOffsetLatitudeMeters)); + + // wrap + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + // Step 2) Snap-to-grid (quantize) + // + // This is the primary means of obfuscation. It gives nice consistent + // results and is very effective at hiding the true location + // (as long as you are not sitting on a grid boundary, which + // step 1 mitigates). + // + // Note we quantize the latitude first, since the longitude + // quantization depends on the latitude value and so leaks information + // about the latitude + double latGranularity = metersToDegreesLatitude(mGridSizeInMeters); + lat = Math.round(lat / latGranularity) * latGranularity; + double lonGranularity = metersToDegreesLongitude(mGridSizeInMeters, lat); + lon = Math.round(lon / lonGranularity) * lonGranularity; + + // wrap again + lat = wrapLatitude(lat); + lon = wrapLongitude(lon); + + // apply + coarse.setLatitude(lat); + coarse.setLongitude(lon); + coarse.setAccuracy(Math.max(mAccuracyInMeters, coarse.getAccuracy())); + + if (D) Log.d(TAG, "fudged " + fine + " to " + coarse); + return coarse; + } + + /** + * Update the random offset over time. + * + * <p>If the random offset was new for every location + * fix then an application can more easily average location results + * over time, + * especially when the location is near a grid boundary. On the + * other hand if the random offset is constant then if an application + * found a way to reverse engineer the offset they would be able + * to detect location at grid boundaries very accurately. So + * we choose a random offset and then very slowly move it, to + * make both approaches very hard. + * + * <p>The random offset does not need to be large, because snap-to-grid + * is the primary obfuscation mechanism. It just needs to be large + * enough to stop information leakage as we cross grid boundaries. + */ + private void updateRandomOffsetLocked() { + long now = SystemClock.elapsedRealtime(); + if (now < mNextInterval) { + return; + } + + if (D) Log.d(TAG, String.format("old offset: %.0f, %.0f (meters)", + mOffsetLongitudeMeters, mOffsetLatitudeMeters)); + + // ok, need to update the random offset + mNextInterval = now + CHANGE_INTERVAL_MS; + + mOffsetLatitudeMeters *= PREVIOUS_WEIGHT; + mOffsetLatitudeMeters += NEW_WEIGHT * nextOffsetLocked(); + mOffsetLongitudeMeters *= PREVIOUS_WEIGHT; + mOffsetLongitudeMeters += NEW_WEIGHT * nextOffsetLocked(); + + if (D) Log.d(TAG, String.format("new offset: %.0f, %.0f (meters)", + mOffsetLongitudeMeters, mOffsetLatitudeMeters)); + } + + private double nextOffsetLocked() { + return mRandom.nextGaussian() * mStandardDeviationInMeters; + } + + private static double wrapLatitude(double lat) { + if (lat > MAX_LATITUDE) { + lat = MAX_LATITUDE; + } + if (lat < -MAX_LATITUDE) { + lat = -MAX_LATITUDE; + } + return lat; + } + + private static double wrapLongitude(double lon) { + lon %= 360.0; // wraps into range (-360.0, +360.0) + if (lon >= 180.0) { + lon -= 360.0; + } + if (lon < -180.0) { + lon += 360.0; + } + return lon; + } + + private static double metersToDegreesLatitude(double distance) { + return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR; + } + + /** + * Requires latitude since longitudinal distances change with distance from equator. + */ + private static double metersToDegreesLongitude(double distance, double lat) { + return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / Math.cos(Math.toRadians(lat)); + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(String.format("offset: %.0f, %.0f (meters)", mOffsetLongitudeMeters, + mOffsetLatitudeMeters)); + } + + /** + * This is the main control: call this to set the best location accuracy + * allowed for coarse applications and all derived values. + */ + private void setAccuracyInMetersLocked(float accuracyInMeters) { + mAccuracyInMeters = Math.max(accuracyInMeters, MINIMUM_ACCURACY_IN_METERS); + if (D) { + Log.d(TAG, "setAccuracyInMetersLocked: new accuracy = " + mAccuracyInMeters); + } + mGridSizeInMeters = mAccuracyInMeters; + mStandardDeviationInMeters = mGridSizeInMeters / 4.0; + } + + /** + * Same as setAccuracyInMetersLocked without the pre-lock requirement. + */ + private void setAccuracyInMeters(float accuracyInMeters) { + synchronized (mLock) { + setAccuracyInMetersLocked(accuracyInMeters); + } + } + + /** + * Loads the coarse accuracy value from secure settings. + */ + private float loadCoarseAccuracy() { + String newSetting = Settings.Secure.getString(mContext.getContentResolver(), + COARSE_ACCURACY_CONFIG_NAME); + if (D) { + Log.d(TAG, "loadCoarseAccuracy: newSetting = \"" + newSetting + "\""); + } + if (newSetting == null) { + return DEFAULT_ACCURACY_IN_METERS; + } + try { + return Float.parseFloat(newSetting); + } catch (NumberFormatException e) { + return DEFAULT_ACCURACY_IN_METERS; + } + } +} diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java index 858a582..6f09232 100644 --- a/services/java/com/android/server/location/LocationProviderInterface.java +++ b/services/java/com/android/server/location/LocationProviderInterface.java @@ -16,42 +16,33 @@ package com.android.server.location; -import android.location.Criteria; -import android.location.Location; -import android.net.NetworkInfo; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + + import android.os.Bundle; import android.os.WorkSource; /** * Location Manager's interface for location providers. - * - * {@hide} + * @hide */ public interface LocationProviderInterface { - String getName(); - boolean requiresNetwork(); - boolean requiresSatellite(); - boolean requiresCell(); - boolean hasMonetaryCost(); - boolean supportsAltitude(); - boolean supportsSpeed(); - boolean supportsBearing(); - int getPowerRequirement(); - boolean meetsCriteria(Criteria criteria); - int getAccuracy(); - boolean isEnabled(); - void enable(); - void disable(); - int getStatus(Bundle extras); - long getStatusUpdateTime(); - void enableLocationTracking(boolean enable); - /* returns false if single shot is not supported */ - boolean requestSingleShotFix(); - String getInternalState(); - void setMinTime(long minTime, WorkSource ws); - void updateNetworkState(int state, NetworkInfo info); - void updateLocation(Location location); - boolean sendExtraCommand(String command, Bundle extras); - void addListener(int uid); - void removeListener(int uid); + public String getName(); + + public void enable(); + public void disable(); + public boolean isEnabled(); + public void setRequest(ProviderRequest request, WorkSource source); + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args); + + // --- deprecated (but still supported) --- + public ProviderProperties getProperties(); + public int getStatus(Bundle extras); + public long getStatusUpdateTime(); + public boolean sendExtraCommand(String command, Bundle extras); } diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java index a227ab6..7faf72c 100644 --- a/services/java/com/android/server/location/LocationProviderProxy.java +++ b/services/java/com/android/server/location/LocationProviderProxy.java @@ -16,458 +16,272 @@ package com.android.server.location; -import android.content.ComponentName; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.location.Criteria; -import android.location.ILocationProvider; -import android.location.Location; -import android.net.NetworkInfo; +import android.location.LocationProvider; import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; -import com.android.internal.location.DummyLocationProvider; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ILocationProvider; +import com.android.internal.location.ProviderRequest; +import com.android.server.LocationManagerService; +import com.android.server.ServiceWatcher; /** - * A class for proxying location providers implemented as services. - * - * {@hide} + * Proxy for ILocationProvider implementations. */ public class LocationProviderProxy implements LocationProviderInterface { - private static final String TAG = "LocationProviderProxy"; - - public static final String SERVICE_ACTION = - "com.android.location.service.NetworkLocationProvider"; + private static final boolean D = LocationManagerService.D; private final Context mContext; private final String mName; - private final Intent mIntent; - private final Handler mHandler; - private final Object mMutex = new Object(); // synchronizes access to non-final members - private Connection mServiceConnection; // never null after ctor + private final ServiceWatcher mServiceWatcher; + + private Object mLock = new Object(); - // cached values set by the location manager - private boolean mLocationTracking = false; + // cached values set by the location manager, synchronized on mLock + private ProviderProperties mProperties; private boolean mEnabled = false; - private long mMinTime = -1; - private WorkSource mMinTimeSource = new WorkSource(); - private int mNetworkState; - private NetworkInfo mNetworkInfo; - - // constructor for proxying location providers implemented in a separate service - public LocationProviderProxy(Context context, String name, String packageName, - Handler handler) { + private ProviderRequest mRequest = null; + private WorkSource mWorksource = new WorkSource(); + + public static LocationProviderProxy createAndBind(Context context, String name, String action, + List<String> initialPackageNames, Handler handler) { + LocationProviderProxy proxy = new LocationProviderProxy(context, name, action, + initialPackageNames, handler); + if (proxy.bind()) { + return proxy; + } else { + return null; + } + } + + private LocationProviderProxy(Context context, String name, String action, + List<String> initialPackageNames, Handler handler) { mContext = context; mName = name; - mIntent = new Intent(SERVICE_ACTION); - mHandler = handler; - reconnect(packageName); + mServiceWatcher = new ServiceWatcher(mContext, TAG, action, initialPackageNames, + mNewServiceWork, handler); } - /** Bind to service. Will reconnect if already connected */ - public void reconnect(String packageName) { - synchronized (mMutex) { - if (mServiceConnection != null) { - mContext.unbindService(mServiceConnection); - } - mServiceConnection = new Connection(); - mIntent.setPackage(packageName); - mContext.bindService(mIntent, mServiceConnection, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | - Context.BIND_ALLOW_OOM_MANAGEMENT); - } + private boolean bind () { + return mServiceWatcher.start(); } - private class Connection implements ServiceConnection, Runnable { - - private ILocationProvider mProvider; - - // for caching requiresNetwork, requiresSatellite, etc. - private DummyLocationProvider mCachedAttributes; // synchronized by mMutex + private ILocationProvider getService() { + return ILocationProvider.Stub.asInterface(mServiceWatcher.getBinder()); + } - public void onServiceConnected(ComponentName className, IBinder service) { - synchronized (this) { - mProvider = ILocationProvider.Stub.asInterface(service); - if (mProvider != null) { - mHandler.post(this); - } - } - } + public String getConnectedPackageName() { + return mServiceWatcher.getBestPackageName(); + } - public void onServiceDisconnected(ComponentName className) { - synchronized (this) { - mProvider = null; + /** + * Work to apply current state to a newly connected provider. + * Remember we can switch the service that implements a providers + * at run-time, so need to apply current state. + */ + private Runnable mNewServiceWork = new Runnable() { + @Override + public void run() { + if (D) Log.d(TAG, "applying state to connected service"); + + boolean enabled; + ProviderProperties properties = null; + ProviderRequest request; + WorkSource source; + ILocationProvider service; + synchronized (mLock) { + enabled = mEnabled; + request = mRequest; + source = mWorksource; + service = getService(); } - } - public synchronized ILocationProvider getProvider() { - return mProvider; - } - - public synchronized DummyLocationProvider getCachedAttributes() { - return mCachedAttributes; - } + if (service == null) return; - public void run() { - synchronized (mMutex) { - if (mServiceConnection != this) { - // This ServiceConnection no longer the one we want to bind to. - return; - } - ILocationProvider provider = getProvider(); - if (provider == null) { - return; + try { + // load properties from provider + properties = service.getProperties(); + if (properties == null) { + Log.e(TAG, mServiceWatcher.getBestPackageName() + + " has invalid locatino provider properties"); } - // resend previous values from the location manager if the service has restarted - try { - if (mEnabled) { - provider.enable(); - } - if (mLocationTracking) { - provider.enableLocationTracking(true); + // apply current state to new service + if (enabled) { + service.enable(); + if (request != null) { + service.setRequest(request, source); } - if (mMinTime >= 0) { - provider.setMinTime(mMinTime, mMinTimeSource); - } - if (mNetworkInfo != null) { - provider.updateNetworkState(mNetworkState, mNetworkInfo); - } - } catch (RemoteException e) { } + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); + } - // init cache of parameters - if (mCachedAttributes == null) { - try { - mCachedAttributes = new DummyLocationProvider(mName, null); - mCachedAttributes.setRequiresNetwork(provider.requiresNetwork()); - mCachedAttributes.setRequiresSatellite(provider.requiresSatellite()); - mCachedAttributes.setRequiresCell(provider.requiresCell()); - mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost()); - mCachedAttributes.setSupportsAltitude(provider.supportsAltitude()); - mCachedAttributes.setSupportsSpeed(provider.supportsSpeed()); - mCachedAttributes.setSupportsBearing(provider.supportsBearing()); - mCachedAttributes.setPowerRequirement(provider.getPowerRequirement()); - mCachedAttributes.setAccuracy(provider.getAccuracy()); - } catch (RemoteException e) { - mCachedAttributes = null; - } - } + synchronized (mLock) { + mProperties = properties; } } }; + @Override public String getName() { return mName; } - private DummyLocationProvider getCachedAttributes() { - synchronized (mMutex) { - return mServiceConnection.getCachedAttributes(); - } - } - - public boolean requiresNetwork() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresNetwork(); - } else { - return false; - } - } - - public boolean requiresSatellite() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresSatellite(); - } else { - return false; - } - } - - public boolean requiresCell() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.requiresCell(); - } else { - return false; - } - } - - public boolean hasMonetaryCost() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.hasMonetaryCost(); - } else { - return false; - } - } - - public boolean supportsAltitude() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsAltitude(); - } else { - return false; - } - } - - public boolean supportsSpeed() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsSpeed(); - } else { - return false; - } - } - - public boolean supportsBearing() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.supportsBearing(); - } else { - return false; - } - } - - public int getPowerRequirement() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.getPowerRequirement(); - } else { - return -1; + @Override + public ProviderProperties getProperties() { + synchronized (mLock) { + return mProperties; } } - public int getAccuracy() { - DummyLocationProvider cachedAttributes = getCachedAttributes(); - if (cachedAttributes != null) { - return cachedAttributes.getAccuracy(); - } else { - return -1; - } - } - - public boolean meetsCriteria(Criteria criteria) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - return provider.meetsCriteria(criteria); - } catch (RemoteException e) { - } - } - } - // default implementation if we lost connection to the provider - if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && - (criteria.getAccuracy() < getAccuracy())) { - return false; - } - int criteriaPower = criteria.getPowerRequirement(); - if ((criteriaPower != Criteria.NO_REQUIREMENT) && - (criteriaPower < getPowerRequirement())) { - return false; - } - if (criteria.isAltitudeRequired() && !supportsAltitude()) { - return false; - } - if (criteria.isSpeedRequired() && !supportsSpeed()) { - return false; - } - if (criteria.isBearingRequired() && !supportsBearing()) { - return false; - } - return true; - } - + @Override public void enable() { - synchronized (mMutex) { + synchronized (mLock) { mEnabled = true; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.enable(); - } catch (RemoteException e) { - } - } } - } + ILocationProvider service = getService(); + if (service == null) return; - public void disable() { - synchronized (mMutex) { - mEnabled = false; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.disable(); - } catch (RemoteException e) { - } - } + try { + service.enable(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public boolean isEnabled() { - synchronized (mMutex) { - return mEnabled; + @Override + public void disable() { + synchronized (mLock) { + mEnabled = false; } - } + ILocationProvider service = getService(); + if (service == null) return; - public int getStatus(Bundle extras) { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); + try { + service.disable(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } - if (provider != null) { - try { - return provider.getStatus(extras); - } catch (RemoteException e) { - } - } - return 0; } - public long getStatusUpdateTime() { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } - if (provider != null) { - try { - return provider.getStatusUpdateTime(); - } catch (RemoteException e) { - } - } - return 0; - } - - public String getInternalState() { - ILocationProvider provider; - synchronized (mMutex) { - provider = mServiceConnection.getProvider(); - } - if (provider != null) { - try { - return provider.getInternalState(); - } catch (RemoteException e) { - Log.e(TAG, "getInternalState failed", e); - } + @Override + public boolean isEnabled() { + synchronized (mLock) { + return mEnabled; } - return null; } - public boolean isLocationTracking() { - synchronized (mMutex) { - return mLocationTracking; + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + synchronized (mLock) { + mRequest = request; + mWorksource = source; } - } + ILocationProvider service = getService(); + if (service == null) return; - public void enableLocationTracking(boolean enable) { - synchronized (mMutex) { - mLocationTracking = enable; - if (!enable) { - mMinTime = -1; - mMinTimeSource.clear(); - } - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.enableLocationTracking(enable); - } catch (RemoteException e) { - } - } + try { + service.setRequest(request, source); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public boolean requestSingleShotFix() { - return false; - } - - public long getMinTime() { - synchronized (mMutex) { - return mMinTime; + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.append("REMOTE SERVICE"); + pw.append(" name=").append(mName); + pw.append(" pkg=").append(mServiceWatcher.getBestPackageName()); + pw.append(" version=").append("" + mServiceWatcher.getBestVersion()); + pw.append('\n'); + + ILocationProvider service = getService(); + if (service == null) { + pw.println("service down (null)"); + return; + } + pw.flush(); + + try { + service.asBinder().dump(fd, args); + } catch (RemoteException e) { + pw.println("service down (RemoteException)"); + Log.w(TAG, e); + } catch (Exception e) { + pw.println("service down (Exception)"); + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } } - public void setMinTime(long minTime, WorkSource ws) { - synchronized (mMutex) { - mMinTime = minTime; - mMinTimeSource.set(ws); - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.setMinTime(minTime, ws); - } catch (RemoteException e) { - } - } - } + @Override + public int getStatus(Bundle extras) { + ILocationProvider service = getService(); + if (service == null) return LocationProvider.TEMPORARILY_UNAVAILABLE; + + try { + return service.getStatus(extras); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); + } + return LocationProvider.TEMPORARILY_UNAVAILABLE; } - public void updateNetworkState(int state, NetworkInfo info) { - synchronized (mMutex) { - mNetworkState = state; - mNetworkInfo = info; - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.updateNetworkState(state, info); - } catch (RemoteException e) { - } - } - } - } + @Override + public long getStatusUpdateTime() { + ILocationProvider service = getService(); + if (service == null) return 0; - public void updateLocation(Location location) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.updateLocation(location); - } catch (RemoteException e) { - } - } + try { + return service.getStatusUpdateTime(); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } + return 0; } + @Override public boolean sendExtraCommand(String command, Bundle extras) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - return provider.sendExtraCommand(command, extras); - } catch (RemoteException e) { - } - } - } - return false; - } + ILocationProvider service = getService(); + if (service == null) return false; - public void addListener(int uid) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.addListener(uid); - } catch (RemoteException e) { - } - } - } - } - - public void removeListener(int uid) { - synchronized (mMutex) { - ILocationProvider provider = mServiceConnection.getProvider(); - if (provider != null) { - try { - provider.removeListener(uid); - } catch (RemoteException e) { - } - } + try { + return service.sendExtraCommand(command, extras); + } catch (RemoteException e) { + Log.w(TAG, e); + } catch (Exception e) { + // never let remote service crash system server + Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e); } + return false; } -} + } diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java index 09d799f..36c43ff 100644 --- a/services/java/com/android/server/location/MockProvider.java +++ b/services/java/com/android/server/location/MockProvider.java @@ -20,15 +20,19 @@ import android.location.Criteria; import android.location.ILocationManager; import android.location.Location; import android.location.LocationProvider; -import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; import android.util.PrintWriterPrinter; + +import java.io.FileDescriptor; import java.io.PrintWriter; +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + /** * A mock location provider used by LocationManagerService to implement test providers. * @@ -36,60 +40,56 @@ import java.io.PrintWriter; */ public class MockProvider implements LocationProviderInterface { private final String mName; + private final ProviderProperties mProperties; private final ILocationManager mLocationManager; - private final boolean mRequiresNetwork; - private final boolean mRequiresSatellite; - private final boolean mRequiresCell; - private final boolean mHasMonetaryCost; - private final boolean mSupportsAltitude; - private final boolean mSupportsSpeed; - private final boolean mSupportsBearing; - private final int mPowerRequirement; - private final int mAccuracy; + private final Location mLocation; + private final Bundle mExtras = new Bundle(); + private int mStatus; private long mStatusUpdateTime; - private final Bundle mExtras = new Bundle(); private boolean mHasLocation; private boolean mHasStatus; private boolean mEnabled; private static final String TAG = "MockProvider"; - public MockProvider(String name, ILocationManager locationManager, - boolean requiresNetwork, boolean requiresSatellite, - boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, - boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) { + public MockProvider(String name, ILocationManager locationManager, + ProviderProperties properties) { + if (properties == null) throw new NullPointerException("properties is null"); + mName = name; mLocationManager = locationManager; - mRequiresNetwork = requiresNetwork; - mRequiresSatellite = requiresSatellite; - mRequiresCell = requiresCell; - mHasMonetaryCost = hasMonetaryCost; - mSupportsAltitude = supportsAltitude; - mSupportsBearing = supportsBearing; - mSupportsSpeed = supportsSpeed; - mPowerRequirement = powerRequirement; - mAccuracy = accuracy; + mProperties = properties; mLocation = new Location(name); } + @Override public String getName() { return mName; } + @Override + public ProviderProperties getProperties() { + return mProperties; + } + + @Override public void disable() { mEnabled = false; } + @Override public void enable() { mEnabled = true; } + @Override public boolean isEnabled() { return mEnabled; } + @Override public int getStatus(Bundle extras) { if (mHasStatus) { extras.clear(); @@ -100,75 +100,20 @@ public class MockProvider implements LocationProviderInterface { } } + @Override public long getStatusUpdateTime() { return mStatusUpdateTime; } - public int getAccuracy() { - return mAccuracy; - } - - public int getPowerRequirement() { - return mPowerRequirement; - } - - public boolean hasMonetaryCost() { - return mHasMonetaryCost; - } - - public boolean requiresCell() { - return mRequiresCell; - } - - public boolean requiresNetwork() { - return mRequiresNetwork; - } - - public boolean requiresSatellite() { - return mRequiresSatellite; - } - - public boolean supportsAltitude() { - return mSupportsAltitude; - } - - public boolean supportsBearing() { - return mSupportsBearing; - } - - public boolean supportsSpeed() { - return mSupportsSpeed; - } - - public boolean meetsCriteria(Criteria criteria) { - if ((criteria.getAccuracy() != Criteria.NO_REQUIREMENT) && - (criteria.getAccuracy() < mAccuracy)) { - return false; - } - int criteriaPower = criteria.getPowerRequirement(); - if ((criteriaPower != Criteria.NO_REQUIREMENT) && - (criteriaPower < mPowerRequirement)) { - return false; - } - if (criteria.isAltitudeRequired() && !mSupportsAltitude) { - return false; - } - if (criteria.isSpeedRequired() && !mSupportsSpeed) { - return false; - } - if (criteria.isBearingRequired() && !mSupportsBearing) { - return false; - } - return true; - } - public void setLocation(Location l) { mLocation.set(l); mHasLocation = true; - try { - mLocationManager.reportLocation(mLocation, false); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling reportLocation"); + if (mEnabled) { + try { + mLocationManager.reportLocation(mLocation, false); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling reportLocation"); + } } } @@ -191,34 +136,9 @@ public class MockProvider implements LocationProviderInterface { mStatusUpdateTime = 0; } - public String getInternalState() { - return null; - } - - public void enableLocationTracking(boolean enable) { - } - - public boolean requestSingleShotFix() { - return false; - } - - public void setMinTime(long minTime, WorkSource ws) { - } - - public void updateNetworkState(int state, NetworkInfo info) { - } - - public void updateLocation(Location location) { - } - - public boolean sendExtraCommand(String command, Bundle extras) { - return false; - } - - public void addListener(int uid) { - } - - public void removeListener(int uid) { + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + dump(pw, ""); } public void dump(PrintWriter pw, String prefix) { @@ -231,4 +151,12 @@ public class MockProvider implements LocationProviderInterface { pw.println(prefix + "mStatusUpdateTime=" + mStatusUpdateTime); pw.println(prefix + "mExtras=" + mExtras); } + + @Override + public void setRequest(ProviderRequest request, WorkSource source) { } + + @Override + public boolean sendExtraCommand(String command, Bundle extras) { + return false; + } } diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java index ea0d1b0..0ce21b7 100644 --- a/services/java/com/android/server/location/PassiveProvider.java +++ b/services/java/com/android/server/location/PassiveProvider.java @@ -16,17 +16,23 @@ package com.android.server.location; +import java.io.FileDescriptor; +import java.io.PrintWriter; + +import com.android.internal.location.ProviderProperties; +import com.android.internal.location.ProviderRequest; + import android.location.Criteria; import android.location.ILocationManager; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; -import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; import android.os.WorkSource; import android.util.Log; + /** * A passive location provider reports locations received from other providers * for clients that want to listen passively without actually triggering @@ -35,103 +41,63 @@ import android.util.Log; * {@hide} */ public class PassiveProvider implements LocationProviderInterface { - private static final String TAG = "PassiveProvider"; + private static final ProviderProperties PROPERTIES = new ProviderProperties( + false, false, false, false, false, false, false, + Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); + private final ILocationManager mLocationManager; - private boolean mTracking; + private boolean mReportLocation; public PassiveProvider(ILocationManager locationManager) { mLocationManager = locationManager; } + @Override public String getName() { return LocationManager.PASSIVE_PROVIDER; } - public boolean requiresNetwork() { - return false; - } - - public boolean requiresSatellite() { - return false; - } - - public boolean requiresCell() { - return false; - } - - public boolean hasMonetaryCost() { - return false; - } - - public boolean supportsAltitude() { - return false; - } - - public boolean supportsSpeed() { - return false; - } - - public boolean supportsBearing() { - return false; - } - - public int getPowerRequirement() { - return -1; - } - - public boolean meetsCriteria(Criteria criteria) { - // We do not want to match the special passive provider based on criteria. - return false; - } - - public int getAccuracy() { - return -1; + @Override + public ProviderProperties getProperties() { + return PROPERTIES; } + @Override public boolean isEnabled() { return true; } + @Override public void enable() { } + @Override public void disable() { } + @Override public int getStatus(Bundle extras) { - if (mTracking) { + if (mReportLocation) { return LocationProvider.AVAILABLE; } else { return LocationProvider.TEMPORARILY_UNAVAILABLE; } } + @Override public long getStatusUpdateTime() { return -1; } - public String getInternalState() { - return null; - } - - public void enableLocationTracking(boolean enable) { - mTracking = enable; - } - - public boolean requestSingleShotFix() { - return false; - } - - public void setMinTime(long minTime, WorkSource ws) { - } - - public void updateNetworkState(int state, NetworkInfo info) { + @Override + public void setRequest(ProviderRequest request, WorkSource source) { + mReportLocation = request.reportLocation; } public void updateLocation(Location location) { - if (mTracking) { + if (mReportLocation) { try { // pass the location back to the location manager mLocationManager.reportLocation(location, true); @@ -141,13 +107,13 @@ public class PassiveProvider implements LocationProviderInterface { } } + @Override public boolean sendExtraCommand(String command, Bundle extras) { return false; } - public void addListener(int uid) { - } - - public void removeListener(int uid) { + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("mReportLocaiton=" + mReportLocation); } } diff --git a/services/java/com/android/server/net/NetworkAlertObserver.java b/services/java/com/android/server/net/BaseNetworkObserver.java index 0d1c3b2..8b2aa5d 100644 --- a/services/java/com/android/server/net/NetworkAlertObserver.java +++ b/services/java/com/android/server/net/BaseNetworkObserver.java @@ -19,26 +19,39 @@ package com.android.server.net; import android.net.INetworkManagementEventObserver; /** + * Base {@link INetworkManagementEventObserver} that provides no-op + * implementations which can be overridden. + * * @hide */ -public abstract class NetworkAlertObserver extends INetworkManagementEventObserver.Stub { +public class BaseNetworkObserver extends INetworkManagementEventObserver.Stub { @Override public void interfaceStatusChanged(String iface, boolean up) { - // ignored; interface changes come through ConnectivityService + // default no-op } @Override public void interfaceRemoved(String iface) { - // ignored; interface changes come through ConnectivityService + // default no-op } @Override public void interfaceLinkStateChanged(String iface, boolean up) { - // ignored; interface changes come through ConnectivityService + // default no-op } @Override public void interfaceAdded(String iface) { - // ignored; interface changes come through ConnectivityService + // default no-op + } + + @Override + public void interfaceClassDataActivityChanged(String label, boolean active) { + // default no-op + } + + @Override + public void limitReached(String limitName, String iface) { + // default no-op } } diff --git a/services/java/com/android/server/net/LockdownVpnTracker.java b/services/java/com/android/server/net/LockdownVpnTracker.java new file mode 100644 index 0000000..f2d6745 --- /dev/null +++ b/services/java/com/android/server/net/LockdownVpnTracker.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net; + +import static android.Manifest.permission.CONNECTIVITY_INTERNAL; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.LinkProperties; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkInfo.State; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.security.Credentials; +import android.security.KeyStore; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.internal.util.Preconditions; +import com.android.server.ConnectivityService; +import com.android.server.EventLogTags; +import com.android.server.connectivity.Vpn; + +/** + * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be + * connected and kicks off VPN connection, managing any required {@code netd} + * firewall rules. + */ +public class LockdownVpnTracker { + private static final String TAG = "LockdownVpnTracker"; + + /** Number of VPN attempts before waiting for user intervention. */ + private static final int MAX_ERROR_COUNT = 4; + + private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET"; + private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS"; + + private final Context mContext; + private final INetworkManagementService mNetService; + private final ConnectivityService mConnService; + private final Vpn mVpn; + private final VpnProfile mProfile; + + private final Object mStateLock = new Object(); + + private PendingIntent mResetIntent; + + private String mAcceptedEgressIface; + private String mAcceptedIface; + private String mAcceptedSourceAddr; + + private int mErrorCount; + + public static boolean isEnabled() { + return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN); + } + + public LockdownVpnTracker(Context context, INetworkManagementService netService, + ConnectivityService connService, Vpn vpn, VpnProfile profile) { + mContext = Preconditions.checkNotNull(context); + mNetService = Preconditions.checkNotNull(netService); + mConnService = Preconditions.checkNotNull(connService); + mVpn = Preconditions.checkNotNull(vpn); + mProfile = Preconditions.checkNotNull(profile); + + final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET); + resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0); + } + + private BroadcastReceiver mResetReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + reset(); + } + }; + + /** + * Watch for state changes to both active egress network, kicking off a VPN + * connection when ready, or setting firewall rules once VPN is connected. + */ + private void handleStateChangedLocked() { + Slog.d(TAG, "handleStateChanged()"); + + final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered(); + final LinkProperties egressProp = mConnService.getActiveLinkProperties(); + + final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); + final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig(); + + // Restart VPN when egress network disconnected or changed + final boolean egressDisconnected = egressInfo == null + || State.DISCONNECTED.equals(egressInfo.getState()); + final boolean egressChanged = egressProp == null + || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName()); + if (egressDisconnected || egressChanged) { + clearSourceRulesLocked(); + mAcceptedEgressIface = null; + mVpn.stopLegacyVpn(); + } + if (egressDisconnected) return; + + final int egressType = egressInfo.getType(); + if (vpnInfo.getDetailedState() == DetailedState.FAILED) { + EventLogTags.writeLockdownVpnError(egressType); + } + + if (mErrorCount > MAX_ERROR_COUNT) { + showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + + } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) { + if (mProfile.isValidLockdownProfile()) { + Slog.d(TAG, "Active network connected; starting VPN"); + EventLogTags.writeLockdownVpnConnecting(egressType); + showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); + + mAcceptedEgressIface = egressProp.getInterfaceName(); + mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp); + + } else { + Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS"); + showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + } + + } else if (vpnInfo.isConnected() && vpnConfig != null) { + final String iface = vpnConfig.interfaze; + final String sourceAddr = vpnConfig.addresses; + + if (TextUtils.equals(iface, mAcceptedIface) + && TextUtils.equals(sourceAddr, mAcceptedSourceAddr)) { + return; + } + + Slog.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddr); + EventLogTags.writeLockdownVpnConnected(egressType); + showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); + + try { + clearSourceRulesLocked(); + + mNetService.setFirewallInterfaceRule(iface, true); + mNetService.setFirewallEgressSourceRule(sourceAddr, true); + + mErrorCount = 0; + mAcceptedIface = iface; + mAcceptedSourceAddr = sourceAddr; + } catch (RemoteException e) { + throw new RuntimeException("Problem setting firewall rules", e); + } + + mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo)); + } + } + + public void init() { + synchronized (mStateLock) { + initLocked(); + } + } + + private void initLocked() { + Slog.d(TAG, "initLocked()"); + + mVpn.setEnableNotifications(false); + + final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET); + mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null); + + try { + // TODO: support non-standard port numbers + mNetService.setFirewallEgressDestRule(mProfile.server, 500, true); + mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true); + } catch (RemoteException e) { + throw new RuntimeException("Problem setting firewall rules", e); + } + + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + + public void shutdown() { + synchronized (mStateLock) { + shutdownLocked(); + } + } + + private void shutdownLocked() { + Slog.d(TAG, "shutdownLocked()"); + + mAcceptedEgressIface = null; + mErrorCount = 0; + + mVpn.stopLegacyVpn(); + try { + mNetService.setFirewallEgressDestRule(mProfile.server, 500, false); + mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false); + } catch (RemoteException e) { + throw new RuntimeException("Problem setting firewall rules", e); + } + clearSourceRulesLocked(); + hideNotification(); + + mContext.unregisterReceiver(mResetReceiver); + mVpn.setEnableNotifications(true); + } + + public void reset() { + synchronized (mStateLock) { + // cycle tracker, reset error count, and trigger retry + shutdownLocked(); + initLocked(); + handleStateChangedLocked(); + } + } + + private void clearSourceRulesLocked() { + try { + if (mAcceptedIface != null) { + mNetService.setFirewallInterfaceRule(mAcceptedIface, false); + mAcceptedIface = null; + } + if (mAcceptedSourceAddr != null) { + mNetService.setFirewallEgressSourceRule(mAcceptedSourceAddr, false); + mAcceptedSourceAddr = null; + } + } catch (RemoteException e) { + throw new RuntimeException("Problem setting firewall rules", e); + } + } + + public void onNetworkInfoChanged(NetworkInfo info) { + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + + public void onVpnStateChanged(NetworkInfo info) { + if (info.getDetailedState() == DetailedState.FAILED) { + mErrorCount++; + } + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + + public NetworkInfo augmentNetworkInfo(NetworkInfo info) { + final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); + info = new NetworkInfo(info); + info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null); + return info; + } + + private void showNotification(int titleRes, int iconRes) { + final Notification.Builder builder = new Notification.Builder(mContext); + builder.setWhen(0); + builder.setSmallIcon(iconRes); + builder.setContentTitle(mContext.getString(titleRes)); + builder.setContentText(mContext.getString(R.string.vpn_lockdown_reset)); + builder.setContentIntent(mResetIntent); + builder.setPriority(Notification.PRIORITY_LOW); + builder.setOngoing(true); + NotificationManager.from(mContext).notify(TAG, 0, builder.build()); + } + + private void hideNotification() { + NotificationManager.from(mContext).cancel(TAG, 0); + } +} diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index fe43d11..43ddf8d 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -24,6 +24,8 @@ import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; import static android.Manifest.permission.READ_PHONE_STATE; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_UID_REMOVED; +import static android.content.Intent.ACTION_USER_ADDED; +import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; import static android.net.ConnectivityManager.TYPE_ETHERNET; @@ -112,11 +114,13 @@ import android.os.Message; import android.os.MessageQueue.IdleHandler; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.UserId; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.format.Formatter; import android.text.format.Time; +import android.util.AtomicFile; import android.util.Log; import android.util.NtpTrustedTime; import android.util.Slog; @@ -127,7 +131,6 @@ import android.util.TrustedTime; import android.util.Xml; import com.android.internal.R; -import com.android.internal.os.AtomicFile; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Objects; @@ -178,7 +181,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int VERSION_ADDED_INFERRED = 7; private static final int VERSION_SWITCH_APP_ID = 8; private static final int VERSION_ADDED_NETWORK_ID = 9; - private static final int VERSION_LATEST = VERSION_ADDED_NETWORK_ID; + private static final int VERSION_SWITCH_UID = 10; + private static final int VERSION_LATEST = VERSION_SWITCH_UID; // @VisibleForTesting public static final int TYPE_WARNING = 0x1; @@ -249,8 +253,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** Currently active network rules for ifaces. */ private HashMap<NetworkPolicy, String[]> mNetworkRules = Maps.newHashMap(); - /** Defined app policies. */ - private SparseIntArray mAppPolicy = new SparseIntArray(); + /** Defined UID policies. */ + private SparseIntArray mUidPolicy = new SparseIntArray(); /** Currently derived rules for each UID. */ private SparseIntArray mUidRules = new SparseIntArray(); @@ -356,12 +360,22 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE); mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler); - // listen for package/uid changes to update policy + // listen for package changes to update policy final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(ACTION_PACKAGE_ADDED); - packageFilter.addAction(ACTION_UID_REMOVED); + packageFilter.addDataScheme("package"); mContext.registerReceiver(mPackageReceiver, packageFilter, null, mHandler); + // listen for UID changes to update policy + mContext.registerReceiver( + mUidRemovedReceiver, new IntentFilter(ACTION_UID_REMOVED), null, mHandler); + + // listen for user changes to update policy + final IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(ACTION_USER_ADDED); + userFilter.addAction(ACTION_USER_REMOVED); + mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); + // listen for stats update events final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED); mContext.registerReceiver( @@ -420,33 +434,58 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - // on background handler thread, and PACKAGE_ADDED and UID_REMOVED - // are protected broadcasts. + // on background handler thread, and PACKAGE_ADDED is protected final String action = intent.getAction(); - final int uid = intent.getIntExtra(EXTRA_UID, 0); - final int appId = UserId.getAppId(uid); + final int uid = intent.getIntExtra(EXTRA_UID, -1); + if (uid == -1) return; + + if (ACTION_PACKAGE_ADDED.equals(action)) { + // update rules for UID, since it might be subject to + // global background data policy + if (LOGV) Slog.v(TAG, "ACTION_PACKAGE_ADDED for uid=" + uid); + synchronized (mRulesLock) { + updateRulesForUidLocked(uid); + } + } + } + }; + + private BroadcastReceiver mUidRemovedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // on background handler thread, and UID_REMOVED is protected + + final int uid = intent.getIntExtra(EXTRA_UID, -1); + if (uid == -1) return; + + // remove any policy and update rules to clean up + if (LOGV) Slog.v(TAG, "ACTION_UID_REMOVED for uid=" + uid); synchronized (mRulesLock) { - if (ACTION_PACKAGE_ADDED.equals(action)) { - // NOTE: PACKAGE_ADDED is currently only sent once, and is - // not broadcast when users are added. + mUidPolicy.delete(uid); + updateRulesForUidLocked(uid); + writePolicyLocked(); + } + } + }; - // update rules for UID, since it might be subject to - // global background data policy. - if (LOGV) Slog.v(TAG, "ACTION_PACKAGE_ADDED for uid=" + uid); - updateRulesForAppLocked(appId); + private BroadcastReceiver mUserReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // on background handler thread, and USER_ADDED and USER_REMOVED + // broadcasts are protected - } else if (ACTION_UID_REMOVED.equals(action)) { - // NOTE: UID_REMOVED is currently only sent once, and is not - // broadcast when users are removed. + final String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId == -1) return; - // remove any policy and update rules to clean up. - if (LOGV) Slog.v(TAG, "ACTION_UID_REMOVED for uid=" + uid); + // Remove any policies for given user; both cleaning up after a + // USER_REMOVED, and one last sanity check during USER_ADDED + removePoliciesForUserLocked(userId); - mAppPolicy.delete(appId); - updateRulesForAppLocked(appId); - writePolicyLocked(); - } + // Update global restrict for new user + synchronized (mRulesLock) { + updateRulesForRestrictBackgroundLocked(); } } }; @@ -570,7 +609,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** * Observer that watches for {@link INetworkManagementService} alerts. */ - private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() { + private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() { @Override public void limitReached(String limitName, String iface) { // only someone like NMS should be calling us @@ -787,11 +826,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } // TODO: move to NotificationManager once we can mock it + // XXX what to do about multi-user? try { final String packageName = mContext.getPackageName(); final int[] idReceived = new int[1]; mNotifManager.enqueueNotificationWithTag( - packageName, tag, 0x0, builder.getNotification(), idReceived); + packageName, tag, 0x0, builder.getNotification(), idReceived, + UserHandle.USER_OWNER); mActiveNotifs.add(tag); } catch (RemoteException e) { // ignored; service lives in system_server @@ -821,11 +862,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); // TODO: move to NotificationManager once we can mock it + // XXX what to do about multi-user? try { final String packageName = mContext.getPackageName(); final int[] idReceived = new int[1]; mNotifManager.enqueueNotificationWithTag(packageName, tag, - 0x0, builder.getNotification(), idReceived); + 0x0, builder.getNotification(), idReceived, UserHandle.USER_OWNER); mActiveNotifs.add(tag); } catch (RemoteException e) { // ignored; service lives in system_server @@ -834,10 +876,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private void cancelNotification(String tag) { // TODO: move to NotificationManager once we can mock it + // XXX what to do about multi-user? try { final String packageName = mContext.getPackageName(); mNotifManager.cancelNotificationWithTag( - packageName, tag, 0x0); + packageName, tag, 0x0, UserHandle.USER_OWNER); } catch (RemoteException e) { // ignored; service lives in system_server } @@ -1102,7 +1145,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // clear any existing policy and read from disk mNetworkPolicy.clear(); - mAppPolicy.clear(); + mUidPolicy.clear(); FileInputStream fis = null; try { @@ -1183,24 +1226,25 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { cycleTimezone, warningBytes, limitBytes, lastWarningSnooze, lastLimitSnooze, metered, inferred)); - } else if (TAG_UID_POLICY.equals(tag) && version < VERSION_SWITCH_APP_ID) { + } else if (TAG_UID_POLICY.equals(tag)) { final int uid = readIntAttribute(in, ATTR_UID); final int policy = readIntAttribute(in, ATTR_POLICY); - final int appId = UserId.getAppId(uid); - if (UserId.isApp(appId)) { - setAppPolicyUnchecked(appId, policy, false); + if (UserHandle.isApp(uid)) { + setUidPolicyUnchecked(uid, policy, false); } else { Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring"); } - } else if (TAG_APP_POLICY.equals(tag) && version >= VERSION_SWITCH_APP_ID) { + } else if (TAG_APP_POLICY.equals(tag)) { final int appId = readIntAttribute(in, ATTR_APP_ID); final int policy = readIntAttribute(in, ATTR_POLICY); - if (UserId.isApp(appId)) { - setAppPolicyUnchecked(appId, policy, false); + // TODO: set for other users during upgrade + final int uid = UserHandle.getUid(UserHandle.USER_OWNER, appId); + if (UserHandle.isApp(uid)) { + setUidPolicyUnchecked(uid, policy, false); } else { - Slog.w(TAG, "unable to apply policy to appId " + appId + "; ignoring"); + Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring"); } } } @@ -1230,7 +1274,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (mRestrictBackground) { final Intent broadcast = new Intent( ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); - mContext.sendBroadcast(broadcast); + mContext.sendBroadcastAsUser(broadcast, UserHandle.ALL); } } @@ -1275,17 +1319,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } // write all known uid policies - for (int i = 0; i < mAppPolicy.size(); i++) { - final int appId = mAppPolicy.keyAt(i); - final int policy = mAppPolicy.valueAt(i); + for (int i = 0; i < mUidPolicy.size(); i++) { + final int uid = mUidPolicy.keyAt(i); + final int policy = mUidPolicy.valueAt(i); // skip writing empty policies if (policy == POLICY_NONE) continue; - out.startTag(null, TAG_APP_POLICY); - writeIntAttribute(out, ATTR_APP_ID, appId); + out.startTag(null, TAG_UID_POLICY); + writeIntAttribute(out, ATTR_UID, uid); writeIntAttribute(out, ATTR_POLICY, policy); - out.endTag(null, TAG_APP_POLICY); + out.endTag(null, TAG_UID_POLICY); } out.endTag(null, TAG_POLICY_LIST); @@ -1300,24 +1344,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void setAppPolicy(int appId, int policy) { + public void setUidPolicy(int uid, int policy) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - if (!UserId.isApp(appId)) { - throw new IllegalArgumentException("cannot apply policy to appId " + appId); + if (!UserHandle.isApp(uid)) { + throw new IllegalArgumentException("cannot apply policy to UID " + uid); } - setAppPolicyUnchecked(appId, policy, true); + setUidPolicyUnchecked(uid, policy, true); } - private void setAppPolicyUnchecked(int appId, int policy, boolean persist) { + private void setUidPolicyUnchecked(int uid, int policy, boolean persist) { final int oldPolicy; synchronized (mRulesLock) { - oldPolicy = getAppPolicy(appId); - mAppPolicy.put(appId, policy); + oldPolicy = getUidPolicy(uid); + mUidPolicy.put(uid, policy); // uid policy changed, recompute rules and persist policy. - updateRulesForAppLocked(appId); + updateRulesForUidLocked(uid); if (persist) { writePolicyLocked(); } @@ -1325,29 +1369,53 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public int getAppPolicy(int appId) { + public int getUidPolicy(int uid) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); synchronized (mRulesLock) { - return mAppPolicy.get(appId, POLICY_NONE); + return mUidPolicy.get(uid, POLICY_NONE); } } @Override - public int[] getAppsWithPolicy(int policy) { + public int[] getUidsWithPolicy(int policy) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - int[] appIds = new int[0]; + int[] uids = new int[0]; synchronized (mRulesLock) { - for (int i = 0; i < mAppPolicy.size(); i++) { - final int appId = mAppPolicy.keyAt(i); - final int appPolicy = mAppPolicy.valueAt(i); - if (appPolicy == policy) { - appIds = appendInt(appIds, appId); + for (int i = 0; i < mUidPolicy.size(); i++) { + final int uid = mUidPolicy.keyAt(i); + final int uidPolicy = mUidPolicy.valueAt(i); + if (uidPolicy == policy) { + uids = appendInt(uids, uid); } } } - return appIds; + return uids; + } + + /** + * Remove any policies associated with given {@link UserHandle}, persisting + * if any changes are made. + */ + private void removePoliciesForUserLocked(int userId) { + if (LOGV) Slog.v(TAG, "removePoliciesForUserLocked()"); + + int[] uids = new int[0]; + for (int i = 0; i < mUidPolicy.size(); i++) { + final int uid = mUidPolicy.keyAt(i); + if (UserHandle.getUserId(uid) == userId) { + uids = appendInt(uids, uid); + } + } + + if (uids.length > 0) { + for (int uid : uids) { + mUidPolicy.delete(uid); + updateRulesForUidLocked(uid); + } + writePolicyLocked(); + } } @Override @@ -1581,14 +1649,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } fout.decreaseIndent(); - fout.println("Policy for apps:"); + fout.println("Policy for UIDs:"); fout.increaseIndent(); - int size = mAppPolicy.size(); + int size = mUidPolicy.size(); for (int i = 0; i < size; i++) { - final int appId = mAppPolicy.keyAt(i); - final int policy = mAppPolicy.valueAt(i); - fout.print("appId="); - fout.print(appId); + final int uid = mUidPolicy.keyAt(i); + final int policy = mUidPolicy.valueAt(i); + fout.print("UID="); + fout.print(uid); fout.print(" policy="); dumpPolicy(fout, policy); fout.println(); @@ -1693,12 +1761,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * Update rules that might be changed by {@link #mRestrictBackground} value. */ private void updateRulesForRestrictBackgroundLocked() { - // update rules for all installed applications final PackageManager pm = mContext.getPackageManager(); - final List<ApplicationInfo> apps = pm.getInstalledApplications(0); - for (ApplicationInfo app : apps) { - final int appId = UserId.getAppId(app.uid); - updateRulesForAppLocked(appId); + final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + + // update rules for all installed applications + final List<UserInfo> users = um.getUsers(); + final List<ApplicationInfo> apps = pm.getInstalledApplications( + PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS); + + for (UserInfo user : users) { + for (ApplicationInfo app : apps) { + final int uid = UserHandle.getUid(user.id, app.uid); + updateRulesForUidLocked(uid); + } } // limit data usage for some internal system services @@ -1706,17 +1781,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { updateRulesForUidLocked(android.os.Process.DRM_UID); } - private void updateRulesForAppLocked(int appId) { - for (UserInfo user : mContext.getPackageManager().getUsers()) { - final int uid = UserId.getUid(user.id, appId); - updateRulesForUidLocked(uid); - } - } - private static boolean isUidValidForRules(int uid) { // allow rules on specific system services, and any apps if (uid == android.os.Process.MEDIA_UID || uid == android.os.Process.DRM_UID - || UserId.isApp(uid)) { + || UserHandle.isApp(uid)) { return true; } @@ -1726,13 +1794,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private void updateRulesForUidLocked(int uid) { if (!isUidValidForRules(uid)) return; - final int appId = UserId.getAppId(uid); - final int appPolicy = getAppPolicy(appId); + final int uidPolicy = getUidPolicy(uid); final boolean uidForeground = isUidForeground(uid); // derive active rules based on policy and active state int uidRules = RULE_ALLOW_ALL; - if (!uidForeground && (appPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) { + if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) { // uid in background, and policy says to block metered data uidRules = RULE_REJECT_METERED; } diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java index 9ddf011..60666b4 100644 --- a/services/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/java/com/android/server/net/NetworkStatsCollection.java @@ -29,8 +29,8 @@ import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; import android.text.format.DateUtils; +import android.util.AtomicFile; -import com.android.internal.os.AtomicFile; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Objects; diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index ba122ec..3a593e4 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -38,23 +38,23 @@ import static android.net.NetworkTemplate.buildTemplateMobileWildcard; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; -import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION; -import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE; -import static android.provider.Settings.Secure.NETSTATS_DEV_PERSIST_BYTES; -import static android.provider.Settings.Secure.NETSTATS_DEV_ROTATE_AGE; -import static android.provider.Settings.Secure.NETSTATS_GLOBAL_ALERT_BYTES; -import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL; -import static android.provider.Settings.Secure.NETSTATS_REPORT_XT_OVER_DEV; -import static android.provider.Settings.Secure.NETSTATS_SAMPLE_ENABLED; -import static android.provider.Settings.Secure.NETSTATS_TIME_CACHE_MAX_AGE; -import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION; -import static android.provider.Settings.Secure.NETSTATS_UID_DELETE_AGE; -import static android.provider.Settings.Secure.NETSTATS_UID_PERSIST_BYTES; -import static android.provider.Settings.Secure.NETSTATS_UID_ROTATE_AGE; -import static android.provider.Settings.Secure.NETSTATS_UID_TAG_BUCKET_DURATION; -import static android.provider.Settings.Secure.NETSTATS_UID_TAG_DELETE_AGE; -import static android.provider.Settings.Secure.NETSTATS_UID_TAG_PERSIST_BYTES; -import static android.provider.Settings.Secure.NETSTATS_UID_TAG_ROTATE_AGE; +import static android.provider.Settings.Global.NETSTATS_DEV_BUCKET_DURATION; +import static android.provider.Settings.Global.NETSTATS_DEV_DELETE_AGE; +import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES; +import static android.provider.Settings.Global.NETSTATS_DEV_ROTATE_AGE; +import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES; +import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL; +import static android.provider.Settings.Global.NETSTATS_REPORT_XT_OVER_DEV; +import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED; +import static android.provider.Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE; +import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION; +import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE; +import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES; +import static android.provider.Settings.Global.NETSTATS_UID_ROTATE_AGE; +import static android.provider.Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION; +import static android.provider.Settings.Global.NETSTATS_UID_TAG_DELETE_AGE; +import static android.provider.Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES; +import static android.provider.Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE; import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE; import static android.telephony.PhoneStateListener.LISTEN_NONE; import static android.text.format.DateUtils.DAY_IN_MILLIS; @@ -99,6 +99,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Secure; import android.telephony.PhoneStateListener; @@ -763,7 +764,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * Observer that watches for {@link INetworkManagementService} alerts. */ - private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() { + private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() { @Override public void limitReached(String limitName, String iface) { // only someone like NMS should be calling us @@ -989,7 +990,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // finally, dispatch updated event to any listeners final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED); updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - mContext.sendBroadcast(updatedIntent, READ_NETWORK_USAGE_HISTORY); + mContext.sendBroadcastAsUser(updatedIntent, UserHandle.ALL, + READ_NETWORK_USAGE_HISTORY); } /** @@ -1216,39 +1218,39 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // TODO: adjust these timings for production builds } - private long getSecureLong(String name, long def) { - return Settings.Secure.getLong(mResolver, name, def); + private long getGlobalLong(String name, long def) { + return Settings.Global.getLong(mResolver, name, def); } - private boolean getSecureBoolean(String name, boolean def) { + private boolean getGlobalBoolean(String name, boolean def) { final int defInt = def ? 1 : 0; - return Settings.Secure.getInt(mResolver, name, defInt) != 0; + return Settings.Global.getInt(mResolver, name, defInt) != 0; } @Override public long getPollInterval() { - return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); + return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); } @Override public long getTimeCacheMaxAge() { - return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS); + return getGlobalLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS); } @Override public long getGlobalAlertBytes(long def) { - return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, def); + return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def); } @Override public boolean getSampleEnabled() { - return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true); + return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true); } @Override public boolean getReportXtOverDev() { - return getSecureBoolean(NETSTATS_REPORT_XT_OVER_DEV, true); + return getGlobalBoolean(NETSTATS_REPORT_XT_OVER_DEV, true); } @Override public Config getDevConfig() { - return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), - getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), - getSecureLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS)); + return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), + getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), + getGlobalLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS)); } @Override public Config getXtConfig() { @@ -1256,19 +1258,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override public Config getUidConfig() { - return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), - getSecureLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS), - getSecureLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS)); + return new Config(getGlobalLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), + getGlobalLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS), + getGlobalLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS)); } @Override public Config getUidTagConfig() { - return new Config(getSecureLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), - getSecureLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS), - getSecureLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS)); + return new Config(getGlobalLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), + getGlobalLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS), + getGlobalLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS)); } @Override public long getDevPersistBytes(long def) { - return getSecureLong(NETSTATS_DEV_PERSIST_BYTES, def); + return getGlobalLong(NETSTATS_DEV_PERSIST_BYTES, def); } @Override public long getXtPersistBytes(long def) { @@ -1276,11 +1278,11 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override public long getUidPersistBytes(long def) { - return getSecureLong(NETSTATS_UID_PERSIST_BYTES, def); + return getGlobalLong(NETSTATS_UID_PERSIST_BYTES, def); } @Override public long getUidTagPersistBytes(long def) { - return getSecureLong(NETSTATS_UID_TAG_PERSIST_BYTES, def); + return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def); } } } diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index 48004bb..ad85c0d 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -25,7 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -class Installer { +public final class Installer { private static final String TAG = "Installer"; private static final boolean LOCAL_DEBUG = false; @@ -324,26 +324,14 @@ class Installer { return execute(builder.toString()); } - /* - * @param packagePathSuffix The name of the path relative to install - * directory. Say if the path name is /data/app/com.test-1.apk, the package - * suffix path will be com.test-1 - */ - public int setForwardLockPerm(String packagePathSuffix, int gid) { - StringBuilder builder = new StringBuilder("protect"); - builder.append(' '); - builder.append(packagePathSuffix); - builder.append(' '); - builder.append(gid); - return execute(builder.toString()); - } - - public int getSizeInfo(String pkgName, String apkPath, String fwdLockApkPath, + public int getSizeInfo(String pkgName, int persona, String apkPath, String fwdLockApkPath, String asecPath, PackageStats pStats) { StringBuilder builder = new StringBuilder("getsize"); builder.append(' '); builder.append(pkgName); builder.append(' '); + builder.append(persona); + builder.append(' '); builder.append(apkPath); builder.append(' '); builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!"); @@ -371,12 +359,20 @@ class Installer { return execute("movefiles"); } + /** + * Links the native library directory in an application's directory to its + * real location. + * + * @param dataPath data directory where the application is + * @param nativeLibPath target native library path + * @return -1 on error + */ public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath) { if (dataPath == null) { - Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null"); + Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null"); return -1; } else if (nativeLibPath == null) { - Slog.e(TAG, "unlinkNativeLibraryDirectory nativeLibPath is null"); + Slog.e(TAG, "linkNativeLibraryDirectory nativeLibPath is null"); return -1; } @@ -387,16 +383,4 @@ class Installer { return execute(builder.toString()); } - - public int unlinkNativeLibraryDirectory(String dataPath) { - if (dataPath == null) { - Slog.e(TAG, "unlinkNativeLibraryDirectory dataPath is null"); - return -1; - } - - StringBuilder builder = new StringBuilder("unlinklib "); - builder.append(dataPath); - - return execute(builder.toString()); - } } diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index f914271..536c612 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -25,6 +25,11 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static com.android.internal.util.ArrayUtils.appendInt; import static com.android.internal.util.ArrayUtils.removeInt; import static libcore.io.OsConstants.S_ISLNK; +import static libcore.io.OsConstants.S_IRWXU; +import static libcore.io.OsConstants.S_IRGRP; +import static libcore.io.OsConstants.S_IXGRP; +import static libcore.io.OsConstants.S_IROTH; +import static libcore.io.OsConstants.S_IXOTH; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; @@ -64,10 +69,13 @@ import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageCleanItem; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.PackageUserState; +import android.content.pm.PackageParser.ActivityIntentInfo; import android.content.pm.PackageStats; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; @@ -76,8 +84,8 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; -import android.content.pm.UserInfo; import android.content.pm.ManifestDigest; +import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VerifierInfo; import android.net.Uri; @@ -100,7 +108,8 @@ import android.os.SELinux; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.UserId; +import android.os.UserHandle; +import android.os.Environment.UserEnvironment; import android.provider.Settings.Secure; import android.security.SystemKeyStore; import android.util.DisplayMetrics; @@ -144,6 +153,7 @@ import java.util.Set; import libcore.io.ErrnoException; import libcore.io.IoUtils; import libcore.io.Libcore; +import libcore.io.OsConstants; import libcore.io.StructStat; /** @@ -165,6 +175,7 @@ public class PackageManagerService extends IPackageManager.Stub { static final boolean DEBUG_UPGRADE = false; private static final boolean DEBUG_INSTALL = false; private static final boolean DEBUG_REMOVE = false; + private static final boolean DEBUG_BROADCASTS = false; private static final boolean DEBUG_SHOW_INFO = false; private static final boolean DEBUG_PACKAGE_INFO = false; private static final boolean DEBUG_INTENT_MATCHING = false; @@ -175,6 +186,7 @@ public class PackageManagerService extends IPackageManager.Stub { private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; + private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID; private static final boolean GET_CERTIFICATES = true; @@ -203,14 +215,21 @@ public class PackageManagerService extends IPackageManager.Stub { /** * Whether verification is enabled by default. */ - // STOPSHIP: change this to true - private static final boolean DEFAULT_VERIFY_ENABLE = false; + private static final boolean DEFAULT_VERIFY_ENABLE = true; /** * The default maximum time to wait for the verification agent to return in * milliseconds. */ - private static final long DEFAULT_VERIFICATION_TIMEOUT = 60 * 1000; + private static final long DEFAULT_VERIFICATION_TIMEOUT = 10 * 1000; + + /** + * The default response for package verification timeout. + * + * This can be either PackageManager.VERIFICATION_ALLOW or + * PackageManager.VERIFICATION_REJECT. + */ + private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW; static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer"; @@ -264,7 +283,7 @@ public class PackageManagerService extends IPackageManager.Stub { // This is the object monitoring mDrmAppPrivateInstallDir. final FileObserver mDrmAppInstallObserver; - // Used for priviledge escalation. MUST NOT BE CALLED WITH mPackages + // Used for privilege escalation. MUST NOT BE CALLED WITH mPackages // LOCK HELD. Can be called with mInstallLock held. final Installer mInstaller; @@ -274,6 +293,12 @@ public class PackageManagerService extends IPackageManager.Stub { final File mAppInstallDir; final File mDalvikCacheDir; + /** + * Directory to which applications installed internally have native + * libraries copied. + */ + private File mAppLibInstallDir; + // Directory containing the private parts (e.g. code and non-resource assets) of forward-locked // apps. final File mDrmAppPrivateInstallDir; @@ -388,6 +413,7 @@ public class PackageManagerService extends IPackageManager.Stub { // package uri's from external media onto secure containers // or internal storage. private IMediaContainerService mContainerService = null; + private int mContainerServiceUserId; static final int SEND_PENDING_BROADCAST = 1; static final int MCS_BOUND = 3; @@ -410,7 +436,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; - static UserManager sUserManager; + static UserManagerService sUserManager; // Stores a list of users whose package restrictions file needs to be updated private HashSet<Integer> mDirtyUsers = new HashSet<Integer>(); @@ -456,8 +482,15 @@ public class PackageManagerService extends IPackageManager.Stub { " DefaultContainerService"); Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); + mContainerServiceUserId = 0; + if (mPendingInstalls.size() > 0) { + mContainerServiceUserId = mPendingInstalls.get(0).getUser().getIdentifier(); + if (mContainerServiceUserId == UserHandle.USER_ALL) { + mContainerServiceUserId = 0; + } + } if (mContext.bindService(service, mDefContainerConn, - Context.BIND_AUTO_CREATE)) { + Context.BIND_AUTO_CREATE, mContainerServiceUserId)) { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); mBound = true; return true; @@ -534,6 +567,15 @@ public class PackageManagerService extends IPackageManager.Stub { } else if (mPendingInstalls.size() > 0) { HandlerParams params = mPendingInstalls.get(0); if (params != null) { + // Check if we're connected to the correct service, if it's an install + // request. + final int installFor = params.getUser().getIdentifier(); + if (installFor != mContainerServiceUserId + && (installFor == UserHandle.USER_ALL + && mContainerServiceUserId != 0)) { + mHandler.sendEmptyMessage(MCS_RECONNECT); + return; + } if (params.startCopy()) { // We are done... look for more work or to // go idle. @@ -650,15 +692,21 @@ public class PackageManagerService extends IPackageManager.Stub { break; } case START_CLEANING_PACKAGE: { - String packageName = (String)msg.obj; Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); + PackageCleanItem item = new PackageCleanItem((String)msg.obj, + msg.arg2 != 0); synchronized (mPackages) { - if (!mSettings.mPackagesToBeCleaned.contains(packageName)) { - mSettings.mPackagesToBeCleaned.add(packageName); + if (msg.arg1 == UserHandle.USER_ALL) { + int[] users = sUserManager.getUserIds(); + for (int user : users) { + mSettings.addPackageToCleanLPw(user, item); + } + } else { + mSettings.addPackageToCleanLPw(msg.arg1, item); } } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - startCleaningPackages(); + startCleaningPackages(-1); } break; case POST_INSTALL: { if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1); @@ -674,26 +722,65 @@ public class PackageManagerService extends IPackageManager.Stub { res.removedInfo.sendBroadcast(false, true); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); + // Determine the set of users who are adding this + // package for the first time vs. those who are seeing + // an update. + int[] firstUsers; + int[] updateUsers = new int[0]; + if (res.origUsers == null || res.origUsers.length == 0) { + firstUsers = res.newUsers; + } else { + firstUsers = new int[0]; + for (int i=0; i<res.newUsers.length; i++) { + int user = res.newUsers[i]; + boolean isNew = true; + for (int j=0; j<res.origUsers.length; j++) { + if (res.origUsers[j] == user) { + isNew = false; + break; + } + } + if (isNew) { + int[] newFirst = new int[firstUsers.length+1]; + System.arraycopy(firstUsers, 0, newFirst, 0, + firstUsers.length); + newFirst[firstUsers.length] = user; + firstUsers = newFirst; + } else { + int[] newUpdate = new int[updateUsers.length+1]; + System.arraycopy(updateUsers, 0, newUpdate, 0, + updateUsers.length); + newUpdate[updateUsers.length] = user; + updateUsers = newUpdate; + } + } + } + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, + res.pkg.applicationInfo.packageName, + extras, null, null, firstUsers); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName, - extras, null, null, UserId.USER_ALL); + extras, null, null, updateUsers); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, res.pkg.applicationInfo.packageName, - extras, null, null, UserId.USER_ALL); + extras, null, null, updateUsers); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, - res.pkg.applicationInfo.packageName, null, - UserId.USER_ALL); + res.pkg.applicationInfo.packageName, null, updateUsers); } if (res.removedInfo.args != null) { // Remove the replaced package's older resources safely now deleteOld = true; } + + // Log current value of "unknown sources" setting + EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED, + getUnknownSourcesSettings()); } // Force a gc to clear up things Runtime.getRuntime().gc(); @@ -764,17 +851,33 @@ public class PackageManagerService extends IPackageManager.Stub { final int verificationId = msg.arg1; final PackageVerificationState state = mPendingVerification.get(verificationId); - if (state != null) { + if ((state != null) && !state.timeoutExtended()) { final InstallArgs args = state.getInstallArgs(); Slog.i(TAG, "Verification timed out for " + args.packageURI.toString()); mPendingVerification.remove(verificationId); - int ret = PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT; - processPendingInstall(args, ret); + int ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE; + + if (getDefaultVerificationResponse() == PackageManager.VERIFICATION_ALLOW) { + Slog.i(TAG, "Continuing with installation of " + + args.packageURI.toString()); + state.setVerifierResponse(Binder.getCallingUid(), + PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT); + broadcastPackageVerified(verificationId, args.packageURI, + PackageManager.VERIFICATION_ALLOW); + try { + ret = args.copyApk(mContainerService, true); + } catch (RemoteException e) { + Slog.e(TAG, "Could not contact the ContainerService"); + } + } else { + broadcastPackageVerified(verificationId, args.packageURI, + PackageManager.VERIFICATION_REJECT); + } + processPendingInstall(args, ret); mHandler.sendEmptyMessage(MCS_UNBIND); } - break; } case PACKAGE_VERIFIED: { @@ -798,6 +901,8 @@ public class PackageManagerService extends IPackageManager.Stub { int ret; if (state.isInstallAllowed()) { ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + broadcastPackageVerified(verificationId, args.packageURI, + response.code); try { ret = args.copyApk(mContainerService, true); } catch (RemoteException e) { @@ -832,9 +937,10 @@ public class PackageManagerService extends IPackageManager.Stub { } } - public static final IPackageManager main(Context context, boolean factoryTest, - boolean onlyCore) { - PackageManagerService m = new PackageManagerService(context, factoryTest, onlyCore); + public static final IPackageManager main(Context context, Installer installer, + boolean factoryTest, boolean onlyCore) { + PackageManagerService m = new PackageManagerService(context, installer, + factoryTest, onlyCore); ServiceManager.addService("package", m); return m; } @@ -861,7 +967,8 @@ public class PackageManagerService extends IPackageManager.Stub { return res; } - public PackageManagerService(Context context, boolean factoryTest, boolean onlyCore) { + public PackageManagerService(Context context, Installer installer, + boolean factoryTest, boolean onlyCore) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START, SystemClock.uptimeMillis()); @@ -874,12 +981,13 @@ public class PackageManagerService extends IPackageManager.Stub { mOnlyCore = onlyCore; mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); mMetrics = new DisplayMetrics(); - mSettings = new Settings(); + mSettings = new Settings(context); mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM); mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, ApplicationInfo.FLAG_SYSTEM); mSettings.addSharedUserLPw("android.uid.log", LOG_UID, ApplicationInfo.FLAG_SYSTEM); mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { @@ -898,7 +1006,7 @@ public class PackageManagerService extends IPackageManager.Stub { mSeparateProcesses = null; } - mInstaller = new Installer(); + mInstaller = installer; WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); Display d = wm.getDefaultDisplay(); @@ -916,11 +1024,12 @@ public class PackageManagerService extends IPackageManager.Stub { mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); - sUserManager = new UserManager(mInstaller, mUserAppDataDir); + sUserManager = new UserManagerService(context, this, + mInstallLock, mPackages); readPermissions(); - mRestoredSettings = mSettings.readLPw(getUsers()); + mRestoredSettings = mSettings.readLPw(sUserManager.getUsers()); long startTime = SystemClock.uptimeMillis(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, @@ -1099,7 +1208,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (mSettings.isDisabledSystemPackageLPr(ps.name)) { Slog.i(TAG, "Expecting better updatd system app for " + ps.name + "; removing system app"); - removePackageLI(scannedPkg, true); + removePackageLI(ps, true); } continue; @@ -1110,8 +1219,7 @@ public class PackageManagerService extends IPackageManager.Stub { String msg = "System package " + ps.name + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); - mInstaller.remove(ps.name, 0); - sUserManager.removePackageForAllUsers(ps.name); + removeDataDirsLI(ps.name); } else { final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name); if (disabledPs.codePath == null || !disabledPs.codePath.exists()) { @@ -1122,6 +1230,7 @@ public class PackageManagerService extends IPackageManager.Stub { } mAppInstallDir = new File(dataDir, "app"); + mAppLibInstallDir = new File(dataDir, "app-lib"); //look for any incomplete package installations ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr(); //clean up list @@ -1160,9 +1269,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (deletedPkg == null) { msg = "Updated system package " + deletedAppName + " no longer exists; wiping its data"; - - mInstaller.remove(deletedAppName, 0); - sUserManager.removePackageForAllUsers(deletedAppName); + removeDataDirsLI(deletedAppName); } else { msg = "Updated system app + " + deletedAppName + " no longer present; removing system privileges for " @@ -1297,13 +1404,7 @@ public class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); - int retCode = mInstaller.remove(ps.name, 0); - if (retCode < 0) { - Slog.w(TAG, "Couldn't remove app data directory for package: " - + ps.name + ", retcode=" + retCode); - } else { - sUserManager.removePackageForAllUsers(ps.name); - } + removeDataDirsLI(ps.name); if (ps.codePath != null) { if (!ps.codePath.delete()) { Slog.w(TAG, "Unable to remove old code file: " + ps.codePath); @@ -1533,19 +1634,17 @@ public class PackageManagerService extends IPackageManager.Stub { PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) { if (!sUserManager.exists(userId)) return null; PackageInfo pi; - if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - // The package has been uninstalled but has retained data and resources. - pi = PackageParser.generatePackageInfo(p, null, flags, 0, 0, null, false, 0, userId); - } else { - final PackageSetting ps = (PackageSetting) p.mExtras; - if (ps == null) { - return null; - } - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; - pi = PackageParser.generatePackageInfo(p, gp.gids, flags, - ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions, - ps.getStopped(userId), ps.getEnabled(userId), userId); - pi.applicationInfo.enabledSetting = ps.getEnabled(userId); + final PackageSetting ps = (PackageSetting) p.mExtras; + if (ps == null) { + return null; + } + final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; + final PackageUserState state = ps.readUserState(userId); + pi = PackageParser.generatePackageInfo(p, gp.gids, flags, + ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions, + state, userId); + if (pi != null) { + pi.applicationInfo.enabledSetting = state.enabled; pi.applicationInfo.enabled = pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_DEFAULT || pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_ENABLED; @@ -1556,6 +1655,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public PackageInfo getPackageInfo(String packageName, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get package info"); // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); @@ -1598,18 +1698,19 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public int getPackageUid(String packageName, int userId) { if (!sUserManager.exists(userId)) return -1; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get package uid"); // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); if(p != null) { - return UserId.getUid(userId, p.applicationInfo.uid); + return UserHandle.getUid(userId, p.applicationInfo.uid); } PackageSetting ps = mSettings.mPackages.get(packageName); if((ps == null) || (ps.pkg == null) || (ps.pkg.applicationInfo == null)) { return -1; } p = ps.pkg; - return p != null ? UserId.getUid(userId, p.applicationInfo.uid) : -1; + return p != null ? UserHandle.getUid(userId, p.applicationInfo.uid) : -1; } } @@ -1712,14 +1813,15 @@ public class PackageManagerService extends IPackageManager.Stub { PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { if (ps.pkg == null) { - PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, flags, userId); + PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, + flags, userId); if (pInfo != null) { return pInfo.applicationInfo; } return null; } - return PackageParser.generateApplicationInfo(ps.pkg, flags, ps.getStopped(userId), - ps.getEnabled(userId), userId); + return PackageParser.generateApplicationInfo(ps.pkg, flags, + ps.readUserState(userId), userId); } return null; } @@ -1729,20 +1831,23 @@ public class PackageManagerService extends IPackageManager.Stub { if (!sUserManager.exists(userId)) return null; PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { - PackageParser.Package pkg = new PackageParser.Package(packageName); - if (ps.pkg == null) { - ps.pkg = new PackageParser.Package(packageName); - ps.pkg.applicationInfo.packageName = packageName; - ps.pkg.applicationInfo.flags = ps.pkgFlags; - ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString; - ps.pkg.applicationInfo.sourceDir = ps.codePathString; - ps.pkg.applicationInfo.dataDir = - getDataPathForPackage(ps.pkg.packageName, 0).getPath(); - ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; - } - // ps.pkg.mSetEnabled = ps.getEnabled(userId); - // ps.pkg.mSetStopped = ps.getStopped(userId); - return generatePackageInfo(ps.pkg, flags, userId); + PackageParser.Package pkg = ps.pkg; + if (pkg == null) { + if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) { + return null; + } + pkg = new PackageParser.Package(packageName); + pkg.applicationInfo.packageName = packageName; + pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY; + pkg.applicationInfo.publicSourceDir = ps.resourcePathString; + pkg.applicationInfo.sourceDir = ps.codePathString; + pkg.applicationInfo.dataDir = + getDataPathForPackage(packageName, 0).getPath(); + pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; + } + // pkg.mSetEnabled = ps.getEnabled(userId); + // pkg.mSetStopped = ps.getStopped(userId); + return generatePackageInfo(pkg, flags, userId); } return null; } @@ -1750,6 +1855,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get application info"); // writer synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); @@ -1760,13 +1866,12 @@ public class PackageManagerService extends IPackageManager.Stub { PackageSetting ps = mSettings.mPackages.get(packageName); if (ps == null) return null; // Note: isEnabledLP() does not apply here - always return info - return PackageParser.generateApplicationInfo(p, flags, ps.getStopped(userId), - ps.getEnabled(userId)); + return PackageParser.generateApplicationInfo(p, flags, ps.readUserState(userId)); } if ("android".equals(packageName)||"system".equals(packageName)) { return mAndroidApplication; } - if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { + if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { return generateApplicationInfoFromSettingsLPw(packageName, flags, userId); } } @@ -1782,9 +1887,11 @@ public class PackageManagerService extends IPackageManager.Stub { public void run() { mHandler.removeCallbacks(this); int retCode = -1; - retCode = mInstaller.freeCache(freeStorageSize); - if (retCode < 0) { - Slog.w(TAG, "Couldn't clear application caches"); + synchronized (mInstallLock) { + retCode = mInstaller.freeCache(freeStorageSize); + if (retCode < 0) { + Slog.w(TAG, "Couldn't clear application caches"); + } } if (observer != null) { try { @@ -1805,9 +1912,11 @@ public class PackageManagerService extends IPackageManager.Stub { public void run() { mHandler.removeCallbacks(this); int retCode = -1; - retCode = mInstaller.freeCache(freeStorageSize); - if (retCode < 0) { - Slog.w(TAG, "Couldn't clear application caches"); + synchronized (mInstallLock) { + retCode = mInstaller.freeCache(freeStorageSize); + if (retCode < 0) { + Slog.w(TAG, "Couldn't clear application caches"); + } } if(pi != null) { try { @@ -1826,6 +1935,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get activity info"); synchronized (mPackages) { PackageParser.Activity a = mActivities.mActivities.get(component); @@ -1833,8 +1943,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) { PackageSetting ps = mSettings.mPackages.get(component.getPackageName()); if (ps == null) return null; - return PackageParser.generateActivityInfo(a, flags, ps.getStopped(userId), - ps.getEnabled(userId), userId); + return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId), + userId); } if (mResolveComponentName.equals(component)) { return mResolveActivity; @@ -1846,6 +1956,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get receiver info"); synchronized (mPackages) { PackageParser.Activity a = mReceivers.mActivities.get(component); if (DEBUG_PACKAGE_INFO) Log.v( @@ -1853,8 +1964,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) { PackageSetting ps = mSettings.mPackages.get(component.getPackageName()); if (ps == null) return null; - return PackageParser.generateActivityInfo(a, flags, ps.getStopped(userId), - ps.getEnabled(userId), userId); + return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId), + userId); } } return null; @@ -1863,6 +1974,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get service info"); synchronized (mPackages) { PackageParser.Service s = mServices.mServices.get(component); if (DEBUG_PACKAGE_INFO) Log.v( @@ -1870,8 +1982,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (s != null && mSettings.isEnabledLPr(s.info, flags, userId)) { PackageSetting ps = mSettings.mPackages.get(component.getPackageName()); if (ps == null) return null; - return PackageParser.generateServiceInfo(s, flags, ps.getStopped(userId), - ps.getEnabled(userId), userId); + return PackageParser.generateServiceInfo(s, flags, ps.readUserState(userId), + userId); } } return null; @@ -1880,6 +1992,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override public ProviderInfo getProviderInfo(ComponentName component, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get provider info"); synchronized (mPackages) { PackageParser.Provider p = mProvidersByComponent.get(component); if (DEBUG_PACKAGE_INFO) Log.v( @@ -1887,8 +2000,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (p != null && mSettings.isEnabledLPr(p.info, flags, userId)) { PackageSetting ps = mSettings.mPackages.get(component.getPackageName()); if (ps == null) return null; - return PackageParser.generateProviderInfo(p, flags, ps.getStopped(userId), - ps.getEnabled(userId), userId); + return PackageParser.generateProviderInfo(p, flags, ps.readUserState(userId), + userId); } } return null; @@ -1933,7 +2046,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private void checkValidCaller(int uid, int userId) { - if (UserId.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0) + if (UserHandle.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0) return; throw new SecurityException("Caller uid=" + uid @@ -1962,7 +2075,7 @@ public class PackageManagerService extends IPackageManager.Stub { public int checkUidPermission(String permName, int uid) { synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(UserId.getAppId(uid)); + Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); if (obj != null) { GrantedPermissions gp = (GrantedPermissions)obj; if (gp.grantedPermissions.contains(permName)) { @@ -1981,6 +2094,34 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.PERMISSION_DENIED; } + /** + * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS + * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller. + * @param message the message to log on security exception + * @return + */ + private void enforceCrossUserPermission(int callingUid, int userId, + boolean requireFullPermission, String message) { + if (userId < 0) { + throw new IllegalArgumentException("Invalid userId " + userId); + } + if (userId == UserHandle.getUserId(callingUid)) return; + if (callingUid != Process.SYSTEM_UID && callingUid != 0) { + if (requireFullPermission) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); + } else { + try { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); + } catch (SecurityException se) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, message); + } + } + } + } + private BasePermission findPermissionTreeLP(String permName) { for(BasePermission bp : mSettings.mPermissionTrees.values()) { if (permName.startsWith(bp.name) && @@ -1996,7 +2137,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (permName != null) { BasePermission bp = findPermissionTreeLP(permName); if (bp != null) { - if (bp.uid == UserId.getAppId(Binder.getCallingUid())) { + if (bp.uid == UserHandle.getAppId(Binder.getCallingUid())) { return bp; } throw new SecurityException("Calling uid " @@ -2199,8 +2340,8 @@ public class PackageManagerService extends IPackageManager.Stub { public int checkUidSignatures(int uid1, int uid2) { // Map to base uids. - uid1 = UserId.getAppId(uid1); - uid2 = UserId.getAppId(uid2); + uid1 = UserHandle.getAppId(uid1); + uid2 = UserHandle.getAppId(uid2); // reader synchronized (mPackages) { Signature[] s1; @@ -2258,7 +2399,7 @@ public class PackageManagerService extends IPackageManager.Stub { } public String[] getPackagesForUid(int uid) { - uid = UserId.getAppId(uid); + uid = UserHandle.getAppId(uid); // reader synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(uid); @@ -2283,7 +2424,7 @@ public class PackageManagerService extends IPackageManager.Stub { public String getNameForUid(int uid) { // reader synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(UserId.getAppId(uid)); + Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); if (obj instanceof SharedUserSetting) { final SharedUserSetting sus = (SharedUserSetting) obj; return sus.name + ":" + sus.userId; @@ -2313,6 +2454,7 @@ public class PackageManagerService extends IPackageManager.Stub { public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "resolve intent"); List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId); return chooseBestActivity(intent, resolvedType, flags, query, userId); } @@ -2394,6 +2536,9 @@ public class PackageManagerService extends IPackageManager.Stub { final int M = prefs.size(); for (int i=0; i<M; i++) { final PreferredActivity pa = prefs.get(i); + if (pa.mUserId != userId) { + continue; + } if (pa.mPref.mMatch != match) { continue; } @@ -2451,6 +2596,7 @@ public class PackageManagerService extends IPackageManager.Stub { public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "query intent activities"); ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -2490,6 +2636,8 @@ public class PackageManagerService extends IPackageManager.Stub { Intent[] specifics, String[] specificTypes, Intent intent, String resolvedType, int flags, int userId) { if (!sUserManager.exists(userId)) return null; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, + "query intent activity options"); final String resultsAction = intent.getAction(); List<ResolveInfo> results = queryIntentActivities(intent, resolvedType, flags @@ -2759,11 +2907,14 @@ public class PackageManagerService extends IPackageManager.Stub { return index; } - public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, String lastRead) { + @Override + public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, String lastRead, + int userId) { final ParceledListSlice<PackageInfo> list = new ParceledListSlice<PackageInfo>(); final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; final String[] keys; - int userId = UserId.getCallingUserId(); + + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "get installed packages"); // writer synchronized (mPackages) { @@ -2838,8 +2989,8 @@ public class PackageManagerService extends IPackageManager.Stub { } else { final PackageParser.Package p = mPackages.get(packageName); if (p != null && ps != null) { - ai = PackageParser.generateApplicationInfo(p, flags, ps.getStopped(userId), - ps.getEnabled(userId), userId); + ai = PackageParser.generateApplicationInfo(p, flags, + ps.readUserState(userId), userId); } } @@ -2862,17 +3013,20 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Package> i = mPackages.values().iterator(); - final int userId = UserId.getCallingUserId(); + final int userId = UserHandle.getCallingUserId(); while (i.hasNext()) { final PackageParser.Package p = i.next(); if (p.applicationInfo != null && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0 && (!mSafeMode || isSystemApp(p))) { PackageSetting ps = mSettings.mPackages.get(p.packageName); - finalList.add(PackageParser.generateApplicationInfo(p, flags, - ps != null ? ps.getStopped(userId) : false, - ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, - userId)); + if (ps != null) { + ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags, + ps.readUserState(userId), userId); + if (ai != null) { + finalList.add(ai); + } + } } } } @@ -2889,14 +3043,12 @@ public class PackageManagerService extends IPackageManager.Stub { PackageSetting ps = provider != null ? mSettings.mPackages.get(provider.owner.packageName) : null; - return provider != null + return ps != null && mSettings.isEnabledLPr(provider.info, flags, userId) && (!mSafeMode || (provider.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0) ? PackageParser.generateProviderInfo(provider, flags, - ps != null ? ps.getStopped(userId) : false, - ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, - userId) + ps.readUserState(userId), userId) : null; } } @@ -2910,20 +3062,21 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() .iterator(); - final int userId = UserId.getCallingUserId(); + final int userId = UserHandle.getCallingUserId(); while (i.hasNext()) { Map.Entry<String, PackageParser.Provider> entry = i.next(); PackageParser.Provider p = entry.getValue(); PackageSetting ps = mSettings.mPackages.get(p.owner.packageName); - if (p.syncable + if (ps != null && p.syncable && (!mSafeMode || (p.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0)) { - outNames.add(entry.getKey()); - outInfo.add(PackageParser.generateProviderInfo(p, 0, - ps != null ? ps.getStopped(userId) : false, - ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, - userId)); + ProviderInfo info = PackageParser.generateProviderInfo(p, 0, + ps.readUserState(userId), userId); + if (info != null) { + outNames.add(entry.getKey()); + outInfo.add(info); + } } } } @@ -2937,24 +3090,25 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); final int userId = processName != null ? - UserId.getUserId(uid) : UserId.getCallingUserId(); + UserHandle.getUserId(uid) : UserHandle.getCallingUserId(); while (i.hasNext()) { final PackageParser.Provider p = i.next(); PackageSetting ps = mSettings.mPackages.get(p.owner.packageName); - if (p.info.authority != null + if (ps != null && p.info.authority != null && (processName == null || (p.info.processName.equals(processName) - && UserId.isSameApp(p.info.applicationInfo.uid, uid))) + && UserHandle.isSameApp(p.info.applicationInfo.uid, uid))) && mSettings.isEnabledLPr(p.info, flags, userId) && (!mSafeMode || (p.info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) { if (finalList == null) { finalList = new ArrayList<ProviderInfo>(3); } - finalList.add(PackageParser.generateProviderInfo(p, flags, - ps != null ? ps.getStopped(userId) : false, - ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, - userId)); + ProviderInfo info = PackageParser.generateProviderInfo(p, flags, + ps.readUserState(userId), userId); + if (info != null) { + finalList.add(info); + } } } } @@ -2987,8 +3141,11 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageParser.Instrumentation p = i.next(); if (targetPackage == null || targetPackage.equals(p.info.targetPackage)) { - finalList.add(PackageParser.generateInstrumentationInfo(p, - flags)); + InstrumentationInfo ii = PackageParser.generateInstrumentationInfo(p, + flags); + if (ii != null) { + finalList.add(ii); + } } } } @@ -3015,7 +3172,7 @@ public class PackageManagerService extends IPackageManager.Stub { continue; } PackageParser.Package pkg = scanPackageLI(file, - flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime); + flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null); // Don't mess around with apps in system partition. if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 && mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) { @@ -3083,7 +3240,7 @@ public class PackageManagerService extends IPackageManager.Stub { * Returns null in case of errors and the error code is stored in mLastScanError */ private PackageParser.Package scanPackageLI(File scanFile, - int parseFlags, int scanMode, long currentTime) { + int parseFlags, int scanMode, long currentTime, UserHandle user) { mLastScanError = PackageManager.INSTALL_SUCCEEDED; String scanPath = scanFile.getPath(); parseFlags |= mDefParseFlags; @@ -3149,7 +3306,7 @@ public class PackageManagerService extends IPackageManager.Stub { InstallArgs args = createInstallArgs(packageFlagsToInstallFlags(ps), ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString); - synchronized (mInstaller) { + synchronized (mInstallLock) { args.cleanUpResourcesLI(); } synchronized (mPackages) { @@ -3183,7 +3340,7 @@ public class PackageManagerService extends IPackageManager.Stub { */ if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { - deletePackageLI(pkg.packageName, true, 0, null, false); + deletePackageLI(pkg.packageName, null, true, 0, null, false); ps = null; } else { /* @@ -3205,7 +3362,7 @@ public class PackageManagerService extends IPackageManager.Stub { + " better than installed " + ps.versionCode); InstallArgs args = createInstallArgs(packageFlagsToInstallFlags(ps), ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString); - synchronized (mInstaller) { + synchronized (mInstallLock) { args.cleanUpResourcesLI(); } } @@ -3236,7 +3393,7 @@ public class PackageManagerService extends IPackageManager.Stub { setApplicationInfoPaths(pkg, codePath, resPath); // Note that we invoke the following method only if we are about to unpack an application PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanMode - | SCAN_UPDATE_SIGNATURE, currentTime); + | SCAN_UPDATE_SIGNATURE, currentTime, user); /* * If the system app should be overridden by a previously installed @@ -3378,8 +3535,8 @@ public class PackageManagerService extends IPackageManager.Stub { return DEX_OPT_DEFERRED; } else { Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName); - ret = mInstaller.dexopt(path, pkg.applicationInfo.uid, - !isForwardLocked(pkg)); + final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); + ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg)); pkg.mDidDexOpt = true; performed = true; } @@ -3439,8 +3596,45 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private int createDataDirsLI(String packageName, int uid) { + int[] users = sUserManager.getUserIds(); + int res = mInstaller.install(packageName, uid, uid); + if (res < 0) { + return res; + } + for (int user : users) { + if (user != 0) { + res = mInstaller.createUserData(packageName, + UserHandle.getUid(user, uid), user); + if (res < 0) { + return res; + } + } + } + return res; + } + + private int removeDataDirsLI(String packageName) { + int[] users = sUserManager.getUserIds(); + int res = 0; + for (int user : users) { + int resInner = mInstaller.remove(packageName, user); + if (resInner < 0) { + res = resInner; + } + } + + final File nativeLibraryFile = new File(mAppLibInstallDir, packageName); + NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile); + if (!nativeLibraryFile.delete()) { + Slog.w(TAG, "Couldn't delete native library directory " + nativeLibraryFile.getPath()); + } + + return res; + } + private PackageParser.Package scanPackageLI(PackageParser.Package pkg, - int parseFlags, int scanMode, long currentTime) { + int parseFlags, int scanMode, long currentTime, UserHandle user) { File scanFile = new File(pkg.mScanPath); if (scanFile == null || pkg.applicationInfo.sourceDir == null || pkg.applicationInfo.publicSourceDir == null) { @@ -3632,7 +3826,7 @@ public class PackageManagerService extends IPackageManager.Stub { // the PkgSetting exists already and doesn't have to be created. pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile, destResourceFile, pkg.applicationInfo.nativeLibraryDir, - pkg.applicationInfo.flags, true, false); + pkg.applicationInfo.flags, user, false); if (pkgSetting == null) { Slog.w(TAG, "Creating application package " + pkg.packageName + " failed"); mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; @@ -3791,11 +3985,9 @@ public class PackageManagerService extends IPackageManager.Stub { || (scanMode&SCAN_BOOTING) != 0)) { // If this is a system app, we can at least delete its // current data so the application will still work. - int ret = mInstaller.remove(pkgName, 0); + int ret = removeDataDirsLI(pkgName); if (ret >= 0) { // TODO: Kill the processes first - // Remove the data directories for all users - sUserManager.removePackageForAllUsers(pkgName); // Old data gone! String prefix = (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0 ? "System package " : "Third party package "; @@ -3807,8 +3999,7 @@ public class PackageManagerService extends IPackageManager.Stub { recovered = true; // And now re-install the app. - ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, - pkg.applicationInfo.uid); + ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid); if (ret == -1) { // Ack should not happen! msg = prefix + pkg.packageName @@ -3817,9 +4008,6 @@ public class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } - // Create data directories for all users - sUserManager.installPackageForAllUsers(pkgName, - pkg.applicationInfo.uid); } if (!recovered) { mHasSystemUidErrors = true; @@ -3857,15 +4045,12 @@ public class PackageManagerService extends IPackageManager.Stub { Log.v(TAG, "Want this data dir: " + dataPath); } //invoke installer to do the actual installation - int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, - pkg.applicationInfo.uid); + int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid); if (ret < 0) { // Error from installer mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } - // Create data directories for all users - sUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); if (dataPath.exists()) { pkg.applicationInfo.dataDir = dataPath.getPath(); @@ -3886,9 +4071,7 @@ public class PackageManagerService extends IPackageManager.Stub { */ if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir != null) { if (pkgSetting.nativeLibraryPathString == null) { - final String nativeLibraryPath = new File(dataPath, LIB_DIR_NAME).getPath(); - pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath; - pkgSetting.nativeLibraryPathString = nativeLibraryPath; + setInternalAppNativeLibraryPath(pkg, pkgSetting); } else { pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString; } @@ -3910,7 +4093,7 @@ public class PackageManagerService extends IPackageManager.Stub { */ if (pkg.applicationInfo.nativeLibraryDir != null) { try { - final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); + File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); final String dataPathString = dataPath.getCanonicalPath(); if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { @@ -3925,37 +4108,43 @@ public class PackageManagerService extends IPackageManager.Stub { Log.i(TAG, "removed obsolete native libraries for system package " + path); } - } else if (nativeLibraryDir.getParentFile().getCanonicalPath() - .equals(dataPathString)) { - /* - * Make sure the native library dir isn't a symlink to - * something. If it is, ask installd to remove it and create - * a directory so we can copy to it afterwards. - */ - boolean isSymLink; - try { - isSymLink = S_ISLNK(Libcore.os.lstat(nativeLibraryDir.getPath()).st_mode); - } catch (ErrnoException e) { - // This shouldn't happen, but we'll fail-safe. - isSymLink = true; + } else if (!isForwardLocked(pkg) && !isExternal(pkg)) { + // Update native library dir if it starts with /data/data + if (nativeLibraryDir.getPath().startsWith(dataPathString)) { + setInternalAppNativeLibraryPath(pkg, pkgSetting); + nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); } - if (isSymLink) { - mInstaller.unlinkNativeLibraryDirectory(dataPathString); + + try { + if (copyNativeLibrariesForInternalApp(scanFile, nativeLibraryDir) != PackageManager.INSTALL_SUCCEEDED) { + Slog.e(TAG, "Unable to copy native libraries"); + mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + return null; + } + } catch (IOException e) { + Slog.e(TAG, "Unable to copy native libraries", e); + mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + return null; } - /* - * If this is an internal application or our - * nativeLibraryPath points to our data directory, unpack - * the libraries if necessary. - */ - NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile, nativeLibraryDir); + if (mInstaller.linkNativeLibraryDirectory(dataPathString, + pkg.applicationInfo.nativeLibraryDir) == -1) { + Slog.e(TAG, "Unable to link native library directory"); + mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + return null; + } } else { Slog.i(TAG, "Linking native library dir for " + path); - mInstaller.linkNativeLibraryDirectory(dataPathString, + int ret = mInstaller.linkNativeLibraryDirectory(dataPathString, pkg.applicationInfo.nativeLibraryDir); + if (ret < 0) { + Slog.w(TAG, "Failed linking native library dir for " + path); + mLastScanError = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; + return null; + } } } catch (IOException ioe) { - Log.e(TAG, "Unable to get canonical file " + ioe.toString()); + Slog.e(TAG, "Unable to get canonical file " + ioe.toString()); } } pkg.mScanPath = path; @@ -3992,7 +4181,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Add the new setting to mPackages mPackages.put(pkg.applicationInfo.packageName, pkg); // Make sure we don't accidentally delete its data. - mSettings.mPackagesToBeCleaned.remove(pkgName); + for (int i=0; i<mSettings.mPackagesToBeCleaned.size(); i++) { + mSettings.mPackagesToBeCleaned.valueAt(i).remove(pkgName); + } // Take care of first install / last update times. if (currentTime != 0) { @@ -4268,20 +4459,75 @@ public class PackageManagerService extends IPackageManager.Stub { return pkg; } - private void killApplication(String pkgName, int uid) { + private void setInternalAppNativeLibraryPath(PackageParser.Package pkg, + PackageSetting pkgSetting) { + final String apkLibPath = getApkName(pkgSetting.codePathString); + final String nativeLibraryPath = new File(mAppLibInstallDir, apkLibPath).getPath(); + pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath; + pkgSetting.nativeLibraryPathString = nativeLibraryPath; + } + + private static int copyNativeLibrariesForInternalApp(File scanFile, final File nativeLibraryDir) + throws IOException { + if (!nativeLibraryDir.isDirectory()) { + nativeLibraryDir.delete(); + + if (!nativeLibraryDir.mkdir()) { + throw new IOException("Cannot create " + nativeLibraryDir.getPath()); + } + + try { + Libcore.os.chmod(nativeLibraryDir.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH + | S_IXOTH); + } catch (ErrnoException e) { + throw new IOException("Cannot chmod native library directory " + + nativeLibraryDir.getPath(), e); + } + } else if (!SELinux.restorecon(nativeLibraryDir)) { + throw new IOException("Cannot set SELinux context for " + nativeLibraryDir.getPath()); + } + + /* + * If this is an internal application or our nativeLibraryPath points to + * the app-lib directory, unpack the libraries if necessary. + */ + return NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile, nativeLibraryDir); + } + + private void killApplication(String pkgName, int appId) { // Request the ActivityManager to kill the process(only for existing packages) // so that we do not end up in a confused state while the user is still using the older // version of the application while the new one gets installed. IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { try { - am.killApplicationWithUid(pkgName, uid); + am.killApplicationWithAppId(pkgName, appId); } catch (RemoteException e) { } } } - void removePackageLI(PackageParser.Package pkg, boolean chatty) { + void removePackageLI(PackageSetting ps, boolean chatty) { + if (DEBUG_INSTALL) { + if (chatty) + Log.d(TAG, "Removing package " + ps.name); + } + + // writer + synchronized (mPackages) { + mPackages.remove(ps.name); + if (ps.codePathString != null) { + mAppDirs.remove(ps.codePathString); + } + + final PackageParser.Package pkg = ps.pkg; + if (pkg != null) { + cleanPackageDataStructuresLILPw(pkg, chatty); + } + } + } + + void removeInstalledPackageLI(PackageParser.Package pkg, boolean chatty) { if (DEBUG_INSTALL) { if (chatty) Log.d(TAG, "Removing package " + pkg.applicationInfo.packageName); @@ -4293,143 +4539,146 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkg.mPath != null) { mAppDirs.remove(pkg.mPath); } + cleanPackageDataStructuresLILPw(pkg, chatty); + } + } - int N = pkg.providers.size(); - StringBuilder r = null; - int i; - for (i=0; i<N; i++) { - PackageParser.Provider p = pkg.providers.get(i); - mProvidersByComponent.remove(new ComponentName(p.info.packageName, - p.info.name)); - if (p.info.authority == null) { - - /* The is another ContentProvider with this authority when - * this app was installed so this authority is null, - * Ignore it as we don't have to unregister the provider. - */ - continue; - } - String names[] = p.info.authority.split(";"); - for (int j = 0; j < names.length; j++) { - if (mProviders.get(names[j]) == p) { - mProviders.remove(names[j]); - if (DEBUG_REMOVE) { - if (chatty) - Log.d(TAG, "Unregistered content provider: " + names[j] - + ", className = " + p.info.name + ", isSyncable = " - + p.info.isSyncable); - } - } - } - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append(p.info.name); - } - } - if (r != null) { - if (DEBUG_REMOVE) Log.d(TAG, " Providers: " + r); + void cleanPackageDataStructuresLILPw(PackageParser.Package pkg, boolean chatty) { + int N = pkg.providers.size(); + StringBuilder r = null; + int i; + for (i=0; i<N; i++) { + PackageParser.Provider p = pkg.providers.get(i); + mProvidersByComponent.remove(new ComponentName(p.info.packageName, + p.info.name)); + if (p.info.authority == null) { + + /* There was another ContentProvider with this authority when + * this app was installed so this authority is null, + * Ignore it as we don't have to unregister the provider. + */ + continue; } - - N = pkg.services.size(); - r = null; - for (i=0; i<N; i++) { - PackageParser.Service s = pkg.services.get(i); - mServices.removeService(s); - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); + String names[] = p.info.authority.split(";"); + for (int j = 0; j < names.length; j++) { + if (mProviders.get(names[j]) == p) { + mProviders.remove(names[j]); + if (DEBUG_REMOVE) { + if (chatty) + Log.d(TAG, "Unregistered content provider: " + names[j] + + ", className = " + p.info.name + ", isSyncable = " + + p.info.isSyncable); } - r.append(s.info.name); } } - if (r != null) { - if (DEBUG_REMOVE) Log.d(TAG, " Services: " + r); + if (chatty) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + r.append(p.info.name); } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Providers: " + r); + } - N = pkg.receivers.size(); - r = null; - for (i=0; i<N; i++) { - PackageParser.Activity a = pkg.receivers.get(i); - mReceivers.removeActivity(a, "receiver"); - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append(a.info.name); + N = pkg.services.size(); + r = null; + for (i=0; i<N; i++) { + PackageParser.Service s = pkg.services.get(i); + mServices.removeService(s); + if (chatty) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); } + r.append(s.info.name); } - if (r != null) { - if (DEBUG_REMOVE) Log.d(TAG, " Receivers: " + r); - } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Services: " + r); + } - N = pkg.activities.size(); - r = null; - for (i=0; i<N; i++) { - PackageParser.Activity a = pkg.activities.get(i); - mActivities.removeActivity(a, "activity"); - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append(a.info.name); + N = pkg.receivers.size(); + r = null; + for (i=0; i<N; i++) { + PackageParser.Activity a = pkg.receivers.get(i); + mReceivers.removeActivity(a, "receiver"); + if (chatty) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); } + r.append(a.info.name); } - if (r != null) { - if (DEBUG_REMOVE) Log.d(TAG, " Activities: " + r); - } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Receivers: " + r); + } - N = pkg.permissions.size(); - r = null; - for (i=0; i<N; i++) { - PackageParser.Permission p = pkg.permissions.get(i); - BasePermission bp = mSettings.mPermissions.get(p.info.name); - if (bp == null) { - bp = mSettings.mPermissionTrees.get(p.info.name); - } - if (bp != null && bp.perm == p) { - bp.perm = null; - if (chatty) { - if (r == null) { - r = new StringBuilder(256); - } else { - r.append(' '); - } - r.append(p.info.name); - } + N = pkg.activities.size(); + r = null; + for (i=0; i<N; i++) { + PackageParser.Activity a = pkg.activities.get(i); + mActivities.removeActivity(a, "activity"); + if (chatty) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); } + r.append(a.info.name); } - if (r != null) { - if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); - } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Activities: " + r); + } - N = pkg.instrumentation.size(); - r = null; - for (i=0; i<N; i++) { - PackageParser.Instrumentation a = pkg.instrumentation.get(i); - mInstrumentation.remove(a.getComponentName()); + N = pkg.permissions.size(); + r = null; + for (i=0; i<N; i++) { + PackageParser.Permission p = pkg.permissions.get(i); + BasePermission bp = mSettings.mPermissions.get(p.info.name); + if (bp == null) { + bp = mSettings.mPermissionTrees.get(p.info.name); + } + if (bp != null && bp.perm == p) { + bp.perm = null; if (chatty) { if (r == null) { r = new StringBuilder(256); } else { r.append(' '); } - r.append(a.info.name); + r.append(p.info.name); } } - if (r != null) { - if (DEBUG_REMOVE) Log.d(TAG, " Instrumentation: " + r); + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); + } + + N = pkg.instrumentation.size(); + r = null; + for (i=0; i<N; i++) { + PackageParser.Instrumentation a = pkg.instrumentation.get(i); + mInstrumentation.remove(a.getComponentName()); + if (chatty) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + r.append(a.info.name); } } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Instrumentation: " + r); + } } private static final boolean isPackageFilename(String name) { @@ -4716,14 +4965,17 @@ public class PackageManagerService extends IPackageManager.Stub { mFlags = flags; final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0; final int N = packageActivities.size(); - ArrayList<ArrayList<PackageParser.ActivityIntentInfo>> listCut = - new ArrayList<ArrayList<PackageParser.ActivityIntentInfo>>(N); + ArrayList<PackageParser.ActivityIntentInfo[]> listCut = + new ArrayList<PackageParser.ActivityIntentInfo[]>(N); ArrayList<PackageParser.ActivityIntentInfo> intentFilters; for (int i = 0; i < N; ++i) { intentFilters = packageActivities.get(i).intents; if (intentFilters != null && intentFilters.size() > 0) { - listCut.add(intentFilters); + PackageParser.ActivityIntentInfo[] array = + new PackageParser.ActivityIntentInfo[intentFilters.size()]; + intentFilters.toArray(array); + listCut.add(array); } } return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId); @@ -4791,6 +5043,11 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + protected ActivityIntentInfo[] newArray(int size) { + return new ActivityIntentInfo[size]; + } + + @Override protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter, int userId) { if (!sUserManager.exists(userId)) return true; PackageParser.Package p = filter.activity.owner; @@ -4800,7 +5057,8 @@ public class PackageManagerService extends IPackageManager.Stub { // System apps are never considered stopped for purposes of // filtering, because there may be no way for the user to // actually re-launch them. - return ps.getStopped(userId) && (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0; + return (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0 + && ps.getStopped(userId); } } return false; @@ -4823,12 +5081,17 @@ public class PackageManagerService extends IPackageManager.Stub { &ApplicationInfo.FLAG_SYSTEM) == 0) { return null; } - final ResolveInfo res = new ResolveInfo(); PackageSetting ps = (PackageSetting) activity.owner.mExtras; - res.activityInfo = PackageParser.generateActivityInfo(activity, mFlags, - ps != null ? ps.getStopped(userId) : false, - ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, - userId); + if (ps == null) { + return null; + } + ActivityInfo ai = PackageParser.generateActivityInfo(activity, mFlags, + ps.readUserState(userId), userId); + if (ai == null) { + return null; + } + final ResolveInfo res = new ResolveInfo(); + res.activityInfo = ai; if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = info; } @@ -4904,14 +5167,17 @@ public class PackageManagerService extends IPackageManager.Stub { mFlags = flags; final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0; final int N = packageServices.size(); - ArrayList<ArrayList<PackageParser.ServiceIntentInfo>> listCut = - new ArrayList<ArrayList<PackageParser.ServiceIntentInfo>>(N); + ArrayList<PackageParser.ServiceIntentInfo[]> listCut = + new ArrayList<PackageParser.ServiceIntentInfo[]>(N); ArrayList<PackageParser.ServiceIntentInfo> intentFilters; for (int i = 0; i < N; ++i) { intentFilters = packageServices.get(i).intents; if (intentFilters != null && intentFilters.size() > 0) { - listCut.add(intentFilters); + PackageParser.ServiceIntentInfo[] array = + new PackageParser.ServiceIntentInfo[intentFilters.size()]; + intentFilters.toArray(array); + listCut.add(array); } } return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId); @@ -4974,6 +5240,11 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + protected PackageParser.ServiceIntentInfo[] newArray(int size) { + return new PackageParser.ServiceIntentInfo[size]; + } + + @Override protected boolean isFilterStopped(PackageParser.ServiceIntentInfo filter, int userId) { if (!sUserManager.exists(userId)) return true; PackageParser.Package p = filter.service.owner; @@ -4983,8 +5254,8 @@ public class PackageManagerService extends IPackageManager.Stub { // System apps are never considered stopped for purposes of // filtering, because there may be no way for the user to // actually re-launch them. - return ps.getStopped(userId) - && (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0; + return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0 + && ps.getStopped(userId); } } return false; @@ -5008,12 +5279,17 @@ public class PackageManagerService extends IPackageManager.Stub { &ApplicationInfo.FLAG_SYSTEM) == 0) { return null; } - final ResolveInfo res = new ResolveInfo(); PackageSetting ps = (PackageSetting) service.owner.mExtras; - res.serviceInfo = PackageParser.generateServiceInfo(service, mFlags, - ps != null ? ps.getStopped(userId) : false, - ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, - userId); + if (ps == null) { + return null; + } + ServiceInfo si = PackageParser.generateServiceInfo(service, mFlags, + ps.readUserState(userId), userId); + if (si == null) { + return null; + } + final ResolveInfo res = new ResolveInfo(); + res.serviceInfo = si; if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = filter; } @@ -5104,13 +5380,14 @@ public class PackageManagerService extends IPackageManager.Stub { }; static final void sendPackageBroadcast(String action, String pkg, - Bundle extras, String targetPkg, IIntentReceiver finishedReceiver, int userId) { + Bundle extras, String targetPkg, IIntentReceiver finishedReceiver, + int[] userIds) { IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { try { - int[] userIds = userId == UserId.USER_ALL - ? sUserManager.getUserIds() - : new int[] {userId}; + if (userIds == null) { + userIds = sUserManager.getUserIds(); + } for (int id : userIds) { final Intent intent = new Intent(action, pkg != null ? Uri.fromParts("package", pkg, null) : null); @@ -5122,11 +5399,18 @@ public class PackageManagerService extends IPackageManager.Stub { } // Modify the UID when posting to other users int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - if (uid > 0 && id > 0) { - uid = UserId.getUid(id, UserId.getAppId(uid)); + if (uid > 0 && UserHandle.getUserId(uid) != id) { + uid = UserHandle.getUid(id, UserHandle.getAppId(uid)); intent.putExtra(Intent.EXTRA_UID, uid); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + if (DEBUG_BROADCASTS) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.d(TAG, "Sending to user " + id + ": " + + intent.toShortString(false, true, false, false) + + " " + intent.getExtras(), here); + } am.broadcastIntent(null, intent, null, finishedReceiver, 0, null, null, null, finishedReceiver != null, false, id); } @@ -5144,8 +5428,9 @@ public class PackageManagerService extends IPackageManager.Stub { return mMediaMounted || Environment.isExternalStorageEmulated(); } - public String nextPackageToClean(String lastPackage) { + public PackageCleanItem nextPackageToClean(PackageCleanItem lastPackage) { // writer + final int userId = UserHandle.getCallingUserId(); synchronized (mPackages) { if (!isExternalMediaAvailable()) { // If the external storage is no longer mounted at this point, @@ -5153,34 +5438,66 @@ public class PackageManagerService extends IPackageManager.Stub { // packages files and can not delete any more. Bail. return null; } - if (lastPackage != null) { - mSettings.mPackagesToBeCleaned.remove(lastPackage); + ArrayList<PackageCleanItem> pkgs = mSettings.mPackagesToBeCleaned.get(userId); + if (pkgs != null) { + if (lastPackage != null) { + pkgs.remove(lastPackage); + } + if (pkgs.size() > 0) { + return pkgs.get(0); + } } - return mSettings.mPackagesToBeCleaned.size() > 0 - ? mSettings.mPackagesToBeCleaned.get(0) : null; + mSettings.mPackagesToBeCleaned.remove(userId); + } + // Move on to the next user to clean. + long ident = Binder.clearCallingIdentity(); + try { + startCleaningPackages(userId); + } finally { + Binder.restoreCallingIdentity(ident); } + return null; } - void schedulePackageCleaning(String packageName) { - mHandler.sendMessage(mHandler.obtainMessage(START_CLEANING_PACKAGE, packageName)); + void schedulePackageCleaning(String packageName, int userId, boolean andCode) { + if (false) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.d(TAG, "Schedule cleaning " + packageName + " user=" + userId + + " andCode=" + andCode, here); + } + mHandler.sendMessage(mHandler.obtainMessage(START_CLEANING_PACKAGE, + userId, andCode ? 1 : 0, packageName)); } - void startCleaningPackages() { + void startCleaningPackages(int lastUser) { // reader + int nextUser = -1; synchronized (mPackages) { if (!isExternalMediaAvailable()) { return; } - if (mSettings.mPackagesToBeCleaned.size() <= 0) { + final int N = mSettings.mPackagesToBeCleaned.size(); + if (N <= 0) { return; } + for (int i=0; i<N; i++) { + int user = mSettings.mPackagesToBeCleaned.keyAt(i); + if (user > lastUser) { + nextUser = user; + break; + } + } + if (nextUser < 0) { + nextUser = mSettings.mPackagesToBeCleaned.keyAt(0); + } } Intent intent = new Intent(PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE); intent.setComponent(DEFAULT_CONTAINER_COMPONENT); IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { try { - am.startService(null, intent, null); + am.startService(null, intent, null, nextUser); } catch (RemoteException e) { } } @@ -5195,9 +5512,11 @@ public class PackageManagerService extends IPackageManager.Stub { public void onEvent(int event, String path) { String removedPackage = null; - int removedUid = -1; + int removedAppId = -1; + int[] removedUsers = null; String addedPackage = null; - int addedUid = -1; + int addedAppId = -1; + int[] addedUsers = null; // TODO post a message to the handler to obtain serial ordering synchronized (mInstallLock) { @@ -5223,15 +5542,25 @@ public class PackageManagerService extends IPackageManager.Stub { return; } PackageParser.Package p = null; + PackageSetting ps = null; // reader synchronized (mPackages) { p = mAppDirs.get(fullPathStr); + if (p != null) { + ps = mSettings.mPackages.get(p.applicationInfo.packageName); + if (ps != null) { + removedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); + } else { + removedUsers = sUserManager.getUserIds(); + } + } + addedUsers = sUserManager.getUserIds(); } if ((event&REMOVE_EVENTS) != 0) { - if (p != null) { - removePackageLI(p, true); - removedPackage = p.applicationInfo.packageName; - removedUid = p.applicationInfo.uid; + if (ps != null) { + removePackageLI(ps, true); + removedPackage = ps.name; + removedAppId = ps.appId; } } @@ -5243,7 +5572,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.PARSE_CHATTY | PackageParser.PARSE_MUST_BE_APK, SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME, - System.currentTimeMillis()); + System.currentTimeMillis(), UserHandle.ALL); if (p != null) { /* * TODO this seems dangerous as the package may have @@ -5256,7 +5585,7 @@ public class PackageManagerService extends IPackageManager.Stub { p.permissions.size() > 0 ? UPDATE_PERMISSIONS_ALL : 0); } addedPackage = p.applicationInfo.packageName; - addedUid = p.applicationInfo.uid; + addedAppId = UserHandle.getAppId(p.applicationInfo.uid); } } } @@ -5269,16 +5598,16 @@ public class PackageManagerService extends IPackageManager.Stub { if (removedPackage != null) { Bundle extras = new Bundle(1); - extras.putInt(Intent.EXTRA_UID, removedUid); + extras.putInt(Intent.EXTRA_UID, removedAppId); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false); sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, - extras, null, null, UserId.USER_ALL); + extras, null, null, removedUsers); } if (addedPackage != null) { Bundle extras = new Bundle(1); - extras.putInt(Intent.EXTRA_UID, addedUid); + extras.putInt(Intent.EXTRA_UID, addedAppId); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, - extras, null, null, UserId.USER_ALL); + extras, null, null, addedUsers); } } @@ -5304,9 +5633,25 @@ public class PackageManagerService extends IPackageManager.Stub { public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); + VerificationParams verificationParams = new VerificationParams(verificationURI, null, null, + manifestDigest); + installPackageWithVerificationAndEncryption(packageURI, observer, flags, + installerPackageName, verificationParams, encryptionParams); + } + + public void installPackageWithVerificationAndEncryption(Uri packageURI, + IPackageInstallObserver observer, int flags, String installerPackageName, + VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, + null); final int uid = Binder.getCallingUid(); + UserHandle user; + if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) { + user = UserHandle.ALL; + } else { + user = new UserHandle(UserHandle.getUserId(uid)); + } final int filteredFlags; @@ -5319,14 +5664,61 @@ public class PackageManagerService extends IPackageManager.Stub { filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB; } + verificationParams.setInstallerUid(uid); + final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName, - verificationURI, manifestDigest, encryptionParams); + verificationParams, encryptionParams, user); mHandler.sendMessage(msg); } + /** + * @hide + */ + @Override + public int installExistingPackage(String packageName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, + null); + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(uid); + + long callingId = Binder.clearCallingIdentity(); + try { + boolean sendAdded = false; + Bundle extras = new Bundle(1); + + // writer + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting == null) { + return PackageManager.INSTALL_FAILED_INVALID_URI; + } + if (!pkgSetting.getInstalled(userId)) { + pkgSetting.setInstalled(true, userId); + mSettings.writePackageRestrictionsLPr(userId); + extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId)); + sendAdded = true; + } + } + + if (sendAdded) { + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, + packageName, extras, null, null, new int[] {userId}); + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + + return PackageManager.INSTALL_SUCCEEDED; + } + @Override public void verifyPendingInstall(int id, int verificationCode) throws RemoteException { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, + "Only package verification agents can verify applications"); + final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED); final PackageVerificationResponse response = new PackageVerificationResponse( verificationCode, Binder.getCallingUid()); @@ -5335,6 +5727,49 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + @Override + public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, + long millisecondsToDelay) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, + "Only package verification agents can extend verification timeouts"); + + final PackageVerificationState state = mPendingVerification.get(id); + final PackageVerificationResponse response = new PackageVerificationResponse( + verificationCodeAtTimeout, Binder.getCallingUid()); + + if (millisecondsToDelay > PackageManager.MAXIMUM_VERIFICATION_TIMEOUT) { + millisecondsToDelay = PackageManager.MAXIMUM_VERIFICATION_TIMEOUT; + } + if (millisecondsToDelay < 0) { + millisecondsToDelay = 0; + } + if ((verificationCodeAtTimeout != PackageManager.VERIFICATION_ALLOW) + && (verificationCodeAtTimeout != PackageManager.VERIFICATION_REJECT)) { + verificationCodeAtTimeout = PackageManager.VERIFICATION_REJECT; + } + + if ((state != null) && !state.timeoutExtended()) { + state.extendTimeout(); + + final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED); + msg.arg1 = id; + msg.obj = response; + mHandler.sendMessageDelayed(msg, millisecondsToDelay); + } + } + + private void broadcastPackageVerified(int verificationId, Uri packageUri, + int verificationCode) { + final Intent intent = new Intent(Intent.ACTION_PACKAGE_VERIFIED); + intent.setDataAndType(packageUri, PACKAGE_MIME_TYPE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId); + intent.putExtra(PackageManager.EXTRA_VERIFICATION_RESULT, verificationCode); + + mContext.sendBroadcast(intent, android.Manifest.permission.PACKAGE_VERIFICATION_AGENT); + } + private ComponentName matchComponentForVerifier(String packageName, List<ResolveInfo> receivers) { ActivityInfo targetReceiver = null; @@ -5447,20 +5882,45 @@ public class PackageManagerService extends IPackageManager.Stub { * @return verification timeout in milliseconds */ private long getVerificationTimeout() { - return android.provider.Settings.Secure.getLong(mContext.getContentResolver(), - android.provider.Settings.Secure.PACKAGE_VERIFIER_TIMEOUT, + return android.provider.Settings.Global.getLong(mContext.getContentResolver(), + android.provider.Settings.Global.PACKAGE_VERIFIER_TIMEOUT, DEFAULT_VERIFICATION_TIMEOUT); } /** + * Get the default verification agent response code. + * + * @return default verification response code + */ + private int getDefaultVerificationResponse() { + return android.provider.Settings.Global.getInt(mContext.getContentResolver(), + android.provider.Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE, + DEFAULT_VERIFICATION_RESPONSE); + } + + /** * Check whether or not package verification has been enabled. * * @return true if verification should be performed */ private boolean isVerificationEnabled() { + if (!DEFAULT_VERIFY_ENABLE) { + return false; + } + + return android.provider.Settings.Global.getInt(mContext.getContentResolver(), + android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 1; + } + + /** + * Get the "allow unknown sources" setting. + * + * @return the current "allow unknown sources" setting + */ + private int getUnknownSourcesSettings() { return android.provider.Settings.Secure.getInt(mContext.getContentResolver(), - android.provider.Settings.Secure.PACKAGE_VERIFIER_ENABLE, - DEFAULT_VERIFY_ENABLE ? 1 : 0) == 1 ? true : false; + android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS, + -1); } public void setInstallerPackageName(String targetPackage, String installerPackageName) { @@ -5615,6 +6075,17 @@ public class PackageManagerService extends IPackageManager.Stub { */ private int mRetries = 0; + /** User handle for the user requesting the information or installation. */ + private final UserHandle mUser; + + HandlerParams(UserHandle user) { + mUser = user; + } + + UserHandle getUser() { + return mUser; + } + final boolean startCopy() { boolean res; try { @@ -5656,6 +6127,7 @@ public class PackageManagerService extends IPackageManager.Stub { private final IPackageStatsObserver mObserver; public MeasureParams(PackageStats stats, IPackageStatsObserver observer) { + super(new UserHandle(stats.userHandle)); mObserver = observer; mStats = stats; } @@ -5663,7 +6135,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override void handleStartCopy() throws RemoteException { synchronized (mInstallLock) { - mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats); + mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats); } final boolean mounted; @@ -5671,19 +6143,20 @@ public class PackageManagerService extends IPackageManager.Stub { mounted = true; } else { final String status = Environment.getExternalStorageState(); - - mounted = status.equals(Environment.MEDIA_MOUNTED) - || status.equals(Environment.MEDIA_MOUNTED_READ_ONLY); + mounted = (Environment.MEDIA_MOUNTED.equals(status) + || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status)); } if (mounted) { - final File externalCacheDir = Environment + final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle); + + final File externalCacheDir = userEnv .getExternalStorageAppCacheDirectory(mStats.packageName); final long externalCacheSize = mContainerService .calculateDirectorySize(externalCacheDir.getPath()); mStats.externalCacheSize = externalCacheSize; - final File externalDataDir = Environment + final File externalDataDir = userEnv .getExternalStorageAppDataDirectory(mStats.packageName); long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir .getPath()); @@ -5693,12 +6166,12 @@ public class PackageManagerService extends IPackageManager.Stub { } mStats.externalDataSize = externalDataSize; - final File externalMediaDir = Environment + final File externalMediaDir = userEnv .getExternalStorageAppMediaDirectory(mStats.packageName); mStats.externalMediaSize = mContainerService .calculateDirectorySize(externalMediaDir.getPath()); - final File externalObbDir = Environment + final File externalObbDir = userEnv .getExternalStorageAppObbDirectory(mStats.packageName); mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir .getPath()); @@ -5729,8 +6202,7 @@ public class PackageManagerService extends IPackageManager.Stub { private final Uri mPackageURI; final String installerPackageName; - final Uri verificationURI; - final ManifestDigest manifestDigest; + final VerificationParams verificationParams; private InstallArgs mArgs; private int mRet; private File mTempPackage; @@ -5738,17 +6210,24 @@ public class PackageManagerService extends IPackageManager.Stub { InstallParams(Uri packageURI, IPackageInstallObserver observer, int flags, - String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest, - ContainerEncryptionParams encryptionParams) { + String installerPackageName, VerificationParams verificationParams, + ContainerEncryptionParams encryptionParams, UserHandle user) { + super(user); this.mPackageURI = packageURI; this.flags = flags; this.observer = observer; this.installerPackageName = installerPackageName; - this.verificationURI = verificationURI; - this.manifestDigest = manifestDigest; + this.verificationParams = verificationParams; this.encryptionParams = encryptionParams; } + public ManifestDigest getManifestDigest() { + if (verificationParams == null) { + return null; + } + return verificationParams.getManifestDigest(); + } + private int installLocationPolicy(PackageInfoLite pkgLite, int flags) { String packageName = pkgLite.packageName; int installLocation = pkgLite.installLocation; @@ -5758,6 +6237,16 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Package pkg = mPackages.get(packageName); if (pkg != null) { if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { + // Check for downgrading. + if ((flags & PackageManager.INSTALL_ALLOW_DOWNGRADE) == 0) { + if (pkgLite.versionCode < pkg.mVersionCode) { + Slog.w(TAG, "Can't install update of " + packageName + + " update version " + pkgLite.versionCode + + " is older than installed version " + + pkg.mVersionCode); + return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE; + } + } // Check for updated system application. if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { if (onSd) { @@ -5850,7 +6339,8 @@ public class PackageManagerService extends IPackageManager.Stub { packageFile = mTempPackage; FileUtils.setPermissions(packageFile.getAbsolutePath(), - FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IROTH, + FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP + | FileUtils.S_IROTH, -1, -1); } else { packageFile = null; @@ -5884,6 +6374,8 @@ public class PackageManagerService extends IPackageManager.Stub { ret = PackageManager.INSTALL_FAILED_INVALID_URI; } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) { ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE; + } else if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) { + ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; } else { // Override with defaults if needed. loc = installLocationPolicy(pkgLite, flags); @@ -5919,8 +6411,9 @@ public class PackageManagerService extends IPackageManager.Stub { verification.setDataAndType(getPackageUri(), PACKAGE_MIME_TYPE); verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - final List<ResolveInfo> receivers = queryIntentReceivers(verification, null, - PackageManager.GET_DISABLED_COMPONENTS, 0 /* TODO: Which userId? */); + final List<ResolveInfo> receivers = queryIntentReceivers(verification, + PACKAGE_MIME_TYPE, PackageManager.GET_DISABLED_COMPONENTS, + 0 /* TODO: Which userId? */); if (DEBUG_VERIFY) { Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent " @@ -5937,9 +6430,29 @@ public class PackageManagerService extends IPackageManager.Stub { verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS, flags); - if (verificationURI != null) { - verification.putExtra(PackageManager.EXTRA_VERIFICATION_URI, - verificationURI); + verification.putExtra(PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME, + pkgLite.packageName); + + verification.putExtra(PackageManager.EXTRA_VERIFICATION_VERSION_CODE, + pkgLite.versionCode); + + if (verificationParams != null) { + if (verificationParams.getVerificationURI() != null) { + verification.putExtra(PackageManager.EXTRA_VERIFICATION_URI, + verificationParams.getVerificationURI()); + } + if (verificationParams.getOriginatingURI() != null) { + verification.putExtra(Intent.EXTRA_ORIGINATING_URI, + verificationParams.getOriginatingURI()); + } + if (verificationParams.getReferrer() != null) { + verification.putExtra(Intent.EXTRA_REFERRER, + verificationParams.getReferrer()); + } + if (verificationParams.getInstallerUid() >= 0) { + verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID, + verificationParams.getInstallerUid()); + } } final PackageVerificationState verificationState = new PackageVerificationState( @@ -6018,12 +6531,12 @@ public class PackageManagerService extends IPackageManager.Stub { // will succeed. if (mArgs != null) { processPendingInstall(mArgs, mRet); - } - if (mTempPackage != null) { - if (!mTempPackage.delete()) { - Slog.w(TAG, "Couldn't delete temporary file: " - + mTempPackage.getAbsolutePath()); + if (mTempPackage != null) { + if (!mTempPackage.delete()) { + Slog.w(TAG, "Couldn't delete temporary file: " + + mTempPackage.getAbsolutePath()); + } } } } @@ -6064,7 +6577,8 @@ public class PackageManagerService extends IPackageManager.Stub { int mRet; MoveParams(InstallArgs srcArgs, IPackageMoveObserver observer, int flags, - String packageName, String dataDir, int uid) { + String packageName, String dataDir, int uid, UserHandle user) { + super(user); this.srcArgs = srcArgs; this.observer = observer; this.flags = flags; @@ -6217,14 +6731,17 @@ public class PackageManagerService extends IPackageManager.Stub { final Uri packageURI; final String installerPackageName; final ManifestDigest manifestDigest; + final UserHandle user; InstallArgs(Uri packageURI, IPackageInstallObserver observer, int flags, - String installerPackageName, ManifestDigest manifestDigest) { + String installerPackageName, ManifestDigest manifestDigest, + UserHandle user) { this.packageURI = packageURI; this.flags = flags; this.observer = observer; this.installerPackageName = installerPackageName; this.manifestDigest = manifestDigest; + this.user = user; } abstract void createCopyFile(); @@ -6275,11 +6792,12 @@ public class PackageManagerService extends IPackageManager.Stub { FileInstallArgs(InstallParams params) { super(params.getPackageUri(), params.observer, params.flags, - params.installerPackageName, params.manifestDigest); + params.installerPackageName, params.getManifestDigest(), + params.getUser()); } FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) { - super(null, null, 0, null, null); + super(null, null, 0, null, null, null); File codeFile = new File(fullCodePath); installDir = codeFile.getParentFile(); codeFileName = fullCodePath; @@ -6288,12 +6806,12 @@ public class PackageManagerService extends IPackageManager.Stub { } FileInstallArgs(Uri packageURI, String pkgName, String dataDir) { - super(packageURI, null, 0, null, null); + super(packageURI, null, 0, null, null, null); installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir; String apkName = getNextCodePath(null, pkgName, ".apk"); codeFileName = new File(installDir, apkName + ".apk").getPath(); resourceFileName = getResourcePathFromCodePath(); - libraryPath = new File(dataDir, LIB_DIR_NAME).getPath(); + libraryPath = new File(mAppLibInstallDir, pkgName).getPath(); } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { @@ -6330,6 +6848,7 @@ public class PackageManagerService extends IPackageManager.Stub { installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir; codeFileName = createTempPackageFile(installDir).getPath(); resourceFileName = getResourcePathFromCodePath(); + libraryPath = getLibraryPathFromCodePath(); created = true; } @@ -6384,6 +6903,23 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } } + + final File nativeLibraryFile = new File(getNativeLibraryPath()); + Slog.i(TAG, "Copying native libraries to " + nativeLibraryFile.getPath()); + if (nativeLibraryFile.exists()) { + NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile); + nativeLibraryFile.delete(); + } + try { + int copyRet = copyNativeLibrariesForInternalApp(codeFile, nativeLibraryFile); + if (copyRet != PackageManager.INSTALL_SUCCEEDED) { + return copyRet; + } + } catch (IOException e) { + Slog.e(TAG, "Copying native libraries failed", e); + ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + return ret; } @@ -6401,6 +6937,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { final File oldCodeFile = new File(getCodePath()); final File oldResourceFile = new File(getResourcePath()); + final File oldLibraryFile = new File(getNativeLibraryPath()); // Rename APK file based on packageName final String apkName = getNextCodePath(oldCodePath, pkgName, ".apk"); @@ -6415,7 +6952,20 @@ public class PackageManagerService extends IPackageManager.Stub { if (isFwdLocked() && !oldResourceFile.renameTo(newResFile)) { return false; } - resourceFileName = getResourcePathFromCodePath(); + resourceFileName = newResFile.getPath(); + + // Rename library path + final File newLibraryFile = new File(getLibraryPathFromCodePath()); + if (newLibraryFile.exists()) { + NativeLibraryHelper.removeNativeBinariesFromDirLI(newLibraryFile); + newLibraryFile.delete(); + } + if (!oldLibraryFile.renameTo(newLibraryFile)) { + Slog.e(TAG, "Cannot rename native library directory " + + oldLibraryFile.getPath() + " to " + newLibraryFile.getPath()); + return false; + } + libraryPath = newLibraryFile.getPath(); // Attempt to set permissions if (!setPermissions()) { @@ -6466,8 +7016,15 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private String getLibraryPathFromCodePath() { + return new File(mAppLibInstallDir, getApkName(getCodePath())).getPath(); + } + @Override String getNativeLibraryPath() { + if (libraryPath == null) { + libraryPath = getLibraryPathFromCodePath(); + } return libraryPath; } @@ -6493,6 +7050,15 @@ public class PackageManagerService extends IPackageManager.Stub { publicSourceFile.delete(); } } + + if (libraryPath != null) { + File nativeLibraryFile = new File(libraryPath); + NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile); + if (!nativeLibraryFile.delete()) { + Slog.w(TAG, "Couldn't delete native library directory " + libraryPath); + } + } + return ret; } @@ -6562,13 +7128,15 @@ public class PackageManagerService extends IPackageManager.Stub { AsecInstallArgs(InstallParams params) { super(params.getPackageUri(), params.observer, params.flags, - params.installerPackageName, params.manifestDigest); + params.installerPackageName, params.getManifestDigest(), + params.getUser()); } AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath, boolean isExternal, boolean isForwardLocked) { super(null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0) - | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), null, null); + | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), + null, null, null); // Extract cid from fullCodePath int eidx = fullCodePath.lastIndexOf("/"); String subStr1 = fullCodePath.substring(0, eidx); @@ -6579,14 +7147,16 @@ public class PackageManagerService extends IPackageManager.Stub { AsecInstallArgs(String cid, boolean isForwardLocked) { super(null, null, (isAsecExternal(cid) ? PackageManager.INSTALL_EXTERNAL : 0) - | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), null, null); + | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), + null, null, null); this.cid = cid; setCachePath(PackageHelper.getSdDir(cid)); } AsecInstallArgs(Uri packageURI, String cid, boolean isExternal, boolean isForwardLocked) { super(packageURI, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0) - | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), null, null); + | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0), + null, null, null); this.cid = cid; } @@ -6735,7 +7305,7 @@ public class PackageManagerService extends IPackageManager.Stub { final int groupOwner; final String protectedFile; if (isFwdLocked()) { - groupOwner = uid; + groupOwner = UserHandle.getSharedAppGid(uid); protectedFile = RES_FILE_NAME; } else { groupOwner = -1; @@ -6817,7 +7387,8 @@ public class PackageManagerService extends IPackageManager.Stub { int doPostCopy(int uid) { if (isFwdLocked()) { if (uid < Process.FIRST_APPLICATION_UID - || !PackageHelper.fixSdPermissions(cid, uid, RES_FILE_NAME)) { + || !PackageHelper.fixSdPermissions(cid, UserHandle.getSharedAppGid(uid), + RES_FILE_NAME)) { Slog.e(TAG, "Failed to finalize " + cid); PackageHelper.destroySdDir(cid); return PackageManager.INSTALL_FAILED_CONTAINER_ERROR; @@ -6910,6 +7481,10 @@ public class PackageManagerService extends IPackageManager.Stub { class PackageInstalledInfo { String name; int uid; + // The set of users that originally had this package installed. + int[] origUsers; + // The set of users that now have this package installed. + int[] newUsers; PackageParser.Package pkg; int returnCode; PackageRemovedInfo removedInfo; @@ -6919,14 +7494,12 @@ public class PackageManagerService extends IPackageManager.Stub { * Install a non-existing package. */ private void installNewPackageLI(PackageParser.Package pkg, - int parseFlags, - int scanMode, + int parseFlags, int scanMode, UserHandle user, String installerPackageName, PackageInstalledInfo res) { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists(); - res.name = pkgName; synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { // A package with the same name is already installed, though @@ -6949,7 +7522,7 @@ public class PackageManagerService extends IPackageManager.Stub { } mLastScanError = PackageManager.INSTALL_SUCCEEDED; PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode, - System.currentTimeMillis()); + System.currentTimeMillis(), user); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { @@ -6966,17 +7539,15 @@ public class PackageManagerService extends IPackageManager.Stub { // delete the package data and cache directories that it created in // scanPackageLocked, unless those directories existed before we even tried to // install. - deletePackageLI( - pkgName, false, - dataDirExists ? PackageManager.DONT_DELETE_DATA : 0, + deletePackageLI(pkgName, UserHandle.ALL, false, + dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0, res.removedInfo, true); } } } private void replacePackageLI(PackageParser.Package pkg, - int parseFlags, - int scanMode, + int parseFlags, int scanMode, UserHandle user, String installerPackageName, PackageInstalledInfo res) { PackageParser.Package oldPackage; @@ -6993,15 +7564,16 @@ public class PackageManagerService extends IPackageManager.Stub { } boolean sysPkg = (isSystemApp(oldPackage)); if (sysPkg) { - replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, installerPackageName, res); + replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, + user, installerPackageName, res); } else { - replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, installerPackageName, res); + replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanMode, + user, installerPackageName, res); } } private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage, - PackageParser.Package pkg, - int parseFlags, int scanMode, + PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user, String installerPackageName, PackageInstalledInfo res) { PackageParser.Package newPackage = null; String pkgName = deletedPackage.packageName; @@ -7016,7 +7588,7 @@ public class PackageManagerService extends IPackageManager.Stub { } // First delete the existing package while retaining the data directory - if (!deletePackageLI(pkgName, true, PackageManager.DONT_DELETE_DATA, + if (!deletePackageLI(pkgName, null, true, PackageManager.DELETE_KEEP_DATA, res.removedInfo, true)) { // If the existing package wasn't successfully deleted res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE; @@ -7025,7 +7597,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Successfully deleted the old package. Now proceed with re-installation mLastScanError = PackageManager.INSTALL_SUCCEEDED; newPackage = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_TIME, - System.currentTimeMillis()); + System.currentTimeMillis(), user); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { @@ -7046,8 +7618,8 @@ public class PackageManagerService extends IPackageManager.Stub { // install. if(updatedSettings) { deletePackageLI( - pkgName, true, - PackageManager.DONT_DELETE_DATA, + pkgName, null, true, + PackageManager.DELETE_KEEP_DATA, res.removedInfo, true); } // Since we failed to install the new package we need to restore the old @@ -7062,7 +7634,7 @@ public class PackageManagerService extends IPackageManager.Stub { int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME; if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode, - origUpdateTime) == null) { + origUpdateTime, null) == null) { Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade"); return; } @@ -7080,8 +7652,7 @@ public class PackageManagerService extends IPackageManager.Stub { } private void replaceSystemPackageLI(PackageParser.Package deletedPackage, - PackageParser.Package pkg, - int parseFlags, int scanMode, + PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user, String installerPackageName, PackageInstalledInfo res) { PackageParser.Package newPackage = null; boolean updatedSettings = false; @@ -7111,7 +7682,7 @@ public class PackageManagerService extends IPackageManager.Stub { res.removedInfo.uid = oldPkg.applicationInfo.uid; res.removedInfo.removedPackage = packageName; // Remove existing system package - removePackageLI(oldPkg, true); + removePackageLI(oldPkgSetting, true); // writer synchronized (mPackages) { if (!mSettings.disableSystemPackageLPw(packageName) && deletedPackage != null) { @@ -7130,7 +7701,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Successfully disabled the old package. Now proceed with re-installation mLastScanError = PackageManager.INSTALL_SUCCEEDED; pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0); + newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user); if (newPackage == null) { Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { @@ -7150,10 +7721,10 @@ public class PackageManagerService extends IPackageManager.Stub { // Re installation failed. Restore old information // Remove new pkg information if (newPackage != null) { - removePackageLI(newPackage, true); + removeInstalledPackageLI(newPackage, true); } // Add back the old system package - scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0); + scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user); // Restore the old system information in Settings synchronized(mPackages) { if (updatedSettings) { @@ -7309,6 +7880,7 @@ public class PackageManagerService extends IPackageManager.Stub { systemApp = (ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } + res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); } } @@ -7327,11 +7899,17 @@ public class PackageManagerService extends IPackageManager.Stub { setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath()); pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath(); if (replace) { - replacePackageLI(pkg, parseFlags, scanMode, + replacePackageLI(pkg, parseFlags, scanMode, args.user, installerPackageName, res); } else { - installNewPackageLI(pkg, parseFlags, scanMode, - installerPackageName,res); + installNewPackageLI(pkg, parseFlags, scanMode, args.user, + installerPackageName, res); + } + synchronized (mPackages) { + final PackageSetting ps = mSettings.mPackages.get(pkgName); + if (ps != null) { + res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); + } } } @@ -7380,17 +7958,23 @@ public class PackageManagerService extends IPackageManager.Stub { } private void deleteTempPackageFiles() { - FilenameFilter filter = new FilenameFilter() { + final FilenameFilter filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.startsWith("vmdl") && name.endsWith(".tmp"); } }; - String tmpFilesList[] = mAppInstallDir.list(filter); - if(tmpFilesList == null) { + deleteTempPackageFilesInDirectory(mAppInstallDir, filter); + deleteTempPackageFilesInDirectory(mDrmAppPrivateInstallDir, filter); + } + + private static final void deleteTempPackageFilesInDirectory(File directory, + FilenameFilter filter) { + final String[] tmpFilesList = directory.list(filter); + if (tmpFilesList == null) { return; } - for(int i = 0; i < tmpFilesList.length; i++) { - File tmpFile = new File(mAppInstallDir, tmpFilesList[i]); + for (int i = 0; i < tmpFilesList.length; i++) { + final File tmpFile = new File(directory, tmpFilesList[i]); tmpFile.delete(); } } @@ -7423,10 +8007,11 @@ public class PackageManagerService extends IPackageManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.DELETE_PACKAGES, null); // Queue up an async operation since the package deletion may take a little while. + final int uid = Binder.getCallingUid(); mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); - final int returnCode = deletePackageX(packageName, true, true, flags); + final int returnCode = deletePackageX(packageName, uid, flags); if (observer != null) { try { observer.packageDeleted(packageName, returnCode); @@ -7452,8 +8037,7 @@ public class PackageManagerService extends IPackageManager.Stub { * persisting settings for later use * sending a broadcast if necessary */ - private int deletePackageX(String packageName, boolean sendBroadCast, - boolean deleteCodeAndResources, int flags) { + private int deletePackageX(String packageName, int uid, int flags) { final PackageRemovedInfo info = new PackageRemovedInfo(); final boolean res; @@ -7468,27 +8052,30 @@ public class PackageManagerService extends IPackageManager.Stub { } synchronized (mInstallLock) { - res = deletePackageLI(packageName, deleteCodeAndResources, - flags | REMOVE_CHATTY, info, true); + res = deletePackageLI(packageName, + (flags & PackageManager.DELETE_ALL_USERS) != 0 + ? UserHandle.ALL : new UserHandle(UserHandle.getUserId(uid)), + true, flags | REMOVE_CHATTY, info, true); } - if (res && sendBroadCast) { + if (res) { boolean systemUpdate = info.isRemovedPackageSystemUpdate; - info.sendBroadcast(deleteCodeAndResources, systemUpdate); + info.sendBroadcast(true, systemUpdate); // If the removed package was a system update, the old system packaged // was re-enabled; we need to broadcast this information if (systemUpdate) { Bundle extras = new Bundle(1); - extras.putInt(Intent.EXTRA_UID, info.removedUid >= 0 ? info.removedUid : info.uid); + extras.putInt(Intent.EXTRA_UID, info.removedAppId >= 0 + ? info.removedAppId : info.uid); extras.putBoolean(Intent.EXTRA_REPLACING, true); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, null, null, UserId.USER_ALL); + extras, null, null, null); sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, - extras, null, null, UserId.USER_ALL); + extras, null, null, null); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, - null, packageName, null, UserId.USER_ALL); + null, packageName, null, null); } } // Force a gc here. @@ -7497,7 +8084,7 @@ public class PackageManagerService extends IPackageManager.Stub { // other processes clean up before deleting resources. if (info.args != null) { synchronized (mInstallLock) { - info.args.doPostDeleteLI(deleteCodeAndResources); + info.args.doPostDeleteLI(true); } } @@ -7507,29 +8094,30 @@ public class PackageManagerService extends IPackageManager.Stub { static class PackageRemovedInfo { String removedPackage; int uid = -1; - int removedUid = -1; + int removedAppId = -1; + int[] removedUsers = null; boolean isRemovedPackageSystemUpdate = false; // Clean up resources deleted packages. InstallArgs args = null; void sendBroadcast(boolean fullRemove, boolean replacing) { Bundle extras = new Bundle(1); - extras.putInt(Intent.EXTRA_UID, removedUid >= 0 ? removedUid : uid); + extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove); if (replacing) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } if (removedPackage != null) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, - extras, null, null, UserId.USER_ALL); + extras, null, null, removedUsers); if (fullRemove && !replacing) { sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, - extras, null, null, UserId.USER_ALL); + extras, null, null, removedUsers); } } - if (removedUid >= 0) { + if (removedAppId >= 0) { sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null, - UserId.getUserId(removedUid)); + removedUsers); } } } @@ -7540,37 +8128,32 @@ public class PackageManagerService extends IPackageManager.Stub { * make sure this flag is set for partially installed apps. If not its meaningless to * delete a partially installed application. */ - private void removePackageDataLI(PackageParser.Package p, PackageRemovedInfo outInfo, + private void removePackageDataLI(PackageSetting ps, PackageRemovedInfo outInfo, int flags, boolean writeSettings) { - String packageName = p.packageName; - if (outInfo != null) { - outInfo.removedPackage = packageName; - } - removePackageLI(p, (flags&REMOVE_CHATTY) != 0); + String packageName = ps.name; + removePackageLI(ps, (flags&REMOVE_CHATTY) != 0); // Retrieve object to delete permissions for shared user later on final PackageSetting deletedPs; // reader synchronized (mPackages) { deletedPs = mSettings.mPackages.get(packageName); - } - if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { - int retCode = mInstaller.remove(packageName, 0); - if (retCode < 0) { - Slog.w(TAG, "Couldn't remove app data or cache directory for package: " - + packageName + ", retcode=" + retCode); - // we don't consider this to be a failure of the core package deletion - } else { - // TODO: Kill the processes first - sUserManager.removePackageForAllUsers(packageName); + if (outInfo != null) { + outInfo.removedPackage = packageName; + outInfo.removedUsers = deletedPs != null + ? deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true) + : null; } - schedulePackageCleaning(packageName); + } + if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { + removeDataDirsLI(packageName); + schedulePackageCleaning(packageName, UserHandle.USER_ALL, true); } // writer synchronized (mPackages) { if (deletedPs != null) { - if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { + if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) { if (outInfo != null) { - outInfo.removedUid = mSettings.removePackageLPw(packageName); + outInfo.removedAppId = mSettings.removePackageLPw(packageName); } if (deletedPs != null) { updatePermissionsLPw(deletedPs.name, null, 0); @@ -7579,7 +8162,7 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids); } } - clearPackagePreferredActivitiesLPw(deletedPs.name); + clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL); } } // can downgrade to reader @@ -7593,38 +8176,32 @@ public class PackageManagerService extends IPackageManager.Stub { /* * Tries to delete system package. */ - private boolean deleteSystemPackageLI(PackageParser.Package p, + private boolean deleteSystemPackageLI(PackageSetting newPs, int flags, PackageRemovedInfo outInfo, boolean writeSettings) { - ApplicationInfo applicationInfo = p.applicationInfo; - //applicable for non-partially installed applications only - if (applicationInfo == null) { - Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo."); - return false; - } - PackageSetting ps = null; + PackageSetting disabledPs = null; // Confirm if the system package has been updated // An updated system app can be deleted. This will also have to restore // the system pkg from system partition // reader synchronized (mPackages) { - ps = mSettings.getDisabledSystemPkgLPr(p.packageName); + disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name); } - if (ps == null) { - Slog.w(TAG, "Attempt to delete unknown system package "+ p.packageName); + if (disabledPs == null) { + Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name); return false; } else { Log.i(TAG, "Deleting system pkg from data partition"); } // Delete the updated package outInfo.isRemovedPackageSystemUpdate = true; - if (ps.versionCode < p.mVersionCode) { + if (disabledPs.versionCode < newPs.versionCode) { // Delete data for downgrades - flags &= ~PackageManager.DONT_DELETE_DATA; + flags &= ~PackageManager.DELETE_KEEP_DATA; } else { // Preserve data by setting flag - flags |= PackageManager.DONT_DELETE_DATA; + flags |= PackageManager.DELETE_KEEP_DATA; } - boolean ret = deleteInstalledPackageLI(p, true, flags, outInfo, + boolean ret = deleteInstalledPackageLI(newPs, true, flags, outInfo, writeSettings); if (!ret) { return false; @@ -7632,17 +8209,18 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { // Reinstate the old system package - mSettings.enableSystemPackageLPw(p.packageName); + mSettings.enableSystemPackageLPw(newPs.name); // Remove any native libraries from the upgraded package. - NativeLibraryHelper.removeNativeBinariesLI(p.applicationInfo.nativeLibraryDir); + NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString); } // Install the system package - PackageParser.Package newPkg = scanPackageLI(ps.codePath, + PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath, PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM, - SCAN_MONITOR | SCAN_NO_PATHS, 0); + SCAN_MONITOR | SCAN_NO_PATHS, 0, null); if (newPkg == null) { - Slog.w(TAG, "Failed to restore system package:"+p.packageName+" with error:" + mLastScanError); + Slog.w(TAG, "Failed to restore system package:" + newPs.name + + " with error:" + mLastScanError); return false; } // writer @@ -7657,28 +8235,20 @@ public class PackageManagerService extends IPackageManager.Stub { return true; } - private boolean deleteInstalledPackageLI(PackageParser.Package p, + private boolean deleteInstalledPackageLI(PackageSetting ps, boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo, boolean writeSettings) { - ApplicationInfo applicationInfo = p.applicationInfo; - if (applicationInfo == null) { - Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo."); - return false; - } if (outInfo != null) { - outInfo.uid = applicationInfo.uid; + outInfo.uid = ps.appId; } // Delete package data from internal structures and also remove data if flag is set - removePackageDataLI(p, outInfo, flags, writeSettings); + removePackageDataLI(ps, outInfo, flags, writeSettings); // Delete application code and resources if (deleteCodeAndResources) { - // TODO can pick up from PackageSettings as well - int installFlags = isExternal(p) ? PackageManager.INSTALL_EXTERNAL : 0; - installFlags |= isForwardLocked(p) ? PackageManager.INSTALL_FORWARD_LOCK : 0; - outInfo.args = createInstallArgs(installFlags, applicationInfo.sourceDir, - applicationInfo.publicSourceDir, applicationInfo.nativeLibraryDir); + outInfo.args = createInstallArgs(packageFlagsToInstallFlags(ps), ps.codePathString, + ps.resourcePathString, ps.nativeLibraryPathString); } return true; } @@ -7686,54 +8256,78 @@ public class PackageManagerService extends IPackageManager.Stub { /* * This method handles package deletion in general */ - private boolean deletePackageLI(String packageName, + private boolean deletePackageLI(String packageName, UserHandle user, boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo, boolean writeSettings) { if (packageName == null) { Slog.w(TAG, "Attempt to delete null packageName."); return false; } - PackageParser.Package p; + PackageSetting ps; boolean dataOnly = false; + int removeUser = -1; + int appId = -1; synchronized (mPackages) { - p = mPackages.get(packageName); - if (p == null) { - //this retrieves partially installed apps - dataOnly = true; - PackageSetting ps = mSettings.mPackages.get(packageName); - if (ps == null) { - Slog.w(TAG, "Package named '" + packageName +"' doesn't exist."); - return false; + ps = mSettings.mPackages.get(packageName); + if (ps == null) { + Slog.w(TAG, "Package named '" + packageName + "' doesn't exist."); + return false; + } + if (!isSystemApp(ps) && user != null + && user.getIdentifier() != UserHandle.USER_ALL) { + // The caller is asking that the package only be deleted for a single + // user. To do this, we just mark its uninstalled state and delete + // its data. + ps.setUserState(user.getIdentifier(), + COMPONENT_ENABLED_STATE_DEFAULT, + false, //installed + true, //stopped + true, //notLaunched + null, null); + if (ps.isAnyInstalled(sUserManager.getUserIds())) { + // Other user still have this package installed, so all + // we need to do is clear this user's data and save that + // it is uninstalled. + removeUser = user.getIdentifier(); + appId = ps.appId; + mSettings.writePackageRestrictionsLPr(removeUser); + } else { + // We need to set it back to 'installed' so the uninstall + // broadcasts will be sent correctly. + ps.setInstalled(true, user.getIdentifier()); } - p = ps.pkg; } } - if (p == null) { - Slog.w(TAG, "Package named '" + packageName +"' doesn't exist."); - return false; + + if (removeUser >= 0) { + // From above, we determined that we are deleting this only + // for a single user. Continue the work here. + if (outInfo != null) { + outInfo.removedPackage = packageName; + outInfo.removedAppId = appId; + outInfo.removedUsers = new int[] {removeUser}; + } + mInstaller.clearUserData(packageName, removeUser); + schedulePackageCleaning(packageName, removeUser, false); + return true; } if (dataOnly) { // Delete application data first - removePackageDataLI(p, outInfo, flags, writeSettings); + removePackageDataLI(ps, outInfo, flags, writeSettings); return true; } - // At this point the package should have ApplicationInfo associated with it - if (p.applicationInfo == null) { - Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo."); - return false; - } boolean ret = false; - if (isSystemApp(p)) { - Log.i(TAG, "Removing system package:"+p.packageName); + if (isSystemApp(ps)) { + Log.i(TAG, "Removing system package:" + ps.name); // When an updated system application is deleted we delete the existing resources as well and // fall back to existing code in system partition - ret = deleteSystemPackageLI(p, flags, outInfo, writeSettings); + ret = deleteSystemPackageLI(ps, flags, outInfo, writeSettings); } else { - Log.i(TAG, "Removing non-system package:"+p.packageName); + Log.i(TAG, "Removing non-system package:" + ps.name); // Kill application pre-emptively especially for apps on sd. - killApplication(packageName, p.applicationInfo.uid); - ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo, + killApplication(packageName, ps.appId); + ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, outInfo, writeSettings); } return ret; @@ -7755,7 +8349,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void clearExternalStorageDataSync(String packageName, boolean allData) { + private void clearExternalStorageDataSync(String packageName, int userId, boolean allData) { final boolean mounted; if (Environment.isExternalStorageEmulated()) { mounted = true; @@ -7771,44 +8365,54 @@ public class PackageManagerService extends IPackageManager.Stub { } final Intent containerIntent = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT); - ClearStorageConnection conn = new ClearStorageConnection(); - if (mContext.bindService(containerIntent, conn, Context.BIND_AUTO_CREATE)) { - try { - long timeout = SystemClock.uptimeMillis() + 5000; - synchronized (conn) { - long now = SystemClock.uptimeMillis(); - while (conn.mContainerService == null && now < timeout) { - try { - conn.wait(timeout - now); - } catch (InterruptedException e) { + int[] users; + if (userId == UserHandle.USER_ALL) { + users = sUserManager.getUserIds(); + } else { + users = new int[] { userId }; + } + for (int curUser : users) { + ClearStorageConnection conn = new ClearStorageConnection(); + if (mContext.bindService(containerIntent, conn, Context.BIND_AUTO_CREATE, curUser)) { + try { + long timeout = SystemClock.uptimeMillis() + 5000; + synchronized (conn) { + long now = SystemClock.uptimeMillis(); + while (conn.mContainerService == null && now < timeout) { + try { + conn.wait(timeout - now); + } catch (InterruptedException e) { + } } } - } - if (conn.mContainerService == null) { - return; - } - final File externalCacheDir = Environment - .getExternalStorageAppCacheDirectory(packageName); - try { - conn.mContainerService.clearDirectory(externalCacheDir.toString()); - } catch (RemoteException e) { - } - if (allData) { - final File externalDataDir = Environment - .getExternalStorageAppDataDirectory(packageName); - try { - conn.mContainerService.clearDirectory(externalDataDir.toString()); - } catch (RemoteException e) { + if (conn.mContainerService == null) { + return; } - final File externalMediaDir = Environment - .getExternalStorageAppMediaDirectory(packageName); + + final UserEnvironment userEnv = new UserEnvironment(curUser); + final File externalCacheDir = userEnv + .getExternalStorageAppCacheDirectory(packageName); try { - conn.mContainerService.clearDirectory(externalMediaDir.toString()); + conn.mContainerService.clearDirectory(externalCacheDir.toString()); } catch (RemoteException e) { } + if (allData) { + final File externalDataDir = userEnv + .getExternalStorageAppDataDirectory(packageName); + try { + conn.mContainerService.clearDirectory(externalDataDir.toString()); + } catch (RemoteException e) { + } + final File externalMediaDir = userEnv + .getExternalStorageAppMediaDirectory(packageName); + try { + conn.mContainerService.clearDirectory(externalMediaDir.toString()); + } catch (RemoteException e) { + } + } + } finally { + mContext.unbindService(conn); } - } finally { - mContext.unbindService(conn); } } } @@ -7818,7 +8422,7 @@ public class PackageManagerService extends IPackageManager.Stub { final IPackageDataObserver observer, final int userId) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CLEAR_APP_USER_DATA, null); - checkValidCaller(Binder.getCallingUid(), userId); + enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "clear application data"); // Queue up an async operation since the package deletion may take a little while. mHandler.post(new Runnable() { public void run() { @@ -7827,7 +8431,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { succeeded = clearApplicationUserDataLI(packageName, userId); } - clearExternalStorageDataSync(packageName, true); + clearExternalStorageDataSync(packageName, userId, true); if (succeeded) { // invoke DeviceStorageMonitor's update method to clear any notifications DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) @@ -7893,7 +8497,7 @@ public class PackageManagerService extends IPackageManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.DELETE_CACHE_FILES, null); // Queue up an async operation since the package deletion may take a little while. - final int userId = UserId.getCallingUserId(); + final int userId = UserHandle.getCallingUserId(); mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); @@ -7901,7 +8505,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { succeded = deleteApplicationCacheFilesLI(packageName, userId); } - clearExternalStorageDataSync(packageName, false); + clearExternalStorageDataSync(packageName, userId, false); if(observer != null) { try { observer.onRemoveCompleted(packageName, succeded); @@ -7941,12 +8545,12 @@ public class PackageManagerService extends IPackageManager.Stub { return true; } - public void getPackageSizeInfo(final String packageName, + public void getPackageSizeInfo(final String packageName, int userHandle, final IPackageStatsObserver observer) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.GET_PACKAGE_SIZE, null); - PackageStats stats = new PackageStats(packageName); + PackageStats stats = new PackageStats(packageName, userHandle); /* * Queue up an async operation since the package measurement may take a @@ -7957,7 +8561,8 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } - private boolean getPackageSizeInfoLI(String packageName, PackageStats pStats) { + private boolean getPackageSizeInfoLI(String packageName, int userHandle, + PackageStats pStats) { if (packageName == null) { Slog.w(TAG, "Attempt to get size of null packageName."); return false; @@ -7994,7 +8599,7 @@ public class PackageManagerService extends IPackageManager.Stub { publicSrcDir = applicationInfo.publicSourceDir; } } - int res = mInstaller.getSizeInfo(packageName, p.mPath, publicSrcDir, + int res = mInstaller.getSizeInfo(packageName, userHandle, p.mPath, publicSrcDir, asecPath, pStats); if (res < 0) { return false; @@ -8046,26 +8651,28 @@ public class PackageManagerService extends IPackageManager.Stub { } public void addPreferredActivity(IntentFilter filter, int match, - ComponentName[] set, ComponentName activity) { + ComponentName[] set, ComponentName activity, int userId) { // writer + int callingUid = Binder.getCallingUid(); + enforceCrossUserPermission(callingUid, userId, true, "add preferred activity"); synchronized (mPackages) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) != PackageManager.PERMISSION_GRANTED) { - if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid()) + if (getUidTargetSdkVersionLockedLPr(callingUid) < Build.VERSION_CODES.FROYO) { Slog.w(TAG, "Ignoring addPreferredActivity() from uid " - + Binder.getCallingUid()); + + callingUid); return; } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null); } - - Slog.i(TAG, "Adding preferred activity " + activity + ":"); + + Slog.i(TAG, "Adding preferred activity " + activity + " for user " + userId + " :"); filter.dump(new LogPrinter(Log.INFO, TAG), " "); mSettings.mPreferredActivities.addFilter( - new PreferredActivity(filter, match, set, activity)); + new PreferredActivity(filter, match, set, activity, userId)); scheduleWriteSettingsLocked(); } } @@ -8101,13 +8708,15 @@ public class PackageManagerService extends IPackageManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null); } - + + final int callingUserId = UserHandle.getCallingUserId(); ArrayList<PreferredActivity> removed = null; Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); String action = filter.getAction(0); String category = filter.getCategory(0); while (it.hasNext()) { PreferredActivity pa = it.next(); + if (pa.mUserId != callingUserId) continue; if (pa.getAction(0).equals(action) && pa.getCategory(0).equals(category)) { if (removed == null) { removed = new ArrayList<PreferredActivity>(); @@ -8123,7 +8732,7 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings.mPreferredActivities.removeFilter(pa); } } - addPreferredActivity(filter, match, set, activity); + addPreferredActivity(filter, match, set, activity, callingUserId); } } @@ -8147,17 +8756,21 @@ public class PackageManagerService extends IPackageManager.Stub { } } - if (clearPackagePreferredActivitiesLPw(packageName)) { + if (clearPackagePreferredActivitiesLPw(packageName, UserHandle.getCallingUserId())) { scheduleWriteSettingsLocked(); } } } - boolean clearPackagePreferredActivitiesLPw(String packageName) { + /** This method takes a specific user id as well as UserHandle.USER_ALL. */ + boolean clearPackagePreferredActivitiesLPw(String packageName, int userId) { ArrayList<PreferredActivity> removed = null; Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); while (it.hasNext()) { PreferredActivity pa = it.next(); + if (userId != UserHandle.USER_ALL && pa.mUserId != userId) { + continue; + } if (pa.mPref.mComponent.getPackageName().equals(packageName)) { if (removed == null) { removed = new ArrayList<PreferredActivity>(); @@ -8179,11 +8792,15 @@ public class PackageManagerService extends IPackageManager.Stub { List<ComponentName> outActivities, String packageName) { int num = 0; + final int userId = UserHandle.getCallingUserId(); // reader synchronized (mPackages) { final Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); while (it.hasNext()) { final PreferredActivity pa = it.next(); + if (pa.mUserId != userId) { + continue; + } if (packageName == null || pa.mPref.mComponent.getPackageName().equals(packageName)) { if (outFilters != null) { @@ -8227,7 +8844,7 @@ public class PackageManagerService extends IPackageManager.Stub { final int uid = Binder.getCallingUid(); final int permission = mContext.checkCallingPermission( android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); - checkValidCaller(uid, userId); + enforceCrossUserPermission(uid, userId, false, "set enabled"); final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); boolean sendNow = false; boolean isApp = (className == null); @@ -8248,7 +8865,7 @@ public class PackageManagerService extends IPackageManager.Stub { + "/" + className); } // Allow root and verify that userId is not being specified by a different user - if (!allowedByPermission && !UserId.isSameApp(uid, pkgSetting.appId)) { + if (!allowedByPermission && !UserHandle.isSameApp(uid, pkgSetting.appId)) { throw new SecurityException( "Permission Denial: attempt to change component state from pid=" + Binder.getCallingPid() @@ -8297,7 +8914,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } mSettings.writePackageRestrictionsLPr(userId); - packageUid = UserId.getUid(userId, pkgSetting.appId); + packageUid = UserHandle.getUid(userId, pkgSetting.appId); components = mPendingBroadcasts.get(packageName); final boolean newPackage = components == null; if (newPackage) { @@ -8346,7 +8963,7 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag); extras.putInt(Intent.EXTRA_UID, packageUid); sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null, - UserId.getUserId(packageUid)); + new int[] {UserHandle.getUserId(packageUid)}); } public void setPackageStoppedState(String packageName, boolean stopped, int userId) { @@ -8355,7 +8972,7 @@ public class PackageManagerService extends IPackageManager.Stub { final int permission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); - checkValidCaller(uid, userId); + enforceCrossUserPermission(uid, userId, true, "stop package"); // writer synchronized (mPackages) { if (mSettings.setPackageStoppedStateLPw(packageName, stopped, allowedByPermission, @@ -8376,7 +8993,7 @@ public class PackageManagerService extends IPackageManager.Stub { public int getApplicationEnabledSetting(String packageName, int userId) { if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED; int uid = Binder.getCallingUid(); - checkValidCaller(uid, userId); + enforceCrossUserPermission(uid, userId, false, "get enabled"); // reader synchronized (mPackages) { return mSettings.getApplicationEnabledSettingLPr(packageName, userId); @@ -8387,7 +9004,7 @@ public class PackageManagerService extends IPackageManager.Stub { public int getComponentEnabledSetting(ComponentName componentName, int userId) { if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED; int uid = Binder.getCallingUid(); - checkValidCaller(uid, userId); + enforceCrossUserPermission(uid, userId, false, "get component enabled"); // reader synchronized (mPackages) { return mSettings.getComponentEnabledSettingLPr(componentName, userId); @@ -8951,7 +9568,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); loadMediaPackages(processCids, uidArr, removeCids); - startCleaningPackages(); + startCleaningPackages(-1); } else { if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); @@ -8972,7 +9589,7 @@ public class PackageManagerService extends IPackageManager.Stub { } String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; - sendPackageBroadcast(action, null, extras, null, finishedReceiver, UserId.USER_ALL); + sendPackageBroadcast(action, null, extras, null, finishedReceiver, null); } } @@ -9017,7 +9634,7 @@ public class PackageManagerService extends IPackageManager.Stub { doGc = true; synchronized (mInstallLock) { final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags, - 0, 0); + 0, 0, null); // Scan the package if (pkg != null) { /* @@ -9126,8 +9743,8 @@ public class PackageManagerService extends IPackageManager.Stub { // Delete package internally PackageRemovedInfo outInfo = new PackageRemovedInfo(); synchronized (mInstallLock) { - boolean res = deletePackageLI(pkgName, false, PackageManager.DONT_DELETE_DATA, - outInfo, false); + boolean res = deletePackageLI(pkgName, null, false, + PackageManager.DELETE_KEEP_DATA, outInfo, false); if (res) { pkgList.add(pkgName); } else { @@ -9151,7 +9768,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkgList.size() > 0) { sendResourcesChangedBroadcast(false, pkgList, uidArr, new IIntentReceiver.Stub() { public void performReceive(Intent intent, int resultCode, String data, - Bundle extras, boolean ordered, boolean sticky) throws RemoteException { + Bundle extras, boolean ordered, boolean sticky, + int sendingUser) throws RemoteException { Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 : 0, 1, keys); mHandler.sendMessage(msg); @@ -9164,9 +9782,12 @@ public class PackageManagerService extends IPackageManager.Stub { } } + /** Binder call */ + @Override public void movePackage(final String packageName, final IPackageMoveObserver observer, final int flags) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); + UserHandle user = new UserHandle(UserHandle.getCallingUserId()); int returnCode = PackageManager.MOVE_SUCCEEDED; int currFlags = 0; int newFlags = 0; @@ -9217,14 +9838,15 @@ public class PackageManagerService extends IPackageManager.Stub { * anyway. */ if (returnCode != PackageManager.MOVE_SUCCEEDED) { - processPendingMove(new MoveParams(null, observer, 0, packageName, null, -1), + processPendingMove(new MoveParams(null, observer, 0, packageName, + null, -1, user), returnCode); } else { Message msg = mHandler.obtainMessage(INIT_COPY); InstallArgs srcArgs = createInstallArgs(currFlags, pkg.applicationInfo.sourceDir, pkg.applicationInfo.publicSourceDir, pkg.applicationInfo.nativeLibraryDir); MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, - pkg.applicationInfo.dataDir, pkg.applicationInfo.uid); + pkg.applicationInfo.dataDir, pkg.applicationInfo.uid, user); msg.obj = mp; mHandler.sendMessage(msg); } @@ -9288,31 +9910,26 @@ public class PackageManagerService extends IPackageManager.Stub { final String newNativePath = mp.targetArgs .getNativeLibraryPath(); - try { - final File newNativeDir = new File(newNativePath); + final File newNativeDir = new File(newNativePath); - final String libParentDir = newNativeDir.getParentFile() - .getCanonicalPath(); - if (newNativeDir.getParentFile().getCanonicalPath() - .equals(pkg.applicationInfo.dataDir)) { - if (mInstaller - .unlinkNativeLibraryDirectory(pkg.applicationInfo.dataDir) < 0) { + if (!isForwardLocked(pkg) && !isExternal(pkg)) { + synchronized (mInstallLock) { + if (mInstaller.linkNativeLibraryDirectory( + pkg.applicationInfo.dataDir, newNativePath) < 0) { returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; - } else { - NativeLibraryHelper.copyNativeBinariesIfNeededLI( - new File(newCodePath), newNativeDir); } - } else { + } + NativeLibraryHelper.copyNativeBinariesIfNeededLI(new File( + newCodePath), newNativeDir); + } else { + synchronized (mInstallLock) { if (mInstaller.linkNativeLibraryDirectory( pkg.applicationInfo.dataDir, newNativePath) < 0) { returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; } } - } catch (IOException e) { - returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; } - if (returnCode == PackageManager.MOVE_SUCCEEDED) { pkg.mPath = newCodePath; // Move dex files around @@ -9415,48 +10032,37 @@ public class PackageManagerService extends IPackageManager.Stub { PackageHelper.APP_INSTALL_AUTO); } - public UserInfo createUser(String name, int flags) { - // TODO(kroot): Add a real permission for creating users - enforceSystemOrRoot("Only the system can create users"); - - UserInfo userInfo = sUserManager.createUser(name, flags); - if (userInfo != null) { - Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); - addedIntent.putExtra(Intent.EXTRA_USERID, userInfo.id); - mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS); - } - return userInfo; - } - - public boolean removeUser(int userId) { - // TODO(kroot): Add a real permission for removing users - enforceSystemOrRoot("Only the system can remove users"); - - if (userId == 0 || !sUserManager.exists(userId)) { - return false; + /** Called by UserManagerService */ + void cleanUpUserLILPw(int userHandle) { + // Disable all the packages for the user first + Set<Entry<String, PackageSetting>> entries = mSettings.mPackages.entrySet(); + for (Entry<String, PackageSetting> entry : entries) { + entry.getValue().removeUser(userHandle); } - - cleanUpUser(userId); - - if (sUserManager.removeUser(userId)) { - // Let other services shutdown any activity - Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED); - addedIntent.putExtra(Intent.EXTRA_USERID, userId); - mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS); + if (mDirtyUsers.remove(userHandle)); + mSettings.removeUserLPr(userHandle); + if (mInstaller != null) { + // Technically, we shouldn't be doing this with the package lock + // held. However, this is very rare, and there is already so much + // other disk I/O going on, that we'll let it slide for now. + mInstaller.removeUserDataDirs(userHandle); } - sUserManager.removePackageFolders(userId); - return true; } - private void cleanUpUser(int userId) { - // Disable all the packages for the user first - synchronized (mPackages) { - Set<Entry<String, PackageSetting>> entries = mSettings.mPackages.entrySet(); - for (Entry<String, PackageSetting> entry : entries) { - entry.getValue().removeUser(userId); + /** Called by UserManagerService */ + void createNewUserLILPw(int userHandle, File path) { + if (mInstaller != null) { + path.mkdir(); + FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG + | FileUtils.S_IXOTH, -1, -1); + for (PackageSetting ps : mSettings.mPackages.values()) { + // Only system apps are initially installed. + ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle); + // Need to create a data directory for all apps under this user. + mInstaller.createUserData(ps.name, + UserHandle.getUid(userHandle, ps.appId), userHandle); } - if (mDirtyUsers.remove(userId)); - mSettings.removeUserLPr(userId); + mSettings.writePackageRestrictionsLPr(userHandle); } } @@ -9472,24 +10078,6 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - public List<UserInfo> getUsers() { - enforceSystemOrRoot("Only the system can query users"); - return sUserManager.getUsers(); - } - - @Override - public UserInfo getUser(int userId) { - enforceSystemOrRoot("Only the system can remove users"); - return sUserManager.getUser(userId); - } - - @Override - public void updateUserName(int userId, String name) { - enforceSystemOrRoot("Only the system can rename users"); - sUserManager.updateUserName(userId, name); - } - - @Override public void setPermissionEnforced(String permission, boolean enforced) { mContext.enforceCallingOrSelfPermission(GRANT_REVOKE_PERMISSIONS, null); if (READ_EXTERNAL_STORAGE.equals(permission)) { diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java index 56f2166..d8f7345 100644 --- a/services/java/com/android/server/pm/PackageSettingBase.java +++ b/services/java/com/android/server/pm/PackageSettingBase.java @@ -20,11 +20,13 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import android.content.pm.PackageUserState; +import android.content.pm.UserInfo; import android.util.SparseArray; -import android.util.SparseIntArray; import java.io.File; import java.util.HashSet; +import java.util.List; /** * Settings base class for pending and resolved classes. @@ -62,19 +64,11 @@ class PackageSettingBase extends GrantedPermissions { boolean permissionsFixed; boolean haveGids; + private static final PackageUserState DEFAULT_USER_STATE = new PackageUserState(); + // Whether this package is currently stopped, thus can not be // started until explicitly launched by the user. - private SparseArray<Boolean> stopped = new SparseArray<Boolean>(); - - // Set to true if we have never launched this app. - private SparseArray<Boolean> notLaunched = new SparseArray<Boolean>(); - - /* Explicitly disabled components */ - private SparseArray<HashSet<String>> disabledComponents = new SparseArray<HashSet<String>>(); - /* Explicitly enabled components */ - private SparseArray<HashSet<String>> enabledComponents = new SparseArray<HashSet<String>>(); - /* Enabled state */ - private SparseIntArray enabled = new SparseIntArray(); + private final SparseArray<PackageUserState> userState = new SparseArray<PackageUserState>(); int installStatus = PKG_INSTALL_COMPLETE; @@ -115,12 +109,11 @@ class PackageSettingBase extends GrantedPermissions { permissionsFixed = base.permissionsFixed; haveGids = base.haveGids; - notLaunched = base.notLaunched; - - disabledComponents = (SparseArray<HashSet<String>>) base.disabledComponents.clone(); - enabledComponents = (SparseArray<HashSet<String>>) base.enabledComponents.clone(); - enabled = (SparseIntArray) base.enabled.clone(); - stopped = (SparseArray<Boolean>) base.stopped.clone(); + userState.clear(); + for (int i=0; i<base.userState.size(); i++) { + userState.put(base.userState.keyAt(i), + new PackageUserState(base.userState.valueAt(i))); + } installStatus = base.installStatus; origPackage = base.origPackage; @@ -171,103 +164,174 @@ class PackageSettingBase extends GrantedPermissions { signatures = base.signatures; permissionsFixed = base.permissionsFixed; haveGids = base.haveGids; - stopped = base.stopped; - notLaunched = base.notLaunched; - disabledComponents = base.disabledComponents; - enabledComponents = base.enabledComponents; - enabled = base.enabled; + userState.clear(); + for (int i=0; i<base.userState.size(); i++) { + userState.put(base.userState.keyAt(i), base.userState.valueAt(i)); + } installStatus = base.installStatus; } + private PackageUserState modifyUserState(int userId) { + PackageUserState state = userState.get(userId); + if (state == null) { + state = new PackageUserState(); + userState.put(userId, state); + } + return state; + } + + public PackageUserState readUserState(int userId) { + PackageUserState state = userState.get(userId); + return state != null ? state : DEFAULT_USER_STATE; + } + void setEnabled(int state, int userId) { - enabled.put(userId, state); + modifyUserState(userId).enabled = state; } int getEnabled(int userId) { - return enabled.get(userId, COMPONENT_ENABLED_STATE_DEFAULT); + return readUserState(userId).enabled; + } + + void setInstalled(boolean inst, int userId) { + modifyUserState(userId).installed = inst; + } + + boolean getInstalled(int userId) { + return readUserState(userId).installed; + } + + boolean isAnyInstalled(int[] users) { + for (int user: users) { + if (readUserState(user).installed) { + return true; + } + } + return false; + } + + int[] queryInstalledUsers(int[] users, boolean installed) { + int num = 0; + for (int user : users) { + if (getInstalled(user) == installed) { + num++; + } + } + int[] res = new int[num]; + num = 0; + for (int user : users) { + if (getInstalled(user) == installed) { + res[num] = user; + num++; + } + } + return res; } boolean getStopped(int userId) { - return stopped.get(userId, false); + return readUserState(userId).stopped; } void setStopped(boolean stop, int userId) { - stopped.put(userId, stop); + modifyUserState(userId).stopped = stop; } boolean getNotLaunched(int userId) { - return notLaunched.get(userId, false); + return readUserState(userId).notLaunched; } void setNotLaunched(boolean stop, int userId) { - notLaunched.put(userId, stop); + modifyUserState(userId).notLaunched = stop; + } + + void setUserState(int userId, int enabled, boolean installed, boolean stopped, + boolean notLaunched, HashSet<String> enabledComponents, + HashSet<String> disabledComponents) { + PackageUserState state = modifyUserState(userId); + state.enabled = enabled; + state.installed = installed; + state.stopped = stopped; + state.notLaunched = notLaunched; + state.enabledComponents = enabledComponents; + state.disabledComponents = disabledComponents; } HashSet<String> getEnabledComponents(int userId) { - return getComponentHashSet(enabledComponents, userId); + return readUserState(userId).enabledComponents; } HashSet<String> getDisabledComponents(int userId) { - return getComponentHashSet(disabledComponents, userId); + return readUserState(userId).disabledComponents; } void setEnabledComponents(HashSet<String> components, int userId) { - enabledComponents.put(userId, components); + modifyUserState(userId).enabledComponents = components; } void setDisabledComponents(HashSet<String> components, int userId) { - disabledComponents.put(userId, components); + modifyUserState(userId).disabledComponents = components; + } + + void setEnabledComponentsCopy(HashSet<String> components, int userId) { + modifyUserState(userId).enabledComponents = components != null + ? new HashSet<String>(components) : null; } - private HashSet<String> getComponentHashSet(SparseArray<HashSet<String>> setArray, int userId) { - HashSet<String> set = setArray.get(userId); - if (set == null) { - set = new HashSet<String>(1); - setArray.put(userId, set); + void setDisabledComponentsCopy(HashSet<String> components, int userId) { + modifyUserState(userId).disabledComponents = components != null + ? new HashSet<String>(components) : null; + } + + PackageUserState modifyUserStateComponents(int userId, boolean disabled, boolean enabled) { + PackageUserState state = modifyUserState(userId); + if (disabled && state.disabledComponents == null) { + state.disabledComponents = new HashSet<String>(1); + } + if (enabled && state.enabledComponents == null) { + state.enabledComponents = new HashSet<String>(1); } - return set; + return state; } void addDisabledComponent(String componentClassName, int userId) { - HashSet<String> disabled = getComponentHashSet(disabledComponents, userId); - disabled.add(componentClassName); + modifyUserStateComponents(userId, true, false).disabledComponents.add(componentClassName); } void addEnabledComponent(String componentClassName, int userId) { - HashSet<String> enabled = getComponentHashSet(enabledComponents, userId); - enabled.add(componentClassName); + modifyUserStateComponents(userId, false, true).enabledComponents.add(componentClassName); } boolean enableComponentLPw(String componentClassName, int userId) { - HashSet<String> disabled = getComponentHashSet(disabledComponents, userId); - HashSet<String> enabled = getComponentHashSet(enabledComponents, userId); - boolean changed = disabled.remove(componentClassName); - changed |= enabled.add(componentClassName); + PackageUserState state = modifyUserStateComponents(userId, false, true); + boolean changed = state.disabledComponents != null + ? state.disabledComponents.remove(componentClassName) : false; + changed |= state.enabledComponents.add(componentClassName); return changed; } boolean disableComponentLPw(String componentClassName, int userId) { - HashSet<String> disabled = getComponentHashSet(disabledComponents, userId); - HashSet<String> enabled = getComponentHashSet(enabledComponents, userId); - boolean changed = enabled.remove(componentClassName); - changed |= disabled.add(componentClassName); + PackageUserState state = modifyUserStateComponents(userId, true, false); + boolean changed = state.enabledComponents != null + ? state.enabledComponents.remove(componentClassName) : false; + changed |= state.disabledComponents.add(componentClassName); return changed; } boolean restoreComponentLPw(String componentClassName, int userId) { - HashSet<String> disabled = getComponentHashSet(disabledComponents, userId); - HashSet<String> enabled = getComponentHashSet(enabledComponents, userId); - boolean changed = enabled.remove(componentClassName); - changed |= disabled.remove(componentClassName); + PackageUserState state = modifyUserStateComponents(userId, true, true); + boolean changed = state.disabledComponents != null + ? state.disabledComponents.remove(componentClassName) : false; + changed |= state.enabledComponents != null + ? state.enabledComponents.remove(componentClassName) : false; return changed; } int getCurrentEnabledStateLPr(String componentName, int userId) { - HashSet<String> disabled = getComponentHashSet(disabledComponents, userId); - HashSet<String> enabled = getComponentHashSet(enabledComponents, userId); - if (enabled.contains(componentName)) { + PackageUserState state = readUserState(userId); + if (state.enabledComponents != null && state.enabledComponents.contains(componentName)) { return COMPONENT_ENABLED_STATE_ENABLED; - } else if (disabled.contains(componentName)) { + } else if (state.disabledComponents != null + && state.disabledComponents.contains(componentName)) { return COMPONENT_ENABLED_STATE_DISABLED; } else { return COMPONENT_ENABLED_STATE_DEFAULT; @@ -275,11 +339,6 @@ class PackageSettingBase extends GrantedPermissions { } void removeUser(int userId) { - enabled.delete(userId); - stopped.delete(userId); - enabledComponents.delete(userId); - disabledComponents.delete(userId); - notLaunched.delete(userId); + userState.delete(userId); } - } diff --git a/services/java/com/android/server/pm/PackageVerificationState.java b/services/java/com/android/server/pm/PackageVerificationState.java index e5b89c1..3214e88 100644 --- a/services/java/com/android/server/pm/PackageVerificationState.java +++ b/services/java/com/android/server/pm/PackageVerificationState.java @@ -43,6 +43,8 @@ class PackageVerificationState { private boolean mRequiredVerificationPassed; + private boolean mExtendedTimeout; + /** * Create a new package verification state where {@code requiredVerifierUid} * is the user ID for the package that must reply affirmative before things @@ -55,6 +57,7 @@ class PackageVerificationState { mRequiredVerifierUid = requiredVerifierUid; mArgs = args; mSufficientVerifierUids = new SparseBooleanArray(); + mExtendedTimeout = false; } public InstallArgs getInstallArgs() { @@ -146,4 +149,22 @@ class PackageVerificationState { return true; } + + /** + * Extend the timeout for this Package to be verified. + */ + public void extendTimeout() { + if (!mExtendedTimeout) { + mExtendedTimeout = true; + } + } + + /** + * Returns whether the timeout was extended for verification. + * + * @return {@code true} if a timeout was already extended. + */ + public boolean timeoutExtended() { + return mExtendedTimeout; + } } diff --git a/services/java/com/android/server/pm/PreferredActivity.java b/services/java/com/android/server/pm/PreferredActivity.java index b100eb1..5539e84 100644 --- a/services/java/com/android/server/pm/PreferredActivity.java +++ b/services/java/com/android/server/pm/PreferredActivity.java @@ -33,22 +33,38 @@ class PreferredActivity extends IntentFilter implements PreferredComponent.Callb private static final String TAG = "PreferredActivity"; private static final boolean DEBUG_FILTERS = false; + static final String ATTR_USER_ID = "userId"; final PreferredComponent mPref; + final int mUserId; PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + this(filter, match, set, activity, 0); + } + + PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, + int userId) { super(filter); + mUserId = userId; mPref = new PreferredComponent(this, match, set, activity); } PreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException { + String userIdString = parser.getAttributeValue(null, ATTR_USER_ID); + if (userIdString != null && userIdString.length() > 0) { + mUserId = Integer.parseInt(userIdString); + } else { + // Old format with no userId specified - assume primary user + mUserId = 0; + } mPref = new PreferredComponent(this, parser); } public void writeToXml(XmlSerializer serializer) throws IOException { + serializer.attribute(null, ATTR_USER_ID, Integer.toString(mUserId)); mPref.writeToXml(serializer); serializer.startTag(null, "filter"); - super.writeToXml(serializer); + super.writeToXml(serializer); serializer.endTag(null, "filter"); } diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 120b650..b075da3 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -32,23 +32,24 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; -import android.app.AppGlobals; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; +import android.content.pm.PackageCleanItem; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PermissionInfo; import android.content.pm.Signature; import android.content.pm.UserInfo; +import android.content.pm.PackageUserState; import android.content.pm.VerifierDeviceIdentity; import android.os.Binder; import android.os.Environment; import android.os.FileUtils; import android.os.Process; -import android.os.RemoteException; -import android.os.UserId; +import android.os.UserHandle; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -79,6 +80,7 @@ final class Settings { private static final String TAG = "PackageSettings"; private static final boolean DEBUG_STOPPED = false; + private static final boolean DEBUG_MU = false; private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage"; private static final String ATTR_ENFORCEMENT = "enforcement"; @@ -90,9 +92,12 @@ final class Settings { private static final String TAG_PACKAGE = "pkg"; private static final String ATTR_NAME = "name"; + private static final String ATTR_USER = "user"; + private static final String ATTR_CODE = "code"; private static final String ATTR_NOT_LAUNCHED = "nl"; private static final String ATTR_ENABLED = "enabled"; private static final String ATTR_STOPPED = "stopped"; + private static final String ATTR_INSTALLED = "inst"; private final File mSettingsFilename; private final File mBackupSettingsFilename; @@ -121,6 +126,10 @@ final class Settings { final IntentResolver<PreferredActivity, PreferredActivity> mPreferredActivities = new IntentResolver<PreferredActivity, PreferredActivity>() { @Override + protected PreferredActivity[] newArray(int size) { + return new PreferredActivity[size]; + } + @Override protected String packageForFilter(PreferredActivity filter) { return filter.mPref.mComponent.getPackageName(); } @@ -150,7 +159,8 @@ final class Settings { // Packages that have been uninstalled and still need their external // storage data deleted. - final ArrayList<String> mPackagesToBeCleaned = new ArrayList<String>(); + final SparseArray<ArrayList<PackageCleanItem>> mPackagesToBeCleaned + = new SparseArray<ArrayList<PackageCleanItem>>(); // Packages that have been renamed since they were first installed. // Keys are the new names of the packages, values are the original @@ -169,12 +179,15 @@ final class Settings { */ private final ArrayList<PendingPackage> mPendingPackages = new ArrayList<PendingPackage>(); + private final Context mContext; + private final File mSystemDir; - Settings() { - this(Environment.getDataDirectory()); + Settings(Context context) { + this(context, Environment.getDataDirectory()); } - Settings(File dataDir) { + Settings(Context context, File dataDir) { + mContext = context; mSystemDir = new File(dataDir, "system"); mSystemDir.mkdirs(); FileUtils.setPermissions(mSystemDir.toString(), @@ -191,10 +204,11 @@ final class Settings { PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryPathString, int pkgFlags, boolean create, boolean add) { + String nativeLibraryPathString, int pkgFlags, UserHandle user, boolean add) { final String name = pkg.packageName; PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath, - resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, create, add); + resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, + user, add); return p; } @@ -355,7 +369,8 @@ final class Settings { private PackageSetting getPackageLPw(String name, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) { + String nativeLibraryPathString, int vc, int pkgFlags, + UserHandle installUser, boolean add) { PackageSetting p = mPackages.get(name); if (p != null) { if (!p.codePath.equals(codePath)) { @@ -398,11 +413,6 @@ final class Settings { } } if (p == null) { - // Create a new PackageSettings entry. this can end up here because - // of code path mismatch or user id mismatch of an updated system partition - if (!create) { - return null; - } if (origPackage != null) { // We are consuming the data from an existing package. p = new PackageSetting(origPackage.name, name, codePath, resourcePath, @@ -436,8 +446,20 @@ final class Settings { List<UserInfo> users = getAllUsers(); if (users != null) { for (UserInfo user : users) { - p.setStopped(true, user.id); - p.setNotLaunched(true, user.id); + // By default we consider this app to be installed + // for the user if no user has been specified (which + // means to leave it at its original value, and the + // original default value is true), or we are being + // asked to install for all users, or this is the + // user we are installing for. + final boolean installed = installUser == null + || installUser.getIdentifier() == UserHandle.USER_ALL + || installUser.getIdentifier() == user.id; + p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT, + installed, + true, // stopped, + true, // notLaunched + null, null); writePackageRestrictionsLPr(user.id); } } @@ -463,12 +485,10 @@ final class Settings { if (users != null) { for (UserInfo user : users) { int userId = user.id; - p.setDisabledComponents( - new HashSet<String>(dis.getDisabledComponents(userId)), - userId); - p.setEnabledComponents( - new HashSet<String>(dis.getEnabledComponents(userId)), - userId); + p.setDisabledComponentsCopy( + dis.getDisabledComponents(userId), userId); + p.setEnabledComponentsCopy( + dis.getEnabledComponents(userId), userId); } } // Add new setting to list of user ids @@ -489,6 +509,25 @@ final class Settings { // user preferences addPackageSettingLPw(p, name, sharedUser); } + } else { + if (installUser != null) { + // The caller has explicitly specified the user they want this + // package installed for, and the package already exists. + // Make sure it conforms to the new request. + List<UserInfo> users = getAllUsers(); + if (users != null) { + for (UserInfo user : users) { + if (installUser.getIdentifier() == UserHandle.USER_ALL + || installUser.getIdentifier() == user.id) { + boolean installed = p.getInstalled(user.id); + if (!installed) { + p.setInstalled(true, user.id); + writePackageRestrictionsLPr(user.id); + } + } + } + } + } } return p; } @@ -527,6 +566,10 @@ final class Settings { if (p.signatures.mSignatures == null) { p.signatures.assignSignatures(pkg.mSignatures); } + // Update flags if needed. + if (pkg.applicationInfo.flags != p.pkgFlags) { + p.pkgFlags = pkg.applicationInfo.flags; + } // If this app defines a shared user id initialize // the shared user signatures as well. if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { @@ -704,13 +747,12 @@ final class Settings { } private File getUserPackagesStateFile(int userId) { - return new File(mSystemDir, - "users/" + userId + "/package-restrictions.xml"); + return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml"); } private File getUserPackagesStateBackupFile(int userId) { - return new File(mSystemDir, - "users/" + userId + "/package-restrictions-backup.xml"); + return new File(Environment.getUserSystemDirectory(userId), + "package-restrictions-backup.xml"); } void writeAllUsersPackageRestrictionsLPr() { @@ -735,6 +777,9 @@ final class Settings { } void readPackageRestrictionsLPr(int userId) { + if (DEBUG_MU) { + Log.i(TAG, "Reading package restrictions for user=" + userId); + } FileInputStream str = null; File userPackagesStateFile = getUserPackagesStateFile(userId); File backupFile = getUserPackagesStateBackupFile(userId); @@ -766,10 +811,14 @@ final class Settings { + "assuming all started"); // At first boot, make sure no packages are stopped. // We usually want to have third party apps initialize - // in the stopped state, but not at first boot. + // in the stopped state, but not at first boot. Also + // consider all applications to be installed. for (PackageSetting pkg : mPackages.values()) { - pkg.setStopped(false, userId); - pkg.setNotLaunched(false, userId); + pkg.setUserState(userId, COMPONENT_ENABLED_STATE_DEFAULT, + true, // installed + false, // stopped + false, // notLaunched + null, null); } return; } @@ -811,17 +860,21 @@ final class Settings { XmlUtils.skipCurrentTag(parser); continue; } - String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED); - int enabled = enabledStr == null ? COMPONENT_ENABLED_STATE_DEFAULT - : Integer.parseInt(enabledStr); - ps.setEnabled(enabled, userId); - String stoppedStr = parser.getAttributeValue(null, ATTR_STOPPED); - boolean stopped = stoppedStr == null ? false : Boolean.parseBoolean(stoppedStr); - ps.setStopped(stopped, userId); - String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED); - boolean notLaunched = stoppedStr == null ? false - : Boolean.parseBoolean(notLaunchedStr); - ps.setNotLaunched(notLaunched, userId); + final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED); + final int enabled = enabledStr == null + ? COMPONENT_ENABLED_STATE_DEFAULT : Integer.parseInt(enabledStr); + final String installedStr = parser.getAttributeValue(null, ATTR_INSTALLED); + final boolean installed = installedStr == null + ? true : Boolean.parseBoolean(installedStr); + final String stoppedStr = parser.getAttributeValue(null, ATTR_STOPPED); + final boolean stopped = stoppedStr == null + ? false : Boolean.parseBoolean(stoppedStr); + final String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED); + final boolean notLaunched = stoppedStr == null + ? false : Boolean.parseBoolean(notLaunchedStr); + + HashSet<String> enabledComponents = null; + HashSet<String> disabledComponents = null; int packageDepth = parser.getDepth(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -833,13 +886,14 @@ final class Settings { } tagName = parser.getName(); if (tagName.equals(TAG_ENABLED_COMPONENTS)) { - HashSet<String> components = readComponentsLPr(parser); - ps.setEnabledComponents(components, userId); + enabledComponents = readComponentsLPr(parser); } else if (tagName.equals(TAG_DISABLED_COMPONENTS)) { - HashSet<String> components = readComponentsLPr(parser); - ps.setDisabledComponents(components, userId); + disabledComponents = readComponentsLPr(parser); } } + + ps.setUserState(userId, enabled, installed, stopped, notLaunched, + enabledComponents, disabledComponents); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: " + parser.getName()); @@ -864,7 +918,7 @@ final class Settings { private HashSet<String> readComponentsLPr(XmlPullParser parser) throws IOException, XmlPullParserException { - HashSet<String> components = new HashSet<String>(); + HashSet<String> components = null; int type; int outerDepth = parser.getDepth(); String tagName; @@ -879,6 +933,9 @@ final class Settings { if (tagName.equals(TAG_ITEM)) { String componentName = parser.getAttributeValue(null, ATTR_NAME); if (componentName != null) { + if (components == null) { + components = new HashSet<String>(); + } components.add(componentName); } } @@ -887,6 +944,9 @@ final class Settings { } void writePackageRestrictionsLPr(int userId) { + if (DEBUG_MU) { + Log.i(TAG, "Writing package restrictions for user=" + userId); + } // Keep the old stopped packages around until we know the new ones have // been successfully written. File userPackagesStateFile = getUserPackagesStateFile(userId); @@ -921,40 +981,44 @@ final class Settings { serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS); for (final PackageSetting pkg : mPackages.values()) { - if (pkg.getStopped(userId) - || pkg.getNotLaunched(userId) - || pkg.getEnabled(userId) != COMPONENT_ENABLED_STATE_DEFAULT - || pkg.getEnabledComponents(userId).size() > 0 - || pkg.getDisabledComponents(userId).size() > 0) { + PackageUserState ustate = pkg.readUserState(userId); + if (ustate.stopped || ustate.notLaunched || !ustate.installed + || ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT + || (ustate.enabledComponents != null + && ustate.enabledComponents.size() > 0) + || (ustate.disabledComponents != null + && ustate.disabledComponents.size() > 0)) { serializer.startTag(null, TAG_PACKAGE); serializer.attribute(null, ATTR_NAME, pkg.name); - boolean stopped = pkg.getStopped(userId); - boolean notLaunched = pkg.getNotLaunched(userId); - int enabled = pkg.getEnabled(userId); - HashSet<String> enabledComponents = pkg.getEnabledComponents(userId); - HashSet<String> disabledComponents = pkg.getDisabledComponents(userId); + if (DEBUG_MU) Log.i(TAG, " pkg=" + pkg.name + ", state=" + ustate.enabled); - if (stopped) { + if (!ustate.installed) { + serializer.attribute(null, ATTR_INSTALLED, "false"); + } + if (ustate.stopped) { serializer.attribute(null, ATTR_STOPPED, "true"); } - if (notLaunched) { + if (ustate.notLaunched) { serializer.attribute(null, ATTR_NOT_LAUNCHED, "true"); } - if (enabled != COMPONENT_ENABLED_STATE_DEFAULT) { - serializer.attribute(null, ATTR_ENABLED, Integer.toString(enabled)); + if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) { + serializer.attribute(null, ATTR_ENABLED, + Integer.toString(ustate.enabled)); } - if (enabledComponents.size() > 0) { + if (ustate.enabledComponents != null + && ustate.enabledComponents.size() > 0) { serializer.startTag(null, TAG_ENABLED_COMPONENTS); - for (final String name : enabledComponents) { + for (final String name : ustate.enabledComponents) { serializer.startTag(null, TAG_ITEM); serializer.attribute(null, ATTR_NAME, name); serializer.endTag(null, TAG_ITEM); } serializer.endTag(null, TAG_ENABLED_COMPONENTS); } - if (disabledComponents.size() > 0) { + if (ustate.disabledComponents != null + && ustate.disabledComponents.size() > 0) { serializer.startTag(null, TAG_DISABLED_COMPONENTS); - for (final String name : disabledComponents) { + for (final String name : ustate.disabledComponents) { serializer.startTag(null, TAG_ITEM); serializer.attribute(null, ATTR_NAME, name); serializer.endTag(null, TAG_ITEM); @@ -1194,9 +1258,17 @@ final class Settings { if (mPackagesToBeCleaned.size() > 0) { for (int i=0; i<mPackagesToBeCleaned.size(); i++) { - serializer.startTag(null, "cleaning-package"); - serializer.attribute(null, ATTR_NAME, mPackagesToBeCleaned.get(i)); - serializer.endTag(null, "cleaning-package"); + final int userId = mPackagesToBeCleaned.keyAt(i); + final String userStr = Integer.toString(userId); + final ArrayList<PackageCleanItem> pkgs = mPackagesToBeCleaned.valueAt(i); + for (int j=0; j<pkgs.size(); j++) { + serializer.startTag(null, "cleaning-package"); + PackageCleanItem item = pkgs.get(j); + serializer.attribute(null, ATTR_NAME, item.packageName); + serializer.attribute(null, ATTR_CODE, item.andCode ? "true" : "false"); + serializer.attribute(null, ATTR_USER, userStr); + serializer.endTag(null, "cleaning-package"); + } } } @@ -1452,6 +1524,17 @@ final class Settings { return ret; } + void addPackageToCleanLPw(int userId, PackageCleanItem pkg) { + ArrayList<PackageCleanItem> pkgs = mPackagesToBeCleaned.get(userId); + if (pkgs == null) { + pkgs = new ArrayList<PackageCleanItem>(); + mPackagesToBeCleaned.put(userId, pkgs); + } + if (!pkgs.contains(pkg)) { + pkgs.add(pkg); + } + } + boolean readLPw(List<UserInfo> users) { FileInputStream str = null; if (mBackupSettingsFilename.exists()) { @@ -1529,8 +1612,21 @@ final class Settings { readDisabledSysPackageLPw(parser); } else if (tagName.equals("cleaning-package")) { String name = parser.getAttributeValue(null, ATTR_NAME); + String userStr = parser.getAttributeValue(null, ATTR_USER); + String codeStr = parser.getAttributeValue(null, ATTR_CODE); if (name != null) { - mPackagesToBeCleaned.add(name); + int user = 0; + boolean andCode = true; + try { + if (userStr != null) { + user = Integer.parseInt(userStr); + } + } catch (NumberFormatException e) { + } + if (codeStr != null) { + andCode = Boolean.parseBoolean(codeStr); + } + addPackageToCleanLPw(user, new PackageCleanItem(name, andCode)); } } else if (tagName.equals("renamed-package")) { String nname = parser.getAttributeValue(null, "new"); @@ -1580,7 +1676,24 @@ final class Settings { mReadMessages.append("Error reading: " + e.toString()); PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); Log.wtf(PackageManagerService.TAG, "Error reading package manager settings", e); + } + if (mBackupStoppedPackagesFilename.exists() + || mStoppedPackagesFilename.exists()) { + // Read old file + readStoppedLPw(); + mBackupStoppedPackagesFilename.delete(); + mStoppedPackagesFilename.delete(); + // Migrate to new file format + writePackageRestrictionsLPr(0); + } else { + if (users == null) { + readPackageRestrictionsLPr(0); + } else { + for (UserInfo user : users) { + readPackageRestrictionsLPr(user.id); + } + } } final int N = mPendingPackages.size(); @@ -1590,7 +1703,8 @@ final class Settings { if (idObj != null && idObj instanceof SharedUserSetting) { PackageSetting p = getPackageLPw(pp.name, null, pp.realName, (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, - pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, true, true); + pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, + UserHandle.ALL, true); if (p == null) { PackageManagerService.reportSettingsProblem(Log.WARN, "Unable to create application package for " + pp.name); @@ -1624,23 +1738,6 @@ final class Settings { } } - if (mBackupStoppedPackagesFilename.exists() - || mStoppedPackagesFilename.exists()) { - // Read old file - readStoppedLPw(); - mBackupStoppedPackagesFilename.delete(); - mStoppedPackagesFilename.delete(); - // Migrate to new file format - writePackageRestrictionsLPr(0); - } else { - if (users == null) { - readPackageRestrictionsLPr(0); - } else { - for (UserInfo user : users) { - readPackageRestrictionsLPr(user.id); - } - } - } mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, " + mSharedUsers.size() + " shared uids\n"); @@ -2276,6 +2373,10 @@ final class Settings { return ps; } + private String compToString(HashSet<String> cmp) { + return cmp != null ? Arrays.toString(cmp.toArray()) : "[]"; + } + boolean isEnabledLPr(ComponentInfo componentInfo, int flags, int userId) { if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { return true; @@ -2286,24 +2387,26 @@ final class Settings { Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = " + componentInfo.packageName + " componentName = " + componentInfo.name); Log.v(PackageManagerService.TAG, "enabledComponents: " - + Arrays.toString(packageSettings.getEnabledComponents(userId).toArray())); + + compToString(packageSettings.getEnabledComponents(userId))); Log.v(PackageManagerService.TAG, "disabledComponents: " - + Arrays.toString(packageSettings.getDisabledComponents(userId).toArray())); + + compToString(packageSettings.getDisabledComponents(userId))); } if (packageSettings == null) { return false; } - final int enabled = packageSettings.getEnabled(userId); - if (enabled == COMPONENT_ENABLED_STATE_DISABLED - || enabled == COMPONENT_ENABLED_STATE_DISABLED_USER + PackageUserState ustate = packageSettings.readUserState(userId); + if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED + || ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_USER || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled - && enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { + && ustate.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { return false; } - if (packageSettings.getEnabledComponents(userId).contains(componentInfo.name)) { + if (ustate.enabledComponents != null + && ustate.enabledComponents.contains(componentInfo.name)) { return true; } - if (packageSettings.getDisabledComponents(userId).contains(componentInfo.name)) { + if (ustate.disabledComponents != null + && ustate.disabledComponents.contains(componentInfo.name)) { return false; } return componentInfo.enabled; @@ -2337,7 +2440,7 @@ final class Settings { boolean setPackageStoppedStateLPw(String packageName, boolean stopped, boolean allowedByPermission, int uid, int userId) { - int appId = UserId.getAppId(uid); + int appId = UserHandle.getAppId(uid); final PackageSetting pkgSetting = mPackages.get(packageName); if (pkgSetting == null) { throw new IllegalArgumentException("Unknown package: " + packageName); @@ -2362,7 +2465,7 @@ final class Settings { if (pkgSetting.installerPackageName != null) { PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgSetting.name, null, - pkgSetting.installerPackageName, null, userId); + pkgSetting.installerPackageName, null, new int[] {userId}); } pkgSetting.setNotLaunched(false, userId); } @@ -2374,9 +2477,7 @@ final class Settings { private List<UserInfo> getAllUsers() { long id = Binder.clearCallingIdentity(); try { - return AppGlobals.getPackageManager().getUsers(); - } catch (RemoteException re) { - // Local to system process, shouldn't happen + return UserManagerService.getInstance().getUsers(); } catch (NullPointerException npe) { // packagemanager not yet initialized } finally { @@ -2413,7 +2514,6 @@ final class Settings { ApplicationInfo.FLAG_RESTORE_ANY_VERSION, "RESTORE_ANY_VERSION", ApplicationInfo.FLAG_EXTERNAL_STORAGE, "EXTERNAL_STORAGE", ApplicationInfo.FLAG_LARGE_HEAP, "LARGE_HEAP", - ApplicationInfo.FLAG_STOPPED, "STOPPED", ApplicationInfo.FLAG_FORWARD_LOCK, "FORWARD_LOCK", ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE", }; @@ -2505,8 +2605,8 @@ final class Settings { first = false; pw.print("anyDensity"); } + pw.println("]"); } - pw.println("]"); pw.print(" timeStamp="); date.setTime(ps.timeStamp); pw.println(sdf.format(date)); @@ -2520,25 +2620,31 @@ final class Settings { pw.print(" installerPackageName="); pw.println(ps.installerPackageName); } pw.print(" signatures="); pw.println(ps.signatures); - pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); - pw.print(" haveGids="); pw.println(ps.haveGids); - pw.print(" pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags)); - pw.print(" installStatus="); pw.print(ps.installStatus); + pw.print(" permissionsFixed="); pw.println(ps.permissionsFixed); + pw.print(" haveGids="); pw.println(ps.haveGids); + pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC); + pw.print(" installStatus="); pw.println(ps.installStatus); for (UserInfo user : users) { - pw.print(" User "); pw.print(user.id); pw.print(": "); + pw.print(" User "); pw.print(user.id); pw.print(": "); + pw.print(" installed="); + pw.print(ps.getInstalled(user.id)); pw.print(" stopped="); pw.print(ps.getStopped(user.id)); + pw.print(" notLaunched="); + pw.print(ps.getNotLaunched(user.id)); pw.print(" enabled="); pw.println(ps.getEnabled(user.id)); - if (ps.getDisabledComponents(user.id).size() > 0) { - pw.println(" disabledComponents:"); - for (String s : ps.getDisabledComponents(user.id)) { + HashSet<String> cmp = ps.getDisabledComponents(user.id); + if (cmp != null && cmp.size() > 0) { + pw.println(" disabledComponents:"); + for (String s : cmp) { pw.print(" "); pw.println(s); } } - if (ps.getEnabledComponents(user.id).size() > 0) { - pw.println(" enabledComponents:"); - for (String s : ps.getEnabledComponents(user.id)) { + cmp = ps.getEnabledComponents(user.id); + if (cmp != null && cmp.size() > 0) { + pw.println(" enabledComponents:"); + for (String s : cmp) { pw.print(" "); pw.println(s); } } diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java deleted file mode 100644 index 4e9e666..0000000 --- a/services/java/com/android/server/pm/UserManager.java +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm; - -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.FastXmlSerializer; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.os.Environment; -import android.os.FileUtils; -import android.os.SystemClock; -import android.os.UserId; -import android.util.Log; -import android.util.Slog; -import android.util.SparseArray; -import android.util.Xml; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -public class UserManager { - private static final String TAG_NAME = "name"; - - private static final String ATTR_FLAGS = "flags"; - - private static final String ATTR_ID = "id"; - - private static final String TAG_USERS = "users"; - - private static final String TAG_USER = "user"; - - private static final String LOG_TAG = "UserManager"; - - private static final String USER_INFO_DIR = "system" + File.separator + "users"; - private static final String USER_LIST_FILENAME = "userlist.xml"; - - private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>(); - - private final File mUsersDir; - private final File mUserListFile; - private int[] mUserIds; - - private Installer mInstaller; - private File mBaseUserPath; - - /** - * Available for testing purposes. - */ - UserManager(File dataDir, File baseUserPath) { - mUsersDir = new File(dataDir, USER_INFO_DIR); - mUsersDir.mkdirs(); - // Make zeroth user directory, for services to migrate their files to that location - File userZeroDir = new File(mUsersDir, "0"); - userZeroDir.mkdirs(); - mBaseUserPath = baseUserPath; - FileUtils.setPermissions(mUsersDir.toString(), - FileUtils.S_IRWXU|FileUtils.S_IRWXG - |FileUtils.S_IROTH|FileUtils.S_IXOTH, - -1, -1); - mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); - readUserList(); - } - - public UserManager(Installer installer, File baseUserPath) { - this(Environment.getDataDirectory(), baseUserPath); - mInstaller = installer; - } - - public List<UserInfo> getUsers() { - synchronized (mUsers) { - ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size()); - for (int i = 0; i < mUsers.size(); i++) { - users.add(mUsers.valueAt(i)); - } - return users; - } - } - - public UserInfo getUser(int userId) { - synchronized (mUsers) { - UserInfo info = mUsers.get(userId); - return info; - } - } - - public boolean exists(int userId) { - synchronized (mUsers) { - return ArrayUtils.contains(mUserIds, userId); - } - } - - public void updateUserName(int userId, String name) { - synchronized (mUsers) { - UserInfo info = mUsers.get(userId); - if (name != null && !name.equals(info.name)) { - info.name = name; - writeUserLocked(info); - } - } - } - - /** - * Returns an array of user ids. This array is cached here for quick access, so do not modify or - * cache it elsewhere. - * @return the array of user ids. - */ - int[] getUserIds() { - return mUserIds; - } - - private void readUserList() { - synchronized (mUsers) { - readUserListLocked(); - } - } - - private void readUserListLocked() { - if (!mUserListFile.exists()) { - fallbackToSingleUserLocked(); - return; - } - FileInputStream fis = null; - try { - fis = new FileInputStream(mUserListFile); - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(fis, null); - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - Slog.e(LOG_TAG, "Unable to read user list"); - fallbackToSingleUserLocked(); - return; - } - - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { - String id = parser.getAttributeValue(null, ATTR_ID); - UserInfo user = readUser(Integer.parseInt(id)); - if (user != null) { - mUsers.put(user.id, user); - } - } - } - updateUserIdsLocked(); - } catch (IOException ioe) { - fallbackToSingleUserLocked(); - } catch (XmlPullParserException pe) { - fallbackToSingleUserLocked(); - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException e) { - } - } - } - } - - private void fallbackToSingleUserLocked() { - // Create the primary user - UserInfo primary = new UserInfo(0, "Primary", - UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); - mUsers.put(0, primary); - updateUserIdsLocked(); - - writeUserListLocked(); - writeUserLocked(primary); - } - - /* - * Writes the user file in this format: - * - * <user flags="20039023" id="0"> - * <name>Primary</name> - * </user> - */ - private void writeUserLocked(UserInfo userInfo) { - FileOutputStream fos = null; - try { - final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); - fos = new FileOutputStream(mUserFile); - final BufferedOutputStream bos = new BufferedOutputStream(fos); - - // XmlSerializer serializer = XmlUtils.serializerInstance(); - final XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(bos, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, TAG_USER); - serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); - serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); - - serializer.startTag(null, TAG_NAME); - serializer.text(userInfo.name); - serializer.endTag(null, TAG_NAME); - - serializer.endTag(null, TAG_USER); - - serializer.endDocument(); - } catch (IOException ioe) { - Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException ioe) { - } - } - } - } - - /* - * Writes the user list file in this format: - * - * <users> - * <user id="0"></user> - * <user id="2"></user> - * </users> - */ - private void writeUserListLocked() { - FileOutputStream fos = null; - try { - fos = new FileOutputStream(mUserListFile); - final BufferedOutputStream bos = new BufferedOutputStream(fos); - - // XmlSerializer serializer = XmlUtils.serializerInstance(); - final XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(bos, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, TAG_USERS); - - for (int i = 0; i < mUsers.size(); i++) { - UserInfo user = mUsers.valueAt(i); - serializer.startTag(null, TAG_USER); - serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); - serializer.endTag(null, TAG_USER); - } - - serializer.endTag(null, TAG_USERS); - - serializer.endDocument(); - } catch (IOException ioe) { - Slog.e(LOG_TAG, "Error writing user list"); - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException ioe) { - } - } - } - } - - private UserInfo readUser(int id) { - int flags = 0; - String name = null; - - FileInputStream fis = null; - try { - File userFile = new File(mUsersDir, Integer.toString(id) + ".xml"); - fis = new FileInputStream(userFile); - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(fis, null); - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - Slog.e(LOG_TAG, "Unable to read user " + id); - return null; - } - - if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { - String storedId = parser.getAttributeValue(null, ATTR_ID); - if (Integer.parseInt(storedId) != id) { - Slog.e(LOG_TAG, "User id does not match the file name"); - return null; - } - String flagString = parser.getAttributeValue(null, ATTR_FLAGS); - flags = Integer.parseInt(flagString); - - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - } - if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) { - type = parser.next(); - if (type == XmlPullParser.TEXT) { - name = parser.getText(); - } - } - } - - UserInfo userInfo = new UserInfo(id, name, flags); - return userInfo; - - } catch (IOException ioe) { - } catch (XmlPullParserException pe) { - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException e) { - } - } - } - return null; - } - - public UserInfo createUser(String name, int flags) { - int userId = getNextAvailableId(); - UserInfo userInfo = new UserInfo(userId, name, flags); - File userPath = new File(mBaseUserPath, Integer.toString(userId)); - if (!createPackageFolders(userId, userPath)) { - return null; - } - synchronized (mUsers) { - mUsers.put(userId, userInfo); - writeUserListLocked(); - writeUserLocked(userInfo); - updateUserIdsLocked(); - } - return userInfo; - } - - /** - * Removes a user and all data directories created for that user. This method should be called - * after the user's processes have been terminated. - * @param id the user's id - */ - public boolean removeUser(int id) { - synchronized (mUsers) { - return removeUserLocked(id); - } - } - - private boolean removeUserLocked(int id) { - // Remove from the list - UserInfo userInfo = mUsers.get(id); - if (userInfo != null) { - // Remove this user from the list - mUsers.remove(id); - // Remove user file - File userFile = new File(mUsersDir, id + ".xml"); - userFile.delete(); - // Update the user list - writeUserListLocked(); - updateUserIdsLocked(); - return true; - } - return false; - } - - public void installPackageForAllUsers(String packageName, int uid) { - for (int userId : mUserIds) { - // Don't do it for the primary user, it will become recursive. - if (userId == 0) - continue; - mInstaller.createUserData(packageName, UserId.getUid(userId, uid), - userId); - } - } - - public void clearUserDataForAllUsers(String packageName) { - for (int userId : mUserIds) { - // Don't do it for the primary user, it will become recursive. - if (userId == 0) - continue; - mInstaller.clearUserData(packageName, userId); - } - } - - public void removePackageForAllUsers(String packageName) { - for (int userId : mUserIds) { - // Don't do it for the primary user, it will become recursive. - if (userId == 0) - continue; - mInstaller.remove(packageName, userId); - } - } - - /** - * Caches the list of user ids in an array, adjusting the array size when necessary. - */ - private void updateUserIdsLocked() { - if (mUserIds == null || mUserIds.length != mUsers.size()) { - mUserIds = new int[mUsers.size()]; - } - for (int i = 0; i < mUsers.size(); i++) { - mUserIds[i] = mUsers.keyAt(i); - } - } - - /** - * Returns the next available user id, filling in any holes in the ids. - * TODO: May not be a good idea to recycle ids, in case it results in confusion - * for data and battery stats collection, or unexpected cross-talk. - * @return - */ - private int getNextAvailableId() { - int i = 0; - while (i < Integer.MAX_VALUE) { - if (mUsers.indexOfKey(i) < 0) { - break; - } - i++; - } - return i; - } - - private boolean createPackageFolders(int id, File userPath) { - // mInstaller may not be available for unit-tests. - if (mInstaller == null) return true; - - // Create the user path - userPath.mkdir(); - FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG - | FileUtils.S_IXOTH, -1, -1); - - mInstaller.cloneUserData(0, id, false); - - return true; - } - - boolean removePackageFolders(int id) { - // mInstaller may not be available for unit-tests. - if (mInstaller == null) return true; - - mInstaller.removeUserDataDirs(id); - return true; - } -} diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java new file mode 100644 index 0000000..be86628 --- /dev/null +++ b/services/java/com/android/server/pm/UserManagerService.java @@ -0,0 +1,723 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.os.ParcelFileDescriptor.MODE_CREATE; +import static android.os.ParcelFileDescriptor.MODE_READ_WRITE; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastXmlSerializer; + +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.IStopUserCallback; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Binder; +import android.os.Environment; +import android.os.FileUtils; +import android.os.IUserManager; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +public class UserManagerService extends IUserManager.Stub { + + private static final String LOG_TAG = "UserManagerService"; + + private static final String TAG_NAME = "name"; + private static final String ATTR_FLAGS = "flags"; + private static final String ATTR_ICON_PATH = "icon"; + private static final String ATTR_ID = "id"; + private static final String ATTR_SERIAL_NO = "serialNumber"; + private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber"; + private static final String TAG_USERS = "users"; + private static final String TAG_USER = "user"; + + private static final String USER_INFO_DIR = "system" + File.separator + "users"; + private static final String USER_LIST_FILENAME = "userlist.xml"; + private static final String USER_PHOTO_FILENAME = "photo.png"; + + private final Context mContext; + private final PackageManagerService mPm; + private final Object mInstallLock; + private final Object mPackagesLock; + + private final File mUsersDir; + private final File mUserListFile; + private final File mBaseUserPath; + + private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>(); + + private final int mUserLimit; + + private int[] mUserIds; + private boolean mGuestEnabled; + private int mNextSerialNumber; + + private static UserManagerService sInstance; + + public static UserManagerService getInstance() { + synchronized (UserManagerService.class) { + return sInstance; + } + } + + /** + * Available for testing purposes. + */ + UserManagerService(File dataDir, File baseUserPath) { + this(null, null, new Object(), new Object(), dataDir, baseUserPath); + } + + /** + * Called by package manager to create the service. This is closely + * associated with the package manager, and the given lock is the + * package manager's own lock. + */ + UserManagerService(Context context, PackageManagerService pm, + Object installLock, Object packagesLock) { + this(context, pm, installLock, packagesLock, + Environment.getDataDirectory(), + new File(Environment.getDataDirectory(), "user")); + } + + /** + * Available for testing purposes. + */ + private UserManagerService(Context context, PackageManagerService pm, + Object installLock, Object packagesLock, + File dataDir, File baseUserPath) { + synchronized (UserManagerService.class) { + mContext = context; + mPm = pm; + mInstallLock = installLock; + mPackagesLock = packagesLock; + mUserLimit = mContext.getResources().getInteger( + com.android.internal.R.integer.config_multiuserMaximumUsers); + mUsersDir = new File(dataDir, USER_INFO_DIR); + mUsersDir.mkdirs(); + // Make zeroth user directory, for services to migrate their files to that location + File userZeroDir = new File(mUsersDir, "0"); + userZeroDir.mkdirs(); + mBaseUserPath = baseUserPath; + FileUtils.setPermissions(mUsersDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); + mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); + readUserList(); + sInstance = this; + } + } + + @Override + public List<UserInfo> getUsers() { + checkManageUsersPermission("query users"); + synchronized (mPackagesLock) { + ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size()); + for (int i = 0; i < mUsers.size(); i++) { + users.add(mUsers.valueAt(i)); + } + return users; + } + } + + @Override + public UserInfo getUserInfo(int userId) { + checkManageUsersPermission("query user"); + synchronized (mPackagesLock) { + return getUserInfoLocked(userId); + } + } + + /* + * Should be locked on mUsers before calling this. + */ + private UserInfo getUserInfoLocked(int userId) { + return mUsers.get(userId); + } + + public boolean exists(int userId) { + synchronized (mPackagesLock) { + return ArrayUtils.contains(mUserIds, userId); + } + } + + @Override + public void setUserName(int userId, String name) { + checkManageUsersPermission("rename users"); + synchronized (mPackagesLock) { + UserInfo info = mUsers.get(userId); + if (name != null && !name.equals(info.name)) { + info.name = name; + writeUserLocked(info); + } + } + sendUserInfoChangedBroadcast(userId); + } + + @Override + public void setUserIcon(int userId, Bitmap bitmap) { + checkManageUsersPermission("update users"); + synchronized (mPackagesLock) { + UserInfo info = mUsers.get(userId); + if (info == null) return; + writeBitmapLocked(info, bitmap); + writeUserLocked(info); + } + sendUserInfoChangedBroadcast(userId); + } + + private void sendUserInfoChangedBroadcast(int userId) { + Intent changedIntent = new Intent(Intent.ACTION_USER_INFO_CHANGED); + changedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + changedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcastAsUser(changedIntent, new UserHandle(userId)); + } + + @Override + public Bitmap getUserIcon(int userId) { + checkManageUsersPermission("read users"); + synchronized (mPackagesLock) { + UserInfo info = mUsers.get(userId); + if (info == null || info.iconPath == null) return null; + return BitmapFactory.decodeFile(info.iconPath); + } + } + + @Override + public void setGuestEnabled(boolean enable) { + checkManageUsersPermission("enable guest users"); + synchronized (mPackagesLock) { + if (mGuestEnabled != enable) { + mGuestEnabled = enable; + // Erase any guest user that currently exists + for (int i = 0; i < mUsers.size(); i++) { + UserInfo user = mUsers.valueAt(i); + if (user.isGuest()) { + if (!enable) { + removeUser(user.id); + } + return; + } + } + // No guest was found + if (enable) { + createUser("Guest", UserInfo.FLAG_GUEST); + } + } + } + } + + @Override + public boolean isGuestEnabled() { + synchronized (mPackagesLock) { + return mGuestEnabled; + } + } + + @Override + public void wipeUser(int userHandle) { + checkManageUsersPermission("wipe user"); + // TODO: + } + + public void makeInitialized(int userId) { + checkManageUsersPermission("makeInitialized"); + synchronized (mPackagesLock) { + UserInfo info = mUsers.get(userId); + if (info != null && (info.flags&UserInfo.FLAG_INITIALIZED) == 0) { + info.flags |= UserInfo.FLAG_INITIALIZED; + writeUserLocked(info); + } + } + } + + /** + * Check if we've hit the limit of how many users can be created. + */ + private boolean isUserLimitReachedLocked() { + int nUsers = mUsers.size(); + return nUsers >= mUserLimit; + } + + /** + * Enforces that only the system UID or root's UID or apps that have the + * {@link android.Manifest.permission.MANAGE_USERS MANAGE_USERS} + * permission can make certain calls to the UserManager. + * + * @param message used as message if SecurityException is thrown + * @throws SecurityException if the caller is not system or root + */ + private static final void checkManageUsersPermission(String message) { + final int uid = Binder.getCallingUid(); + if (uid != Process.SYSTEM_UID && uid != 0 + && ActivityManager.checkComponentPermission( + android.Manifest.permission.MANAGE_USERS, + uid, -1, true) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need MANAGE_USERS permission to: " + message); + } + } + + private void writeBitmapLocked(UserInfo info, Bitmap bitmap) { + try { + File dir = new File(mUsersDir, Integer.toString(info.id)); + File file = new File(dir, USER_PHOTO_FILENAME); + if (!dir.exists()) { + dir.mkdir(); + FileUtils.setPermissions( + dir.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + } + FileOutputStream os; + if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, os = new FileOutputStream(file))) { + info.iconPath = file.getAbsolutePath(); + } + try { + os.close(); + } catch (IOException ioe) { + // What the ... ! + } + } catch (FileNotFoundException e) { + Slog.w(LOG_TAG, "Error setting photo for user ", e); + } + } + + /** + * Returns an array of user ids. This array is cached here for quick access, so do not modify or + * cache it elsewhere. + * @return the array of user ids. + */ + public int[] getUserIds() { + synchronized (mPackagesLock) { + return mUserIds; + } + } + + int[] getUserIdsLPr() { + return mUserIds; + } + + private void readUserList() { + synchronized (mPackagesLock) { + readUserListLocked(); + } + } + + private void readUserListLocked() { + mGuestEnabled = false; + if (!mUserListFile.exists()) { + fallbackToSingleUserLocked(); + return; + } + FileInputStream fis = null; + AtomicFile userListFile = new AtomicFile(mUserListFile); + try { + fis = userListFile.openRead(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read user list"); + fallbackToSingleUserLocked(); + return; + } + + mNextSerialNumber = -1; + if (parser.getName().equals(TAG_USERS)) { + String lastSerialNumber = parser.getAttributeValue(null, ATTR_NEXT_SERIAL_NO); + if (lastSerialNumber != null) { + mNextSerialNumber = Integer.parseInt(lastSerialNumber); + } + } + + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { + String id = parser.getAttributeValue(null, ATTR_ID); + UserInfo user = readUser(Integer.parseInt(id)); + if (user != null) { + mUsers.put(user.id, user); + if (user.isGuest()) { + mGuestEnabled = true; + } + if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) { + mNextSerialNumber = user.id + 1; + } + } + } + } + updateUserIdsLocked(); + } catch (IOException ioe) { + fallbackToSingleUserLocked(); + } catch (XmlPullParserException pe) { + fallbackToSingleUserLocked(); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } + } + } + + private void fallbackToSingleUserLocked() { + // Create the primary user + UserInfo primary = new UserInfo(0, "Primary", null, + UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); + mUsers.put(0, primary); + updateUserIdsLocked(); + + writeUserListLocked(); + writeUserLocked(primary); + } + + /* + * Writes the user file in this format: + * + * <user flags="20039023" id="0"> + * <name>Primary</name> + * </user> + */ + private void writeUserLocked(UserInfo userInfo) { + FileOutputStream fos = null; + AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + ".xml")); + try { + fos = userFile.startWrite(); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_USER); + serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); + serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber)); + serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); + if (userInfo.iconPath != null) { + serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath); + } + + serializer.startTag(null, TAG_NAME); + serializer.text(userInfo.name); + serializer.endTag(null, TAG_NAME); + + serializer.endTag(null, TAG_USER); + + serializer.endDocument(); + userFile.finishWrite(fos); + } catch (Exception ioe) { + Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); + userFile.failWrite(fos); + } + } + + /* + * Writes the user list file in this format: + * + * <users nextSerialNumber="3"> + * <user id="0"></user> + * <user id="2"></user> + * </users> + */ + private void writeUserListLocked() { + FileOutputStream fos = null; + AtomicFile userListFile = new AtomicFile(mUserListFile); + try { + fos = userListFile.startWrite(); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_USERS); + serializer.attribute(null, ATTR_NEXT_SERIAL_NO, Integer.toString(mNextSerialNumber)); + + for (int i = 0; i < mUsers.size(); i++) { + UserInfo user = mUsers.valueAt(i); + serializer.startTag(null, TAG_USER); + serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); + serializer.endTag(null, TAG_USER); + } + + serializer.endTag(null, TAG_USERS); + + serializer.endDocument(); + userListFile.finishWrite(fos); + } catch (Exception e) { + userListFile.failWrite(fos); + Slog.e(LOG_TAG, "Error writing user list"); + } + } + + private UserInfo readUser(int id) { + int flags = 0; + int serialNumber = id; + String name = null; + String iconPath = null; + + FileInputStream fis = null; + try { + AtomicFile userFile = + new AtomicFile(new File(mUsersDir, Integer.toString(id) + ".xml")); + fis = userFile.openRead(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read user " + id); + return null; + } + + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { + String storedId = parser.getAttributeValue(null, ATTR_ID); + if (Integer.parseInt(storedId) != id) { + Slog.e(LOG_TAG, "User id does not match the file name"); + return null; + } + String serialNumberValue = parser.getAttributeValue(null, ATTR_SERIAL_NO); + if (serialNumberValue != null) { + serialNumber = Integer.parseInt(serialNumberValue); + } + String flagString = parser.getAttributeValue(null, ATTR_FLAGS); + flags = Integer.parseInt(flagString); + iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH); + + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + } + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + name = parser.getText(); + } + } + } + + UserInfo userInfo = new UserInfo(id, name, iconPath, flags); + userInfo.serialNumber = serialNumber; + return userInfo; + + } catch (IOException ioe) { + } catch (XmlPullParserException pe) { + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } + } + return null; + } + + @Override + public UserInfo createUser(String name, int flags) { + checkManageUsersPermission("Only the system can create users"); + + final long ident = Binder.clearCallingIdentity(); + final UserInfo userInfo; + try { + synchronized (mInstallLock) { + synchronized (mPackagesLock) { + if (isUserLimitReachedLocked()) return null; + int userId = getNextAvailableIdLocked(); + userInfo = new UserInfo(userId, name, null, flags); + File userPath = new File(mBaseUserPath, Integer.toString(userId)); + userInfo.serialNumber = mNextSerialNumber++; + mUsers.put(userId, userInfo); + writeUserListLocked(); + writeUserLocked(userInfo); + updateUserIdsLocked(); + mPm.createNewUserLILPw(userId, userPath); + } + } + if (userInfo != null) { + Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id); + mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, + android.Manifest.permission.MANAGE_USERS); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + return userInfo; + } + + /** + * Removes a user and all data directories created for that user. This method should be called + * after the user's processes have been terminated. + * @param id the user's id + */ + public boolean removeUser(int userHandle) { + checkManageUsersPermission("Only the system can remove users"); + final UserInfo user; + synchronized (mPackagesLock) { + user = mUsers.get(userHandle); + if (userHandle == 0 || user == null) { + return false; + } + } + + int res; + try { + res = ActivityManagerNative.getDefault().stopUser(userHandle, + new IStopUserCallback.Stub() { + @Override + public void userStopped(int userId) { + finishRemoveUser(userId); + } + @Override + public void userStopAborted(int userId) { + } + }); + } catch (RemoteException e) { + return false; + } + + return res == ActivityManager.USER_OP_SUCCESS; + } + + void finishRemoveUser(int userHandle) { + synchronized (mInstallLock) { + synchronized (mPackagesLock) { + // Cleanup package manager settings + mPm.cleanUpUserLILPw(userHandle); + + // Remove this user from the list + mUsers.remove(userHandle); + // Remove user file + AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + ".xml")); + userFile.delete(); + // Update the user list + writeUserListLocked(); + updateUserIdsLocked(); + removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle)); + } + } + + // Let other services shutdown any activity + long ident = Binder.clearCallingIdentity(); + try { + Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED); + addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle); + mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL, + android.Manifest.permission.MANAGE_USERS); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void removeDirectoryRecursive(File parent) { + if (parent.isDirectory()) { + String[] files = parent.list(); + for (String filename : files) { + File child = new File(parent, filename); + removeDirectoryRecursive(child); + } + } + parent.delete(); + } + + @Override + public int getUserSerialNumber(int userHandle) { + synchronized (mPackagesLock) { + if (!exists(userHandle)) return -1; + return getUserInfoLocked(userHandle).serialNumber; + } + } + + @Override + public int getUserHandle(int userSerialNumber) { + synchronized (mPackagesLock) { + for (int userId : mUserIds) { + if (getUserInfoLocked(userId).serialNumber == userSerialNumber) return userId; + } + // Not found + return -1; + } + } + + /** + * Caches the list of user ids in an array, adjusting the array size when necessary. + */ + private void updateUserIdsLocked() { + int[] newUsers = new int[mUsers.size()]; + for (int i = 0; i < mUsers.size(); i++) { + newUsers[i] = mUsers.keyAt(i); + } + mUserIds = newUsers; + } + + /** + * Returns the next available user id, filling in any holes in the ids. + * TODO: May not be a good idea to recycle ids, in case it results in confusion + * for data and battery stats collection, or unexpected cross-talk. + * @return + */ + private int getNextAvailableIdLocked() { + synchronized (mPackagesLock) { + int i = 10; + while (i < Integer.MAX_VALUE) { + if (mUsers.indexOfKey(i) < 0) { + break; + } + i++; + } + return i; + } + } +} diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java new file mode 100644 index 0000000..5f4a786 --- /dev/null +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -0,0 +1,1160 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import com.android.server.LightsService; +import com.android.server.TwilightService; +import com.android.server.TwilightService.TwilightState; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.SystemSensorManager; +import android.hardware.display.DisplayManager; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.text.format.DateUtils; +import android.util.FloatMath; +import android.util.Slog; +import android.util.Spline; +import android.util.TimeUtils; +import android.view.Display; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.Executor; + +/** + * Controls the power state of the display. + * + * Handles the proximity sensor, light sensor, and animations between states + * including the screen off animation. + * + * This component acts independently of the rest of the power manager service. + * In particular, it does not share any state and it only communicates + * via asynchronous callbacks to inform the power manager that something has + * changed. + * + * Everything this class does internally is serialized on its handler although + * it may be accessed by other threads from the outside. + * + * Note that the power manager service guarantees that it will hold a suspend + * blocker as long as the display is not ready. So most of the work done here + * does not need to worry about holding a suspend blocker unless it happens + * independently of the display ready signal. + * + * For debugging, you can make the electron beam and brightness animations run + * slower by changing the "animator duration scale" option in Development Settings. + */ +final class DisplayPowerController { + private static final String TAG = "DisplayPowerController"; + + private static boolean DEBUG = false; + private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false; + private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false; + + // If true, uses the electron beam on animation. + // We might want to turn this off if we cannot get a guarantee that the screen + // actually turns on and starts showing new content after the call to set the + // screen state returns. Playing the animation can also be somewhat slow. + private static final boolean USE_ELECTRON_BEAM_ON_ANIMATION = false; + + // If true, enables the use of the screen auto-brightness adjustment setting. + private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = + PowerManager.useScreenAutoBrightnessAdjustmentFeature(); + + // The maximum range of gamma adjustment possible using the screen + // auto-brightness adjustment setting. + private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f; + + // If true, enables the use of the current time as an auto-brightness adjustment. + // The basic idea here is to expand the dynamic range of auto-brightness + // when it is especially dark outside. The light sensor tends to perform + // poorly at low light levels so we compensate for it by making an + // assumption about the environment. + private static final boolean USE_TWILIGHT_ADJUSTMENT = true; + + // Specifies the maximum magnitude of the time of day adjustment. + private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f; + + // The amount of time after or before sunrise over which to start adjusting + // the gamma. We want the change to happen gradually so that it is below the + // threshold of perceptibility and so that the adjustment has maximum effect + // well after dusk. + private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2; + + private static final int ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS = 250; + private static final int ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS = 450; + + private static final int MSG_UPDATE_POWER_STATE = 1; + private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2; + private static final int MSG_LIGHT_SENSOR_DEBOUNCED = 3; + + private static final int PROXIMITY_UNKNOWN = -1; + private static final int PROXIMITY_NEGATIVE = 0; + private static final int PROXIMITY_POSITIVE = 1; + + // Proximity sensor debounce delay in milliseconds. + private static final int PROXIMITY_SENSOR_DEBOUNCE_DELAY = 250; + + // Trigger proximity if distance is less than 5 cm. + private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f; + + // Light sensor event rate in microseconds. + private static final int LIGHT_SENSOR_RATE = 1000000; + + // Brightness animation ramp rate in brightness units per second. + private static final int BRIGHTNESS_RAMP_RATE_FAST = 200; + private static final int BRIGHTNESS_RAMP_RATE_SLOW = 40; + + // Filter time constant in milliseconds for computing a moving + // average of light samples. Different constants are used + // to calculate the average light level when adapting to brighter or + // dimmer environments. + // This parameter only controls the filtering of light samples. + private static final long BRIGHTENING_LIGHT_TIME_CONSTANT = 600; + private static final long DIMMING_LIGHT_TIME_CONSTANT = 4000; + + // Stability requirements in milliseconds for accepting a new brightness + // level. This is used for debouncing the light sensor. Different constants + // are used to debounce the light sensor when adapting to brighter or dimmer + // environments. + // This parameter controls how quickly brightness changes occur in response to + // an observed change in light level. + private static final long BRIGHTENING_LIGHT_DEBOUNCE = 2500; + private static final long DIMMING_LIGHT_DEBOUNCE = 10000; + + private final Object mLock = new Object(); + + // Notifier for sending asynchronous notifications. + private final Notifier mNotifier; + + // A suspend blocker. + private final SuspendBlocker mSuspendBlocker; + + // Our handler. + private final DisplayControllerHandler mHandler; + + // Asynchronous callbacks into the power manager service. + // Only invoked from the handler thread while no locks are held. + private final Callbacks mCallbacks; + private Handler mCallbackHandler; + + // The lights service. + private final LightsService mLights; + + // The twilight service. + private final TwilightService mTwilight; + + // The display manager. + private final DisplayManager mDisplayManager; + + // The sensor manager. + private final SensorManager mSensorManager; + + // The proximity sensor, or null if not available or needed. + private Sensor mProximitySensor; + + // The light sensor, or null if not available or needed. + private Sensor mLightSensor; + + // The dim screen brightness. + private final int mScreenBrightnessDimConfig; + + // True if auto-brightness should be used. + private boolean mUseSoftwareAutoBrightnessConfig; + + // The auto-brightness spline adjustment. + // The brightness values have been scaled to a range of 0..1. + private Spline mScreenAutoBrightnessSpline; + + // Amount of time to delay auto-brightness after screen on while waiting for + // the light sensor to warm-up in milliseconds. + // May be 0 if no warm-up is required. + private int mLightSensorWarmUpTimeConfig; + + // The pending power request. + // Initially null until the first call to requestPowerState. + // Guarded by mLock. + private DisplayPowerRequest mPendingRequestLocked; + + // True if a request has been made to wait for the proximity sensor to go negative. + // Guarded by mLock. + private boolean mPendingWaitForNegativeProximityLocked; + + // True if the pending power request or wait for negative proximity flag + // has been changed since the last update occurred. + // Guarded by mLock. + private boolean mPendingRequestChangedLocked; + + // Set to true when the important parts of the pending power request have been applied. + // The important parts are mainly the screen state. Brightness changes may occur + // concurrently. + // Guarded by mLock. + private boolean mDisplayReadyLocked; + + // Set to true if a power state update is required. + // Guarded by mLock. + private boolean mPendingUpdatePowerStateLocked; + + /* The following state must only be accessed by the handler thread. */ + + // The currently requested power state. + // The power controller will progressively update its internal state to match + // the requested power state. Initially null until the first update. + private DisplayPowerRequest mPowerRequest; + + // The current power state. + // Must only be accessed on the handler thread. + private DisplayPowerState mPowerState; + + // True if the device should wait for negative proximity sensor before + // waking up the screen. This is set to false as soon as a negative + // proximity sensor measurement is observed or when the device is forced to + // go to sleep by the user. While true, the screen remains off. + private boolean mWaitingForNegativeProximity; + + // The actual proximity sensor threshold value. + private float mProximityThreshold; + + // Set to true if the proximity sensor listener has been registered + // with the sensor manager. + private boolean mProximitySensorEnabled; + + // The debounced proximity sensor state. + private int mProximity = PROXIMITY_UNKNOWN; + + // The raw non-debounced proximity sensor state. + private int mPendingProximity = PROXIMITY_UNKNOWN; + private long mPendingProximityDebounceTime; + + // True if the screen was turned off because of the proximity sensor. + // When the screen turns on again, we report user activity to the power manager. + private boolean mScreenOffBecauseOfProximity; + + // Set to true if the light sensor is enabled. + private boolean mLightSensorEnabled; + + // The time when the light sensor was enabled. + private long mLightSensorEnableTime; + + // The currently accepted average light sensor value. + private float mLightMeasurement; + + // True if the light sensor measurement is valid. + private boolean mLightMeasurementValid; + + // The number of light sensor samples that have been collected since the + // last time a light sensor reading was accepted. + private int mRecentLightSamples; + + // The moving average of recent light sensor values. + private float mRecentLightAverage; + + // True if recent light samples are getting brighter than the previous + // stable light measurement. + private boolean mRecentLightBrightening; + + // The time constant to use for filtering based on whether the + // light appears to be brightening or dimming. + private long mRecentLightTimeConstant; + + // The most recent light sample. + private float mLastLightSample; + + // The time of the most light recent sample. + private long mLastLightSampleTime; + + // The time when we accumulated the first recent light sample into mRecentLightSamples. + private long mFirstRecentLightSampleTime; + + // The upcoming debounce light sensor time. + // This is only valid when mLightMeasurementValue && mRecentLightSamples >= 1. + private long mPendingLightSensorDebounceTime; + + // The screen brightness level that has been chosen by the auto-brightness + // algorithm. The actual brightness should ramp towards this value. + // We preserve this value even when we stop using the light sensor so + // that we can quickly revert to the previous auto-brightness level + // while the light sensor warms up. + // Use -1 if there is no current auto-brightness value available. + private int mScreenAutoBrightness = -1; + + // The last screen auto-brightness gamma. (For printing in dump() only.) + private float mLastScreenAutoBrightnessGamma = 1.0f; + + // True if the screen auto-brightness value is actually being used to + // set the display brightness. + private boolean mUsingScreenAutoBrightness; + + // Animators. + private ObjectAnimator mElectronBeamOnAnimator; + private ObjectAnimator mElectronBeamOffAnimator; + private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator; + + // Twilight changed. We might recalculate auto-brightness values. + private boolean mTwilightChanged; + + /** + * Creates the display power controller. + */ + public DisplayPowerController(Looper looper, Context context, Notifier notifier, + LightsService lights, TwilightService twilight, SuspendBlocker suspendBlocker, + Callbacks callbacks, Handler callbackHandler) { + mHandler = new DisplayControllerHandler(looper); + mNotifier = notifier; + mSuspendBlocker = suspendBlocker; + mCallbacks = callbacks; + mCallbackHandler = callbackHandler; + + mLights = lights; + mTwilight = twilight; + mSensorManager = new SystemSensorManager(mHandler.getLooper()); + mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); + + final Resources resources = context.getResources(); + mScreenBrightnessDimConfig = resources.getInteger( + com.android.internal.R.integer.config_screenBrightnessDim); + mUseSoftwareAutoBrightnessConfig = resources.getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available); + if (mUseSoftwareAutoBrightnessConfig) { + int[] lux = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLevels); + int[] screenBrightness = resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); + + mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); + if (mScreenAutoBrightnessSpline == null) { + Slog.e(TAG, "Error in config.xml. config_autoBrightnessLcdBacklightValues " + + "(size " + screenBrightness.length + ") " + + "must be monotic and have exactly one more entry than " + + "config_autoBrightnessLevels (size " + lux.length + ") " + + "which must be strictly increasing. " + + "Auto-brightness will be disabled."); + mUseSoftwareAutoBrightnessConfig = false; + } + + mLightSensorWarmUpTimeConfig = resources.getInteger( + com.android.internal.R.integer.config_lightSensorWarmupTime); + } + + if (!DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) { + mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + if (mProximitySensor != null) { + mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(), + TYPICAL_PROXIMITY_THRESHOLD); + } + } + + if (mUseSoftwareAutoBrightnessConfig + && !DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { + mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + } + + if (mUseSoftwareAutoBrightnessConfig && USE_TWILIGHT_ADJUSTMENT) { + mTwilight.registerListener(mTwilightListener, mHandler); + } + } + + private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) { + try { + final int n = brightness.length; + float[] x = new float[n]; + float[] y = new float[n]; + y[0] = (float)brightness[0] / PowerManager.BRIGHTNESS_ON; + for (int i = 1; i < n; i++) { + x[i] = lux[i - 1]; + y[i] = (float)brightness[i] / PowerManager.BRIGHTNESS_ON; + } + + Spline spline = Spline.createMonotoneCubicSpline(x, y); + if (false) { + Slog.d(TAG, "Auto-brightness spline: " + spline); + for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) { + Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v))); + } + } + return spline; + } catch (IllegalArgumentException ex) { + Slog.e(TAG, "Could not create auto-brightness spline.", ex); + return null; + } + } + + /** + * Returns true if the proximity sensor screen-off function is available. + */ + public boolean isProximitySensorAvailable() { + return mProximitySensor != null; + } + + /** + * Requests a new power state. + * The controller makes a copy of the provided object and then + * begins adjusting the power state to match what was requested. + * + * @param request The requested power state. + * @param waitForNegativeProximity If true, issues a request to wait for + * negative proximity before turning the screen back on, assuming the screen + * was turned off by the proximity sensor. + * @return True if display is ready, false if there are important changes that must + * be made asynchronously (such as turning the screen on), in which case the caller + * should grab a wake lock, watch for {@link Callbacks#onStateChanged()} then try + * the request again later until the state converges. + */ + public boolean requestPowerState(DisplayPowerRequest request, + boolean waitForNegativeProximity) { + if (DEBUG) { + Slog.d(TAG, "requestPowerState: " + + request + ", waitForNegativeProximity=" + waitForNegativeProximity); + } + + synchronized (mLock) { + boolean changed = false; + + if (waitForNegativeProximity + && !mPendingWaitForNegativeProximityLocked) { + mPendingWaitForNegativeProximityLocked = true; + changed = true; + } + + if (mPendingRequestLocked == null) { + mPendingRequestLocked = new DisplayPowerRequest(request); + changed = true; + } else if (!mPendingRequestLocked.equals(request)) { + mPendingRequestLocked.copyFrom(request); + changed = true; + } + + if (changed) { + mDisplayReadyLocked = false; + } + + if (changed && !mPendingRequestChangedLocked) { + mPendingRequestChangedLocked = true; + sendUpdatePowerStateLocked(); + } + + return mDisplayReadyLocked; + } + } + + private void sendUpdatePowerState() { + synchronized (mLock) { + sendUpdatePowerStateLocked(); + } + } + + private void sendUpdatePowerStateLocked() { + if (!mPendingUpdatePowerStateLocked) { + mPendingUpdatePowerStateLocked = true; + Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + } + + private void initialize() { + final Executor executor = AsyncTask.THREAD_POOL_EXECUTOR; + Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); + mPowerState = new DisplayPowerState(new ElectronBeam(display), + new PhotonicModulator(executor, + mLights.getLight(LightsService.LIGHT_ID_BACKLIGHT), + mSuspendBlocker)); + + mElectronBeamOnAnimator = ObjectAnimator.ofFloat( + mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 0.0f, 1.0f); + mElectronBeamOnAnimator.setDuration(ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS); + mElectronBeamOnAnimator.addListener(mAnimatorListener); + + mElectronBeamOffAnimator = ObjectAnimator.ofFloat( + mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 1.0f, 0.0f); + mElectronBeamOffAnimator.setDuration(ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS); + mElectronBeamOffAnimator.addListener(mAnimatorListener); + + mScreenBrightnessRampAnimator = new RampAnimator<DisplayPowerState>( + mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS); + } + + private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + @Override + public void onAnimationEnd(Animator animation) { + sendUpdatePowerState(); + } + @Override + public void onAnimationRepeat(Animator animation) { + } + @Override + public void onAnimationCancel(Animator animation) { + } + }; + + private void updatePowerState() { + // Update the power state request. + final boolean mustNotify; + boolean mustInitialize = false; + boolean updateAutoBrightness = mTwilightChanged; + mTwilightChanged = false; + + synchronized (mLock) { + mPendingUpdatePowerStateLocked = false; + if (mPendingRequestLocked == null) { + return; // wait until first actual power request + } + + if (mPowerRequest == null) { + mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked); + mWaitingForNegativeProximity = mPendingWaitForNegativeProximityLocked; + mPendingWaitForNegativeProximityLocked = false; + mPendingRequestChangedLocked = false; + mustInitialize = true; + } else if (mPendingRequestChangedLocked) { + if (mPowerRequest.screenAutoBrightnessAdjustment + != mPendingRequestLocked.screenAutoBrightnessAdjustment) { + updateAutoBrightness = true; + } + mPowerRequest.copyFrom(mPendingRequestLocked); + mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked; + mPendingWaitForNegativeProximityLocked = false; + mPendingRequestChangedLocked = false; + mDisplayReadyLocked = false; + } + + mustNotify = !mDisplayReadyLocked; + } + + // Initialize things the first time the power state is changed. + if (mustInitialize) { + initialize(); + } + + // Apply the proximity sensor. + if (mProximitySensor != null) { + if (mPowerRequest.useProximitySensor + && mPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { + setProximitySensorEnabled(true); + if (!mScreenOffBecauseOfProximity + && mProximity == PROXIMITY_POSITIVE) { + mScreenOffBecauseOfProximity = true; + setScreenOn(false); + } + } else if (mWaitingForNegativeProximity + && mScreenOffBecauseOfProximity + && mProximity == PROXIMITY_POSITIVE + && mPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { + setProximitySensorEnabled(true); + } else { + setProximitySensorEnabled(false); + mWaitingForNegativeProximity = false; + } + if (mScreenOffBecauseOfProximity + && mProximity != PROXIMITY_POSITIVE) { + mScreenOffBecauseOfProximity = false; + setScreenOn(true); + sendOnProximityNegative(); + } + } else { + mWaitingForNegativeProximity = false; + } + + // Turn on the light sensor if needed. + if (mLightSensor != null) { + setLightSensorEnabled(mPowerRequest.useAutoBrightness + && wantScreenOn(mPowerRequest.screenState), updateAutoBrightness); + } + + // Set the screen brightness. + if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM) { + // Screen is dimmed. Overrides everything else. + animateScreenBrightness( + clampScreenBrightness(mScreenBrightnessDimConfig), + BRIGHTNESS_RAMP_RATE_FAST); + mUsingScreenAutoBrightness = false; + } else if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT) { + if (mScreenAutoBrightness >= 0 && mLightSensorEnabled) { + // Use current auto-brightness value. + animateScreenBrightness( + clampScreenBrightness(mScreenAutoBrightness), + mUsingScreenAutoBrightness ? BRIGHTNESS_RAMP_RATE_SLOW : + BRIGHTNESS_RAMP_RATE_FAST); + mUsingScreenAutoBrightness = true; + } else { + // Light sensor is disabled or not ready yet. + // Use the current brightness setting from the request, which is expected + // provide a nominal default value for the case where auto-brightness + // is not ready yet. + animateScreenBrightness( + clampScreenBrightness(mPowerRequest.screenBrightness), + BRIGHTNESS_RAMP_RATE_FAST); + mUsingScreenAutoBrightness = false; + } + } else { + // Screen is off. Don't bother changing the brightness. + mUsingScreenAutoBrightness = false; + } + + // Animate the screen on or off. + if (!mScreenOffBecauseOfProximity) { + if (wantScreenOn(mPowerRequest.screenState)) { + // Want screen on. + // Wait for previous off animation to complete beforehand. + // It is relatively short but if we cancel it and switch to the + // on animation immediately then the results are pretty ugly. + if (!mElectronBeamOffAnimator.isStarted()) { + setScreenOn(true); + if (USE_ELECTRON_BEAM_ON_ANIMATION) { + if (!mElectronBeamOnAnimator.isStarted()) { + if (mPowerState.getElectronBeamLevel() == 1.0f) { + mPowerState.dismissElectronBeam(); + } else if (mPowerState.prepareElectronBeam(true)) { + mElectronBeamOnAnimator.start(); + } else { + mElectronBeamOnAnimator.end(); + } + } + } else { + mPowerState.setElectronBeamLevel(1.0f); + mPowerState.dismissElectronBeam(); + } + } + } else { + // Want screen off. + // Wait for previous on animation to complete beforehand. + if (!mElectronBeamOnAnimator.isStarted()) { + if (!mElectronBeamOffAnimator.isStarted()) { + if (mPowerState.getElectronBeamLevel() == 0.0f) { + setScreenOn(false); + } else if (mPowerState.prepareElectronBeam(false) + && mPowerState.isScreenOn()) { + mElectronBeamOffAnimator.start(); + } else { + mElectronBeamOffAnimator.end(); + } + } + } + } + } + + // Report whether the display is ready for use. + // We mostly care about the screen state here, ignoring brightness changes + // which will be handled asynchronously. + if (mustNotify + && !mElectronBeamOnAnimator.isStarted() + && !mElectronBeamOffAnimator.isStarted() + && mPowerState.waitUntilClean(mCleanListener)) { + synchronized (mLock) { + if (!mPendingRequestChangedLocked) { + mDisplayReadyLocked = true; + } + } + sendOnStateChanged(); + } + } + + private void setScreenOn(boolean on) { + if (!mPowerState.isScreenOn() == on) { + mPowerState.setScreenOn(on); + if (on) { + mNotifier.onScreenOn(); + } else { + mNotifier.onScreenOff(); + } + } + } + + private int clampScreenBrightness(int value) { + return Math.min(Math.max(Math.max(value, mScreenBrightnessDimConfig), 0), 255); + } + + private void animateScreenBrightness(int target, int rate) { + if (mScreenBrightnessRampAnimator.animateTo(target, rate)) { + mNotifier.onScreenBrightness(target); + } + } + + private final Runnable mCleanListener = new Runnable() { + @Override + public void run() { + sendUpdatePowerState(); + } + }; + + private void setProximitySensorEnabled(boolean enable) { + if (enable) { + if (!mProximitySensorEnabled) { + mProximitySensorEnabled = true; + mPendingProximity = PROXIMITY_UNKNOWN; + mSensorManager.registerListener(mProximitySensorListener, mProximitySensor, + SensorManager.SENSOR_DELAY_NORMAL, mHandler); + } + } else { + if (mProximitySensorEnabled) { + mProximitySensorEnabled = false; + mProximity = PROXIMITY_UNKNOWN; + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + mSensorManager.unregisterListener(mProximitySensorListener); + } + } + } + + private void handleProximitySensorEvent(long time, boolean positive) { + if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) { + return; // no change + } + if (mPendingProximity == PROXIMITY_POSITIVE && positive) { + return; // no change + } + + // Only accept a proximity sensor reading if it remains + // stable for the entire debounce delay. + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + mPendingProximity = positive ? PROXIMITY_POSITIVE : PROXIMITY_NEGATIVE; + mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_DEBOUNCE_DELAY; + debounceProximitySensor(); + } + + private void debounceProximitySensor() { + if (mPendingProximity != PROXIMITY_UNKNOWN) { + final long now = SystemClock.uptimeMillis(); + if (mPendingProximityDebounceTime <= now) { + mProximity = mPendingProximity; + sendUpdatePowerState(); + } else { + Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime); + } + } + } + + private void setLightSensorEnabled(boolean enable, boolean updateAutoBrightness) { + if (enable) { + if (!mLightSensorEnabled) { + updateAutoBrightness = true; + mLightSensorEnabled = true; + mLightSensorEnableTime = SystemClock.uptimeMillis(); + mSensorManager.registerListener(mLightSensorListener, mLightSensor, + LIGHT_SENSOR_RATE, mHandler); + } + } else { + if (mLightSensorEnabled) { + mLightSensorEnabled = false; + mLightMeasurementValid = false; + mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED); + mSensorManager.unregisterListener(mLightSensorListener); + } + } + if (updateAutoBrightness) { + updateAutoBrightness(false); + } + } + + private void handleLightSensorEvent(long time, float lux) { + // Take the first few readings during the warm-up period and apply them + // immediately without debouncing. + if (!mLightMeasurementValid + || (time - mLightSensorEnableTime) < mLightSensorWarmUpTimeConfig) { + mLightMeasurement = lux; + mLightMeasurementValid = true; + mRecentLightSamples = 0; + updateAutoBrightness(true); + } + + // Update our moving average. + if (lux != mLightMeasurement && (mRecentLightSamples == 0 + || (lux < mLightMeasurement && mRecentLightBrightening) + || (lux > mLightMeasurement && !mRecentLightBrightening))) { + // If the newest light sample doesn't seem to be going in the + // same general direction as recent samples, then start over. + setRecentLight(time, lux, lux > mLightMeasurement); + } else if (mRecentLightSamples >= 1) { + // Add the newest light sample to the moving average. + accumulateRecentLight(time, lux); + } + if (DEBUG) { + Slog.d(TAG, "handleLightSensorEvent: lux=" + lux + + ", mLightMeasurementValid=" + mLightMeasurementValid + + ", mLightMeasurement=" + mLightMeasurement + + ", mRecentLightSamples=" + mRecentLightSamples + + ", mRecentLightAverage=" + mRecentLightAverage + + ", mRecentLightBrightening=" + mRecentLightBrightening + + ", mRecentLightTimeConstant=" + mRecentLightTimeConstant + + ", mFirstRecentLightSampleTime=" + + TimeUtils.formatUptime(mFirstRecentLightSampleTime) + + ", mPendingLightSensorDebounceTime=" + + TimeUtils.formatUptime(mPendingLightSensorDebounceTime)); + } + + // Debounce. + mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED); + debounceLightSensor(); + } + + private void setRecentLight(long time, float lux, boolean brightening) { + mRecentLightBrightening = brightening; + mRecentLightTimeConstant = brightening ? + BRIGHTENING_LIGHT_TIME_CONSTANT : DIMMING_LIGHT_TIME_CONSTANT; + mRecentLightSamples = 1; + mRecentLightAverage = lux; + mLastLightSample = lux; + mLastLightSampleTime = time; + mFirstRecentLightSampleTime = time; + mPendingLightSensorDebounceTime = time + (brightening ? + BRIGHTENING_LIGHT_DEBOUNCE : DIMMING_LIGHT_DEBOUNCE); + } + + private void accumulateRecentLight(long time, float lux) { + final long timeDelta = time - mLastLightSampleTime; + mRecentLightSamples += 1; + mRecentLightAverage += (lux - mRecentLightAverage) * + timeDelta / (mRecentLightTimeConstant + timeDelta); + mLastLightSample = lux; + mLastLightSampleTime = time; + } + + private void debounceLightSensor() { + if (mLightMeasurementValid && mRecentLightSamples >= 1) { + final long now = SystemClock.uptimeMillis(); + if (mPendingLightSensorDebounceTime <= now) { + accumulateRecentLight(now, mLastLightSample); + mLightMeasurement = mRecentLightAverage; + + if (DEBUG) { + Slog.d(TAG, "debounceLightSensor: Accepted new measurement " + + mLightMeasurement + " after " + + (now - mFirstRecentLightSampleTime) + " ms based on " + + mRecentLightSamples + " recent samples."); + } + + updateAutoBrightness(true); + + // Now that we have debounced the light sensor data, we have the + // option of either leaving the sensor in a debounced state or + // restarting the debounce cycle by setting mRecentLightSamples to 0. + // + // If we leave the sensor debounced, then new average light measurements + // may be accepted immediately as long as they are trending in the same + // direction as they were before. If the measurements start + // jittering or trending in the opposite direction then the debounce + // cycle will automatically be restarted. The benefit is that the + // auto-brightness control can be more responsive to changes over a + // broad range. + // + // For now, we choose to be more responsive and leave the following line + // commented out. + // + // mRecentLightSamples = 0; + } else { + Message msg = mHandler.obtainMessage(MSG_LIGHT_SENSOR_DEBOUNCED); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, mPendingLightSensorDebounceTime); + } + } + } + + private void updateAutoBrightness(boolean sendUpdate) { + if (!mLightMeasurementValid) { + return; + } + + float value = mScreenAutoBrightnessSpline.interpolate(mLightMeasurement); + float gamma = 1.0f; + + if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT + && mPowerRequest.screenAutoBrightnessAdjustment != 0.0f) { + final float adjGamma = FloatMath.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA, + Math.min(1.0f, Math.max(-1.0f, + -mPowerRequest.screenAutoBrightnessAdjustment))); + gamma *= adjGamma; + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma); + } + } + + if (USE_TWILIGHT_ADJUSTMENT) { + TwilightState state = mTwilight.getCurrentState(); + if (state != null && state.isNight()) { + final long now = System.currentTimeMillis(); + final float earlyGamma = + getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise()); + final float lateGamma = + getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise()); + gamma *= earlyGamma * lateGamma; + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma + + ", lateGamma=" + lateGamma); + } + } + } + + if (gamma != 1.0f) { + final float in = value; + value = FloatMath.pow(value, gamma); + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma + + ", in=" + in + ", out=" + value); + } + } + + int newScreenAutoBrightness = clampScreenBrightness( + (int)Math.round(value * PowerManager.BRIGHTNESS_ON)); + if (mScreenAutoBrightness != newScreenAutoBrightness) { + if (DEBUG) { + Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness=" + + mScreenAutoBrightness + ", newScreenAutoBrightness=" + + newScreenAutoBrightness); + } + + mScreenAutoBrightness = newScreenAutoBrightness; + mLastScreenAutoBrightnessGamma = gamma; + if (sendUpdate) { + sendUpdatePowerState(); + } + } + } + + private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) { + if (lastSunset < 0 || nextSunrise < 0 + || now < lastSunset || now > nextSunrise) { + return 1.0f; + } + + if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) { + return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA, + (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME); + } + + if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) { + return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA, + (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME); + } + + return TWILIGHT_ADJUSTMENT_MAX_GAMMA; + } + + private static float lerp(float x, float y, float alpha) { + return x + (y - x) * alpha; + } + + private void sendOnStateChanged() { + mCallbackHandler.post(mOnStateChangedRunnable); + } + + private final Runnable mOnStateChangedRunnable = new Runnable() { + @Override + public void run() { + mCallbacks.onStateChanged(); + } + }; + + private void sendOnProximityNegative() { + mCallbackHandler.post(mOnProximityNegativeRunnable); + } + + private final Runnable mOnProximityNegativeRunnable = new Runnable() { + @Override + public void run() { + mCallbacks.onProximityNegative(); + } + }; + + public void dump(final PrintWriter pw) { + synchronized (mLock) { + pw.println(); + pw.println("Display Controller Locked State:"); + pw.println(" mDisplayReadyLocked=" + mDisplayReadyLocked); + pw.println(" mPendingRequestLocked=" + mPendingRequestLocked); + pw.println(" mPendingRequestChangedLocked=" + mPendingRequestChangedLocked); + pw.println(" mPendingWaitForNegativeProximityLocked=" + + mPendingWaitForNegativeProximityLocked); + pw.println(" mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked); + } + + pw.println(); + pw.println("Display Controller Configuration:"); + pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig); + pw.println(" mUseSoftwareAutoBrightnessConfig=" + + mUseSoftwareAutoBrightnessConfig); + pw.println(" mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline); + pw.println(" mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig); + + mHandler.runWithScissors(new Runnable() { + @Override + public void run() { + dumpLocal(pw); + } + }, 1000); + } + + private void dumpLocal(PrintWriter pw) { + pw.println(); + pw.println("Display Controller Thread State:"); + pw.println(" mPowerRequest=" + mPowerRequest); + pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity); + + pw.println(" mProximitySensor=" + mProximitySensor); + pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled); + pw.println(" mProximityThreshold=" + mProximityThreshold); + pw.println(" mProximity=" + proximityToString(mProximity)); + pw.println(" mPendingProximity=" + proximityToString(mPendingProximity)); + pw.println(" mPendingProximityDebounceTime=" + + TimeUtils.formatUptime(mPendingProximityDebounceTime)); + pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity); + + pw.println(" mLightSensor=" + mLightSensor); + pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); + pw.println(" mLightSensorEnableTime=" + + TimeUtils.formatUptime(mLightSensorEnableTime)); + pw.println(" mLightMeasurement=" + mLightMeasurement); + pw.println(" mLightMeasurementValid=" + mLightMeasurementValid); + pw.println(" mLastLightSample=" + mLastLightSample); + pw.println(" mLastLightSampleTime=" + + TimeUtils.formatUptime(mLastLightSampleTime)); + pw.println(" mRecentLightSamples=" + mRecentLightSamples); + pw.println(" mRecentLightAverage=" + mRecentLightAverage); + pw.println(" mRecentLightBrightening=" + mRecentLightBrightening); + pw.println(" mRecentLightTimeConstant=" + mRecentLightTimeConstant); + pw.println(" mFirstRecentLightSampleTime=" + + TimeUtils.formatUptime(mFirstRecentLightSampleTime)); + pw.println(" mPendingLightSensorDebounceTime=" + + TimeUtils.formatUptime(mPendingLightSensorDebounceTime)); + pw.println(" mScreenAutoBrightness=" + mScreenAutoBrightness); + pw.println(" mUsingScreenAutoBrightness=" + mUsingScreenAutoBrightness); + pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma); + pw.println(" mTwilight.getCurrentState()=" + mTwilight.getCurrentState()); + + if (mElectronBeamOnAnimator != null) { + pw.println(" mElectronBeamOnAnimator.isStarted()=" + + mElectronBeamOnAnimator.isStarted()); + } + if (mElectronBeamOffAnimator != null) { + pw.println(" mElectronBeamOffAnimator.isStarted()=" + + mElectronBeamOffAnimator.isStarted()); + } + + if (mPowerState != null) { + mPowerState.dump(pw); + } + } + + private static String proximityToString(int state) { + switch (state) { + case PROXIMITY_UNKNOWN: + return "Unknown"; + case PROXIMITY_NEGATIVE: + return "Negative"; + case PROXIMITY_POSITIVE: + return "Positive"; + default: + return Integer.toString(state); + } + } + + private static boolean wantScreenOn(int state) { + switch (state) { + case DisplayPowerRequest.SCREEN_STATE_BRIGHT: + case DisplayPowerRequest.SCREEN_STATE_DIM: + return true; + } + return false; + } + + /** + * Asynchronous callbacks from the power controller to the power manager service. + */ + public interface Callbacks { + void onStateChanged(); + void onProximityNegative(); + } + + private final class DisplayControllerHandler extends Handler { + public DisplayControllerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_POWER_STATE: + updatePowerState(); + break; + + case MSG_PROXIMITY_SENSOR_DEBOUNCED: + debounceProximitySensor(); + break; + + case MSG_LIGHT_SENSOR_DEBOUNCED: + debounceLightSensor(); + break; + } + } + } + + private final SensorEventListener mProximitySensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (mProximitySensorEnabled) { + final long time = SystemClock.uptimeMillis(); + final float distance = event.values[0]; + boolean positive = distance >= 0.0f && distance < mProximityThreshold; + handleProximitySensorEvent(time, positive); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + + private final SensorEventListener mLightSensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + if (mLightSensorEnabled) { + final long time = SystemClock.uptimeMillis(); + final float lux = event.values[0]; + handleLightSensorEvent(time, lux); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // Not used. + } + }; + + private final TwilightService.TwilightListener mTwilightListener = + new TwilightService.TwilightListener() { + @Override + public void onTwilightStateChanged() { + mTwilightChanged = true; + updatePowerState(); + } + }; +} diff --git a/services/java/com/android/server/power/DisplayPowerRequest.java b/services/java/com/android/server/power/DisplayPowerRequest.java new file mode 100644 index 0000000..2d74292 --- /dev/null +++ b/services/java/com/android/server/power/DisplayPowerRequest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.os.PowerManager; + +/** + * Describes the requested power state of the display. + * + * This object is intended to describe the general characteristics of the + * power state, such as whether the screen should be on or off and the current + * brightness controls leaving the {@link DisplayPowerController} to manage the + * details of how the transitions between states should occur. The goal is for + * the {@link PowerManagerService} to focus on the global power state and not + * have to micro-manage screen off animations, auto-brightness and other effects. + */ +final class DisplayPowerRequest { + public static final int SCREEN_STATE_OFF = 0; + public static final int SCREEN_STATE_DIM = 1; + public static final int SCREEN_STATE_BRIGHT = 2; + + // The requested minimum screen power state: off, dim or bright. + public int screenState; + + // If true, the proximity sensor overrides the screen state when an object is + // nearby, turning it off temporarily until the object is moved away. + public boolean useProximitySensor; + + // The desired screen brightness in the range 0 (minimum / off) to 255 (brightest). + // The display power controller may choose to clamp the brightness. + // When auto-brightness is enabled, this field should specify a nominal default + // value to use while waiting for the light sensor to report enough data. + public int screenBrightness; + + // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter). + public float screenAutoBrightnessAdjustment; + + // If true, enables automatic brightness control. + public boolean useAutoBrightness; + + public DisplayPowerRequest() { + screenState = SCREEN_STATE_BRIGHT; + useProximitySensor = false; + screenBrightness = PowerManager.BRIGHTNESS_ON; + screenAutoBrightnessAdjustment = 0.0f; + useAutoBrightness = false; + } + + public DisplayPowerRequest(DisplayPowerRequest other) { + copyFrom(other); + } + + public void copyFrom(DisplayPowerRequest other) { + screenState = other.screenState; + useProximitySensor = other.useProximitySensor; + screenBrightness = other.screenBrightness; + screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment; + useAutoBrightness = other.useAutoBrightness; + } + + @Override + public boolean equals(Object o) { + return o instanceof DisplayPowerRequest + && equals((DisplayPowerRequest)o); + } + + public boolean equals(DisplayPowerRequest other) { + return other != null + && screenState == other.screenState + && useProximitySensor == other.useProximitySensor + && screenBrightness == other.screenBrightness + && screenAutoBrightnessAdjustment == other.screenAutoBrightnessAdjustment + && useAutoBrightness == other.useAutoBrightness; + } + + @Override + public int hashCode() { + return 0; // don't care + } + + @Override + public String toString() { + return "screenState=" + screenState + + ", useProximitySensor=" + useProximitySensor + + ", screenBrightness=" + screenBrightness + + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment + + ", useAutoBrightness=" + useAutoBrightness; + } +} diff --git a/services/java/com/android/server/power/DisplayPowerState.java b/services/java/com/android/server/power/DisplayPowerState.java new file mode 100644 index 0000000..1bd7811 --- /dev/null +++ b/services/java/com/android/server/power/DisplayPowerState.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.os.Looper; +import android.os.PowerManager; +import android.util.FloatProperty; +import android.util.IntProperty; +import android.util.Slog; +import android.view.Choreographer; + +import java.io.PrintWriter; + +/** + * Represents the current display power state and realizes it. + * + * This component is similar in nature to a {@link View} except that it describes + * the properties of a display. When properties are changed, the component + * invalidates itself and posts a callback to the {@link Choreographer} to + * apply the changes. This mechanism enables the display power state to be + * animated smoothly by the animation framework. + * + * This component must only be created or accessed by the {@link Looper} thread + * that belongs to the {@link DisplayPowerController}. + * + * We don't need to worry about holding a suspend blocker here because the + * {@link DisplayPowerController} does that for us whenever there is a pending invalidate. + */ +final class DisplayPowerState { + private static final String TAG = "DisplayPowerState"; + + private static boolean DEBUG = false; + + private static final int DIRTY_SCREEN_ON = 1 << 0; + private static final int DIRTY_ELECTRON_BEAM = 1 << 1; + private static final int DIRTY_BRIGHTNESS = 1 << 2; + + private final Choreographer mChoreographer; + private final ElectronBeam mElectronBeam; + private final PhotonicModulator mScreenBrightnessModulator; + + private int mDirty; + private boolean mScreenOn; + private float mElectronBeamLevel; + private int mScreenBrightness; + + private Runnable mCleanListener; + + public DisplayPowerState(ElectronBeam electronBean, + PhotonicModulator screenBrightnessModulator) { + mChoreographer = Choreographer.getInstance(); + mElectronBeam = electronBean; + mScreenBrightnessModulator = screenBrightnessModulator; + + // At boot time, we know that the screen is on and the electron beam + // animation is not playing. We don't know the screen's brightness though, + // so prepare to set it to a known state when the state is next applied. + // Although we set the brightness to full on here, the display power controller + // will reset the brightness to a new level immediately before the changes + // actually have a chance to be applied. + mScreenOn = true; + mElectronBeamLevel = 1.0f; + mScreenBrightness = PowerManager.BRIGHTNESS_ON; + invalidate(DIRTY_BRIGHTNESS); + } + + public static final FloatProperty<DisplayPowerState> ELECTRON_BEAM_LEVEL = + new FloatProperty<DisplayPowerState>("electronBeamLevel") { + @Override + public void setValue(DisplayPowerState object, float value) { + object.setElectronBeamLevel(value); + } + + @Override + public Float get(DisplayPowerState object) { + return object.getElectronBeamLevel(); + } + }; + + public static final IntProperty<DisplayPowerState> SCREEN_BRIGHTNESS = + new IntProperty<DisplayPowerState>("screenBrightness") { + @Override + public void setValue(DisplayPowerState object, int value) { + object.setScreenBrightness(value); + } + + @Override + public Integer get(DisplayPowerState object) { + return object.getScreenBrightness(); + } + }; + + /** + * Sets whether the screen is on or off. + */ + public void setScreenOn(boolean on) { + if (mScreenOn != on) { + if (DEBUG) { + Slog.d(TAG, "setScreenOn: on=" + on); + } + + mScreenOn = on; + invalidate(DIRTY_SCREEN_ON); + } + } + + /** + * Returns true if the screen is on. + */ + public boolean isScreenOn() { + return mScreenOn; + } + + /** + * Prepares the electron beam to turn on or off. + * This method should be called before starting an animation because it + * can take a fair amount of time to prepare the electron beam surface. + * + * @param warmUp True if the electron beam should start warming up. + * @return True if the electron beam was prepared. + */ + public boolean prepareElectronBeam(boolean warmUp) { + boolean success = mElectronBeam.prepare(warmUp); + invalidate(DIRTY_ELECTRON_BEAM); + return success; + } + + /** + * Dismisses the electron beam surface. + */ + public void dismissElectronBeam() { + mElectronBeam.dismiss(); + } + + /** + * Sets the level of the electron beam steering current. + * + * The display is blanked when the level is 0.0. In normal use, the electron + * beam should have a value of 1.0. The electron beam is unstable in between + * these states and the picture quality may be compromised. For best effect, + * the electron beam should be warmed up or cooled off slowly. + * + * Warning: Electron beam emits harmful radiation. Avoid direct exposure to + * skin or eyes. + * + * @param level The level, ranges from 0.0 (full off) to 1.0 (full on). + */ + public void setElectronBeamLevel(float level) { + if (mElectronBeamLevel != level) { + if (DEBUG) { + Slog.d(TAG, "setElectronBeamLevel: level=" + level); + } + + mElectronBeamLevel = level; + invalidate(DIRTY_ELECTRON_BEAM); + } + } + + /** + * Gets the level of the electron beam steering current. + */ + public float getElectronBeamLevel() { + return mElectronBeamLevel; + } + + /** + * Sets the display brightness. + * + * @param brightness The brightness, ranges from 0 (minimum / off) to 255 (brightest). + */ + public void setScreenBrightness(int brightness) { + if (mScreenBrightness != brightness) { + if (DEBUG) { + Slog.d(TAG, "setScreenBrightness: brightness=" + brightness); + } + + mScreenBrightness = brightness; + invalidate(DIRTY_BRIGHTNESS); + } + } + + /** + * Gets the screen brightness. + */ + public int getScreenBrightness() { + return mScreenBrightness; + } + + /** + * Returns true if no properties have been invalidated. + * Otherwise, returns false and promises to invoke the specified listener + * when the properties have all been applied. + * The listener always overrides any previously set listener. + */ + public boolean waitUntilClean(Runnable listener) { + if (mDirty != 0) { + mCleanListener = listener; + return false; + } else { + mCleanListener = null; + return true; + } + } + + public void dump(PrintWriter pw) { + pw.println(); + pw.println("Display Power State:"); + pw.println(" mDirty=" + Integer.toHexString(mDirty)); + pw.println(" mScreenOn=" + mScreenOn); + pw.println(" mScreenBrightness=" + mScreenBrightness); + pw.println(" mElectronBeamLevel=" + mElectronBeamLevel); + + mElectronBeam.dump(pw); + } + + private void invalidate(int dirty) { + if (mDirty == 0) { + mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, + mTraversalRunnable, null); + } + + mDirty |= dirty; + } + + private void apply() { + if (mDirty != 0) { + if ((mDirty & DIRTY_SCREEN_ON) != 0 && !mScreenOn) { + mScreenBrightnessModulator.setBrightness(0, true /*sync*/); + PowerManagerService.nativeSetScreenState(false); + } + + if ((mDirty & DIRTY_ELECTRON_BEAM) != 0) { + mElectronBeam.draw(mElectronBeamLevel); + } + + if ((mDirty & DIRTY_SCREEN_ON) != 0 && mScreenOn) { + PowerManagerService.nativeSetScreenState(true); + } + + if ((mDirty & (DIRTY_BRIGHTNESS | DIRTY_SCREEN_ON | DIRTY_ELECTRON_BEAM)) != 0 + && mScreenOn) { + mScreenBrightnessModulator.setBrightness( + (int)(mScreenBrightness * mElectronBeamLevel), false /*sync*/); + } + + mDirty = 0; + + if (mCleanListener != null) { + mCleanListener.run(); + } + } + } + + private final Runnable mTraversalRunnable = new Runnable() { + @Override + public void run() { + if (mDirty != 0) { + apply(); + } + } + }; +} diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java new file mode 100644 index 0000000..0c68997 --- /dev/null +++ b/services/java/com/android/server/power/ElectronBeam.java @@ -0,0 +1,653 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; +import android.opengl.GLES10; +import android.opengl.GLUtils; +import android.os.Looper; +import android.os.Process; +import android.util.FloatMath; +import android.util.Slog; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.Surface; +import android.view.SurfaceSession; + +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * Bzzzoooop! *crackle* + * + * Animates a screen transition from on to off or off to on by applying + * some GL transformations to a screenshot. + * + * This component must only be created or accessed by the {@link Looper} thread + * that belongs to the {@link DisplayPowerController}. + */ +final class ElectronBeam { + private static final String TAG = "ElectronBeam"; + + private static final boolean DEBUG = false; + + // The layer for the electron beam surface. + // This is currently hardcoded to be one layer above the boot animation. + private static final int ELECTRON_BEAM_LAYER = 0x40000001; + + // The relative proportion of the animation to spend performing + // the horizontal stretch effect. The remainder is spent performing + // the vertical stretch effect. + private static final float HSTRETCH_DURATION = 0.4f; + private static final float VSTRETCH_DURATION = 1.0f - HSTRETCH_DURATION; + + // Set to true when the animation context has been fully prepared. + private boolean mPrepared; + private boolean mWarmUp; + + private final Display mDisplay; + private final DisplayInfo mDisplayInfo = new DisplayInfo(); + private int mDisplayLayerStack; // layer stack associated with primary display + private int mDisplayRotation; + private int mDisplayWidth; // real width, not rotated + private int mDisplayHeight; // real height, not rotated + private SurfaceSession mSurfaceSession; + private Surface mSurface; + private EGLDisplay mEglDisplay; + private EGLConfig mEglConfig; + private EGLContext mEglContext; + private EGLSurface mEglSurface; + private boolean mSurfaceVisible; + + // Texture names. We only use one texture, which contains the screenshot. + private final int[] mTexNames = new int[1]; + private boolean mTexNamesGenerated; + + // Vertex and corresponding texture coordinates. + // We have 4 2D vertices, so 8 elements. The vertices form a quad. + private final FloatBuffer mVertexBuffer = createNativeFloatBuffer(8); + private final FloatBuffer mTexCoordBuffer = createNativeFloatBuffer(8); + + public ElectronBeam(Display display) { + mDisplay = display; + } + + /** + * Warms up the electron beam in preparation for turning on or off. + * This method prepares a GL context, and captures a screen shot. + * + * @param warmUp True if the electron beam is about to be turned on, false if + * it is about to be turned off. + * @return True if the electron beam is ready, false if it is uncontrollable. + */ + public boolean prepare(boolean warmUp) { + if (DEBUG) { + Slog.d(TAG, "prepare: warmUp=" + warmUp); + } + + mWarmUp = warmUp; + + // Get the display size and adjust it for rotation. + mDisplay.getDisplayInfo(mDisplayInfo); + mDisplayLayerStack = mDisplay.getLayerStack(); + mDisplayRotation = mDisplayInfo.rotation; + if (mDisplayRotation == Surface.ROTATION_90 + || mDisplayRotation == Surface.ROTATION_270) { + mDisplayWidth = mDisplayInfo.logicalHeight; + mDisplayHeight = mDisplayInfo.logicalWidth; + } else { + mDisplayWidth = mDisplayInfo.logicalWidth; + mDisplayHeight = mDisplayInfo.logicalHeight; + } + + // Prepare the surface for drawing. + if (!createEglContext() + || !createEglSurface() + || !captureScreenshotTextureAndSetViewport()) { + dismiss(); + return false; + } + + mPrepared = true; + return true; + } + + /** + * Dismisses the electron beam animation surface and cleans up. + * + * To prevent stray photons from leaking out after the electron beam has been + * turned off, it is a good idea to defer dismissing the animation until the + * electron beam has been turned back on fully. + */ + public void dismiss() { + if (DEBUG) { + Slog.d(TAG, "dismiss"); + } + + destroyScreenshotTexture(); + destroyEglSurface(); + mPrepared = false; + } + + /** + * Draws an animation frame showing the electron beam activated at the + * specified level. + * + * @param level The electron beam level. + * @return True if successful. + */ + public boolean draw(float level) { + if (DEBUG) { + Slog.d(TAG, "drawFrame: level=" + level); + } + + if (!attachEglContext()) { + return false; + } + try { + // Clear frame to solid black. + GLES10.glClearColor(0f, 0f, 0f, 1f); + GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT); + + // Draw the frame. + if (level < HSTRETCH_DURATION) { + drawHStretch(1.0f - (level / HSTRETCH_DURATION)); + } else { + drawVStretch(1.0f - ((level - HSTRETCH_DURATION) / VSTRETCH_DURATION)); + } + if (checkGlErrors("drawFrame")) { + return false; + } + + EGL14.eglSwapBuffers(mEglDisplay, mEglSurface); + } finally { + detachEglContext(); + } + + return showEglSurface(); + } + + /** + * Draws a frame where the content of the electron beam is collapsing inwards upon + * itself vertically with red / green / blue channels dispersing and eventually + * merging down to a single horizontal line. + * + * @param stretch The stretch factor. 0.0 is no collapse, 1.0 is full collapse. + */ + private void drawVStretch(float stretch) { + // compute interpolation scale factors for each color channel + final float ar = scurve(stretch, 7.5f); + final float ag = scurve(stretch, 8.0f); + final float ab = scurve(stretch, 8.5f); + if (DEBUG) { + Slog.d(TAG, "drawVStretch: stretch=" + stretch + + ", ar=" + ar + ", ag=" + ag + ", ab=" + ab); + } + + // set blending + GLES10.glBlendFunc(GLES10.GL_ONE, GLES10.GL_ONE); + GLES10.glEnable(GLES10.GL_BLEND); + + // bind vertex buffer + GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer); + GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); + + // bind texture and set blending for drawing planes + GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, mTexNames[0]); + GLES10.glTexEnvx(GLES10.GL_TEXTURE_ENV, GLES10.GL_TEXTURE_ENV_MODE, + mWarmUp ? GLES10.GL_MODULATE : GLES10.GL_REPLACE); + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, + GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR); + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, + GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_LINEAR); + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, + GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_CLAMP_TO_EDGE); + GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D, + GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_CLAMP_TO_EDGE); + GLES10.glEnable(GLES10.GL_TEXTURE_2D); + GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, mTexCoordBuffer); + GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); + + // draw the red plane + setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ar); + GLES10.glColorMask(true, false, false, true); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + + // draw the green plane + setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag); + GLES10.glColorMask(false, true, false, true); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + + // draw the blue plane + setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ab); + GLES10.glColorMask(false, false, true, true); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + + // clean up after drawing planes + GLES10.glDisable(GLES10.GL_TEXTURE_2D); + GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY); + GLES10.glColorMask(true, true, true, true); + + // draw the white highlight (we use the last vertices) + if (!mWarmUp) { + GLES10.glColor4f(ag, ag, ag, 1.0f); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + } + + // clean up + GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); + GLES10.glDisable(GLES10.GL_BLEND); + } + + /** + * Draws a frame where the electron beam has been stretched out into + * a thin white horizontal line that fades as it expands outwards. + * + * @param stretch The stretch factor. 0.0 is no stretch / no fade, + * 1.0 is maximum stretch / maximum fade. + */ + private void drawHStretch(float stretch) { + // compute interpolation scale factor + final float ag = scurve(stretch, 8.0f); + if (DEBUG) { + Slog.d(TAG, "drawHStretch: stretch=" + stretch + ", ag=" + ag); + } + + if (stretch < 1.0f) { + // bind vertex buffer + GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer); + GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); + + // draw narrow fading white line + setHStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag); + GLES10.glColor4f(1.0f - ag, 1.0f - ag, 1.0f - ag, 1.0f); + GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); + + // clean up + GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY); + } + } + + private static void setVStretchQuad(FloatBuffer vtx, float dw, float dh, float a) { + final float w = dw + (dw * a); + final float h = dh - (dh * a); + final float x = (dw - w) * 0.5f; + final float y = (dh - h) * 0.5f; + setQuad(vtx, x, y, w, h); + } + + private static void setHStretchQuad(FloatBuffer vtx, float dw, float dh, float a) { + final float w = dw + (dw * a); + final float h = 1.0f; + final float x = (dw - w) * 0.5f; + final float y = (dh - h) * 0.5f; + setQuad(vtx, x, y, w, h); + } + + private static void setQuad(FloatBuffer vtx, float x, float y, float w, float h) { + if (DEBUG) { + Slog.d(TAG, "setQuad: x=" + x + ", y=" + y + ", w=" + w + ", h=" + h); + } + vtx.put(0, x); + vtx.put(1, y); + vtx.put(2, x); + vtx.put(3, y + h); + vtx.put(4, x + w); + vtx.put(5, y + h); + vtx.put(6, x + w); + vtx.put(7, y); + } + + private boolean captureScreenshotTextureAndSetViewport() { + // TODO: Use a SurfaceTexture to avoid the extra texture upload. + Bitmap bitmap = Surface.screenshot(mDisplayWidth, mDisplayHeight, + 0, ELECTRON_BEAM_LAYER - 1); + if (bitmap == null) { + Slog.e(TAG, "Could not take a screenshot!"); + return false; + } + try { + if (!attachEglContext()) { + return false; + } + try { + if (!mTexNamesGenerated) { + GLES10.glGenTextures(1, mTexNames, 0); + if (checkGlErrors("glGenTextures")) { + return false; + } + mTexNamesGenerated = true; + } + + GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, mTexNames[0]); + if (checkGlErrors("glBindTexture")) { + return false; + } + + float u = 1.0f; + float v = 1.0f; + GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0); + if (checkGlErrors("glTexImage2D, first try", false)) { + // Try a power of two size texture instead. + int tw = nextPowerOfTwo(mDisplayWidth); + int th = nextPowerOfTwo(mDisplayHeight); + int format = GLUtils.getInternalFormat(bitmap); + GLES10.glTexImage2D(GLES10.GL_TEXTURE_2D, 0, + format, tw, th, 0, + format, GLES10.GL_UNSIGNED_BYTE, null); + if (checkGlErrors("glTexImage2D, second try")) { + return false; + } + + GLUtils.texSubImage2D(GLES10.GL_TEXTURE_2D, 0, 0, 0, bitmap); + if (checkGlErrors("glTexSubImage2D")) { + return false; + } + + u = (float)mDisplayWidth / tw; + v = (float)mDisplayHeight / th; + } + + // Set up texture coordinates for a quad. + // We might need to change this if the texture ends up being + // a different size from the display for some reason. + mTexCoordBuffer.put(0, 0f); + mTexCoordBuffer.put(1, v); + mTexCoordBuffer.put(2, 0f); + mTexCoordBuffer.put(3, 0f); + mTexCoordBuffer.put(4, u); + mTexCoordBuffer.put(5, 0f); + mTexCoordBuffer.put(6, u); + mTexCoordBuffer.put(7, v); + + // Set up our viewport. + GLES10.glViewport(0, 0, mDisplayWidth, mDisplayHeight); + GLES10.glMatrixMode(GLES10.GL_PROJECTION); + GLES10.glLoadIdentity(); + GLES10.glOrthof(0, mDisplayWidth, 0, mDisplayHeight, 0, 1); + GLES10.glMatrixMode(GLES10.GL_MODELVIEW); + GLES10.glLoadIdentity(); + GLES10.glMatrixMode(GLES10.GL_TEXTURE); + GLES10.glLoadIdentity(); + } finally { + detachEglContext(); + } + } finally { + bitmap.recycle(); + } + return true; + } + + private void destroyScreenshotTexture() { + if (mTexNamesGenerated) { + mTexNamesGenerated = false; + if (attachEglContext()) { + try { + GLES10.glDeleteTextures(1, mTexNames, 0); + checkGlErrors("glDeleteTextures"); + } finally { + detachEglContext(); + } + } + } + } + + private boolean createEglContext() { + if (mEglDisplay == null) { + mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL14.EGL_NO_DISPLAY) { + logEglError("eglGetDisplay"); + return false; + } + + int[] version = new int[2]; + if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) { + mEglDisplay = null; + logEglError("eglInitialize"); + return false; + } + } + + if (mEglConfig == null) { + int[] eglConfigAttribList = new int[] { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + EGL14.EGL_NONE + }; + int[] numEglConfigs = new int[1]; + EGLConfig[] eglConfigs = new EGLConfig[1]; + if (!EGL14.eglChooseConfig(mEglDisplay, eglConfigAttribList, 0, + eglConfigs, 0, eglConfigs.length, numEglConfigs, 0)) { + logEglError("eglChooseConfig"); + return false; + } + mEglConfig = eglConfigs[0]; + } + + if (mEglContext == null) { + int[] eglContextAttribList = new int[] { + EGL14.EGL_NONE + }; + mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, + EGL14.EGL_NO_CONTEXT, eglContextAttribList, 0); + if (mEglContext == null) { + logEglError("eglCreateContext"); + return false; + } + } + return true; + } + + /* not used because it is too expensive to create / destroy contexts all of the time + private void destroyEglContext() { + if (mEglContext != null) { + if (!EGL14.eglDestroyContext(mEglDisplay, mEglContext)) { + logEglError("eglDestroyContext"); + } + mEglContext = null; + } + }*/ + + private boolean createEglSurface() { + if (mSurfaceSession == null) { + mSurfaceSession = new SurfaceSession(); + } + + Surface.openTransaction(); + try { + if (mSurface == null) { + try { + mSurface = new Surface(mSurfaceSession, + "ElectronBeam", mDisplayWidth, mDisplayHeight, + PixelFormat.OPAQUE, Surface.OPAQUE | Surface.HIDDEN); + } catch (Surface.OutOfResourcesException ex) { + Slog.e(TAG, "Unable to create surface.", ex); + return false; + } + } + + mSurface.setLayerStack(mDisplayLayerStack); + mSurface.setSize(mDisplayWidth, mDisplayHeight); + + switch (mDisplayRotation) { + case Surface.ROTATION_0: + mSurface.setPosition(0, 0); + mSurface.setMatrix(1, 0, 0, 1); + break; + case Surface.ROTATION_90: + mSurface.setPosition(0, mDisplayWidth); + mSurface.setMatrix(0, -1, 1, 0); + break; + case Surface.ROTATION_180: + mSurface.setPosition(mDisplayWidth, mDisplayHeight); + mSurface.setMatrix(-1, 0, 0, -1); + break; + case Surface.ROTATION_270: + mSurface.setPosition(mDisplayHeight, 0); + mSurface.setMatrix(0, 1, -1, 0); + break; + } + } finally { + Surface.closeTransaction(); + } + + if (mEglSurface == null) { + int[] eglSurfaceAttribList = new int[] { + EGL14.EGL_NONE + }; + mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, + eglSurfaceAttribList, 0); + if (mEglSurface == null) { + logEglError("eglCreateWindowSurface"); + return false; + } + } + return true; + } + + private void destroyEglSurface() { + if (mEglSurface != null) { + if (!EGL14.eglDestroySurface(mEglDisplay, mEglSurface)) { + logEglError("eglDestroySurface"); + } + mEglSurface = null; + } + + if (mSurface != null) { + Surface.openTransaction(); + try { + mSurface.destroy(); + } finally { + Surface.closeTransaction(); + } + mSurface = null; + mSurfaceVisible = false; + } + } + + private boolean showEglSurface() { + if (!mSurfaceVisible) { + Surface.openTransaction(); + try { + mSurface.setLayer(ELECTRON_BEAM_LAYER); + mSurface.show(); + } finally { + Surface.closeTransaction(); + } + mSurfaceVisible = true; + } + return true; + } + + private boolean attachEglContext() { + if (mEglSurface == null) { + return false; + } + if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + logEglError("eglMakeCurrent"); + return false; + } + return true; + } + + private void detachEglContext() { + if (mEglDisplay != null) { + EGL14.eglMakeCurrent(mEglDisplay, + EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + } + } + + /** + * Interpolates a value in the range 0 .. 1 along a sigmoid curve + * yielding a result in the range 0 .. 1 scaled such that: + * scurve(0) == 0, scurve(0.5) == 0.5, scurve(1) == 1. + */ + private static float scurve(float value, float s) { + // A basic sigmoid has the form y = 1.0f / FloatMap.exp(-x * s). + // Here we take the input datum and shift it by 0.5 so that the + // domain spans the range -0.5 .. 0.5 instead of 0 .. 1. + final float x = value - 0.5f; + + // Next apply the sigmoid function to the scaled value + // which produces a value in the range 0 .. 1 so we subtract + // 0.5 to get a value in the range -0.5 .. 0.5 instead. + final float y = sigmoid(x, s) - 0.5f; + + // To obtain the desired boundary conditions we need to scale + // the result so that it fills a range of -1 .. 1. + final float v = sigmoid(0.5f, s) - 0.5f; + + // And finally remap the value back to a range of 0 .. 1. + return y / v * 0.5f + 0.5f; + } + + private static float sigmoid(float x, float s) { + return 1.0f / (1.0f + FloatMath.exp(-x * s)); + } + + private static int nextPowerOfTwo(int value) { + return 1 << (32 - Integer.numberOfLeadingZeros(value)); + } + + private static FloatBuffer createNativeFloatBuffer(int size) { + ByteBuffer bb = ByteBuffer.allocateDirect(size * 4); + bb.order(ByteOrder.nativeOrder()); + return bb.asFloatBuffer(); + } + + private static void logEglError(String func) { + Slog.e(TAG, func + " failed: error " + EGL14.eglGetError(), new Throwable()); + } + + private static boolean checkGlErrors(String func) { + return checkGlErrors(func, true); + } + + private static boolean checkGlErrors(String func, boolean log) { + boolean hadError = false; + int error; + while ((error = GLES10.glGetError()) != GLES10.GL_NO_ERROR) { + if (log) { + Slog.e(TAG, func + " failed: error " + error, new Throwable()); + } + hadError = true; + } + return hadError; + } + + public void dump(PrintWriter pw) { + pw.println(); + pw.println("Electron Beam State:"); + pw.println(" mPrepared=" + mPrepared); + pw.println(" mWarmUp=" + mWarmUp); + pw.println(" mDisplayLayerStack=" + mDisplayLayerStack); + pw.println(" mDisplayRotation=" + mDisplayRotation); + pw.println(" mDisplayWidth=" + mDisplayWidth); + pw.println(" mDisplayHeight=" + mDisplayHeight); + pw.println(" mSurfaceVisible=" + mSurfaceVisible); + } +} diff --git a/services/java/com/android/server/power/Notifier.java b/services/java/com/android/server/power/Notifier.java new file mode 100644 index 0000000..ce1e147 --- /dev/null +++ b/services/java/com/android/server/power/Notifier.java @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import com.android.internal.app.IBatteryStats; +import com.android.server.EventLogTags; + +import android.app.ActivityManagerNative; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.WorkSource; +import android.util.EventLog; +import android.util.Slog; +import android.view.WindowManagerPolicy; +import android.view.WindowManagerPolicy.ScreenOnListener; + +/** + * Sends broadcasts about important power state changes. + * + * This methods of this class may be called by the power manager service while + * its lock is being held. Internally it takes care of sending broadcasts to + * notify other components of the system or applications asynchronously. + * + * The notifier is designed to collapse unnecessary broadcasts when it is not + * possible for the system to have observed an intermediate state. + * + * For example, if the device wakes up, goes to sleep and wakes up again immediately + * before the go to sleep broadcast has been sent, then no broadcast will be + * sent about the system going to sleep and waking up. + */ +final class Notifier { + private static final String TAG = "PowerManagerNotifier"; + + private static final boolean DEBUG = false; + + private static final int POWER_STATE_UNKNOWN = 0; + private static final int POWER_STATE_AWAKE = 1; + private static final int POWER_STATE_ASLEEP = 2; + + private static final int MSG_USER_ACTIVITY = 1; + private static final int MSG_BROADCAST = 2; + + private final Object mLock = new Object(); + + private final Context mContext; + private final IBatteryStats mBatteryStats; + private final SuspendBlocker mSuspendBlocker; + private final WindowManagerPolicy mPolicy; + private final ScreenOnListener mScreenOnListener; + + private final NotifierHandler mHandler; + private final Intent mScreenOnIntent; + private final Intent mScreenOffIntent; + + // The current power state. + private int mActualPowerState; + private int mLastGoToSleepReason; + + // The currently broadcasted power state. This reflects what other parts of the + // system have observed. + private int mBroadcastedPowerState; + private boolean mBroadcastInProgress; + private long mBroadcastStartTime; + + // True if a user activity message should be sent. + private boolean mUserActivityPending; + + public Notifier(Looper looper, Context context, IBatteryStats batteryStats, + SuspendBlocker suspendBlocker, WindowManagerPolicy policy, + ScreenOnListener screenOnListener) { + mContext = context; + mBatteryStats = batteryStats; + mSuspendBlocker = suspendBlocker; + mPolicy = policy; + mScreenOnListener = screenOnListener; + + mHandler = new NotifierHandler(looper); + mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON); + mScreenOnIntent.addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF); + mScreenOffIntent.addFlags( + Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + } + + /** + * Called when a wake lock is acquired. + */ + public void onWakeLockAcquired(int flags, String tag, int ownerUid, int ownerPid, + WorkSource workSource) { + if (DEBUG) { + Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag + + "\", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + + ", workSource=" + workSource); + } + + try { + final int monitorType = getBatteryStatsWakeLockMonitorType(flags); + if (workSource != null) { + mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag, monitorType); + } else { + mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, monitorType); + } + } catch (RemoteException ex) { + // Ignore + } + } + + /** + * Called when a wake lock is released. + */ + public void onWakeLockReleased(int flags, String tag, int ownerUid, int ownerPid, + WorkSource workSource) { + if (DEBUG) { + Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag + + "\", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + + ", workSource=" + workSource); + } + + try { + final int monitorType = getBatteryStatsWakeLockMonitorType(flags); + if (workSource != null) { + mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, monitorType); + } else { + mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, monitorType); + } + } catch (RemoteException ex) { + // Ignore + } + } + + private static int getBatteryStatsWakeLockMonitorType(int flags) { + switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.PARTIAL_WAKE_LOCK: + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + return BatteryStats.WAKE_TYPE_PARTIAL; + default: + return BatteryStats.WAKE_TYPE_FULL; + } + } + + /** + * Called when the screen is turned on. + */ + public void onScreenOn() { + if (DEBUG) { + Slog.d(TAG, "onScreenOn"); + } + + try { + mBatteryStats.noteScreenOn(); + } catch (RemoteException ex) { + // Ignore + } + } + + /** + * Called when the screen is turned off. + */ + public void onScreenOff() { + if (DEBUG) { + Slog.d(TAG, "onScreenOff"); + } + + try { + mBatteryStats.noteScreenOff(); + } catch (RemoteException ex) { + // Ignore + } + } + + /** + * Called when the screen changes brightness. + */ + public void onScreenBrightness(int brightness) { + if (DEBUG) { + Slog.d(TAG, "onScreenBrightness: brightness=" + brightness); + } + + try { + mBatteryStats.noteScreenBrightness(brightness); + } catch (RemoteException ex) { + // Ignore + } + } + + /** + * Called when the device is waking up from sleep and the + * display is about to be turned on. + */ + public void onWakeUpStarted() { + if (DEBUG) { + Slog.d(TAG, "onWakeUpStarted"); + } + + synchronized (mLock) { + if (mActualPowerState != POWER_STATE_AWAKE) { + mActualPowerState = POWER_STATE_AWAKE; + updatePendingBroadcastLocked(); + } + } + } + + /** + * Called when the device has finished waking up from sleep + * and the display has been turned on. + */ + public void onWakeUpFinished() { + if (DEBUG) { + Slog.d(TAG, "onWakeUpFinished"); + } + } + + /** + * Called when the device is going to sleep. + */ + public void onGoToSleepStarted(int reason) { + if (DEBUG) { + Slog.d(TAG, "onGoToSleepStarted"); + } + + synchronized (mLock) { + mLastGoToSleepReason = reason; + } + } + + /** + * Called when the device has finished going to sleep and the + * display has been turned off. + * + * This is a good time to make transitions that we don't want the user to see, + * such as bringing the key guard to focus. There's no guarantee for this, + * however because the user could turn the device on again at any time. + * Some things may need to be protected by other mechanisms that defer screen on. + */ + public void onGoToSleepFinished() { + if (DEBUG) { + Slog.d(TAG, "onGoToSleepFinished"); + } + + synchronized (mLock) { + if (mActualPowerState != POWER_STATE_ASLEEP) { + mActualPowerState = POWER_STATE_ASLEEP; + if (mUserActivityPending) { + mUserActivityPending = false; + mHandler.removeMessages(MSG_USER_ACTIVITY); + } + updatePendingBroadcastLocked(); + } + } + } + + /** + * Called when there has been user activity. + */ + public void onUserActivity(int event, int uid) { + if (DEBUG) { + Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid); + } + + try { + mBatteryStats.noteUserActivity(uid, event); + } catch (RemoteException ex) { + // Ignore + } + + synchronized (mLock) { + if (!mUserActivityPending) { + mUserActivityPending = true; + Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + } + } + + private void updatePendingBroadcastLocked() { + if (!mBroadcastInProgress + && mActualPowerState != POWER_STATE_UNKNOWN + && mActualPowerState != mBroadcastedPowerState) { + mBroadcastInProgress = true; + mSuspendBlocker.acquire(); + Message msg = mHandler.obtainMessage(MSG_BROADCAST); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + } + + private void sendUserActivity() { + synchronized (mLock) { + if (!mUserActivityPending) { + return; + } + mUserActivityPending = false; + } + + mPolicy.userActivity(); + } + + private void sendNextBroadcast() { + final int powerState; + final int goToSleepReason; + synchronized (mLock) { + if (mActualPowerState == POWER_STATE_UNKNOWN + || mActualPowerState == mBroadcastedPowerState) { + mBroadcastInProgress = false; + mSuspendBlocker.release(); + return; + } + + powerState = mActualPowerState; + goToSleepReason = mLastGoToSleepReason; + + mBroadcastedPowerState = powerState; + mBroadcastStartTime = SystemClock.uptimeMillis(); + } + + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, 1); + + if (powerState == POWER_STATE_AWAKE) { + sendWakeUpBroadcast(); + } else { + sendGoToSleepBroadcast(goToSleepReason); + } + } + + private void sendWakeUpBroadcast() { + if (DEBUG) { + Slog.d(TAG, "Sending wake up broadcast."); + } + + EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0); + + mPolicy.screenTurningOn(mScreenOnListener); + try { + ActivityManagerNative.getDefault().wakingUp(); + } catch (RemoteException e) { + // ignore it + } + + if (ActivityManagerNative.isSystemReady()) { + mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null, + mWakeUpBroadcastDone, mHandler, 0, null, null); + } else { + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1); + sendNextBroadcast(); + } + } + + private final BroadcastReceiver mWakeUpBroadcastDone = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 1, + SystemClock.uptimeMillis() - mBroadcastStartTime, 1); + sendNextBroadcast(); + } + }; + + private void sendGoToSleepBroadcast(int reason) { + if (DEBUG) { + Slog.d(TAG, "Sending go to sleep broadcast."); + } + + int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER; + switch (reason) { + case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: + why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN; + break; + case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT: + why = WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT; + break; + } + + EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0); + + mPolicy.screenTurnedOff(why); + try { + ActivityManagerNative.getDefault().goingToSleep(); + } catch (RemoteException e) { + // ignore it. + } + + if (ActivityManagerNative.isSystemReady()) { + mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null, + mGoToSleepBroadcastDone, mHandler, 0, null, null); + } else { + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1); + sendNextBroadcast(); + } + } + + private final BroadcastReceiver mGoToSleepBroadcastDone = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0, + SystemClock.uptimeMillis() - mBroadcastStartTime, 1); + sendNextBroadcast(); + } + }; + + private final class NotifierHandler extends Handler { + public NotifierHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_USER_ACTIVITY: + sendUserActivity(); + break; + + case MSG_BROADCAST: + sendNextBroadcast(); + break; + } + } + } +} diff --git a/services/java/com/android/server/power/PhotonicModulator.java b/services/java/com/android/server/power/PhotonicModulator.java new file mode 100644 index 0000000..c9b5d90 --- /dev/null +++ b/services/java/com/android/server/power/PhotonicModulator.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import com.android.server.LightsService; + +import java.util.concurrent.Executor; + +/** + * Sets the value of a light asynchronously. + * + * This is done to avoid blocking the looper on devices for which + * setting the backlight brightness is especially slow. + */ +final class PhotonicModulator { + private static final int UNKNOWN_LIGHT_VALUE = -1; + + private final Object mLock = new Object(); + + private final LightsService.Light mLight; + private final Executor mExecutor; + private final SuspendBlocker mSuspendBlocker; + + private boolean mPendingChange; + private int mPendingLightValue; + private int mActualLightValue; + + public PhotonicModulator(Executor executor, LightsService.Light light, + SuspendBlocker suspendBlocker) { + mExecutor = executor; + mLight = light; + mSuspendBlocker = suspendBlocker; + mPendingLightValue = UNKNOWN_LIGHT_VALUE; + mActualLightValue = UNKNOWN_LIGHT_VALUE; + } + + /** + * Sets the backlight brightness, synchronously or asynchronously. + * + * @param lightValue The new light value, from 0 to 255. + * @param sync If true, waits for the brightness change to complete before returning. + */ + public void setBrightness(int lightValue, boolean sync) { + synchronized (mLock) { + if (lightValue != mPendingLightValue) { + mPendingLightValue = lightValue; + if (!mPendingChange) { + mPendingChange = true; + mSuspendBlocker.acquire(); + mExecutor.execute(mTask); + } + } + if (sync) { + while (mPendingChange) { + try { + mLock.wait(); + } catch (InterruptedException ex) { + // ignore it + } + } + } + } + } + + private final Runnable mTask = new Runnable() { + @Override + public void run() { + for (;;) { + final int newLightValue; + synchronized (mLock) { + newLightValue = mPendingLightValue; + if (newLightValue == mActualLightValue) { + mSuspendBlocker.release(); + mPendingChange = false; + mLock.notifyAll(); + return; + } + mActualLightValue = newLightValue; + } + mLight.setBrightness(newLightValue); + } + } + }; +} diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java new file mode 100644 index 0000000..fda619c --- /dev/null +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -0,0 +1,2122 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import com.android.internal.app.IBatteryStats; +import com.android.server.BatteryService; +import com.android.server.EventLogTags; +import com.android.server.LightsService; +import com.android.server.TwilightService; +import com.android.server.Watchdog; +import com.android.server.am.ActivityManagerService; +import com.android.server.display.DisplayManagerService; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.BatteryManager; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.IPowerManager; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.WorkSource; +import android.provider.Settings; +import android.service.dreams.IDreamManager; +import android.util.EventLog; +import android.util.Log; +import android.util.Slog; +import android.util.TimeUtils; +import android.view.WindowManagerPolicy; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +import libcore.util.Objects; + +/** + * The power manager service is responsible for coordinating power management + * functions on the device. + */ +public final class PowerManagerService extends IPowerManager.Stub + implements Watchdog.Monitor { + private static final String TAG = "PowerManagerService"; + + private static final boolean DEBUG = false; + private static final boolean DEBUG_SPEW = DEBUG && true; + + // Message: Sent when a user activity timeout occurs to update the power state. + private static final int MSG_USER_ACTIVITY_TIMEOUT = 1; + // Message: Sent when the device enters or exits a napping or dreaming state. + private static final int MSG_SANDMAN = 2; + + // Dirty bit: mWakeLocks changed + private static final int DIRTY_WAKE_LOCKS = 1 << 0; + // Dirty bit: mWakefulness changed + private static final int DIRTY_WAKEFULNESS = 1 << 1; + // Dirty bit: user activity was poked or may have timed out + private static final int DIRTY_USER_ACTIVITY = 1 << 2; + // Dirty bit: actual display power state was updated asynchronously + private static final int DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED = 1 << 3; + // Dirty bit: mBootCompleted changed + private static final int DIRTY_BOOT_COMPLETED = 1 << 4; + // Dirty bit: settings changed + private static final int DIRTY_SETTINGS = 1 << 5; + // Dirty bit: mIsPowered changed + private static final int DIRTY_IS_POWERED = 1 << 6; + // Dirty bit: mStayOn changed + private static final int DIRTY_STAY_ON = 1 << 7; + // Dirty bit: battery state changed + private static final int DIRTY_BATTERY_STATE = 1 << 8; + + // Wakefulness: The device is asleep and can only be awoken by a call to wakeUp(). + // The screen should be off or in the process of being turned off by the display controller. + private static final int WAKEFULNESS_ASLEEP = 0; + // Wakefulness: The device is fully awake. It can be put to sleep by a call to goToSleep(). + // When the user activity timeout expires, the device may start napping. + private static final int WAKEFULNESS_AWAKE = 1; + // Wakefulness: The device is napping. It is deciding whether to dream or go to sleep + // but hasn't gotten around to it yet. It can be awoken by a call to wakeUp(), which + // ends the nap. User activity may brighten the screen but does not end the nap. + private static final int WAKEFULNESS_NAPPING = 2; + // Wakefulness: The device is dreaming. It can be awoken by a call to wakeUp(), + // which ends the dream. The device goes to sleep when goToSleep() is called, when + // the dream ends or when unplugged. + // User activity may brighten the screen but does not end the dream. + private static final int WAKEFULNESS_DREAMING = 3; + + // Summarizes the state of all active wakelocks. + private static final int WAKE_LOCK_CPU = 1 << 0; + private static final int WAKE_LOCK_SCREEN_BRIGHT = 1 << 1; + private static final int WAKE_LOCK_SCREEN_DIM = 1 << 2; + private static final int WAKE_LOCK_BUTTON_BRIGHT = 1 << 3; + private static final int WAKE_LOCK_PROXIMITY_SCREEN_OFF = 1 << 4; + + // Summarizes the user activity state. + private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0; + private static final int USER_ACTIVITY_SCREEN_DIM = 1 << 1; + + // Default and minimum screen off timeout in milliseconds. + private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000; + private static final int MINIMUM_SCREEN_OFF_TIMEOUT = 10 * 1000; + + // The screen dim duration, in seconds. + // This is subtracted from the end of the screen off timeout so the + // minimum screen off timeout should be longer than this. + private static final int SCREEN_DIM_DURATION = 7 * 1000; + + private Context mContext; + private LightsService mLightsService; + private BatteryService mBatteryService; + private IBatteryStats mBatteryStats; + private HandlerThread mHandlerThread; + private PowerManagerHandler mHandler; + private WindowManagerPolicy mPolicy; + private Notifier mNotifier; + private DisplayPowerController mDisplayPowerController; + private SettingsObserver mSettingsObserver; + private IDreamManager mDreamManager; + private LightsService.Light mAttentionLight; + + private final Object mLock = new Object(); + + // A bitfield that indicates what parts of the power state have + // changed and need to be recalculated. + private int mDirty; + + // Indicates whether the device is awake or asleep or somewhere in between. + // This is distinct from the screen power state, which is managed separately. + private int mWakefulness; + + // True if MSG_SANDMAN has been scheduled. + private boolean mSandmanScheduled; + + // Table of all suspend blockers. + // There should only be a few of these. + private final ArrayList<SuspendBlocker> mSuspendBlockers = new ArrayList<SuspendBlocker>(); + + // Table of all wake locks acquired by applications. + private final ArrayList<WakeLock> mWakeLocks = new ArrayList<WakeLock>(); + + // A bitfield that summarizes the state of all active wakelocks. + private int mWakeLockSummary; + + // If true, instructs the display controller to wait for the proximity sensor to + // go negative before turning the screen on. + private boolean mRequestWaitForNegativeProximity; + + // Timestamp of the last time the device was awoken or put to sleep. + private long mLastWakeTime; + private long mLastSleepTime; + + // True if we need to send a wake up or go to sleep finished notification + // when the display is ready. + private boolean mSendWakeUpFinishedNotificationWhenReady; + private boolean mSendGoToSleepFinishedNotificationWhenReady; + + // Timestamp of the last call to user activity. + private long mLastUserActivityTime; + private long mLastUserActivityTimeNoChangeLights; + + // A bitfield that summarizes the effect of the user activity timer. + // A zero value indicates that the user activity timer has expired. + private int mUserActivitySummary; + + // The desired display power state. The actual state may lag behind the + // requested because it is updated asynchronously by the display power controller. + private final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest(); + + // The time the screen was last turned off, in elapsedRealtime() timebase. + private long mLastScreenOffEventElapsedRealTime; + + // True if the display power state has been fully applied, which means the display + // is actually on or actually off or whatever was requested. + private boolean mDisplayReady; + + // True if holding a wake-lock to block suspend of the CPU. + private boolean mHoldingWakeLockSuspendBlocker; + + // The suspend blocker used to keep the CPU alive when wake locks have been acquired. + private final SuspendBlocker mWakeLockSuspendBlocker; + + // True if systemReady() has been called. + private boolean mSystemReady; + + // True if boot completed occurred. We keep the screen on until this happens. + private boolean mBootCompleted; + + // True if the device is plugged into a power source. + private boolean mIsPowered; + + // True if the device should wake up when plugged or unplugged. + private boolean mWakeUpWhenPluggedOrUnpluggedConfig; + + // True if dreams are supported on this device. + private boolean mDreamsSupportedConfig; + + // True if dreams are enabled by the user. + private boolean mDreamsEnabledSetting; + + // True if dreams should be activated on sleep. + private boolean mDreamsActivateOnSleepSetting; + + // The screen off timeout setting value in milliseconds. + private int mScreenOffTimeoutSetting; + + // The maximum allowable screen off timeout according to the device + // administration policy. Overrides other settings. + private int mMaximumScreenOffTimeoutFromDeviceAdmin = Integer.MAX_VALUE; + + // The stay on while plugged in setting. + // A bitfield of battery conditions under which to make the screen stay on. + private int mStayOnWhilePluggedInSetting; + + // True if the device should stay on. + private boolean mStayOn; + + // Screen brightness setting limits. + private int mScreenBrightnessSettingMinimum; + private int mScreenBrightnessSettingMaximum; + private int mScreenBrightnessSettingDefault; + + // The screen brightness setting, from 0 to 255. + // Use -1 if no value has been set. + private int mScreenBrightnessSetting; + + // The screen auto-brightness adjustment setting, from -1 to 1. + // Use 0 if there is no adjustment. + private float mScreenAutoBrightnessAdjustmentSetting; + + // The screen brightness mode. + // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants. + private int mScreenBrightnessModeSetting; + + // The screen brightness setting override from the window manager + // to allow the current foreground activity to override the brightness. + // Use -1 to disable. + private int mScreenBrightnessOverrideFromWindowManager = -1; + + // The screen brightness setting override from the settings application + // to temporarily adjust the brightness until next updated, + // Use -1 to disable. + private int mTemporaryScreenBrightnessSettingOverride = -1; + + // The screen brightness adjustment setting override from the settings + // application to temporarily adjust the auto-brightness adjustment factor + // until next updated, in the range -1..1. + // Use NaN to disable. + private float mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN; + + private native void nativeInit(); + private static native void nativeShutdown(); + private static native void nativeReboot(String reason) throws IOException; + + private static native void nativeSetPowerState(boolean screenOn, boolean screenBright); + private static native void nativeAcquireSuspendBlocker(String name); + private static native void nativeReleaseSuspendBlocker(String name); + + static native void nativeSetScreenState(boolean on); + + public PowerManagerService() { + synchronized (mLock) { + mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService"); + mWakeLockSuspendBlocker.acquire(); + mHoldingWakeLockSuspendBlocker = true; + mWakefulness = WAKEFULNESS_AWAKE; + } + + nativeInit(); + nativeSetPowerState(true, true); + } + + /** + * Initialize the power manager. + * Must be called before any other functions within the power manager are called. + */ + public void init(Context context, LightsService ls, + ActivityManagerService am, BatteryService bs, IBatteryStats bss, + DisplayManagerService dm) { + // Forcibly turn the screen on at boot so that it is in a known power state. + // We do this in init() rather than in the constructor because setting the + // screen state requires a call into surface flinger which then needs to call back + // into the activity manager to check permissions. Unfortunately the + // activity manager is not running when the constructor is called, so we + // have to defer setting the screen state until this point. + nativeSetScreenState(true); + + mContext = context; + mLightsService = ls; + mBatteryService = bs; + mBatteryStats = bss; + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); + + Watchdog.getInstance().addMonitor(this); + } + + public void setPolicy(WindowManagerPolicy policy) { + synchronized (mLock) { + mPolicy = policy; + } + } + + public void systemReady(TwilightService twilight) { + synchronized (mLock) { + mSystemReady = true; + + PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); + mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting(); + mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting(); + + mNotifier = new Notifier(mHandler.getLooper(), mContext, mBatteryStats, + createSuspendBlockerLocked("PowerManagerService.Broadcasts"), + mPolicy, mScreenOnListener); + mDisplayPowerController = new DisplayPowerController(mHandler.getLooper(), + mContext, mNotifier, mLightsService, twilight, + createSuspendBlockerLocked("PowerManagerService.Display"), + mDisplayPowerControllerCallbacks, mHandler); + + mSettingsObserver = new SettingsObserver(mHandler); + mAttentionLight = mLightsService.getLight(LightsService.LIGHT_ID_ATTENTION); + + // Register for broadcasts from other components of the system. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(new BatteryReceiver(), filter); + + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BOOT_COMPLETED); + mContext.registerReceiver(new BootCompletedReceiver(), filter); + + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DOCK_EVENT); + mContext.registerReceiver(new DockReceiver(), filter); + + // Register for settings changes. + final ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ENABLED), false, mSettingsObserver); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP), false, mSettingsObserver); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_OFF_TIMEOUT), false, mSettingsObserver); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.STAY_ON_WHILE_PLUGGED_IN), false, mSettingsObserver); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_BRIGHTNESS), false, mSettingsObserver); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_BRIGHTNESS_MODE), false, mSettingsObserver); + + // Go. + readConfigurationLocked(); + updateSettingsLocked(); + mDirty |= DIRTY_BATTERY_STATE; + updatePowerStateLocked(); + } + } + + private void readConfigurationLocked() { + final Resources resources = mContext.getResources(); + + mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean( + com.android.internal.R.bool.config_unplugTurnsOnScreen); + mDreamsSupportedConfig = resources.getBoolean( + com.android.internal.R.bool.config_enableDreams); + } + + private void updateSettingsLocked() { + final ContentResolver resolver = mContext.getContentResolver(); + + mDreamsEnabledSetting = (Settings.Secure.getInt(resolver, + Settings.Secure.SCREENSAVER_ENABLED, 0) != 0); + mDreamsActivateOnSleepSetting = (Settings.Secure.getInt(resolver, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 0) != 0); + mScreenOffTimeoutSetting = Settings.System.getInt(resolver, + Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT); + mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver, + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, + BatteryManager.BATTERY_PLUGGED_AC); + + final int oldScreenBrightnessSetting = mScreenBrightnessSetting; + mScreenBrightnessSetting = Settings.System.getInt(resolver, + Settings.System.SCREEN_BRIGHTNESS, mScreenBrightnessSettingDefault); + if (oldScreenBrightnessSetting != mScreenBrightnessSetting) { + mTemporaryScreenBrightnessSettingOverride = -1; + } + + final float oldScreenAutoBrightnessAdjustmentSetting = + mScreenAutoBrightnessAdjustmentSetting; + mScreenAutoBrightnessAdjustmentSetting = Settings.System.getFloat(resolver, + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f); + if (oldScreenAutoBrightnessAdjustmentSetting != mScreenAutoBrightnessAdjustmentSetting) { + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN; + } + + mScreenBrightnessModeSetting = Settings.System.getInt(resolver, + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + + mDirty |= DIRTY_SETTINGS; + } + + private void handleSettingsChangedLocked() { + updateSettingsLocked(); + updatePowerStateLocked(); + } + + @Override // Binder call + public void acquireWakeLock(IBinder lock, int flags, String tag, WorkSource ws) { + if (lock == null) { + throw new IllegalArgumentException("lock must not be null"); + } + PowerManager.validateWakeLockParameters(flags, tag); + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); + if (ws != null && ws.size() != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, null); + } else { + ws = null; + } + + final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); + final long ident = Binder.clearCallingIdentity(); + try { + acquireWakeLockInternal(lock, flags, tag, ws, uid, pid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void acquireWakeLockInternal(IBinder lock, int flags, String tag, WorkSource ws, + int uid, int pid) { + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "acquireWakeLockInternal: lock=" + Objects.hashCode(lock) + + ", flags=0x" + Integer.toHexString(flags) + + ", tag=\"" + tag + "\", ws=" + ws + ", uid=" + uid + ", pid=" + pid); + } + + WakeLock wakeLock; + int index = findWakeLockIndexLocked(lock); + if (index >= 0) { + wakeLock = mWakeLocks.get(index); + if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) { + // Update existing wake lock. This shouldn't happen but is harmless. + notifyWakeLockReleasedLocked(wakeLock); + wakeLock.updateProperties(flags, tag, ws, uid, pid); + notifyWakeLockAcquiredLocked(wakeLock); + } + } else { + wakeLock = new WakeLock(lock, flags, tag, ws, uid, pid); + try { + lock.linkToDeath(wakeLock, 0); + } catch (RemoteException ex) { + throw new IllegalArgumentException("Wake lock is already dead."); + } + notifyWakeLockAcquiredLocked(wakeLock); + mWakeLocks.add(wakeLock); + } + + applyWakeLockFlagsOnAcquireLocked(wakeLock); + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + } + + private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock) { + if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { + wakeUpNoUpdateLocked(SystemClock.uptimeMillis()); + } + } + + @Override // Binder call + public void releaseWakeLock(IBinder lock, int flags) { + if (lock == null) { + throw new IllegalArgumentException("lock must not be null"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); + + final long ident = Binder.clearCallingIdentity(); + try { + releaseWakeLockInternal(lock, flags); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void releaseWakeLockInternal(IBinder lock, int flags) { + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock) + + ", flags=0x" + Integer.toHexString(flags)); + } + + int index = findWakeLockIndexLocked(lock); + if (index < 0) { + return; + } + + WakeLock wakeLock = mWakeLocks.get(index); + mWakeLocks.remove(index); + notifyWakeLockReleasedLocked(wakeLock); + wakeLock.mLock.unlinkToDeath(wakeLock, 0); + + if ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0) { + mRequestWaitForNegativeProximity = true; + } + + applyWakeLockFlagsOnReleaseLocked(wakeLock); + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + } + + private void handleWakeLockDeath(WakeLock wakeLock) { + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "handleWakeLockDeath: lock=" + Objects.hashCode(wakeLock.mLock)); + } + + int index = mWakeLocks.indexOf(wakeLock); + if (index < 0) { + return; + } + + mWakeLocks.remove(index); + notifyWakeLockReleasedLocked(wakeLock); + + applyWakeLockFlagsOnReleaseLocked(wakeLock); + mDirty |= DIRTY_WAKE_LOCKS; + updatePowerStateLocked(); + } + } + + private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) { + if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0) { + userActivityNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_OTHER, + PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS, + wakeLock.mOwnerUid); + } + } + + @Override // Binder call + public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) { + if (lock == null) { + throw new IllegalArgumentException("lock must not be null"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); + if (ws != null && ws.size() != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, null); + } else { + ws = null; + } + + final long ident = Binder.clearCallingIdentity(); + try { + updateWakeLockWorkSourceInternal(lock, ws); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void updateWakeLockWorkSourceInternal(IBinder lock, WorkSource ws) { + synchronized (mLock) { + int index = findWakeLockIndexLocked(lock); + if (index < 0) { + throw new IllegalArgumentException("Wake lock not active"); + } + + WakeLock wakeLock = mWakeLocks.get(index); + if (!wakeLock.hasSameWorkSource(ws)) { + notifyWakeLockReleasedLocked(wakeLock); + wakeLock.updateWorkSource(ws); + notifyWakeLockAcquiredLocked(wakeLock); + } + } + } + + private int findWakeLockIndexLocked(IBinder lock) { + final int count = mWakeLocks.size(); + for (int i = 0; i < count; i++) { + if (mWakeLocks.get(i).mLock == lock) { + return i; + } + } + return -1; + } + + private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) { + if (mSystemReady) { + mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, + wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource); + } + } + + private void notifyWakeLockReleasedLocked(WakeLock wakeLock) { + if (mSystemReady) { + mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag, + wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource); + } + } + + @Override // Binder call + public boolean isWakeLockLevelSupported(int level) { + final long ident = Binder.clearCallingIdentity(); + try { + return isWakeLockLevelSupportedInternal(level); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private boolean isWakeLockLevelSupportedInternal(int level) { + synchronized (mLock) { + switch (level) { + case PowerManager.PARTIAL_WAKE_LOCK: + case PowerManager.SCREEN_DIM_WAKE_LOCK: + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + case PowerManager.FULL_WAKE_LOCK: + return true; + + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + return mSystemReady && mDisplayPowerController.isProximitySensorAvailable(); + + default: + return false; + } + } + } + + @Override // Binder call + public void userActivity(long eventTime, int event, int flags) { + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + userActivityInternal(eventTime, event, flags, uid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Called from native code. + private void userActivityFromNative(long eventTime, int event, int flags) { + userActivityInternal(eventTime, event, flags, Process.SYSTEM_UID); + } + + private void userActivityInternal(long eventTime, int event, int flags, int uid) { + synchronized (mLock) { + if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) { + updatePowerStateLocked(); + } + } + } + + private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { + if (DEBUG_SPEW) { + Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime + + ", event=" + event + ", flags=0x" + Integer.toHexString(flags) + + ", uid=" + uid); + } + + if (eventTime < mLastSleepTime || eventTime < mLastWakeTime + || mWakefulness == WAKEFULNESS_ASLEEP || !mBootCompleted || !mSystemReady) { + return false; + } + + mNotifier.onUserActivity(event, uid); + + if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) { + if (eventTime > mLastUserActivityTimeNoChangeLights + && eventTime > mLastUserActivityTime) { + mLastUserActivityTimeNoChangeLights = eventTime; + mDirty |= DIRTY_USER_ACTIVITY; + return true; + } + } else { + if (eventTime > mLastUserActivityTime) { + mLastUserActivityTime = eventTime; + mDirty |= DIRTY_USER_ACTIVITY; + return true; + } + } + return false; + } + + @Override // Binder call + public void wakeUp(long eventTime) { + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + wakeUpInternal(eventTime); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Called from native code. + private void wakeUpFromNative(long eventTime) { + wakeUpInternal(eventTime); + } + + private void wakeUpInternal(long eventTime) { + synchronized (mLock) { + if (wakeUpNoUpdateLocked(eventTime)) { + updatePowerStateLocked(); + } + } + } + + private boolean wakeUpNoUpdateLocked(long eventTime) { + if (DEBUG_SPEW) { + Slog.d(TAG, "wakeUpNoUpdateLocked: eventTime=" + eventTime); + } + + if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE + || !mBootCompleted || !mSystemReady) { + return false; + } + + switch (mWakefulness) { + case WAKEFULNESS_ASLEEP: + Slog.i(TAG, "Waking up from sleep..."); + mNotifier.onWakeUpStarted(); + mSendWakeUpFinishedNotificationWhenReady = true; + mSendGoToSleepFinishedNotificationWhenReady = false; + break; + case WAKEFULNESS_DREAMING: + Slog.i(TAG, "Waking up from dream..."); + break; + case WAKEFULNESS_NAPPING: + Slog.i(TAG, "Waking up from nap..."); + break; + } + + mLastWakeTime = eventTime; + mWakefulness = WAKEFULNESS_AWAKE; + mDirty |= DIRTY_WAKEFULNESS; + + userActivityNoUpdateLocked( + eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + return true; + } + + @Override // Binder call + public void goToSleep(long eventTime, int reason) { + if (eventTime > SystemClock.uptimeMillis()) { + throw new IllegalArgumentException("event time must not be in the future"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + goToSleepInternal(eventTime, reason); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Called from native code. + private void goToSleepFromNative(long eventTime, int reason) { + goToSleepInternal(eventTime, reason); + } + + private void goToSleepInternal(long eventTime, int reason) { + synchronized (mLock) { + if (goToSleepNoUpdateLocked(eventTime, reason)) { + updatePowerStateLocked(); + } + } + } + + private boolean goToSleepNoUpdateLocked(long eventTime, int reason) { + if (DEBUG_SPEW) { + Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime + ", reason=" + reason); + } + + if (eventTime < mLastWakeTime || mWakefulness == WAKEFULNESS_ASLEEP + || !mBootCompleted || !mSystemReady) { + return false; + } + + switch (reason) { + case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN: + Slog.i(TAG, "Going to sleep due to device administration policy..."); + break; + case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT: + Slog.i(TAG, "Going to sleep due to screen timeout..."); + break; + default: + Slog.i(TAG, "Going to sleep by user request..."); + reason = PowerManager.GO_TO_SLEEP_REASON_USER; + break; + } + + mLastSleepTime = eventTime; + mDirty |= DIRTY_WAKEFULNESS; + mWakefulness = WAKEFULNESS_ASLEEP; + mNotifier.onGoToSleepStarted(reason); + mSendGoToSleepFinishedNotificationWhenReady = true; + mSendWakeUpFinishedNotificationWhenReady = false; + + // Report the number of wake locks that will be cleared by going to sleep. + int numWakeLocksCleared = 0; + final int numWakeLocks = mWakeLocks.size(); + for (int i = 0; i < numWakeLocks; i++) { + final WakeLock wakeLock = mWakeLocks.get(i); + switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK: + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + case PowerManager.SCREEN_DIM_WAKE_LOCK: + numWakeLocksCleared += 1; + break; + } + } + EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numWakeLocksCleared); + return true; + } + + /** + * Updates the global power state based on dirty bits recorded in mDirty. + * + * This is the main function that performs power state transitions. + * We centralize them here so that we can recompute the power state completely + * each time something important changes, and ensure that we do it the same + * way each time. The point is to gather all of the transition logic here. + */ + private void updatePowerStateLocked() { + if (!mSystemReady || mDirty == 0) { + return; + } + + // Phase 0: Basic state updates. + updateIsPoweredLocked(mDirty); + updateStayOnLocked(mDirty); + + // Phase 1: Update wakefulness. + // Loop because the wake lock and user activity computations are influenced + // by changes in wakefulness. + final long now = SystemClock.uptimeMillis(); + int dirtyPhase2 = 0; + for (;;) { + int dirtyPhase1 = mDirty; + dirtyPhase2 |= dirtyPhase1; + mDirty = 0; + + updateWakeLockSummaryLocked(dirtyPhase1); + updateUserActivitySummaryLocked(now, dirtyPhase1); + if (!updateWakefulnessLocked(dirtyPhase1)) { + break; + } + } + + // Phase 2: Update dreams and display power state. + updateDreamLocked(dirtyPhase2); + updateDisplayPowerStateLocked(dirtyPhase2); + + // Phase 3: Send notifications, if needed. + sendPendingNotificationsLocked(); + + // Phase 4: Update suspend blocker. + // Because we might release the last suspend blocker here, we need to make sure + // we finished everything else first! + updateSuspendBlockerLocked(); + } + + private void sendPendingNotificationsLocked() { + if (mDisplayReady) { + if (mSendWakeUpFinishedNotificationWhenReady) { + mSendWakeUpFinishedNotificationWhenReady = false; + mNotifier.onWakeUpFinished(); + } + if (mSendGoToSleepFinishedNotificationWhenReady) { + mSendGoToSleepFinishedNotificationWhenReady = false; + mNotifier.onGoToSleepFinished(); + } + } + } + + /** + * Updates the value of mIsPowered. + * Sets DIRTY_IS_POWERED if a change occurred. + */ + private void updateIsPoweredLocked(int dirty) { + if ((dirty & DIRTY_BATTERY_STATE) != 0) { + boolean wasPowered = mIsPowered; + mIsPowered = mBatteryService.isPowered(); + + if (wasPowered != mIsPowered) { + mDirty |= DIRTY_IS_POWERED; + + // Treat plugging and unplugging the devices as a user activity. + // Users find it disconcerting when they plug or unplug the device + // and it shuts off right away. + // Some devices also wake the device when plugged or unplugged because + // they don't have a charging LED. + final long now = SystemClock.uptimeMillis(); + if (mWakeUpWhenPluggedOrUnpluggedConfig) { + wakeUpNoUpdateLocked(now); + } + userActivityNoUpdateLocked( + now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + } + } + } + + /** + * Updates the value of mStayOn. + * Sets DIRTY_STAY_ON if a change occurred. + */ + private void updateStayOnLocked(int dirty) { + if ((dirty & (DIRTY_BATTERY_STATE | DIRTY_SETTINGS)) != 0) { + if (mStayOnWhilePluggedInSetting != 0 + && !isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { + mStayOn = mBatteryService.isPowered(mStayOnWhilePluggedInSetting); + } else { + mStayOn = false; + } + } + } + + /** + * Updates the value of mWakeLockSummary to summarize the state of all active wake locks. + * Note that most wake-locks are ignored when the system is asleep. + * + * This function must have no other side-effects. + */ + private void updateWakeLockSummaryLocked(int dirty) { + if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS)) != 0) { + mWakeLockSummary = 0; + + final int numWakeLocks = mWakeLocks.size(); + for (int i = 0; i < numWakeLocks; i++) { + final WakeLock wakeLock = mWakeLocks.get(i); + switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.PARTIAL_WAKE_LOCK: + mWakeLockSummary |= WAKE_LOCK_CPU; + break; + case PowerManager.FULL_WAKE_LOCK: + if (mWakefulness != WAKEFULNESS_ASLEEP) { + mWakeLockSummary |= WAKE_LOCK_CPU + | WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT; + } + break; + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + if (mWakefulness != WAKEFULNESS_ASLEEP) { + mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_SCREEN_BRIGHT; + } + break; + case PowerManager.SCREEN_DIM_WAKE_LOCK: + if (mWakefulness != WAKEFULNESS_ASLEEP) { + mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_SCREEN_DIM; + } + break; + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + if (mWakefulness != WAKEFULNESS_ASLEEP) { + mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_PROXIMITY_SCREEN_OFF; + } + break; + } + } + + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness=" + + wakefulnessToString(mWakefulness) + + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)); + } + } + } + + /** + * Updates the value of mUserActivitySummary to summarize the user requested + * state of the system such as whether the screen should be bright or dim. + * Note that user activity is ignored when the system is asleep. + * + * This function must have no other side-effects. + */ + private void updateUserActivitySummaryLocked(long now, int dirty) { + // Update the status of the user activity timeout timer. + if ((dirty & (DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) { + mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); + + long nextTimeout = 0; + if (mWakefulness != WAKEFULNESS_ASLEEP) { + final int screenOffTimeout = getScreenOffTimeoutLocked(); + final int screenDimDuration = getScreenDimDurationLocked(); + + mUserActivitySummary = 0; + if (mLastUserActivityTime >= mLastWakeTime) { + nextTimeout = mLastUserActivityTime + + screenOffTimeout - screenDimDuration; + if (now < nextTimeout) { + mUserActivitySummary |= USER_ACTIVITY_SCREEN_BRIGHT; + } else { + nextTimeout = mLastUserActivityTime + screenOffTimeout; + if (now < nextTimeout) { + mUserActivitySummary |= USER_ACTIVITY_SCREEN_DIM; + } + } + } + if (mUserActivitySummary == 0 + && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) { + nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout; + if (now < nextTimeout + && mDisplayPowerRequest.screenState + != DisplayPowerRequest.SCREEN_STATE_OFF) { + mUserActivitySummary = mDisplayPowerRequest.screenState + == DisplayPowerRequest.SCREEN_STATE_BRIGHT ? + USER_ACTIVITY_SCREEN_BRIGHT : USER_ACTIVITY_SCREEN_DIM; + } + } + if (mUserActivitySummary != 0) { + Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, nextTimeout); + } + } else { + mUserActivitySummary = 0; + } + + if (DEBUG_SPEW) { + Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness=" + + wakefulnessToString(mWakefulness) + + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout)); + } + } + } + + /** + * Called when a user activity timeout has occurred. + * Simply indicates that something about user activity has changed so that the new + * state can be recomputed when the power state is updated. + * + * This function must have no other side-effects besides setting the dirty + * bit and calling update power state. Wakefulness transitions are handled elsewhere. + */ + private void handleUserActivityTimeout() { // runs on handler thread + synchronized (mLock) { + if (DEBUG_SPEW) { + Slog.d(TAG, "handleUserActivityTimeout"); + } + + mDirty |= DIRTY_USER_ACTIVITY; + updatePowerStateLocked(); + } + } + + private int getScreenOffTimeoutLocked() { + int timeout = mScreenOffTimeoutSetting; + if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { + timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin); + } + return Math.max(timeout, MINIMUM_SCREEN_OFF_TIMEOUT); + } + + private int getScreenDimDurationLocked() { + return SCREEN_DIM_DURATION; + } + + /** + * Updates the wakefulness of the device. + * + * This is the function that decides whether the device should start napping + * based on the current wake locks and user activity state. It may modify mDirty + * if the wakefulness changes. + * + * Returns true if the wakefulness changed and we need to restart power state calculation. + */ + private boolean updateWakefulnessLocked(int dirty) { + boolean changed = false; + if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED + | DIRTY_WAKEFULNESS | DIRTY_STAY_ON)) != 0) { + if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) { + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakefulnessLocked: Nap time..."); + } + mWakefulness = WAKEFULNESS_NAPPING; + mDirty |= DIRTY_WAKEFULNESS; + changed = true; + } + } + return changed; + } + + // Also used when exiting a dream to determine whether we should go back + // to being fully awake or else go to sleep for good. + private boolean isItBedTimeYetLocked() { + return mBootCompleted && !mStayOn + && (mWakeLockSummary + & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM + | WAKE_LOCK_PROXIMITY_SCREEN_OFF)) == 0 + && (mUserActivitySummary + & (USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) == 0; + } + + /** + * Determines whether to post a message to the sandman to update the dream state. + */ + private void updateDreamLocked(int dirty) { + if ((dirty & (DIRTY_WAKEFULNESS | DIRTY_SETTINGS + | DIRTY_IS_POWERED | DIRTY_STAY_ON | DIRTY_BATTERY_STATE)) != 0) { + scheduleSandmanLocked(); + } + } + + private void scheduleSandmanLocked() { + if (!mSandmanScheduled) { + mSandmanScheduled = true; + Message msg = mHandler.obtainMessage(MSG_SANDMAN); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + } + } + + /** + * Called when the device enters or exits a napping or dreaming state. + * + * We do this asynchronously because we must call out of the power manager to start + * the dream and we don't want to hold our lock while doing so. There is a risk that + * the device will wake or go to sleep in the meantime so we have to handle that case. + */ + private void handleSandman() { // runs on handler thread + // Handle preconditions. + boolean startDreaming = false; + synchronized (mLock) { + mSandmanScheduled = false; + boolean canDream = canDreamLocked(); + if (DEBUG_SPEW) { + Log.d(TAG, "handleSandman: canDream=" + canDream + + ", mWakefulness=" + wakefulnessToString(mWakefulness)); + } + + if (canDream && mWakefulness == WAKEFULNESS_NAPPING) { + startDreaming = true; + } + } + + // Get the dream manager, if needed. + if (startDreaming && mDreamManager == null) { + mDreamManager = IDreamManager.Stub.asInterface( + ServiceManager.checkService("dreams")); + if (mDreamManager == null) { + Slog.w(TAG, "Unable to find IDreamManager."); + } + } + + // Start dreaming if needed. + // We only control the dream on the handler thread, so we don't need to worry about + // concurrent attempts to start or stop the dream. + boolean isDreaming = false; + if (mDreamManager != null) { + try { + isDreaming = mDreamManager.isDreaming(); + if (startDreaming && !isDreaming) { + Slog.i(TAG, "Entering dreamland."); + mDreamManager.dream(); + isDreaming = mDreamManager.isDreaming(); + if (!isDreaming) { + Slog.i(TAG, "Could not enter dreamland. Sleep will be dreamless."); + } + } + } catch (RemoteException ex) { + } + } + + // Update dream state. + // We might need to stop the dream again if the preconditions changed. + boolean continueDreaming = false; + synchronized (mLock) { + if (isDreaming && canDreamLocked()) { + if (mWakefulness == WAKEFULNESS_NAPPING) { + mWakefulness = WAKEFULNESS_DREAMING; + mDirty |= DIRTY_WAKEFULNESS; + updatePowerStateLocked(); + continueDreaming = true; + } else if (mWakefulness == WAKEFULNESS_DREAMING) { + continueDreaming = true; + } + } + if (!continueDreaming) { + handleDreamFinishedLocked(); + } + + // Allow the sandman to detect when the dream has ended. + // FIXME: The DreamManagerService should tell us explicitly. + if (mWakefulness == WAKEFULNESS_DREAMING + || mWakefulness == WAKEFULNESS_NAPPING) { + if (!mSandmanScheduled) { + mSandmanScheduled = true; + Message msg = mHandler.obtainMessage(MSG_SANDMAN); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, 1000); + } + } + } + + // Stop dreaming if needed. + // It's possible that something else changed to make us need to start the dream again. + // If so, then the power manager will have posted another message to the handler + // to take care of it later. + if (mDreamManager != null) { + try { + if (!continueDreaming && isDreaming) { + Slog.i(TAG, "Leaving dreamland."); + mDreamManager.awaken(); + } + } catch (RemoteException ex) { + } + } + } + + /** + * Returns true if the device is allowed to dream in its current state, + * assuming there has been no recent user activity and no wake locks are held. + */ + private boolean canDreamLocked() { + return mIsPowered + && mDreamsSupportedConfig + && mDreamsEnabledSetting + && mDreamsActivateOnSleepSetting + && !mBatteryService.isBatteryLow(); + } + + /** + * Called when a dream is ending to figure out what to do next. + */ + private void handleDreamFinishedLocked() { + if (mWakefulness == WAKEFULNESS_NAPPING + || mWakefulness == WAKEFULNESS_DREAMING) { + if (isItBedTimeYetLocked()) { + goToSleepNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + updatePowerStateLocked(); + } else { + wakeUpNoUpdateLocked(SystemClock.uptimeMillis()); + updatePowerStateLocked(); + } + } + } + + + /** + * Updates the display power state asynchronously. + * When the update is finished, mDisplayReady will be set to true. The display + * controller posts a message to tell us when the actual display power state + * has been updated so we come back here to double-check and finish up. + * + * This function recalculates the display power state each time. + */ + private void updateDisplayPowerStateLocked(int dirty) { + if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS + | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED + | DIRTY_SETTINGS)) != 0) { + int newScreenState = getDesiredScreenPowerState(); + if (newScreenState != mDisplayPowerRequest.screenState) { + if (newScreenState == DisplayPowerRequest.SCREEN_STATE_OFF + && mDisplayPowerRequest.screenState + != DisplayPowerRequest.SCREEN_STATE_OFF) { + mLastScreenOffEventElapsedRealTime = SystemClock.elapsedRealtime(); + } + + mDisplayPowerRequest.screenState = newScreenState; + nativeSetPowerState( + newScreenState != DisplayPowerRequest.SCREEN_STATE_OFF, + newScreenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT); + } + + int screenBrightness = mScreenBrightnessSettingDefault; + float screenAutoBrightnessAdjustment = 0.0f; + boolean autoBrightness = (mScreenBrightnessModeSetting == + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) { + screenBrightness = mScreenBrightnessOverrideFromWindowManager; + autoBrightness = false; + } else if (isValidBrightness(mTemporaryScreenBrightnessSettingOverride)) { + screenBrightness = mTemporaryScreenBrightnessSettingOverride; + } else if (isValidBrightness(mScreenBrightnessSetting)) { + screenBrightness = mScreenBrightnessSetting; + } + if (autoBrightness) { + screenBrightness = mScreenBrightnessSettingDefault; + if (isValidAutoBrightnessAdjustment( + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride)) { + screenAutoBrightnessAdjustment = + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride; + } else if (isValidAutoBrightnessAdjustment( + mScreenAutoBrightnessAdjustmentSetting)) { + screenAutoBrightnessAdjustment = mScreenAutoBrightnessAdjustmentSetting; + } + } + screenBrightness = Math.max(Math.min(screenBrightness, + mScreenBrightnessSettingMaximum), mScreenBrightnessSettingMinimum); + screenAutoBrightnessAdjustment = Math.max(Math.min( + screenAutoBrightnessAdjustment, 1.0f), -1.0f); + mDisplayPowerRequest.screenBrightness = screenBrightness; + mDisplayPowerRequest.screenAutoBrightnessAdjustment = + screenAutoBrightnessAdjustment; + mDisplayPowerRequest.useAutoBrightness = autoBrightness; + + mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked(); + + mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest, + mRequestWaitForNegativeProximity); + mRequestWaitForNegativeProximity = false; + + if (DEBUG_SPEW) { + Slog.d(TAG, "updateScreenStateLocked: displayReady=" + mDisplayReady + + ", newScreenState=" + newScreenState + + ", mWakefulness=" + mWakefulness + + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) + + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + + ", mBootCompleted=" + mBootCompleted); + } + } + } + + private static boolean isValidBrightness(int value) { + return value >= 0 && value <= 255; + } + + private static boolean isValidAutoBrightnessAdjustment(float value) { + // Handles NaN by always returning false. + return value >= -1.0f && value <= 1.0f; + } + + private int getDesiredScreenPowerState() { + if (mWakefulness == WAKEFULNESS_ASLEEP) { + return DisplayPowerRequest.SCREEN_STATE_OFF; + } + + if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0 + || (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0 + || !mBootCompleted) { + return DisplayPowerRequest.SCREEN_STATE_BRIGHT; + } + + return DisplayPowerRequest.SCREEN_STATE_DIM; + } + + private final DisplayPowerController.Callbacks mDisplayPowerControllerCallbacks = + new DisplayPowerController.Callbacks() { + @Override + public void onStateChanged() { + mDirty |= DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED; + updatePowerStateLocked(); + } + + @Override + public void onProximityNegative() { + userActivityNoUpdateLocked(SystemClock.uptimeMillis(), + PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + updatePowerStateLocked(); + } + }; + + private boolean shouldUseProximitySensorLocked() { + return (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0; + } + + /** + * Updates the suspend blocker that keeps the CPU alive. + * + * This function must have no other side-effects. + */ + private void updateSuspendBlockerLocked() { + boolean wantCpu = isCpuNeededLocked(); + if (wantCpu != mHoldingWakeLockSuspendBlocker) { + mHoldingWakeLockSuspendBlocker = wantCpu; + if (wantCpu) { + if (DEBUG) { + Slog.d(TAG, "updateSuspendBlockerLocked: Acquiring suspend blocker."); + } + mWakeLockSuspendBlocker.acquire(); + } else { + if (DEBUG) { + Slog.d(TAG, "updateSuspendBlockerLocked: Releasing suspend blocker."); + } + mWakeLockSuspendBlocker.release(); + } + } + } + + private boolean isCpuNeededLocked() { + return !mBootCompleted + || mWakeLockSummary != 0 + || mUserActivitySummary != 0 + || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF + || !mDisplayReady; + } + + @Override // Binder call + public boolean isScreenOn() { + final long ident = Binder.clearCallingIdentity(); + try { + return isScreenOnInternal(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private boolean isScreenOnInternal() { + synchronized (mLock) { + return !mSystemReady + || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF; + } + } + + private void handleBatteryStateChangedLocked() { + mDirty |= DIRTY_BATTERY_STATE; + updatePowerStateLocked(); + } + + private void handleBootCompletedLocked() { + final long now = SystemClock.uptimeMillis(); + mBootCompleted = true; + mDirty |= DIRTY_BOOT_COMPLETED; + userActivityNoUpdateLocked( + now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID); + updatePowerStateLocked(); + } + + private void handleDockStateChangedLocked(int dockState) { + // TODO + } + + /** + * Reboot the device immediately, passing 'reason' (may be null) + * to the underlying __reboot system call. Should not return. + */ + @Override // Binder call + public void reboot(String reason) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); + + final long ident = Binder.clearCallingIdentity(); + try { + rebootInternal(reason); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void rebootInternal(final String reason) { + if (mHandler == null || !mSystemReady) { + throw new IllegalStateException("Too early to call reboot()"); + } + + Runnable runnable = new Runnable() { + public void run() { + synchronized (this) { + ShutdownThread.reboot(mContext, reason, false); + } + } + }; + + // ShutdownThread must run on a looper capable of displaying the UI. + Message msg = Message.obtain(mHandler, runnable); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + + // PowerManager.reboot() is documented not to return so just wait for the inevitable. + synchronized (runnable) { + while (true) { + try { + runnable.wait(); + } catch (InterruptedException e) { + } + } + } + } + + /** + * Crash the runtime (causing a complete restart of the Android framework). + * Requires REBOOT permission. Mostly for testing. Should not return. + */ + @Override // Binder call + public void crash(String message) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null); + + final long ident = Binder.clearCallingIdentity(); + try { + crashInternal(message); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void crashInternal(final String message) { + Thread t = new Thread("PowerManagerService.crash()") { + public void run() { + throw new RuntimeException(message); + } + }; + try { + t.start(); + t.join(); + } catch (InterruptedException e) { + Log.wtf(TAG, e); + } + } + + @Override // Binder call + public void clearUserActivityTimeout(long now, long timeout) { + // TODO Auto-generated method stub + // Only used by phone app, delete this + } + + @Override // Binder call + public void setPokeLock(int pokey, IBinder lock, String tag) { + // TODO Auto-generated method stub + // Only used by phone app, delete this + } + + /** + * Set the setting that determines whether the device stays on when plugged in. + * The argument is a bit string, with each bit specifying a power source that, + * when the device is connected to that source, causes the device to stay on. + * See {@link android.os.BatteryManager} for the list of power sources that + * can be specified. Current values include {@link android.os.BatteryManager#BATTERY_PLUGGED_AC} + * and {@link android.os.BatteryManager#BATTERY_PLUGGED_USB} + * + * Used by "adb shell svc power stayon ..." + * + * @param val an {@code int} containing the bits that specify which power sources + * should cause the device to stay on. + */ + @Override // Binder call + public void setStayOnSetting(int val) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setStayOnSettingInternal(val); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setStayOnSettingInternal(int val) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val); + } + + /** + * Used by device administration to set the maximum screen off timeout. + * + * This method must only be called by the device administration policy manager. + */ + @Override // Binder call + public void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs) { + final long ident = Binder.clearCallingIdentity(); + try { + setMaximumScreenOffTimeoutFromDeviceAdminInternal(timeMs); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) { + synchronized (mLock) { + mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + + private boolean isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() { + return mMaximumScreenOffTimeoutFromDeviceAdmin >= 0 + && mMaximumScreenOffTimeoutFromDeviceAdmin < Integer.MAX_VALUE; + } + + @Override // Binder call + public void preventScreenOn(boolean prevent) { + // TODO Auto-generated method stub + // Only used by phone app, delete this + } + + /** + * Used by the phone application to make the attention LED flash when ringing. + */ + @Override // Binder call + public void setAttentionLight(boolean on, int color) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setAttentionLightInternal(on, color); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setAttentionLightInternal(boolean on, int color) { + LightsService.Light light; + synchronized (mLock) { + if (!mSystemReady) { + return; + } + light = mAttentionLight; + } + + // Control light outside of lock. + light.setFlashing(color, LightsService.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0); + } + + /** + * Used by the Watchdog. + */ + public long timeSinceScreenWasLastOn() { + synchronized (mLock) { + if (mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { + return 0; + } + return SystemClock.elapsedRealtime() - mLastScreenOffEventElapsedRealTime; + } + } + + /** + * Used by the window manager to override the screen brightness based on the + * current foreground activity. + * + * This method must only be called by the window manager. + * + * @param brightness The overridden brightness, or -1 to disable the override. + */ + public void setScreenBrightnessOverrideFromWindowManager(int brightness) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setScreenBrightnessOverrideFromWindowManagerInternal(brightness); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setScreenBrightnessOverrideFromWindowManagerInternal(int brightness) { + synchronized (mLock) { + if (mScreenBrightnessOverrideFromWindowManager != brightness) { + mScreenBrightnessOverrideFromWindowManager = brightness; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + + /** + * Used by the window manager to override the button brightness based on the + * current foreground activity. + * + * This method must only be called by the window manager. + * + * @param brightness The overridden brightness, or -1 to disable the override. + */ + public void setButtonBrightnessOverrideFromWindowManager(int brightness) { + // Do nothing. + // Button lights are not currently supported in the new implementation. + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + } + + /** + * Used by the settings application and brightness control widgets to + * temporarily override the current screen brightness setting so that the + * user can observe the effect of an intended settings change without applying + * it immediately. + * + * The override will be canceled when the setting value is next updated. + * + * @param brightness The overridden brightness. + * + * @see Settings.System#SCREEN_BRIGHTNESS + */ + @Override // Binder call + public void setTemporaryScreenBrightnessSettingOverride(int brightness) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setTemporaryScreenBrightnessSettingOverrideInternal(brightness); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setTemporaryScreenBrightnessSettingOverrideInternal(int brightness) { + synchronized (mLock) { + if (mTemporaryScreenBrightnessSettingOverride != brightness) { + mTemporaryScreenBrightnessSettingOverride = brightness; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + + /** + * Used by the settings application and brightness control widgets to + * temporarily override the current screen auto-brightness adjustment setting so that the + * user can observe the effect of an intended settings change without applying + * it immediately. + * + * The override will be canceled when the setting value is next updated. + * + * @param adj The overridden brightness, or Float.NaN to disable the override. + * + * @see Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ + */ + @Override // Binder call + public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + + final long ident = Binder.clearCallingIdentity(); + try { + setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(adj); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(float adj) { + synchronized (mLock) { + // Note: This condition handles NaN because NaN is not equal to any other + // value, including itself. + if (mTemporaryScreenAutoBrightnessAdjustmentSettingOverride != adj) { + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = adj; + mDirty |= DIRTY_SETTINGS; + updatePowerStateLocked(); + } + } + } + + /** + * Low-level function turn the device off immediately, without trying + * to be clean. Most people should use {@link ShutdownThread} for a clean shutdown. + */ + public static void lowLevelShutdown() { + nativeShutdown(); + } + + /** + * Low-level function to reboot the device. + * + * @param reason code to pass to the kernel (e.g. "recovery"), or null. + * @throws IOException if reboot fails for some reason (eg, lack of + * permission) + */ + public static void lowLevelReboot(String reason) throws IOException { + nativeReboot(reason); + } + + @Override // Watchdog.Monitor implementation + public void monitor() { + // Grab and release lock for watchdog monitor to detect deadlocks. + synchronized (mLock) { + } + } + + @Override // Binder call + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump PowerManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("POWER MANAGER (dumpsys power)\n"); + + final DisplayPowerController dpc; + synchronized (mLock) { + pw.println("Power Manager State:"); + pw.println(" mDirty=0x" + Integer.toHexString(mDirty)); + pw.println(" mWakefulness=" + wakefulnessToString(mWakefulness)); + pw.println(" mIsPowered=" + mIsPowered); + pw.println(" mStayOn=" + mStayOn); + pw.println(" mBootCompleted=" + mBootCompleted); + pw.println(" mSystemReady=" + mSystemReady); + pw.println(" mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)); + pw.println(" mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)); + pw.println(" mRequestWaitForNegativeProximity=" + mRequestWaitForNegativeProximity); + pw.println(" mSandmanScheduled=" + mSandmanScheduled); + pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastWakeTime)); + pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastSleepTime)); + pw.println(" mSendWakeUpFinishedNotificationWhenReady=" + + mSendWakeUpFinishedNotificationWhenReady); + pw.println(" mSendGoToSleepFinishedNotificationWhenReady=" + + mSendGoToSleepFinishedNotificationWhenReady); + pw.println(" mLastUserActivityTime=" + TimeUtils.formatUptime(mLastUserActivityTime)); + pw.println(" mLastUserActivityTimeNoChangeLights=" + + TimeUtils.formatUptime(mLastUserActivityTimeNoChangeLights)); + pw.println(" mDisplayReady=" + mDisplayReady); + pw.println(" mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker); + + pw.println(); + pw.println("Settings and Configuration:"); + pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); + pw.println(" mDreamsEnabledSetting=" + mDreamsEnabledSetting); + pw.println(" mDreamsActivateOnSleepSetting=" + mDreamsActivateOnSleepSetting); + pw.println(" mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting); + pw.println(" mMaximumScreenOffTimeoutFromDeviceAdmin=" + + mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced=" + + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")"); + pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting); + pw.println(" mScreenBrightnessSetting=" + mScreenBrightnessSetting); + pw.println(" mScreenAutoBrightnessAdjustmentSetting=" + + mScreenAutoBrightnessAdjustmentSetting); + pw.println(" mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting); + pw.println(" mScreenBrightnessOverrideFromWindowManager=" + + mScreenBrightnessOverrideFromWindowManager); + pw.println(" mTemporaryScreenBrightnessSettingOverride=" + + mTemporaryScreenBrightnessSettingOverride); + pw.println(" mTemporaryScreenAutoBrightnessAdjustmentSettingOverride=" + + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride); + pw.println(" mScreenBrightnessSettingMinimum=" + mScreenBrightnessSettingMinimum); + pw.println(" mScreenBrightnessSettingMaximum=" + mScreenBrightnessSettingMaximum); + pw.println(" mScreenBrightnessSettingDefault=" + mScreenBrightnessSettingDefault); + + pw.println(); + pw.println("Wake Locks: size=" + mWakeLocks.size()); + for (WakeLock wl : mWakeLocks) { + pw.println(" " + wl); + } + + pw.println(); + pw.println("Suspend Blockers: size=" + mSuspendBlockers.size()); + for (SuspendBlocker sb : mSuspendBlockers) { + pw.println(" " + sb); + } + + dpc = mDisplayPowerController; + } + + if (dpc != null) { + dpc.dump(pw); + } + } + + private SuspendBlocker createSuspendBlockerLocked(String name) { + SuspendBlocker suspendBlocker = new SuspendBlockerImpl(name); + mSuspendBlockers.add(suspendBlocker); + return suspendBlocker; + } + + private static String wakefulnessToString(int wakefulness) { + switch (wakefulness) { + case WAKEFULNESS_ASLEEP: + return "Asleep"; + case WAKEFULNESS_AWAKE: + return "Awake"; + case WAKEFULNESS_DREAMING: + return "Dreaming"; + case WAKEFULNESS_NAPPING: + return "Napping"; + default: + return Integer.toString(wakefulness); + } + } + + private static WorkSource copyWorkSource(WorkSource workSource) { + return workSource != null ? new WorkSource(workSource) : null; + } + + private final class BatteryReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + handleBatteryStateChangedLocked(); + } + } + } + + private final class BootCompletedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + handleBootCompletedLocked(); + } + } + } + + private final class DockReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + handleDockStateChangedLocked(dockState); + } + } + } + + private final class SettingsObserver extends ContentObserver { + public SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + synchronized (mLock) { + handleSettingsChangedLocked(); + } + } + } + + private final WindowManagerPolicy.ScreenOnListener mScreenOnListener = + new WindowManagerPolicy.ScreenOnListener() { + @Override + public void onScreenOn() { + } + }; + + /** + * Handler for asynchronous operations performed by the power manager. + */ + private final class PowerManagerHandler extends Handler { + public PowerManagerHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_USER_ACTIVITY_TIMEOUT: + handleUserActivityTimeout(); + break; + case MSG_SANDMAN: + handleSandman(); + break; + } + } + } + + /** + * Represents a wake lock that has been acquired by an application. + */ + private final class WakeLock implements IBinder.DeathRecipient { + public final IBinder mLock; + public int mFlags; + public String mTag; + public WorkSource mWorkSource; + public int mOwnerUid; + public int mOwnerPid; + + public WakeLock(IBinder lock, int flags, String tag, WorkSource workSource, + int ownerUid, int ownerPid) { + mLock = lock; + mFlags = flags; + mTag = tag; + mWorkSource = copyWorkSource(workSource); + mOwnerUid = ownerUid; + mOwnerPid = ownerPid; + } + + @Override + public void binderDied() { + PowerManagerService.this.handleWakeLockDeath(this); + } + + public boolean hasSameProperties(int flags, String tag, WorkSource workSource, + int ownerUid, int ownerPid) { + return mFlags == flags + && mTag.equals(tag) + && hasSameWorkSource(workSource) + && mOwnerUid == ownerUid + && mOwnerPid == ownerPid; + } + + public void updateProperties(int flags, String tag, WorkSource workSource, + int ownerUid, int ownerPid) { + mFlags = flags; + mTag = tag; + updateWorkSource(workSource); + mOwnerUid = ownerUid; + mOwnerPid = ownerPid; + } + + public boolean hasSameWorkSource(WorkSource workSource) { + return Objects.equal(mWorkSource, workSource); + } + + public void updateWorkSource(WorkSource workSource) { + mWorkSource = copyWorkSource(workSource); + } + + @Override + public String toString() { + return getLockLevelString() + + " '" + mTag + "'" + getLockFlagsString() + + " (uid=" + mOwnerUid + ", pid=" + mOwnerPid + ", ws=" + mWorkSource + ")"; + } + + private String getLockLevelString() { + switch (mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { + case PowerManager.FULL_WAKE_LOCK: + return "FULL_WAKE_LOCK "; + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + return "SCREEN_BRIGHT_WAKE_LOCK "; + case PowerManager.SCREEN_DIM_WAKE_LOCK: + return "SCREEN_DIM_WAKE_LOCK "; + case PowerManager.PARTIAL_WAKE_LOCK: + return "PARTIAL_WAKE_LOCK "; + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + return "PROXIMITY_SCREEN_OFF_WAKE_LOCK"; + default: + return "??? "; + } + } + + private String getLockFlagsString() { + String result = ""; + if ((mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { + result += " ACQUIRE_CAUSES_WAKEUP"; + } + if ((mFlags & PowerManager.ON_AFTER_RELEASE) != 0) { + result += " ON_AFTER_RELEASE"; + } + return result; + } + } + + private final class SuspendBlockerImpl implements SuspendBlocker { + private final String mName; + private int mReferenceCount; + + public SuspendBlockerImpl(String name) { + mName = name; + } + + @Override + protected void finalize() throws Throwable { + try { + if (mReferenceCount != 0) { + Log.wtf(TAG, "Suspend blocker \"" + mName + + "\" was finalized without being released!"); + mReferenceCount = 0; + nativeReleaseSuspendBlocker(mName); + } + } finally { + super.finalize(); + } + } + + @Override + public void acquire() { + synchronized (this) { + mReferenceCount += 1; + if (mReferenceCount == 1) { + nativeAcquireSuspendBlocker(mName); + } + } + } + + @Override + public void release() { + synchronized (this) { + mReferenceCount -= 1; + if (mReferenceCount == 0) { + nativeReleaseSuspendBlocker(mName); + } else if (mReferenceCount < 0) { + Log.wtf(TAG, "Suspend blocker \"" + mName + + "\" was released without being acquired!", new Throwable()); + mReferenceCount = 0; + } + } + } + + @Override + public String toString() { + synchronized (this) { + return mName + ": ref count=" + mReferenceCount; + } + } + } +} diff --git a/services/java/com/android/server/power/RampAnimator.java b/services/java/com/android/server/power/RampAnimator.java new file mode 100644 index 0000000..6f063c3 --- /dev/null +++ b/services/java/com/android/server/power/RampAnimator.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.animation.ValueAnimator; +import android.util.IntProperty; +import android.view.Choreographer; + +/** + * A custom animator that progressively updates a property value at + * a given variable rate until it reaches a particular target value. + */ +final class RampAnimator<T> { + private final T mObject; + private final IntProperty<T> mProperty; + private final Choreographer mChoreographer; + + private int mCurrentValue; + private int mTargetValue; + private int mRate; + + private boolean mAnimating; + private float mAnimatedValue; // higher precision copy of mCurrentValue + private long mLastFrameTimeNanos; + + private boolean mFirstTime = true; + + public RampAnimator(T object, IntProperty<T> property) { + mObject = object; + mProperty = property; + mChoreographer = Choreographer.getInstance(); + } + + /** + * Starts animating towards the specified value. + * + * If this is the first time the property is being set, the value jumps + * directly to the target. + * + * @param target The target value. + * @param rate The convergence rate, in units per second. + * @return True if the target differs from the previous target. + */ + public boolean animateTo(int target, int rate) { + // Immediately jump to the target the first time. + if (mFirstTime) { + mFirstTime = false; + mProperty.setValue(mObject, target); + mCurrentValue = target; + return true; + } + + // Adjust the rate based on the closest target. + // If a faster rate is specified, then use the new rate so that we converge + // more rapidly based on the new request. + // If a slower rate is specified, then use the new rate only if the current + // value is somewhere in between the new and the old target meaning that + // we will be ramping in a different direction to get there. + // Otherwise, continue at the previous rate. + if (!mAnimating + || rate > mRate + || (target <= mCurrentValue && mCurrentValue <= mTargetValue) + || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) { + mRate = rate; + } + + final boolean changed = (mTargetValue != target); + mTargetValue = target; + + // Start animating. + if (!mAnimating && target != mCurrentValue) { + mAnimating = true; + mAnimatedValue = mCurrentValue; + mLastFrameTimeNanos = System.nanoTime(); + postCallback(); + } + + return changed; + } + + private void postCallback() { + mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mCallback, null); + } + + private final Runnable mCallback = new Runnable() { + @Override // Choreographer callback + public void run() { + final long frameTimeNanos = mChoreographer.getFrameTimeNanos(); + final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos) + * 0.000000001f; + final float amount = timeDelta * mRate / ValueAnimator.getDurationScale(); + mLastFrameTimeNanos = frameTimeNanos; + + // Advance the animated value towards the target at the specified rate + // and clamp to the target. This gives us the new current value but + // we keep the animated value around to allow for fractional increments + // towards the target. + int oldCurrentValue = mCurrentValue; + if (mTargetValue > mCurrentValue) { + mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); + } else { + mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); + } + mCurrentValue = (int)Math.round(mAnimatedValue); + + if (oldCurrentValue != mCurrentValue) { + mProperty.setValue(mObject, mCurrentValue); + } + + if (mTargetValue != mCurrentValue) { + postCallback(); + } else { + mAnimating = false; + } + } + }; +} diff --git a/services/java/com/android/server/pm/ShutdownThread.java b/services/java/com/android/server/power/ShutdownThread.java index 3675d41..c7f7390 100644 --- a/services/java/com/android/server/pm/ShutdownThread.java +++ b/services/java/com/android/server/power/ShutdownThread.java @@ -15,7 +15,7 @@ */ -package com.android.server.pm; +package com.android.server.power; import android.app.ActivityManagerNative; import android.app.AlertDialog; @@ -23,7 +23,7 @@ import android.app.Dialog; import android.app.IActivityManager; import android.app.ProgressDialog; import android.bluetooth.BluetoothAdapter; -import android.bluetooth.IBluetooth; +import android.bluetooth.IBluetoothManager; import android.nfc.NfcAdapter; import android.nfc.INfcAdapter; import android.content.BroadcastReceiver; @@ -37,13 +37,13 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.Vibrator; import android.os.SystemVibrator; import android.os.storage.IMountService; import android.os.storage.IMountShutdownObserver; import com.android.internal.telephony.ITelephony; -import com.android.server.PowerManagerService; import android.util.Log; import android.view.WindowManager; @@ -297,8 +297,8 @@ public final class ShutdownThread extends Thread { // First send the high-level shut down broadcast. mActionDone = false; - mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null, - br, mHandler, 0, null, null); + mContext.sendOrderedBroadcastAsUser(new Intent(Intent.ACTION_SHUTDOWN), + UserHandle.ALL, null, br, mHandler, 0, null, null); final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; synchronized (mActionDoneSync) { @@ -385,9 +385,9 @@ public final class ShutdownThread extends Thread { INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc")); final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); - final IBluetooth bluetooth = - IBluetooth.Stub.asInterface(ServiceManager.checkService( - BluetoothAdapter.BLUETOOTH_SERVICE)); + final IBluetoothManager bluetooth = + IBluetoothManager.Stub.asInterface(ServiceManager.checkService( + BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE)); try { nfcOff = nfc == null || @@ -402,8 +402,7 @@ public final class ShutdownThread extends Thread { } try { - bluetoothOff = bluetooth == null || - bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF; + bluetoothOff = bluetooth == null || !bluetooth.isEnabled(); if (!bluetoothOff) { Log.w(TAG, "Disabling Bluetooth..."); bluetooth.disable(false); // disable but don't persist new state @@ -429,8 +428,7 @@ public final class ShutdownThread extends Thread { while (SystemClock.elapsedRealtime() < endTime) { if (!bluetoothOff) { try { - bluetoothOff = - bluetooth.getBluetoothState() == BluetoothAdapter.STATE_OFF; + bluetoothOff = !bluetooth.isEnabled(); } catch (RemoteException ex) { Log.e(TAG, "RemoteException during bluetooth shutdown", ex); bluetoothOff = true; diff --git a/services/java/com/android/server/power/SuspendBlocker.java b/services/java/com/android/server/power/SuspendBlocker.java new file mode 100644 index 0000000..70b278a --- /dev/null +++ b/services/java/com/android/server/power/SuspendBlocker.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +/** + * Low-level suspend blocker mechanism equivalent to holding a partial wake lock. + * + * This interface is used internally to avoid introducing internal dependencies + * on the high-level wake lock mechanism. + */ +interface SuspendBlocker { + /** + * Acquires the suspend blocker. + * Prevents the CPU from going to sleep. + * + * Calls to acquire() nest and must be matched by the same number + * of calls to release(). + */ + void acquire(); + + /** + * Releases the suspend blocker. + * Allows the CPU to go to sleep if no other suspend blockers are held. + * + * It is an error to call release() if the suspend blocker has not been acquired. + * The system may crash. + */ + void release(); +} diff --git a/services/java/com/android/server/updatable/CertPinInstallReceiver.java b/services/java/com/android/server/updatable/CertPinInstallReceiver.java new file mode 100644 index 0000000..c03fbc3 --- /dev/null +++ b/services/java/com/android/server/updatable/CertPinInstallReceiver.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.updates; + +public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver { + + public CertPinInstallReceiver() { + super("/data/misc/keychain/", "pins", "metadata/", "version"); + } +} diff --git a/services/java/com/android/server/updatable/ConfigUpdateInstallReceiver.java b/services/java/com/android/server/updatable/ConfigUpdateInstallReceiver.java new file mode 100644 index 0000000..a74a648 --- /dev/null +++ b/services/java/com/android/server/updatable/ConfigUpdateInstallReceiver.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.updates; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.os.FileUtils; +import android.util.Base64; +import android.util.EventLog; +import android.util.Slog; + +import com.android.server.EventLogTags; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; + +import libcore.io.IoUtils; + +public class ConfigUpdateInstallReceiver extends BroadcastReceiver { + + private static final String TAG = "ConfigUpdateInstallReceiver"; + + private static final String EXTRA_CONTENT_PATH = "CONTENT_PATH"; + private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH"; + private static final String EXTRA_SIGNATURE = "SIGNATURE"; + private static final String EXTRA_VERSION_NUMBER = "VERSION"; + + private static final String UPDATE_CERTIFICATE_KEY = "config_update_certificate"; + + private final File updateDir; + private final File updateContent; + private final File updateVersion; + + public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath, + String updateMetadataPath, String updateVersionPath) { + this.updateDir = new File(updateDir); + this.updateContent = new File(updateDir, updateContentPath); + File updateMetadataDir = new File(updateDir, updateMetadataPath); + this.updateVersion = new File(updateMetadataDir, updateVersionPath); + } + + @Override + public void onReceive(final Context context, final Intent intent) { + new Thread() { + @Override + public void run() { + try { + // get the certificate from Settings.Secure + X509Certificate cert = getCert(context.getContentResolver()); + // get the content path from the extras + String altContent = getAltContent(intent); + // get the version from the extras + int altVersion = getVersionFromIntent(intent); + // get the previous value from the extras + String altRequiredHash = getRequiredHashFromIntent(intent); + // get the signature from the extras + String altSig = getSignatureFromIntent(intent); + // get the version currently being used + int currentVersion = getCurrentVersion(); + // get the hash of the currently used value + String currentHash = getCurrentHash(getCurrentContent()); + if (!verifyVersion(currentVersion, altVersion)) { + EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, + "New version is not greater than current version"); + } else if (!verifyPreviousHash(currentHash, altRequiredHash)) { + EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, + "Current hash did not match required value"); + } else if (!verifySignature(altContent, altVersion, altRequiredHash, altSig, + cert)) { + EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, + "Signature did not verify"); + } else { + // install the new content + Slog.i(TAG, "Found new update, installing..."); + install(altContent, altVersion); + Slog.i(TAG, "Installation successful"); + } + } catch (Exception e) { + Slog.e(TAG, "Could not update content!", e); + // keep the error message <= 100 chars + String errMsg = e.toString(); + if (errMsg.length() > 100) { + errMsg = errMsg.substring(0, 99); + } + EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, errMsg); + } + } + }.start(); + } + + private X509Certificate getCert(ContentResolver cr) { + // get the cert from settings + String cert = Settings.Secure.getString(cr, UPDATE_CERTIFICATE_KEY); + // convert it into a real certificate + try { + byte[] derCert = Base64.decode(cert.getBytes(), Base64.DEFAULT); + InputStream istream = new ByteArrayInputStream(derCert); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509Certificate) cf.generateCertificate(istream); + } catch (CertificateException e) { + throw new IllegalStateException("Got malformed certificate from settings, ignoring", e); + } + } + + private String getContentFromIntent(Intent i) { + String extraValue = i.getStringExtra(EXTRA_CONTENT_PATH); + if (extraValue == null) { + throw new IllegalStateException("Missing required content path, ignoring."); + } + return extraValue; + } + + private int getVersionFromIntent(Intent i) throws NumberFormatException { + String extraValue = i.getStringExtra(EXTRA_VERSION_NUMBER); + if (extraValue == null) { + throw new IllegalStateException("Missing required version number, ignoring."); + } + return Integer.parseInt(extraValue.trim()); + } + + private String getRequiredHashFromIntent(Intent i) { + String extraValue = i.getStringExtra(EXTRA_REQUIRED_HASH); + if (extraValue == null) { + throw new IllegalStateException("Missing required previous hash, ignoring."); + } + return extraValue.trim(); + } + + private String getSignatureFromIntent(Intent i) { + String extraValue = i.getStringExtra(EXTRA_SIGNATURE); + if (extraValue == null) { + throw new IllegalStateException("Missing required signature, ignoring."); + } + return extraValue.trim(); + } + + private int getCurrentVersion() throws NumberFormatException { + try { + String strVersion = IoUtils.readFileAsString(updateVersion.getCanonicalPath()).trim(); + return Integer.parseInt(strVersion); + } catch (IOException e) { + Slog.i(TAG, "Couldn't find current metadata, assuming first update", e); + return 0; + } + } + + private String getAltContent(Intent i) throws IOException { + String contents = IoUtils.readFileAsString(getContentFromIntent(i)); + return contents.trim(); + } + + private String getCurrentContent() { + try { + return IoUtils.readFileAsString(updateContent.getCanonicalPath()).trim(); + } catch (IOException e) { + Slog.i(TAG, "Failed to read current content, assuming first update!", e); + return null; + } + } + + private static String getCurrentHash(String content) { + if (content == null) { + return "0"; + } + try { + MessageDigest dgst = MessageDigest.getInstance("SHA512"); + byte[] encoded = content.getBytes(); + byte[] fingerprint = dgst.digest(encoded); + return IntegralToString.bytesToHexString(fingerprint, false); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + + private boolean verifyVersion(int current, int alternative) { + return (current < alternative); + } + + private boolean verifyPreviousHash(String current, String required) { + // this is an optional value- if the required field is NONE then we ignore it + if (required.equals("NONE")) { + return true; + } + // otherwise, verify that we match correctly + return current.equals(required); + } + + private boolean verifySignature(String content, int version, String requiredPrevious, + String signature, X509Certificate cert) throws Exception { + Signature signer = Signature.getInstance("SHA512withRSA"); + signer.initVerify(cert); + signer.update(content.getBytes()); + signer.update(Long.toString(version).getBytes()); + signer.update(requiredPrevious.getBytes()); + return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT)); + } + + private void writeUpdate(File dir, File file, String content) { + FileOutputStream out = null; + File tmp = null; + try { + // create the temporary file + tmp = File.createTempFile("journal", "", dir); + // create the parents for the destination file + File parent = file.getParentFile(); + parent.mkdirs(); + // check that they were created correctly + if (!parent.exists()) { + throw new IOException("Failed to create directory " + parent.getCanonicalPath()); + } + // mark tmp -rw-r--r-- + tmp.setReadable(true, false); + // write to it + out = new FileOutputStream(tmp); + out.write(content.getBytes()); + // sync to disk + out.getFD().sync(); + // atomic rename + if (!tmp.renameTo(file)) { + throw new IOException("Failed to atomically rename " + file.getCanonicalPath()); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to write update", e); + } finally { + if (tmp != null) { + tmp.delete(); + } + IoUtils.closeQuietly(out); + } + } + + private void install(String content, int version) { + writeUpdate(updateDir, updateContent, content); + writeUpdate(updateDir, updateVersion, Long.toString(version)); + } +} diff --git a/services/java/com/android/server/usb/UsbDebuggingManager.java b/services/java/com/android/server/usb/UsbDebuggingManager.java new file mode 100644 index 0000000..1bb3a2c --- /dev/null +++ b/services/java/com/android/server/usb/UsbDebuggingManager.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions an + * limitations under the License. + */ + +package com.android.server.usb; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Environment; +import android.os.FileUtils; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.SystemClock; +import android.util.Slog; +import android.util.Base64; + +import java.lang.Thread; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.security.MessageDigest; +import java.util.Arrays; + +public class UsbDebuggingManager implements Runnable { + private static final String TAG = "UsbDebuggingManager"; + private static final boolean DEBUG = false; + + private final String ADBD_SOCKET = "adbd"; + private final String ADB_DIRECTORY = "misc/adb"; + private final String ADB_KEYS_FILE = "adb_keys"; + private final int BUFFER_SIZE = 4096; + + private final Context mContext; + private final Handler mHandler; + private final HandlerThread mHandlerThread; + private Thread mThread; + private boolean mAdbEnabled = false; + private String mFingerprints; + private LocalSocket mSocket = null; + private OutputStream mOutputStream = null; + + public UsbDebuggingManager(Context context) { + mHandlerThread = new HandlerThread("UsbDebuggingHandler"); + mHandlerThread.start(); + mHandler = new UsbDebuggingHandler(mHandlerThread.getLooper()); + mContext = context; + } + + private void listenToSocket() throws IOException { + try { + byte[] buffer = new byte[BUFFER_SIZE]; + LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET, + LocalSocketAddress.Namespace.RESERVED); + InputStream inputStream = null; + + mSocket = new LocalSocket(); + mSocket.connect(address); + + mOutputStream = mSocket.getOutputStream(); + inputStream = mSocket.getInputStream(); + + while (true) { + int count = inputStream.read(buffer); + if (count < 0) { + Slog.e(TAG, "got " + count + " reading"); + break; + } + + if (buffer[0] == 'P' && buffer[1] == 'K') { + String key = new String(Arrays.copyOfRange(buffer, 2, count)); + Slog.d(TAG, "Received public key: " + key); + Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_CONFIRM); + msg.obj = key; + mHandler.sendMessage(msg); + } + else { + Slog.e(TAG, "Wrong message: " + (new String(Arrays.copyOfRange(buffer, 0, 2)))); + break; + } + } + } catch (IOException ex) { + Slog.e(TAG, "Communication error: ", ex); + throw ex; + } finally { + closeSocket(); + } + } + + @Override + public void run() { + while (mAdbEnabled) { + try { + listenToSocket(); + } catch (Exception e) { + /* Don't loop too fast if adbd dies, before init restarts it */ + SystemClock.sleep(1000); + } + } + } + + private void closeSocket() { + try { + mOutputStream.close(); + } catch (IOException e) { + Slog.e(TAG, "Failed closing output stream: " + e); + } + + try { + mSocket.close(); + } catch (IOException ex) { + Slog.e(TAG, "Failed closing socket: " + ex); + } + } + + private void sendResponse(String msg) { + if (mOutputStream != null) { + try { + mOutputStream.write(msg.getBytes()); + } + catch (IOException ex) { + Slog.e(TAG, "Failed to write response:", ex); + } + } + } + + class UsbDebuggingHandler extends Handler { + private static final int MESSAGE_ADB_ENABLED = 1; + private static final int MESSAGE_ADB_DISABLED = 2; + private static final int MESSAGE_ADB_ALLOW = 3; + private static final int MESSAGE_ADB_DENY = 4; + private static final int MESSAGE_ADB_CONFIRM = 5; + + public UsbDebuggingHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_ADB_ENABLED: + if (mAdbEnabled) + break; + + mAdbEnabled = true; + + mThread = new Thread(UsbDebuggingManager.this); + mThread.start(); + + break; + + case MESSAGE_ADB_DISABLED: + if (!mAdbEnabled) + break; + + mAdbEnabled = false; + closeSocket(); + + try { + mThread.join(); + } catch (Exception ex) { + } + + mThread = null; + mOutputStream = null; + mSocket = null; + break; + + case MESSAGE_ADB_ALLOW: { + String key = (String)msg.obj; + String fingerprints = getFingerprints(key); + + if (!fingerprints.equals(mFingerprints)) { + Slog.e(TAG, "Fingerprints do not match. Got " + + fingerprints + ", expected " + mFingerprints); + break; + } + + if (msg.arg1 == 1) { + writeKey(key); + } + + sendResponse("OK"); + break; + } + + case MESSAGE_ADB_DENY: + sendResponse("NO"); + break; + + case MESSAGE_ADB_CONFIRM: { + String key = (String)msg.obj; + mFingerprints = getFingerprints(key); + showConfirmationDialog(key, mFingerprints); + break; + } + } + } + } + + private String getFingerprints(String key) { + String hex = "0123456789ABCDEF"; + StringBuilder sb = new StringBuilder(); + MessageDigest digester; + + try { + digester = MessageDigest.getInstance("MD5"); + } catch (Exception ex) { + Slog.e(TAG, "Error getting digester: " + ex); + return ""; + } + + byte[] base64_data = key.split("\\s+")[0].getBytes(); + byte[] digest = digester.digest(Base64.decode(base64_data, Base64.DEFAULT)); + + for (int i = 0; i < digest.length; i++) { + sb.append(hex.charAt((digest[i] >> 4) & 0xf)); + sb.append(hex.charAt(digest[i] & 0xf)); + if (i < digest.length - 1) + sb.append(":"); + } + return sb.toString(); + } + + private void showConfirmationDialog(String key, String fingerprints) { + Intent dialogIntent = new Intent(); + + dialogIntent.setClassName("com.android.systemui", + "com.android.systemui.usb.UsbDebuggingActivity"); + dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + dialogIntent.putExtra("key", key); + dialogIntent.putExtra("fingerprints", fingerprints); + try { + mContext.startActivity(dialogIntent); + } catch (ActivityNotFoundException e) { + Slog.e(TAG, "unable to start UsbDebuggingActivity"); + } + } + + private void writeKey(String key) { + File dataDir = Environment.getDataDirectory(); + File adbDir = new File(dataDir, ADB_DIRECTORY); + + if (!adbDir.exists()) { + Slog.e(TAG, "ADB data directory does not exist"); + return; + } + + try { + File keyFile = new File(adbDir, ADB_KEYS_FILE); + + if (!keyFile.exists()) { + keyFile.createNewFile(); + FileUtils.setPermissions(keyFile.toString(), + FileUtils.S_IRUSR | FileUtils.S_IWUSR | + FileUtils.S_IRGRP, -1, -1); + } + + FileOutputStream fo = new FileOutputStream(keyFile, true); + fo.write(key.getBytes()); + fo.write('\n'); + fo.close(); + } + catch (IOException ex) { + Slog.e(TAG, "Error writing key:" + ex); + } + } + + + public void setAdbEnabled(boolean enabled) { + mHandler.sendEmptyMessage(enabled ? UsbDebuggingHandler.MESSAGE_ADB_ENABLED + : UsbDebuggingHandler.MESSAGE_ADB_DISABLED); + } + + public void allowUsbDebugging(boolean alwaysAllow, String publicKey) { + Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_ALLOW); + msg.arg1 = alwaysAllow ? 1 : 0; + msg.obj = publicKey; + mHandler.sendMessage(msg); + } + + public void denyUsbDebugging() { + mHandler.sendEmptyMessage(UsbDebuggingHandler.MESSAGE_ADB_DENY); + } + + + public void dump(FileDescriptor fd, PrintWriter pw) { + pw.println(" USB Debugging State:"); + pw.println(" Connected to adbd: " + (mOutputStream != null)); + pw.println(" Last key received: " + mFingerprints); + pw.println(" User keys:"); + try { + pw.println(FileUtils.readTextFile(new File("/data/misc/adb/adb_keys"), 0, null)); + } catch (IOException e) { + pw.println("IOException: " + e); + } + pw.println(" System keys:"); + try { + pw.println(FileUtils.readTextFile(new File("/adb_keys"), 0, null)); + } catch (IOException e) { + pw.println("IOException: " + e); + } + } +} diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index a115345..3ef6d4c 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -41,6 +41,7 @@ import android.os.Message; import android.os.Parcelable; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.UserHandle; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.os.SystemClock; @@ -114,6 +115,7 @@ public class UsbDeviceManager { private boolean mAudioSourceEnabled; private Map<String, List<Pair<String, String>>> mOemModeMap; private String[] mAccessoryStrings; + private UsbDebuggingManager mDebuggingManager; private class AdbSettingsObserver extends ContentObserver { public AdbSettingsObserver() { @@ -166,6 +168,10 @@ public class UsbDeviceManager { if (DEBUG) Slog.d(TAG, "accessory attached at boot"); startAccessoryMode(); } + + if ("1".equals(SystemProperties.get("ro.adb.secure"))) { + mDebuggingManager = new UsbDebuggingManager(context); + } } public void systemReady() { @@ -177,12 +183,9 @@ public class UsbDeviceManager { // We do not show the USB notification if the primary volume supports mass storage. // The legacy mass storage UI will be used instead. boolean massStorageSupported = false; - StorageManager storageManager = (StorageManager) - mContext.getSystemService(Context.STORAGE_SERVICE); - StorageVolume[] volumes = storageManager.getVolumeList(); - if (volumes.length > 0) { - massStorageSupported = volumes[0].allowMassStorage(); - } + final StorageManager storageManager = StorageManager.from(mContext); + final StorageVolume primary = storageManager.getPrimaryVolume(); + massStorageSupported = primary != null && primary.allowMassStorage(); mUseUsbNotification = !massStorageSupported; // make sure the ADB_ENABLED setting value matches the current state @@ -425,6 +428,9 @@ public class UsbDeviceManager { setEnabledFunctions(mDefaultFunctions, true); updateAdbNotification(); } + if (mDebuggingManager != null) { + mDebuggingManager.setAdbEnabled(mAdbEnabled); + } } private void setEnabledFunctions(String functions, boolean makeDefault) { @@ -532,7 +538,7 @@ public class UsbDeviceManager { } } - mContext.sendStickyBroadcast(intent); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } private void updateAudioSourceFunction() { @@ -555,7 +561,7 @@ public class UsbDeviceManager { Slog.e(TAG, "could not open audio source PCM file", e); } } - mContext.sendStickyBroadcast(intent); + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); mAudioSourceEnabled = enabled; } } @@ -601,6 +607,9 @@ public class UsbDeviceManager { if (mCurrentAccessory != null) { mSettingsManager.accessoryAttached(mCurrentAccessory); } + if (mDebuggingManager != null) { + mDebuggingManager.setAdbEnabled(mAdbEnabled); + } break; } } @@ -802,10 +811,25 @@ public class UsbDeviceManager { return usbFunctions; } + public void allowUsbDebugging(boolean alwaysAllow, String publicKey) { + if (mDebuggingManager != null) { + mDebuggingManager.allowUsbDebugging(alwaysAllow, publicKey); + } + } + + public void denyUsbDebugging() { + if (mDebuggingManager != null) { + mDebuggingManager.denyUsbDebugging(); + } + } + public void dump(FileDescriptor fd, PrintWriter pw) { if (mHandler != null) { mHandler.dump(fd, pw); } + if (mDebuggingManager != null) { + mDebuggingManager.dump(fd, pw); + } } private native String[] nativeGetAccessoryStrings(); diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index 0205ef8..bebcd56 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -164,6 +164,16 @@ public class UsbService extends IUsbManager.Stub { } } + public void allowUsbDebugging(boolean alwaysAllow, String publicKey) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.allowUsbDebugging(alwaysAllow, publicKey); + } + + public void denyUsbDebugging() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + mDeviceManager.denyUsbDebugging(); + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) diff --git a/services/java/com/android/server/usb/UsbSettingsManager.java b/services/java/com/android/server/usb/UsbSettingsManager.java index 9b3459b..a8453d3 100644 --- a/services/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/java/com/android/server/usb/UsbSettingsManager.java @@ -35,6 +35,7 @@ import android.hardware.usb.UsbManager; import android.os.Binder; import android.os.FileUtils; import android.os.Process; +import android.os.UserHandle; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.Xml; @@ -546,7 +547,7 @@ class UsbSettingsManager { } // Send broadcast to running activity with registered intent - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); // Start activity with registered intent resolveActivity(intent, matches, defaultPackage, device, null); @@ -559,7 +560,7 @@ class UsbSettingsManager { Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED); intent.putExtra(UsbManager.EXTRA_DEVICE, device); if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } public void accessoryAttached(UsbAccessory accessory) { @@ -586,7 +587,7 @@ class UsbSettingsManager { Intent intent = new Intent( UsbManager.ACTION_USB_ACCESSORY_DETACHED); intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory); - mContext.sendBroadcast(intent); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); } private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches, diff --git a/services/java/com/android/server/wm/AppWindowAnimator.java b/services/java/com/android/server/wm/AppWindowAnimator.java index 13e8bc5..2445b98 100644 --- a/services/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/java/com/android/server/wm/AppWindowAnimator.java @@ -4,16 +4,15 @@ package com.android.server.wm; import android.graphics.Matrix; import android.util.Slog; +import android.view.Display; import android.view.Surface; import android.view.WindowManagerPolicy; import android.view.animation.Animation; import android.view.animation.Transformation; import java.io.PrintWriter; +import java.util.ArrayList; -/** - * - */ public class AppWindowAnimator { static final String TAG = "AppWindowAnimator"; @@ -48,12 +47,15 @@ public class AppWindowAnimator { Animation thumbnailAnimation; final Transformation thumbnailTransformation = new Transformation(); + /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */ + ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<WindowStateAnimator>(); + static final Animation sDummyAnimation = new DummyAnimation(); - public AppWindowAnimator(final WindowManagerService service, final AppWindowToken atoken) { - mService = service; + public AppWindowAnimator(final AppWindowToken atoken) { mAppToken = atoken; - mAnimator = service.mAnimator; + mService = atoken.service; + mAnimator = atoken.mAnimator; } public void setAnimation(Animation anim, boolean initialized) { @@ -123,7 +125,7 @@ public class AppWindowAnimator { if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) { mService.setInputMethodAnimLayerAdjustment(adj); } - if (w == mService.mWallpaperTarget && mService.mLowerWallpaperTarget == null) { + if (w == mAnimator.mWallpaperTarget && mAnimator.mLowerWallpaperTarget == null) { mService.setWallpaperAnimLayerAdjustmentLocked(adj); } } @@ -133,11 +135,13 @@ public class AppWindowAnimator { thumbnailTransformation.clear(); thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation); thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY); - final boolean screenAnimation = mAnimator.mScreenRotationAnimation != null - && mAnimator.mScreenRotationAnimation.isAnimating(); + + ScreenRotationAnimation screenRotationAnimation = + mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY); + final boolean screenAnimation = screenRotationAnimation != null + && screenRotationAnimation.isAnimating(); if (screenAnimation) { - thumbnailTransformation.postCompose( - mAnimator.mScreenRotationAnimation.getEnterTransformation()); + thumbnailTransformation.postCompose(screenRotationAnimation.getEnterTransformation()); } // cache often used attributes locally final float tmpFloats[] = mService.mTmpFloats; @@ -168,7 +172,7 @@ public class AppWindowAnimator { } transformation.clear(); final boolean more = animation.getTransformation(currentTime, transformation); - if (WindowManagerService.DEBUG_ANIM) Slog.v( + if (false && WindowManagerService.DEBUG_ANIM) Slog.v( TAG, "Stepped animation in " + mAppToken + ": more=" + more + ", xform=" + transformation); if (!more) { animation = null; @@ -233,10 +237,8 @@ public class AppWindowAnimator { return false; } - mAnimator.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("AppWindowToken", mAnimator.mPendingLayoutChanges); - } + mAnimator.setAppLayoutChanges(this, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, + "AppWindowToken"); clearAnimation(); animating = false; @@ -255,9 +257,9 @@ public class AppWindowAnimator { transformation.clear(); - final int N = mAppToken.windows.size(); + final int N = mAllAppWinAnimators.size(); for (int i=0; i<N; i++) { - mAppToken.windows.get(i).mWinAnimator.finishExit(); + mAllAppWinAnimators.get(i).finishExit(); } mAppToken.updateReportedVisibilityLocked(); @@ -266,9 +268,9 @@ public class AppWindowAnimator { boolean showAllWindowsLocked() { boolean isAnimating = false; - final int NW = mAppToken.allAppWindows.size(); + final int NW = mAllAppWinAnimators.size(); for (int i=0; i<NW; i++) { - WindowStateAnimator winAnimator = mAppToken.allAppWindows.get(i).mWinAnimator; + WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i); if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "performing show on: " + winAnimator); winAnimator.performShowLocked(); diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index 6ecbb8e..13b072c 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -104,7 +104,7 @@ class AppWindowToken extends WindowToken { appToken = _token; mInputApplicationHandle = new InputApplicationHandle(this); mAnimator = service.mAnimator; - mAppAnimator = new AppWindowAnimator(_service, this); + mAppAnimator = new AppWindowAnimator(this); } void sendAppVisibilityToClients() { diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java index 27af313..5b77b20 100644 --- a/services/java/com/android/server/wm/BlackFrame.java +++ b/services/java/com/android/server/wm/BlackFrame.java @@ -35,7 +35,7 @@ public class BlackFrame { final int layer; final Surface surface; - BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b) + BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack) throws Surface.OutOfResourcesException { left = l; top = t; @@ -43,14 +43,15 @@ public class BlackFrame { int w = r-l; int h = b-t; if (WindowManagerService.DEBUG_SURFACE_TRACE) { - surface = new WindowStateAnimator.SurfaceTrace(session, 0, "BlackSurface(" + surface = new WindowStateAnimator.SurfaceTrace(session, "BlackSurface(" + l + ", " + t + ")", - -1, w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); + w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM | Surface.HIDDEN); } else { - surface = new Surface(session, 0, "BlackSurface", - -1, w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); + surface = new Surface(session, "BlackSurface", + w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM | Surface.HIDDEN); } surface.setAlpha(1); + surface.setLayerStack(layerStack); surface.setLayer(layer); surface.show(); if (WindowManagerService.SHOW_TRANSACTIONS || @@ -103,7 +104,7 @@ public class BlackFrame { } public BlackFrame(SurfaceSession session, Rect outer, Rect inner, - int layer) throws Surface.OutOfResourcesException { + int layer, final int layerStack) throws Surface.OutOfResourcesException { boolean success = false; mOuterRect = new Rect(outer); @@ -111,19 +112,19 @@ public class BlackFrame { try { if (outer.top < inner.top) { mBlackSurfaces[0] = new BlackSurface(session, layer, - outer.left, outer.top, inner.right, inner.top); + outer.left, outer.top, inner.right, inner.top, layerStack); } if (outer.left < inner.left) { mBlackSurfaces[1] = new BlackSurface(session, layer, - outer.left, inner.top, inner.left, outer.bottom); + outer.left, inner.top, inner.left, outer.bottom, layerStack); } if (outer.bottom > inner.bottom) { mBlackSurfaces[2] = new BlackSurface(session, layer, - inner.left, inner.bottom, outer.right, outer.bottom); + inner.left, inner.bottom, outer.right, outer.bottom, layerStack); } if (outer.right > inner.right) { mBlackSurfaces[3] = new BlackSurface(session, layer, - inner.right, outer.top, outer.right, inner.bottom); + inner.right, outer.top, outer.right, inner.bottom, layerStack); } success = true; } finally { diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java index e8f56c8..9bca834 100644 --- a/services/java/com/android/server/wm/DimAnimator.java +++ b/services/java/com/android/server/wm/DimAnimator.java @@ -39,24 +39,25 @@ class DimAnimator { int mLastDimWidth, mLastDimHeight; - DimAnimator (SurfaceSession session) { + DimAnimator (SurfaceSession session, final int layerStack) { if (mDimSurface == null) { try { if (WindowManagerService.DEBUG_SURFACE_TRACE) { - mDimSurface = new WindowStateAnimator.SurfaceTrace(session, 0, + mDimSurface = new WindowStateAnimator.SurfaceTrace(session, "DimAnimator", - -1, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); + 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM | Surface.HIDDEN); } else { - mDimSurface = new Surface(session, 0, - "DimAnimator", - -1, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); + mDimSurface = new Surface(session, "DimAnimator", + 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM | Surface.HIDDEN); } if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": CREATE"); + mDimSurface.setLayerStack(layerStack); mDimSurface.setAlpha(0.0f); + mDimSurface.show(); } catch (Exception e) { Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e); } @@ -211,5 +212,20 @@ class DimAnimator { mDimHeight = dimHeight; mDimTarget = dimTarget; } + + Parameters(Parameters o) { + mDimWinAnimator = o.mDimWinAnimator; + mDimWidth = o.mDimWidth; + mDimHeight = o.mDimHeight; + mDimTarget = o.mDimTarget; + } + + public void printTo(String prefix, PrintWriter pw) { + pw.print(prefix); + pw.print("mDimWinAnimator="); pw.print(mDimWinAnimator.mWin.mAttrs.getTitle()); + pw.print(" "); pw.print(mDimWidth); pw.print(" x "); + pw.print(mDimHeight); + pw.print(" mDimTarget="); pw.println(mDimTarget); + } } -}
\ No newline at end of file +} diff --git a/services/java/com/android/server/wm/DimSurface.java b/services/java/com/android/server/wm/DimSurface.java index 9fca418..ddbd70d 100644 --- a/services/java/com/android/server/wm/DimSurface.java +++ b/services/java/com/android/server/wm/DimSurface.java @@ -30,24 +30,25 @@ class DimSurface { int mLayer = -1; int mLastDimWidth, mLastDimHeight; - DimSurface(SurfaceSession session) { + DimSurface(SurfaceSession session, final int layerStack) { if (mDimSurface == null) { try { if (WindowManagerService.DEBUG_SURFACE_TRACE) { - mDimSurface = new WindowStateAnimator.SurfaceTrace(session, 0, + mDimSurface = new WindowStateAnimator.SurfaceTrace(session, "DimSurface", - -1, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); + 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM | Surface.HIDDEN); } else { - mDimSurface = new Surface(session, 0, - "DimSurface", - -1, 16, 16, PixelFormat.OPAQUE, - Surface.FX_SURFACE_DIM); + mDimSurface = new Surface(session, "DimSurface", + 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM | Surface.HIDDEN); } if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": CREATE"); + mDimSurface.setLayerStack(layerStack); mDimSurface.setAlpha(0.0f); + mDimSurface.show(); } catch (Exception e) { Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e); } diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java new file mode 100644 index 0000000..3898ebc --- /dev/null +++ b/services/java/com/android/server/wm/DisplayContent.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.os.RemoteCallbackList; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.IDisplayContentChangeListener; + +import java.io.PrintWriter; +import java.util.ArrayList; + +class DisplayContentList extends ArrayList<DisplayContent> { +} + +/** + * Utility class for keeping track of the WindowStates and other pertinent contents of a + * particular Display. + * + * IMPORTANT: No method from this class should ever be used without holding + * WindowManagerService.mWindowMap. + */ +class DisplayContent { + + /** Unique identifier of this stack. */ + private final int mDisplayId; + + /** Z-ordered (bottom-most first) list of all Window objects. Assigned to an element + * from mDisplayWindows; */ + private WindowList mWindows = new WindowList(); + + // Specification for magnifying the display content. + MagnificationSpec mMagnificationSpec; + + // Callback for observing content changes on a display. + RemoteCallbackList<IDisplayContentChangeListener> mDisplayContentChangeListeners; + + // This protects the following display size properties, so that + // getDisplaySize() doesn't need to acquire the global lock. This is + // needed because the window manager sometimes needs to use ActivityThread + // while it has its global state locked (for example to load animation + // resources), but the ActivityThread also needs get the current display + // size sometimes when it has its package lock held. + // + // These will only be modified with both mWindowMap and mDisplaySizeLock + // held (in that order) so the window manager doesn't need to acquire this + // lock when needing these values in its normal operation. + final Object mDisplaySizeLock = new Object(); + int mInitialDisplayWidth = 0; + int mInitialDisplayHeight = 0; + int mInitialDisplayDensity = 0; + int mBaseDisplayWidth = 0; + int mBaseDisplayHeight = 0; + int mBaseDisplayDensity = 0; + final DisplayInfo mDisplayInfo = new DisplayInfo(); + final Display mDisplay; + + // Accessed directly by all users. + boolean layoutNeeded; + int pendingLayoutChanges; + final boolean isDefaultDisplay; + + DisplayContent(Display display) { + mDisplay = display; + mDisplayId = display.getDisplayId(); + display.getDisplayInfo(mDisplayInfo); + isDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY; + } + + int getDisplayId() { + return mDisplayId; + } + + WindowList getWindowList() { + return mWindows; + } + + Display getDisplay() { + return mDisplay; + } + + DisplayInfo getDisplayInfo() { + return mDisplayInfo; + } + + public void updateDisplayInfo() { + mDisplay.getDisplayInfo(mDisplayInfo); + } + + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId); + final String subPrefix = " " + prefix; + pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x"); + pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity); + pw.print("dpi"); + if (mInitialDisplayWidth != mBaseDisplayWidth + || mInitialDisplayHeight != mBaseDisplayHeight + || mInitialDisplayDensity != mBaseDisplayDensity) { + pw.print(" base="); + pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight); + pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi"); + } + pw.print(" cur="); + pw.print(mDisplayInfo.logicalWidth); + pw.print("x"); pw.print(mDisplayInfo.logicalHeight); + pw.print(" app="); + pw.print(mDisplayInfo.appWidth); + pw.print("x"); pw.print(mDisplayInfo.appHeight); + pw.print(" rng="); pw.print(mDisplayInfo.smallestNominalAppWidth); + pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight); + pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth); + pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight); + pw.print(subPrefix); pw.print("layoutNeeded="); pw.print(layoutNeeded); + if (mMagnificationSpec != null) { + pw.print(" mMagnificationSpec="); pw.print(mMagnificationSpec); + } + pw.println(); + } +} diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java index b2cf3e0..545fce5 100644 --- a/services/java/com/android/server/wm/DragState.java +++ b/services/java/com/android/server/wm/DragState.java @@ -23,12 +23,14 @@ import com.android.server.wm.WindowManagerService.H; import android.content.ClipData; import android.content.ClipDescription; +import android.graphics.Point; import android.graphics.Region; import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.util.Slog; +import android.view.Display; import android.view.DragEvent; import android.view.InputChannel; import android.view.Surface; @@ -58,6 +60,7 @@ class DragState { WindowState mTargetWindow; ArrayList<WindowState> mNotifiedWindows; boolean mDragInProgress; + Display mDisplay; private final Region mTmpRegion = new Region(); @@ -84,7 +87,11 @@ class DragState { mNotifiedWindows = null; } - void register() { + /** + * @param display The Display that the window being dragged is on. + */ + void register(Display display) { + mDisplay = display; if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "registering drag input channel"); if (mClientChannel != null) { Slog.e(WindowManagerService.TAG, "Duplicate register of drag input channel"); @@ -101,7 +108,8 @@ class DragState { mDragApplicationHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; - mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null); + mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, + mDisplay.getDisplayId()); mDragWindowHandle.name = "drag"; mDragWindowHandle.inputChannel = mServerChannel; mDragWindowHandle.layer = getDragLayerLw(); @@ -125,8 +133,10 @@ class DragState { // The drag window covers the entire display mDragWindowHandle.frameLeft = 0; mDragWindowHandle.frameTop = 0; - mDragWindowHandle.frameRight = mService.mCurDisplayWidth; - mDragWindowHandle.frameBottom = mService.mCurDisplayHeight; + Point p = new Point(); + mDisplay.getRealSize(p); + mDragWindowHandle.frameRight = p.x; + mDragWindowHandle.frameBottom = p.y; // Pause rotations before a drag. if (WindowManagerService.DEBUG_ORIENTATION) { @@ -179,9 +189,10 @@ class DragState { Slog.d(WindowManagerService.TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); } - final int N = mService.mWindows.size(); + final WindowList windows = mService.getWindowListLocked(mDisplay); + final int N = windows.size(); for (int i = 0; i < N; i++) { - sendDragStartedLw(mService.mWindows.get(i), touchX, touchY, mDataDescription); + sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription); } } @@ -380,7 +391,8 @@ class DragState { WindowState touchedWin = null; final int x = (int) xf; final int y = (int) yf; - final ArrayList<WindowState> windows = mService.mWindows; + + final WindowList windows = mService.getWindowListLocked(mDisplay); final int N = windows.size(); for (int i = N - 1; i >= 0; i--) { WindowState child = windows.get(i); diff --git a/services/java/com/android/server/wm/FakeWindowImpl.java b/services/java/com/android/server/wm/FakeWindowImpl.java index 2527f46..5ec72cc 100644 --- a/services/java/com/android/server/wm/FakeWindowImpl.java +++ b/services/java/com/android/server/wm/FakeWindowImpl.java @@ -22,6 +22,7 @@ import com.android.server.input.InputWindowHandle; import android.os.Looper; import android.os.Process; import android.util.Slog; +import android.view.Display; import android.view.InputChannel; import android.view.InputEventReceiver; import android.view.InputQueue; @@ -56,7 +57,7 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { mApplicationHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; - mWindowHandle = new InputWindowHandle(mApplicationHandle, null); + mWindowHandle = new InputWindowHandle(mApplicationHandle, null, Display.DEFAULT_DISPLAY); mWindowHandle.name = name; mWindowHandle.inputChannel = mServerChannel; mWindowLayer = getLayerLw(windowType); diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java index 285d230..aa18ee4 100644 --- a/services/java/com/android/server/wm/InputMonitor.java +++ b/services/java/com/android/server/wm/InputMonitor.java @@ -19,11 +19,13 @@ package com.android.server.wm; import com.android.server.input.InputManagerService; import com.android.server.input.InputApplicationHandle; import com.android.server.input.InputWindowHandle; +import com.android.server.wm.WindowManagerService.AllWindowsIterator; import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; import android.util.Slog; +import android.view.Display; import android.view.InputChannel; import android.view.KeyEvent; import android.view.WindowManager; @@ -128,7 +130,7 @@ final class InputMonitor implements InputManagerService.Callbacks { return 0; // abort dispatching } - private void addInputWindowHandleLw(InputWindowHandle windowHandle) { + private void addInputWindowHandleLw(final InputWindowHandle windowHandle) { if (mInputWindowHandles == null) { mInputWindowHandles = new InputWindowHandle[16]; } @@ -139,6 +141,44 @@ final class InputMonitor implements InputManagerService.Callbacks { mInputWindowHandles[mInputWindowHandleCount++] = windowHandle; } + private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle, + final WindowState child, final int flags, final int type, + final boolean isVisible, final boolean hasFocus, final boolean hasWallpaper) { + // Add a window to our list of input windows. + inputWindowHandle.name = child.toString(); + inputWindowHandle.layoutParamsFlags = flags; + inputWindowHandle.layoutParamsType = type; + inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); + inputWindowHandle.visible = isVisible; + inputWindowHandle.canReceiveKeys = child.canReceiveKeys(); + inputWindowHandle.hasFocus = hasFocus; + inputWindowHandle.hasWallpaper = hasWallpaper; + inputWindowHandle.paused = child.mAppToken != null ? child.mAppToken.paused : false; + inputWindowHandle.layer = child.mLayer; + inputWindowHandle.ownerPid = child.mSession.mPid; + inputWindowHandle.ownerUid = child.mSession.mUid; + inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures; + + final Rect frame = child.mFrame; + inputWindowHandle.frameLeft = frame.left; + inputWindowHandle.frameTop = frame.top; + inputWindowHandle.frameRight = frame.right; + inputWindowHandle.frameBottom = frame.bottom; + + if (child.mGlobalScale != 1) { + // If we are scaling the window, input coordinates need + // to be inversely scaled to map from what is on screen + // to what is actually being touched in the UI. + inputWindowHandle.scaleFactor = 1.0f/child.mGlobalScale; + } else { + inputWindowHandle.scaleFactor = 1; + } + + child.getTouchableRegion(inputWindowHandle.touchableRegion); + + addInputWindowHandleLw(inputWindowHandle); + } + private void clearInputWindowHandlesLw() { while (mInputWindowHandleCount != 0) { mInputWindowHandles[--mInputWindowHandleCount] = null; @@ -163,7 +203,9 @@ final class InputMonitor implements InputManagerService.Callbacks { // As an optimization, we could try to prune the list of windows but this turns // out to be difficult because only the native code knows for sure which window // currently has touch focus. - final ArrayList<WindowState> windows = mService.mWindows; + final WindowStateAnimator universeBackground = mService.mAnimator.mUniverseBackground; + final int aboveUniverseLayer = mService.mAnimator.mAboveUniverseLayer; + boolean addedUniverse = false; // If there's a drag in flight, provide a pseudowindow to catch drag input final boolean inDrag = (mService.mDragState != null); @@ -185,9 +227,11 @@ final class InputMonitor implements InputManagerService.Callbacks { addInputWindowHandleLw(mService.mFakeWindows.get(i).mWindowHandle); } - final int N = windows.size(); - for (int i = N - 1; i >= 0; i--) { - final WindowState child = windows.get(i); + // Add all windows on the default display. + final AllWindowsIterator iterator = mService.new AllWindowsIterator( + WindowManagerService.REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState child = iterator.next(); final InputChannel inputChannel = child.mInputChannel; final InputWindowHandle inputWindowHandle = child.mInputWindowHandle; if (inputChannel == null || inputWindowHandle == null || child.mRemoved) { @@ -202,46 +246,28 @@ final class InputMonitor implements InputManagerService.Callbacks { final boolean isVisible = child.isVisibleLw(); final boolean hasWallpaper = (child == mService.mWallpaperTarget) && (type != WindowManager.LayoutParams.TYPE_KEYGUARD); + final boolean onDefaultDisplay = (child.getDisplayId() == Display.DEFAULT_DISPLAY); // If there's a drag in progress and 'child' is a potential drop target, // make sure it's been told about the drag - if (inDrag && isVisible) { + if (inDrag && isVisible && onDefaultDisplay) { mService.mDragState.sendDragStartedIfNeededLw(child); } - // Add a window to our list of input windows. - inputWindowHandle.name = child.toString(); - inputWindowHandle.layoutParamsFlags = flags; - inputWindowHandle.layoutParamsType = type; - inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); - inputWindowHandle.visible = isVisible; - inputWindowHandle.canReceiveKeys = child.canReceiveKeys(); - inputWindowHandle.hasFocus = hasFocus; - inputWindowHandle.hasWallpaper = hasWallpaper; - inputWindowHandle.paused = child.mAppToken != null ? child.mAppToken.paused : false; - inputWindowHandle.layer = child.mLayer; - inputWindowHandle.ownerPid = child.mSession.mPid; - inputWindowHandle.ownerUid = child.mSession.mUid; - inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures; - - final Rect frame = child.mFrame; - inputWindowHandle.frameLeft = frame.left; - inputWindowHandle.frameTop = frame.top; - inputWindowHandle.frameRight = frame.right; - inputWindowHandle.frameBottom = frame.bottom; - - if (child.mGlobalScale != 1) { - // If we are scaling the window, input coordinates need - // to be inversely scaled to map from what is on screen - // to what is actually being touched in the UI. - inputWindowHandle.scaleFactor = 1.0f/child.mGlobalScale; - } else { - inputWindowHandle.scaleFactor = 1; + if (universeBackground != null && !addedUniverse + && child.mBaseLayer < aboveUniverseLayer && onDefaultDisplay) { + final WindowState u = universeBackground.mWin; + if (u.mInputChannel != null && u.mInputWindowHandle != null) { + addInputWindowHandleLw(u.mInputWindowHandle, u, u.mAttrs.flags, + u.mAttrs.type, true, u == mInputFocus, false); + } + addedUniverse = true; } - child.getTouchableRegion(inputWindowHandle.touchableRegion); - - addInputWindowHandleLw(inputWindowHandle); + if (child.mWinAnimator != universeBackground) { + addInputWindowHandleLw(inputWindowHandle, child, flags, type, + isVisible, hasFocus, hasWallpaper); + } } // Send windows to native code. diff --git a/services/java/com/android/server/wm/KeyguardDisableHandler.java b/services/java/com/android/server/wm/KeyguardDisableHandler.java new file mode 100644 index 0000000..d935b8b --- /dev/null +++ b/services/java/com/android/server/wm/KeyguardDisableHandler.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.TokenWatcher; +import android.util.Log; +import android.util.Pair; +import android.view.WindowManagerPolicy; + +public class KeyguardDisableHandler extends Handler { + private static final String TAG = "KeyguardDisableHandler"; + + private static final int ALLOW_DISABLE_YES = 1; + private static final int ALLOW_DISABLE_NO = 0; + private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager + private int mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; // sync'd by mKeyguardTokenWatcher + + // Message.what constants + static final int KEYGUARD_DISABLE = 1; + static final int KEYGUARD_REENABLE = 2; + static final int KEYGUARD_POLICY_CHANGED = 3; + + final Context mContext; + final WindowManagerPolicy mPolicy; + KeyguardTokenWatcher mKeyguardTokenWatcher; + + public KeyguardDisableHandler(final Context context, final WindowManagerPolicy policy) { + mContext = context; + mPolicy = policy; + } + + @SuppressWarnings("unchecked") + @Override + public void handleMessage(Message msg) { + if (mKeyguardTokenWatcher == null) { + mKeyguardTokenWatcher = new KeyguardTokenWatcher(this); + } + + switch (msg.what) { + case KEYGUARD_DISABLE: + final Pair<IBinder, String> pair = (Pair<IBinder, String>)msg.obj; + mKeyguardTokenWatcher.acquire(pair.first, pair.second); + break; + + case KEYGUARD_REENABLE: + mKeyguardTokenWatcher.release((IBinder)msg.obj); + break; + + case KEYGUARD_POLICY_CHANGED: + mPolicy.enableKeyguard(true); + // lazily evaluate this next time we're asked to disable keyguard + mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; + break; + } + } + + class KeyguardTokenWatcher extends TokenWatcher { + + public KeyguardTokenWatcher(final Handler handler) { + super(handler, TAG); + } + + @Override + public void acquired() { + // We fail safe and prevent disabling keyguard in the unlikely event this gets + // called before DevicePolicyManagerService has started. + if (mAllowDisableKeyguard == ALLOW_DISABLE_UNKNOWN) { + DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); + if (dpm != null) { + mAllowDisableKeyguard = dpm.getPasswordQuality(null) + == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED ? + ALLOW_DISABLE_YES : ALLOW_DISABLE_NO; + } + } + if (mAllowDisableKeyguard == ALLOW_DISABLE_YES) { + mPolicy.enableKeyguard(false); + } else { + Log.v(TAG, "Not disabling keyguard since device policy is enforced"); + } + } + + @Override + public void released() { + mPolicy.enableKeyguard(true); + } + } +} diff --git a/services/java/com/android/server/wm/MagnificationSpec.java b/services/java/com/android/server/wm/MagnificationSpec.java new file mode 100644 index 0000000..31aae66 --- /dev/null +++ b/services/java/com/android/server/wm/MagnificationSpec.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +public class MagnificationSpec { + public float mScale = 1.0f; + public float mOffsetX; + public float mOffsetY; + + public void initialize(float scale, float offsetX, float offsetY) { + mScale = scale; + mOffsetX = offsetX; + mOffsetY = offsetY; + } + + public boolean isNop() { + return mScale == 1.0f && mOffsetX == 0 && mOffsetY == 0; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("<scale:"); + builder.append(mScale); + builder.append(",offsetX:"); + builder.append(mOffsetX); + builder.append(",offsetY:"); + builder.append(mOffsetY); + builder.append(">"); + return builder.toString(); + } +} diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java index 938db9e..8d2e2e8 100644 --- a/services/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java @@ -25,6 +25,7 @@ import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.Slog; +import android.view.Display; import android.view.Surface; import android.view.SurfaceSession; import android.view.animation.Animation; @@ -41,11 +42,13 @@ class ScreenRotationAnimation { static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200; final Context mContext; + final Display mDisplay; Surface mSurface; BlackFrame mCustomBlackFrame; BlackFrame mExitingBlackFrame; BlackFrame mEnteringBlackFrame; int mWidth, mHeight; + int mExitAnimId, mEnterAnimId; int mOriginalRotation; int mOriginalWidth, mOriginalHeight; @@ -185,9 +188,13 @@ class ScreenRotationAnimation { pw.println(); } - public ScreenRotationAnimation(Context context, SurfaceSession session, - boolean inTransaction, int originalWidth, int originalHeight, int originalRotation) { + public ScreenRotationAnimation(Context context, Display display, SurfaceSession session, + boolean inTransaction, int originalWidth, int originalHeight, int originalRotation, + int exitAnim, int enterAnim) { mContext = context; + mDisplay = display; + mExitAnimId = exitAnim; + mEnterAnimId = enterAnim; // Screenshot does NOT include rotation! if (originalRotation == Surface.ROTATION_90 @@ -212,17 +219,20 @@ class ScreenRotationAnimation { try { try { if (WindowManagerService.DEBUG_SURFACE_TRACE) { - mSurface = new SurfaceTrace(session, 0, "FreezeSurface", -1, mWidth, mHeight, - PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN); + mSurface = new SurfaceTrace(session, "FreezeSurface", + mWidth, mHeight, + PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN); } else { - mSurface = new Surface(session, 0, "FreezeSurface", -1, mWidth, mHeight, - PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN); + mSurface = new Surface(session, "FreezeSurface", + mWidth, mHeight, + PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN); } if (!mSurface.isValid()) { // Screenshot failed, punt. mSurface = null; return; } + mSurface.setLayerStack(mDisplay.getLayerStack()); mSurface.setLayer(FREEZE_LAYER + 1); mSurface.setAlpha(0); mSurface.show(); @@ -234,7 +244,7 @@ class ScreenRotationAnimation { WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, " FREEZE " + mSurface + ": CREATE"); - setRotation(originalRotation); + setRotationInTransaction(originalRotation); } finally { if (!inTransaction) { Surface.closeTransaction(); @@ -254,7 +264,7 @@ class ScreenRotationAnimation { return delta; } - void setSnapshotTransform(Matrix matrix, float alpha) { + private void setSnapshotTransformInTransaction(Matrix matrix, float alpha) { if (mSurface != null) { matrix.getValues(mTmpFloats); mSurface.setPosition(mTmpFloats[Matrix.MTRANS_X], @@ -297,7 +307,7 @@ class ScreenRotationAnimation { } // Must be called while in a transaction. - private void setRotation(int rotation) { + private void setRotationInTransaction(int rotation) { mCurRotation = rotation; // Compute the transformation matrix that must be applied @@ -307,13 +317,13 @@ class ScreenRotationAnimation { createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta); - setSnapshotTransform(mSnapshotInitialMatrix, 1.0f); + setSnapshotTransformInTransaction(mSnapshotInitialMatrix, 1.0f); } // Must be called while in a transaction. - public boolean setRotation(int rotation, SurfaceSession session, + public boolean setRotationInTransaction(int rotation, SurfaceSession session, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) { - setRotation(rotation); + setRotationInTransaction(rotation); if (TWO_PHASE_ANIMATION) { return startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight, false); @@ -369,58 +379,68 @@ class ScreenRotationAnimation { + finalWidth + " finalHeight=" + finalHeight + " origWidth=" + mOriginalWidth + " origHeight=" + mOriginalHeight); - switch (delta) { - case Surface.ROTATION_0: - mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_0_exit); - mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_0_enter); - if (USE_CUSTOM_BLACK_FRAME) { - mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_0_frame); - } - break; - case Surface.ROTATION_90: - mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_plus_90_exit); - mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_plus_90_enter); - if (USE_CUSTOM_BLACK_FRAME) { - mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_plus_90_frame); - } - break; - case Surface.ROTATION_180: - mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_180_exit); - mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_180_enter); - mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_180_frame); - break; - case Surface.ROTATION_270: - mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_minus_90_exit); - mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_minus_90_enter); - if (USE_CUSTOM_BLACK_FRAME) { - mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_minus_90_frame); - } - break; + final boolean customAnim; + if (mExitAnimId != 0 && mEnterAnimId != 0) { + customAnim = true; + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, mExitAnimId); + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, mEnterAnimId); + } else { + customAnim = false; + switch (delta) { + case Surface.ROTATION_0: + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_0_exit); + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_0_enter); + if (USE_CUSTOM_BLACK_FRAME) { + mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_0_frame); + } + break; + case Surface.ROTATION_90: + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_plus_90_exit); + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_plus_90_enter); + if (USE_CUSTOM_BLACK_FRAME) { + mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_plus_90_frame); + } + break; + case Surface.ROTATION_180: + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_180_exit); + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_180_enter); + if (USE_CUSTOM_BLACK_FRAME) { + mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_180_frame); + } + break; + case Surface.ROTATION_270: + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_minus_90_exit); + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_minus_90_enter); + if (USE_CUSTOM_BLACK_FRAME) { + mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_minus_90_frame); + } + break; + } } - // Compute partial steps between original and final sizes. These - // are used for the dimensions of the exiting and entering elements, - // so they are never stretched too significantly. - final int halfWidth = (finalWidth + mOriginalWidth) / 2; - final int halfHeight = (finalHeight + mOriginalHeight) / 2; - // Initialize the animations. This is a hack, redefining what "parent" // means to allow supplying the last and next size. In this definition // "%p" is the original (let's call it "previous") size, and "%" is the // screen's current/new size. if (TWO_PHASE_ANIMATION && firstStart) { + // Compute partial steps between original and final sizes. These + // are used for the dimensions of the exiting and entering elements, + // so they are never stretched too significantly. + final int halfWidth = (finalWidth + mOriginalWidth) / 2; + final int halfHeight = (finalHeight + mOriginalHeight) / 2; + if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations"); mStartEnterAnimation.initialize(finalWidth, finalHeight, halfWidth, halfHeight); @@ -472,6 +492,7 @@ class ScreenRotationAnimation { mRotateFrameAnimation.scaleCurrentDuration(animationScale); } + final int layerStack = mDisplay.getLayerStack(); if (USE_CUSTOM_BLACK_FRAME && mCustomBlackFrame == null) { if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( WindowManagerService.TAG, @@ -490,7 +511,8 @@ class ScreenRotationAnimation { Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1, mOriginalWidth*2, mOriginalHeight*2); Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight); - mCustomBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 3); + mCustomBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 3, + layerStack); mCustomBlackFrame.setMatrix(mFrameInitialMatrix); } catch (Surface.OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); @@ -502,25 +524,25 @@ class ScreenRotationAnimation { } } - if (mExitingBlackFrame == null) { + if (!customAnim && mExitingBlackFrame == null) { if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( WindowManagerService.TAG, ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation"); Surface.openTransaction(); - - // Compute the transformation matrix that must be applied - // the the black frame to make it stay in the initial position - // before the new screen rotation. This is different than the - // snapshot transformation because the snapshot is always based - // of the native orientation of the screen, not the orientation - // we were last in. - createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix); - try { + // Compute the transformation matrix that must be applied + // the the black frame to make it stay in the initial position + // before the new screen rotation. This is different than the + // snapshot transformation because the snapshot is always based + // of the native orientation of the screen, not the orientation + // we were last in. + createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix); + Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1, mOriginalWidth*2, mOriginalHeight*2); Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight); - mExitingBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 2); + mExitingBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 2, + layerStack); mExitingBlackFrame.setMatrix(mFrameInitialMatrix); } catch (Surface.OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); @@ -532,7 +554,7 @@ class ScreenRotationAnimation { } } - if (false && mEnteringBlackFrame == null) { + if (customAnim && mEnteringBlackFrame == null) { if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( WindowManagerService.TAG, ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation"); @@ -542,7 +564,8 @@ class ScreenRotationAnimation { Rect outer = new Rect(-finalWidth*1, -finalHeight*1, finalWidth*2, finalHeight*2); Rect inner = new Rect(0, 0, finalWidth, finalHeight); - mEnteringBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER); + mEnteringBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER, + layerStack); } catch (Surface.OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); } finally { @@ -834,7 +857,7 @@ class ScreenRotationAnimation { return more; } - void updateSurfaces() { + void updateSurfacesInTransaction() { if (!mStarted) { return; } @@ -874,7 +897,7 @@ class ScreenRotationAnimation { } } - setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha()); + setSnapshotTransformInTransaction(mSnapshotFinalMatrix, mExitTransformation.getAlpha()); } public boolean stepAnimationLocked(long now) { diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java index 61c0e9c..16beeab 100644 --- a/services/java/com/android/server/wm/Session.java +++ b/services/java/com/android/server/wm/Session.java @@ -33,6 +33,7 @@ import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Slog; +import android.view.Display; import android.view.IWindow; import android.view.IWindowSession; import android.view.InputChannel; @@ -134,15 +135,33 @@ final class Session extends IWindowSession.Stub } } + @Override public int add(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) { - return mService.addWindow(this, window, seq, attrs, viewVisibility, outContentInsets, - outInputChannel); + return addToDisplay(window, seq, attrs, viewVisibility, Display.DEFAULT_DISPLAY, + outContentInsets, outInputChannel); + } + + @Override + public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, + int viewVisibility, int displayId, Rect outContentInsets, + InputChannel outInputChannel) { + return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, + outContentInsets, outInputChannel); } - + + @Override public int addWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets) { - return mService.addWindow(this, window, seq, attrs, viewVisibility, outContentInsets, null); + return addToDisplayWithoutInputChannel(window, seq, attrs, viewVisibility, + Display.DEFAULT_DISPLAY, outContentInsets); + } + + @Override + public int addToDisplayWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs, + int viewVisibility, int displayId, Rect outContentInsets) { + return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, + outContentInsets, null); } public void remove(IWindow window) { @@ -261,7 +280,7 @@ final class Session extends IWindowSession.Stub // !!! FIXME: put all this heavy stuff onto the mH looper, as well as // the actual drag event dispatch stuff in the dragstate - mService.mDragState.register(); + mService.mDragState.register(callingWin.mDisplayContent.getDisplay()); mService.mInputMonitor.updateInputWindowsLw(true /*force*/); if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel, mService.mDragState.mServerChannel)) { @@ -389,6 +408,31 @@ final class Session extends IWindowSession.Stub mService.wallpaperCommandComplete(window, result); } + public void setUniverseTransform(IBinder window, float alpha, float offx, float offy, + float dsdx, float dtdx, float dsdy, float dtdy) { + synchronized(mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + mService.setUniverseTransformLocked( + mService.windowForClientLocked(this, window, true), + alpha, offx, offy, dsdx, dtdx, dsdy, dtdy); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) { + synchronized(mService.mWindowMap) { + final long identity = Binder.clearCallingIdentity(); + try { + mService.onRectangleOnScreenRequested(token, rectangle, immediate); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + void windowAddedLocked() { if (mSurfaceSession == null) { if (WindowManagerService.localLOGV) Slog.v( diff --git a/services/java/com/android/server/wm/StrictModeFlash.java b/services/java/com/android/server/wm/StrictModeFlash.java index 768d2db..90bbd08 100644 --- a/services/java/com/android/server/wm/StrictModeFlash.java +++ b/services/java/com/android/server/wm/StrictModeFlash.java @@ -22,8 +22,6 @@ import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; -import android.util.DisplayMetrics; -import android.util.Slog; import android.view.Display; import android.view.Surface; import android.view.SurfaceSession; @@ -39,13 +37,16 @@ class StrictModeFlash { public StrictModeFlash(Display display, SurfaceSession session) { try { - mSurface = new Surface(session, 0, "StrictModeFlash", -1, 1, 1, PixelFormat.TRANSLUCENT, 0); + mSurface = new Surface(session, "StrictModeFlash", + 1, 1, PixelFormat.TRANSLUCENT, Surface.HIDDEN); } catch (Surface.OutOfResourcesException e) { return; } + mSurface.setLayerStack(display.getLayerStack()); mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary. mSurface.setPosition(0, 0); + mSurface.show(); mDrawNeeded = true; } diff --git a/services/java/com/android/server/wm/Watermark.java b/services/java/com/android/server/wm/Watermark.java index 5497eb4..ac152c9 100644 --- a/services/java/com/android/server/wm/Watermark.java +++ b/services/java/com/android/server/wm/Watermark.java @@ -35,6 +35,7 @@ import android.view.Surface.OutOfResourcesException; * Displays a watermark on top of the window manager's windows. */ class Watermark { + final Display mDisplay; final String[] mTokens; final String mText; final Paint mTextPaint; @@ -50,7 +51,7 @@ class Watermark { int mLastDH; boolean mDrawNeeded; - Watermark(DisplayMetrics dm, SurfaceSession session, String[] tokens) { + Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) { if (false) { Log.i(WindowManagerService.TAG, "*********************** WATERMARK"); for (int i=0; i<tokens.length; i++) { @@ -58,6 +59,7 @@ class Watermark { } } + mDisplay = display; mTokens = tokens; StringBuilder builder = new StringBuilder(32); @@ -111,8 +113,9 @@ class Watermark { mTextPaint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor); try { - mSurface = new Surface(session, 0, - "WatermarkSurface", -1, 1, 1, PixelFormat.TRANSLUCENT, 0); + mSurface = new Surface(session, "WatermarkSurface", + 1, 1, PixelFormat.TRANSLUCENT, Surface.HIDDEN); + mSurface.setLayerStack(mDisplay.getLayerStack()); mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100); mSurface.setPosition(0, 0); mSurface.show(); @@ -171,4 +174,4 @@ class Watermark { } } } -}
\ No newline at end of file +} diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index 62cf711..0a4e6d3 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -8,19 +8,23 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_UPDATE_ROTATION; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_MAY_CHANGE; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_FORCE_HIDING_CHANGED; +import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE; -import static com.android.server.wm.WindowManagerService.H.SET_DIM_PARAMETERS; +import static com.android.server.wm.WindowManagerService.H.UPDATE_ANIM_PARAMETERS; import android.content.Context; import android.os.SystemClock; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseIntArray; +import android.view.Display; import android.view.Surface; -import android.view.WindowManager; import android.view.WindowManagerPolicy; import android.view.animation.Animation; -import com.android.internal.policy.impl.PhoneWindowManager; +import com.android.server.wm.WindowManagerService.AppWindowAnimParams; +import com.android.server.wm.WindowManagerService.LayoutToAnimatorParams; import java.io.PrintWriter; import java.util.ArrayList; @@ -32,26 +36,20 @@ import java.util.ArrayList; public class WindowAnimator { private static final String TAG = "WindowAnimator"; - // mForceHiding states. - private static final int KEYGUARD_NOT_SHOWN = 0; - private static final int KEYGUARD_ANIMATING_IN = 1; - private static final int KEYGUARD_SHOWN = 2; - private static final int KEYGUARD_ANIMATING_OUT = 3; - int mForceHiding; - final WindowManagerService mService; final Context mContext; final WindowManagerPolicy mPolicy; - ArrayList<WindowStateAnimator> mWinAnimators = new ArrayList<WindowStateAnimator>(); - boolean mAnimating; - WindowState mWindowAnimationBackground; - int mWindowAnimationBackgroundColor; + + final Runnable mAnimationRunnable; + int mAdjResult; - int mPendingLayoutChanges; + // Layout changes for individual Displays. Indexed by displayId. + SparseIntArray mPendingLayoutChanges = new SparseIntArray(); + // TODO: Assign these from each iteration through DisplayContent. Only valid between loops. /** Overall window dimensions */ int mDw, mDh; @@ -65,105 +63,211 @@ public class WindowAnimator { * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */ private int mAnimTransactionSequence; - /** The one and only screen rotation if one is happening */ - ScreenRotationAnimation mScreenRotationAnimation = null; - // Window currently running an animation that has requested it be detached // from the wallpaper. This means we need to ensure the wallpaper is // visible behind it in case it animates in a way that would allow it to be - // seen. + // seen. If multiple windows satisfy this, use the lowest window. WindowState mWindowDetachedWallpaper = null; - WindowState mDetachedWallpaper = null; - DimSurface mWindowAnimationBackgroundSurface = null; + + WindowStateAnimator mUniverseBackground = null; + int mAboveUniverseLayer = 0; int mBulkUpdateParams = 0; - DimAnimator mDimAnimator = null; - DimAnimator.Parameters mDimParams = null; + SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = + new SparseArray<WindowAnimator.DisplayContentsAnimator>(); static final int WALLPAPER_ACTION_PENDING = 1; int mPendingActions; - WindowAnimator(final WindowManagerService service, final Context context, - final WindowManagerPolicy policy) { - mService = service; - mContext = context; - mPolicy = policy; + WindowState mWallpaperTarget = null; + AppWindowAnimator mWpAppAnimator = null; + WindowState mLowerWallpaperTarget = null; + WindowState mUpperWallpaperTarget = null; + + ArrayList<AppWindowAnimator> mAppAnimators = new ArrayList<AppWindowAnimator>(); + + ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); + + /** Parameters being passed from this into mService. */ + static class AnimatorToLayoutParams { + boolean mUpdateQueued; + int mBulkUpdateParams; + SparseIntArray mPendingLayoutChanges; + WindowState mWindowDetachedWallpaper; + } + /** Do not modify unless holding mService.mWindowMap or this and mAnimToLayout in that order */ + final AnimatorToLayoutParams mAnimToLayout = new AnimatorToLayoutParams(); + + boolean mInitialized = false; + + // forceHiding states. + static final int KEYGUARD_NOT_SHOWN = 0; + static final int KEYGUARD_ANIMATING_IN = 1; + static final int KEYGUARD_SHOWN = 2; + static final int KEYGUARD_ANIMATING_OUT = 3; + int mForceHiding = KEYGUARD_NOT_SHOWN; + + private String forceHidingToString() { + switch (mForceHiding) { + case KEYGUARD_NOT_SHOWN: return "KEYGUARD_NOT_SHOWN"; + case KEYGUARD_ANIMATING_IN: return "KEYGUARD_ANIMATING_IN"; + case KEYGUARD_SHOWN: return "KEYGUARD_SHOWN"; + case KEYGUARD_ANIMATING_OUT:return "KEYGUARD_ANIMATING_OUT"; + default: return "KEYGUARD STATE UNKNOWN " + mForceHiding; + } } - void hideWallpapersLocked(final WindowState w) { - if ((mService.mWallpaperTarget == w && mService.mLowerWallpaperTarget == null) - || mService.mWallpaperTarget == null) { - for (final WindowToken token : mService.mWallpaperTokens) { - for (final WindowState wallpaper : token.windows) { - final WindowStateAnimator winAnimator = wallpaper.mWinAnimator; - if (!winAnimator.mLastHidden) { - winAnimator.hide(); - mService.dispatchWallpaperVisibility(wallpaper, false); - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + WindowAnimator(final WindowManagerService service) { + mService = service; + mContext = service.mContext; + mPolicy = service.mPolicy; + + mAnimationRunnable = new Runnable() { + @Override + public void run() { + // TODO(cmautner): When full isolation is achieved for animation, the first lock + // goes away and only the WindowAnimator.this remains. + synchronized(mService.mWindowMap) { + synchronized(WindowAnimator.this) { + copyLayoutToAnimParamsLocked(); + animateLocked(); } } - token.hidden = true; } + }; + } + + void addDisplayLocked(final int displayId) { + DisplayContentsAnimator displayAnimator = getDisplayContentsAnimatorLocked(displayId); + displayAnimator.mWindowAnimationBackgroundSurface = + new DimSurface(mService.mFxSession, displayId); + displayAnimator.mDimAnimator = new DimAnimator(mService.mFxSession, displayId); + if (displayId == Display.DEFAULT_DISPLAY) { + mInitialized = true; } } - private void testWallpaperAndBackgroundLocked() { - if (mWindowDetachedWallpaper != mDetachedWallpaper) { - if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG, - "Detached wallpaper changed from " + mWindowDetachedWallpaper - + " to " + mDetachedWallpaper); - mWindowDetachedWallpaper = mDetachedWallpaper; - mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; + void removeDisplayLocked(final int displayId) { + mDisplayContentsAnimators.delete(displayId); + } + + /** Locked on mAnimToLayout */ + void updateAnimToLayoutLocked() { + final AnimatorToLayoutParams animToLayout = mAnimToLayout; + synchronized (animToLayout) { + animToLayout.mBulkUpdateParams = mBulkUpdateParams; + animToLayout.mPendingLayoutChanges = mPendingLayoutChanges.clone(); + animToLayout.mWindowDetachedWallpaper = mWindowDetachedWallpaper; + + if (!animToLayout.mUpdateQueued) { + animToLayout.mUpdateQueued = true; + mService.mH.sendMessage(mService.mH.obtainMessage(UPDATE_ANIM_PARAMETERS)); + } } + } - if (mWindowAnimationBackgroundColor != 0) { - // If the window that wants black is the current wallpaper - // target, then the black goes *below* the wallpaper so we - // don't cause the wallpaper to suddenly disappear. - WindowState target = mWindowAnimationBackground; - if (mService.mWallpaperTarget == target - || mService.mLowerWallpaperTarget == target - || mService.mUpperWallpaperTarget == target) { - final int N = mService.mWindows.size(); - for (int i = 0; i < N; i++) { - WindowState w = mService.mWindows.get(i); - if (w.mIsWallpaper) { - target = w; - break; + /** Copy all WindowManagerService params into local params here. Locked on 'this'. */ + private void copyLayoutToAnimParamsLocked() { + final LayoutToAnimatorParams layoutToAnim = mService.mLayoutToAnim; + synchronized(layoutToAnim) { + layoutToAnim.mAnimationScheduled = false; + + if (!layoutToAnim.mParamsModified) { + return; + } + layoutToAnim.mParamsModified = false; + + if ((layoutToAnim.mChanges & LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED) != 0) { + layoutToAnim.mChanges &= ~LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED; + mWallpaperTokens = new ArrayList<WindowToken>(layoutToAnim.mWallpaperTokens); + } + + mWallpaperTarget = layoutToAnim.mWallpaperTarget; + mWpAppAnimator = mWallpaperTarget == null + ? null : mWallpaperTarget.mAppToken == null + ? null : mWallpaperTarget.mAppToken.mAppAnimator; + mLowerWallpaperTarget = layoutToAnim.mLowerWallpaperTarget; + mUpperWallpaperTarget = layoutToAnim.mUpperWallpaperTarget; + + // Set the new DimAnimator params. + final int numDisplays = mDisplayContentsAnimators.size(); + for (int i = 0; i < numDisplays; i++) { + final int displayId = mDisplayContentsAnimators.keyAt(i); + DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); + + displayAnimator.mWinAnimators.clear(); + final WinAnimatorList winAnimators = layoutToAnim.mWinAnimatorLists.get(displayId); + if (winAnimators != null) { + displayAnimator.mWinAnimators.addAll(winAnimators); + } + + DimAnimator.Parameters dimParams = layoutToAnim.mDimParams.get(displayId); + if (dimParams == null) { + displayAnimator.mDimParams = null; + } else { + final WindowStateAnimator newWinAnimator = dimParams.mDimWinAnimator; + + // Only set dim params on the highest dimmed layer. + final WindowStateAnimator existingDimWinAnimator = + displayAnimator.mDimParams == null ? + null : displayAnimator.mDimParams.mDimWinAnimator; + // Don't turn on for an unshown surface, or for any layer but the highest + // dimmed layer. + if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null + || !existingDimWinAnimator.mSurfaceShown + || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) { + displayAnimator.mDimParams = new DimAnimator.Parameters(dimParams); } } } - if (mWindowAnimationBackgroundSurface == null) { - mWindowAnimationBackgroundSurface = new DimSurface(mService.mFxSession); + + mAppAnimators.clear(); + final int N = layoutToAnim.mAppWindowAnimParams.size(); + for (int i = 0; i < N; i++) { + final AppWindowAnimParams params = layoutToAnim.mAppWindowAnimParams.get(i); + AppWindowAnimator appAnimator = params.mAppAnimator; + appAnimator.mAllAppWinAnimators.clear(); + appAnimator.mAllAppWinAnimators.addAll(params.mWinAnimators); + mAppAnimators.add(appAnimator); } - final int dw = mDw; - final int dh = mDh; - mWindowAnimationBackgroundSurface.show(dw, dh, - target.mWinAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM, - mWindowAnimationBackgroundColor); - } else if (mWindowAnimationBackgroundSurface != null) { - mWindowAnimationBackgroundSurface.hide(); } } - private void updateWindowsAppsAndRotationAnimationsLocked() { - final ArrayList<AppWindowToken> appTokens = mService.mAnimatingAppTokens; + void hideWallpapersLocked(final WindowState w) { + if ((mWallpaperTarget == w && mLowerWallpaperTarget == null) || mWallpaperTarget == null) { + final int numTokens = mWallpaperTokens.size(); + for (int i = numTokens - 1; i >= 0; i--) { + final WindowToken token = mWallpaperTokens.get(i); + final int numWindows = token.windows.size(); + for (int j = numWindows - 1; j >= 0; j--) { + final WindowState wallpaper = token.windows.get(j); + final WindowStateAnimator winAnimator = wallpaper.mWinAnimator; + if (!winAnimator.mLastHidden) { + winAnimator.hide(); + mService.dispatchWallpaperVisibility(wallpaper, false); + setPendingLayoutChanges(Display.DEFAULT_DISPLAY, + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); + } + } + token.hidden = true; + } + } + } + + private void updateAppWindowsLocked() { int i; - final int NAT = appTokens.size(); + final int NAT = mAppAnimators.size(); for (i=0; i<NAT; i++) { - final AppWindowAnimator appAnimator = appTokens.get(i).mAppAnimator; + final AppWindowAnimator appAnimator = mAppAnimators.get(i); final boolean wasAnimating = appAnimator.animation != null && appAnimator.animation != AppWindowAnimator.sDummyAnimation; if (appAnimator.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) { mAnimating = true; } else if (wasAnimating) { // stopped animating, do one more pass through the layout - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("appToken " + appAnimator.mAppToken + " done", - mPendingLayoutChanges); - } + setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, + "appToken " + appAnimator.mAppToken + " done"); if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating " + appAnimator.mAppToken); } @@ -178,36 +282,26 @@ public class WindowAnimator { mAnimating = true; } else if (wasAnimating) { // stopped animating, do one more pass through the layout - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("exiting appToken " + appAnimator.mAppToken - + " done", mPendingLayoutChanges); - } + setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, + "exiting appToken " + appAnimator.mAppToken + " done"); if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken); } } - - if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) { - if (mScreenRotationAnimation.stepAnimationLocked(mCurrentTime)) { - mAnimating = true; - } else { - mBulkUpdateParams |= SET_UPDATE_ROTATION; - mScreenRotationAnimation.kill(); - mScreenRotationAnimation = null; - } - } } - private void updateWindowsAndWallpaperLocked() { + private void updateWindowsLocked(final int displayId) { ++mAnimTransactionSequence; + final WinAnimatorList winAnimatorList = + getDisplayContentsAnimatorLocked(displayId).mWinAnimators; ArrayList<WindowStateAnimator> unForceHiding = null; boolean wallpaperInUnForceHiding = false; + mForceHiding = KEYGUARD_NOT_SHOWN; - for (int i = mService.mWindows.size() - 1; i >= 0; i--) { - WindowState win = mService.mWindows.get(i); - WindowStateAnimator winAnimator = win.mWinAnimator; + for (int i = winAnimatorList.size() - 1; i >= 0; i--) { + WindowStateAnimator winAnimator = winAnimatorList.get(i); + WindowState win = winAnimator.mWin; final int flags = winAnimator.mAttrFlags; if (winAnimator.mSurface != null) { @@ -219,56 +313,13 @@ public class WindowAnimator { ", nowAnimating=" + nowAnimating); } - // If this window is animating, make a note that we have - // an animating window and take care of a request to run - // a detached wallpaper animation. - if (nowAnimating) { - if (winAnimator.mAnimation != null) { - if ((flags & FLAG_SHOW_WALLPAPER) != 0 - && winAnimator.mAnimation.getDetachWallpaper()) { - mDetachedWallpaper = win; - } - final int backgroundColor = winAnimator.mAnimation.getBackgroundColor(); - if (backgroundColor != 0) { - if (mWindowAnimationBackground == null - || (winAnimator.mAnimLayer < - mWindowAnimationBackground.mWinAnimator.mAnimLayer)) { - mWindowAnimationBackground = win; - mWindowAnimationBackgroundColor = backgroundColor; - } - } - } - mAnimating = true; - } - - // If this window's app token is running a detached wallpaper - // animation, make a note so we can ensure the wallpaper is - // displayed behind it. - final AppWindowAnimator appAnimator = - win.mAppToken == null ? null : win.mAppToken.mAppAnimator; - if (appAnimator != null && appAnimator.animation != null - && appAnimator.animating) { - if ((flags & FLAG_SHOW_WALLPAPER) != 0 - && appAnimator.animation.getDetachWallpaper()) { - mDetachedWallpaper = win; - } - final int backgroundColor = appAnimator.animation.getBackgroundColor(); - if (backgroundColor != 0) { - if (mWindowAnimationBackground == null - || (winAnimator.mAnimLayer < - mWindowAnimationBackground.mWinAnimator.mAnimLayer)) { - mWindowAnimationBackground = win; - mWindowAnimationBackgroundColor = backgroundColor; - } - } - } - - if (wasAnimating && !winAnimator.mAnimating && mService.mWallpaperTarget == win) { + if (wasAnimating && !winAnimator.mAnimating && mWallpaperTarget == win) { mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + setPendingLayoutChanges(Display.DEFAULT_DISPLAY, + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2", - mPendingLayoutChanges); + mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY)); } } @@ -278,10 +329,11 @@ public class WindowAnimator { WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Animation started that could impact force hide: " + win); mBulkUpdateParams |= SET_FORCE_HIDING_CHANGED; - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + setPendingLayoutChanges(displayId, + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 3", - mPendingLayoutChanges); + mPendingLayoutChanges.get(displayId)); } mService.mFocusMayChange = true; } @@ -326,7 +378,7 @@ public class WindowAnimator { unForceHiding = new ArrayList<WindowStateAnimator>(); } unForceHiding.add(winAnimator); - if ((win.mAttrs.flags&WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + if ((flags & FLAG_SHOW_WALLPAPER) != 0) { wallpaperInUnForceHiding = true; } } @@ -338,12 +390,13 @@ public class WindowAnimator { } } } - if (changed && (flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + if (changed && (flags & FLAG_SHOW_WALLPAPER) != 0) { mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + setPendingLayoutChanges(Display.DEFAULT_DISPLAY, + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4", - mPendingLayoutChanges); + mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY)); } } } @@ -353,16 +406,16 @@ public class WindowAnimator { if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) { if (atoken == null || atoken.allDrawn) { if (winAnimator.performShowLocked()) { - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; + mPendingLayoutChanges.put(displayId, + WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 5", - mPendingLayoutChanges); + mPendingLayoutChanges.get(displayId)); } } } } - final AppWindowAnimator appAnimator = - atoken == null ? null : atoken.mAppAnimator; + final AppWindowAnimator appAnimator = winAnimator.mAppAnimator; if (appAnimator != null && appAnimator.thumbnail != null) { if (appAnimator.thumbnailTransactionSeq != mAnimTransactionSequence) { appAnimator.thumbnailTransactionSeq = mAnimTransactionSequence; @@ -388,38 +441,134 @@ public class WindowAnimator { } } + private void updateWallpaperLocked(int displayId) { + final DisplayContentsAnimator displayAnimator = + getDisplayContentsAnimatorLocked(displayId); + final WinAnimatorList winAnimatorList = displayAnimator.mWinAnimators; + WindowStateAnimator windowAnimationBackground = null; + int windowAnimationBackgroundColor = 0; + WindowState detachedWallpaper = null; + final DimSurface windowAnimationBackgroundSurface = + displayAnimator.mWindowAnimationBackgroundSurface; + + for (int i = winAnimatorList.size() - 1; i >= 0; i--) { + WindowStateAnimator winAnimator = winAnimatorList.get(i); + if (winAnimator.mSurface == null) { + continue; + } + + final int flags = winAnimator.mAttrFlags; + final WindowState win = winAnimator.mWin; + + // If this window is animating, make a note that we have + // an animating window and take care of a request to run + // a detached wallpaper animation. + if (winAnimator.mAnimating) { + if (winAnimator.mAnimation != null) { + if ((flags & FLAG_SHOW_WALLPAPER) != 0 + && winAnimator.mAnimation.getDetachWallpaper()) { + detachedWallpaper = win; + } + final int backgroundColor = winAnimator.mAnimation.getBackgroundColor(); + if (backgroundColor != 0) { + if (windowAnimationBackground == null || (winAnimator.mAnimLayer < + windowAnimationBackground.mAnimLayer)) { + windowAnimationBackground = winAnimator; + windowAnimationBackgroundColor = backgroundColor; + } + } + } + mAnimating = true; + } + + // If this window's app token is running a detached wallpaper + // animation, make a note so we can ensure the wallpaper is + // displayed behind it. + final AppWindowAnimator appAnimator = winAnimator.mAppAnimator; + if (appAnimator != null && appAnimator.animation != null + && appAnimator.animating) { + if ((flags & FLAG_SHOW_WALLPAPER) != 0 + && appAnimator.animation.getDetachWallpaper()) { + detachedWallpaper = win; + } + + final int backgroundColor = appAnimator.animation.getBackgroundColor(); + if (backgroundColor != 0) { + if (windowAnimationBackground == null || (winAnimator.mAnimLayer < + windowAnimationBackground.mAnimLayer)) { + windowAnimationBackground = winAnimator; + windowAnimationBackgroundColor = backgroundColor; + } + } + } + } // end forall windows + + if (mWindowDetachedWallpaper != detachedWallpaper) { + if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG, + "Detached wallpaper changed from " + mWindowDetachedWallpaper + + " to " + detachedWallpaper); + mWindowDetachedWallpaper = detachedWallpaper; + mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; + } + + if (windowAnimationBackgroundColor != 0) { + // If the window that wants black is the current wallpaper + // target, then the black goes *below* the wallpaper so we + // don't cause the wallpaper to suddenly disappear. + int animLayer = windowAnimationBackground.mAnimLayer; + WindowState win = windowAnimationBackground.mWin; + if (mWallpaperTarget == win + || mLowerWallpaperTarget == win || mUpperWallpaperTarget == win) { + final int N = winAnimatorList.size(); + for (int i = 0; i < N; i++) { + WindowStateAnimator winAnimator = winAnimatorList.get(i); + if (winAnimator.mIsWallpaper) { + animLayer = winAnimator.mAnimLayer; + break; + } + } + } + + windowAnimationBackgroundSurface.show(mDw, mDh, + animLayer - WindowManagerService.LAYER_OFFSET_DIM, + windowAnimationBackgroundColor); + } else { + windowAnimationBackgroundSurface.hide(); + } + } + private void testTokenMayBeDrawnLocked() { // See if any windows have been drawn, so they (and others // associated with them) can now be shown. - final ArrayList<AppWindowToken> appTokens = mService.mAnimatingAppTokens; - final int NT = appTokens.size(); + final int NT = mAppAnimators.size(); for (int i=0; i<NT; i++) { - AppWindowToken wtoken = appTokens.get(i); + AppWindowAnimator appAnimator = mAppAnimators.get(i); + AppWindowToken wtoken = appAnimator.mAppToken; final boolean allDrawn = wtoken.allDrawn; - if (allDrawn != wtoken.mAppAnimator.allDrawn) { - wtoken.mAppAnimator.allDrawn = allDrawn; + if (allDrawn != appAnimator.allDrawn) { + appAnimator.allDrawn = allDrawn; if (allDrawn) { // The token has now changed state to having all // windows shown... what to do, what to do? - if (wtoken.mAppAnimator.freezingScreen) { - wtoken.mAppAnimator.showAllWindowsLocked(); + if (appAnimator.freezingScreen) { + appAnimator.showAllWindowsLocked(); mService.unsetAppFreezingScreenLocked(wtoken, false, true); if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(TAG, "Setting mOrientationChangeComplete=true because wtoken " + wtoken + " numInteresting=" + wtoken.numInterestingWindows + " numDrawn=" + wtoken.numDrawnWindows); // This will set mOrientationChangeComplete and cause a pass through layout. - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + setAppLayoutChanges(appAnimator, + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, + "testTokenMayBeDrawnLocked: freezingScreen"); } else { - mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("testTokenMayBeDrawnLocked", - mPendingLayoutChanges); - } - + setAppLayoutChanges(appAnimator, + WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, + "testTokenMayBeDrawnLocked"); + // We can now show all of the drawn windows! if (!mService.mOpeningApps.contains(wtoken)) { - mAnimating |= wtoken.mAppAnimator.showAllWindowsLocked(); + mAnimating |= appAnimator.showAllWindowsLocked(); } } } @@ -427,64 +576,82 @@ public class WindowAnimator { } } - private void performAnimationsLocked() { - mForceHiding = KEYGUARD_NOT_SHOWN; - mDetachedWallpaper = null; - mWindowAnimationBackground = null; - mWindowAnimationBackgroundColor = 0; + private void performAnimationsLocked(final int displayId) { + updateWindowsLocked(displayId); + updateWallpaperLocked(displayId); + } - updateWindowsAndWallpaperLocked(); - if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { - mPendingActions |= WALLPAPER_ACTION_PENDING; + // TODO(cmautner): Change the following comment when no longer locked on mWindowMap */ + /** Locked on mService.mWindowMap and this. */ + private void animateLocked() { + if (!mInitialized) { + return; } - testTokenMayBeDrawnLocked(); - } - - synchronized void animate() { - mPendingLayoutChanges = 0; + mPendingLayoutChanges.clear(); mCurrentTime = SystemClock.uptimeMillis(); - mBulkUpdateParams = 0; + mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE; boolean wasAnimating = mAnimating; mAnimating = false; if (WindowManagerService.DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime); } - // Update animations of all applications, including those - // associated with exiting/removed apps + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i( + TAG, ">>> OPEN TRANSACTION animateLocked"); Surface.openTransaction(); - try { - updateWindowsAppsAndRotationAnimationsLocked(); - performAnimationsLocked(); - testWallpaperAndBackgroundLocked(); + updateAppWindowsLocked(); + + final int numDisplays = mDisplayContentsAnimators.size(); + for (int i = 0; i < numDisplays; i++) { + final int displayId = mDisplayContentsAnimators.keyAt(i); + DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); + + final ScreenRotationAnimation screenRotationAnimation = + displayAnimator.mScreenRotationAnimation; + if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { + if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) { + mAnimating = true; + } else { + mBulkUpdateParams |= SET_UPDATE_ROTATION; + screenRotationAnimation.kill(); + displayAnimator.mScreenRotationAnimation = null; + } + } - // THIRD LOOP: Update the surfaces of all windows. + // Update animations of all applications, including those + // associated with exiting/removed apps + performAnimationsLocked(displayId); - if (mScreenRotationAnimation != null) { - mScreenRotationAnimation.updateSurfaces(); + final WinAnimatorList winAnimatorList = displayAnimator.mWinAnimators; + final int N = winAnimatorList.size(); + for (int j = 0; j < N; j++) { + winAnimatorList.get(j).prepareSurfaceLocked(true); + } } - final int N = mWinAnimators.size(); - for (int i = 0; i < N; i++) { - mWinAnimators.get(i).prepareSurfaceLocked(true); - } + testTokenMayBeDrawnLocked(); - if (mDimParams != null) { - mDimAnimator.updateParameters(mContext.getResources(), mDimParams, mCurrentTime); - } - if (mDimAnimator != null && mDimAnimator.mDimShown) { - mAnimating |= mDimAnimator.updateSurface(isDimming(), mCurrentTime, - !mService.okToDisplay()); - } + for (int i = 0; i < numDisplays; i++) { + final int displayId = mDisplayContentsAnimators.keyAt(i); + DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); - if (mService.mBlackFrame != null) { - if (mScreenRotationAnimation != null) { - mService.mBlackFrame.setMatrix( - mScreenRotationAnimation.getEnterTransformation().getMatrix()); - } else { - mService.mBlackFrame.clearMatrix(); + final ScreenRotationAnimation screenRotationAnimation = + displayAnimator.mScreenRotationAnimation; + if (screenRotationAnimation != null) { + screenRotationAnimation.updateSurfacesInTransaction(); + } + + final DimAnimator.Parameters dimParams = displayAnimator.mDimParams; + final DimAnimator dimAnimator = displayAnimator.mDimAnimator; + if (dimParams != null) { + dimAnimator.updateParameters( + mContext.getResources(), dimParams, mCurrentTime); + } + if (dimAnimator != null && dimAnimator.mDimShown) { + mAnimating |= dimAnimator.updateSurface(isDimmingLocked(displayId), + mCurrentTime, !mService.okToDisplay()); } } @@ -495,19 +662,33 @@ public class WindowAnimator { Log.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { Surface.closeTransaction(); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i( + TAG, "<<< CLOSE TRANSACTION animateLocked"); } - mService.bulkSetParameters(mBulkUpdateParams, mPendingLayoutChanges); + for (int i = mPendingLayoutChanges.size() - 1; i >= 0; i--) { + if ((mPendingLayoutChanges.valueAt(i) + & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { + mPendingActions |= WALLPAPER_ACTION_PENDING; + } + } + + if (mBulkUpdateParams != 0 || mPendingLayoutChanges.size() > 0) { + updateAnimToLayoutLocked(); + } if (mAnimating) { - mService.scheduleAnimationLocked(); + synchronized (mService.mLayoutToAnim) { + mService.scheduleAnimationLocked(); + } } else if (wasAnimating) { mService.requestTraversalLocked(); } if (WindowManagerService.DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams) - + " mPendingLayoutChanges=" + Integer.toHexString(mPendingLayoutChanges)); + + " mPendingLayoutChanges(DEFAULT_DISPLAY)=" + + Integer.toHexString(mPendingLayoutChanges.get(Display.DEFAULT_DISPLAY))); } } @@ -524,72 +705,107 @@ public class WindowAnimator { mInnerDh = appHeight; } - void startDimming(final WindowStateAnimator winAnimator, final float target, - final int width, final int height) { - if (mDimAnimator == null) { - mDimAnimator = new DimAnimator(mService.mFxSession); - } - // Only set dim params on the highest dimmed layer. - final WindowStateAnimator dimWinAnimator = mDimParams == null - ? null : mDimParams.mDimWinAnimator; - // Don't turn on for an unshown surface, or for any layer but the highest dimmed one. - if (winAnimator.mSurfaceShown && - (dimWinAnimator == null || !dimWinAnimator.mSurfaceShown - || dimWinAnimator.mAnimLayer < winAnimator.mAnimLayer)) { - mService.mH.sendMessage(mService.mH.obtainMessage(SET_DIM_PARAMETERS, - new DimAnimator.Parameters(winAnimator, width, height, target))); - } - } - - // TODO(cmautner): Move into Handler - void stopDimming() { - mService.mH.sendMessage(mService.mH.obtainMessage(SET_DIM_PARAMETERS, null)); + boolean isDimmingLocked(int displayId) { + return getDisplayContentsAnimatorLocked(displayId).mDimParams != null; } - boolean isDimming() { - return mDimParams != null; + boolean isDimmingLocked(final WindowStateAnimator winAnimator) { + DimAnimator.Parameters dimParams = + getDisplayContentsAnimatorLocked(winAnimator.mWin.getDisplayId()).mDimParams; + return dimParams != null && dimParams.mDimWinAnimator == winAnimator; } - boolean isDimming(final WindowStateAnimator winAnimator) { - return mDimParams != null && mDimParams.mDimWinAnimator == winAnimator; - } - - public void dump(PrintWriter pw, String prefix, boolean dumpAll) { + public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) { if (dumpAll) { if (mWindowDetachedWallpaper != null) { pw.print(prefix); pw.print("mWindowDetachedWallpaper="); - pw.println(mWindowDetachedWallpaper); + pw.println(mWindowDetachedWallpaper); } pw.print(prefix); pw.print("mAnimTransactionSequence="); - pw.println(mAnimTransactionSequence); - if (mWindowAnimationBackgroundSurface != null) { - pw.print(prefix); pw.print("mWindowAnimationBackgroundSurface:"); - mWindowAnimationBackgroundSurface.printTo(prefix + " ", pw); + pw.print(mAnimTransactionSequence); + pw.println(" mForceHiding=" + forceHidingToString()); + for (int i = 0; i < mDisplayContentsAnimators.size(); i++) { + pw.print(prefix); pw.print("DisplayContentsAnimator #"); + pw.println(mDisplayContentsAnimators.keyAt(i)); + DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); + final String subPrefix = " " + prefix; + final String subSubPrefix = " " + subPrefix; + if (displayAnimator.mWindowAnimationBackgroundSurface != null) { + pw.println(subPrefix + "mWindowAnimationBackgroundSurface:"); + displayAnimator.mWindowAnimationBackgroundSurface.printTo(subSubPrefix, pw); + } + if (displayAnimator.mDimAnimator != null) { + pw.println(subPrefix + "mDimAnimator:"); + displayAnimator.mDimAnimator.printTo(subSubPrefix, pw); + } else { + pw.println(subPrefix + "no DimAnimator "); + } + if (displayAnimator.mDimParams != null) { + pw.println(subPrefix + "mDimParams:"); + displayAnimator.mDimParams.printTo(subSubPrefix, pw); + } else { + pw.println(subPrefix + "no DimParams "); + } + if (displayAnimator.mScreenRotationAnimation != null) { + pw.println(subPrefix + "mScreenRotationAnimation:"); + displayAnimator.mScreenRotationAnimation.printTo(subSubPrefix, pw); + } else { + pw.print(subPrefix + "no ScreenRotationAnimation "); + } } - if (mDimAnimator != null) { - pw.print(prefix); pw.print("mDimAnimator:"); - mDimAnimator.printTo(prefix + " ", pw); - } else { - pw.print(prefix); pw.print("no DimAnimator "); + pw.println(); + } + } + + void clearPendingActions() { + synchronized (this) { + mPendingActions = 0; + } + } + + void setPendingLayoutChanges(final int displayId, final int changes) { + mPendingLayoutChanges.put(displayId, mPendingLayoutChanges.get(displayId) | changes); + } + + void setAppLayoutChanges(final AppWindowAnimator appAnimator, final int changes, String s) { + // Used to track which displays layout changes have been done. + SparseIntArray displays = new SparseIntArray(); + for (int i = appAnimator.mAllAppWinAnimators.size() - 1; i >= 0; i--) { + WindowStateAnimator winAnimator = appAnimator.mAllAppWinAnimators.get(i); + final int displayId = winAnimator.mWin.mDisplayContent.getDisplayId(); + if (displays.indexOfKey(displayId) < 0) { + setPendingLayoutChanges(displayId, changes); + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + mService.debugLayoutRepeats(s, mPendingLayoutChanges.get(displayId)); + } + // Keep from processing this display again. + displays.put(displayId, changes); } } } - static class SetAnimationParams { - final WindowStateAnimator mWinAnimator; - final Animation mAnimation; - final int mAnimDw; - final int mAnimDh; - public SetAnimationParams(final WindowStateAnimator winAnimator, - final Animation animation, final int animDw, final int animDh) { - mWinAnimator = winAnimator; - mAnimation = animation; - mAnimDw = animDw; - mAnimDh = animDh; + private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) { + DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId); + if (displayAnimator == null) { + displayAnimator = new DisplayContentsAnimator(); + mDisplayContentsAnimators.put(displayId, displayAnimator); } + return displayAnimator; + } + + void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) { + getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation = animation; + } + + ScreenRotationAnimation getScreenRotationAnimationLocked(int displayId) { + return getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation; } - synchronized void clearPendingActions() { - mPendingActions = 0; + private static class DisplayContentsAnimator { + WinAnimatorList mWinAnimators = new WinAnimatorList(); + DimAnimator mDimAnimator = null; + DimAnimator.Parameters mDimParams = null; + DimSurface mWindowAnimationBackgroundSurface = null; + ScreenRotationAnimation mScreenRotationAnimation = null; } } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 7011343..55a7c46 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -31,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_DREAM; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import com.android.internal.app.IBatteryStats; @@ -42,12 +43,12 @@ import com.android.internal.view.IInputMethodManager; import com.android.internal.view.WindowManagerPolicyThread; import com.android.server.AttributeCache; import com.android.server.EventLogTags; -import com.android.server.PowerManagerService; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; -import com.android.server.input.InputFilter; +import com.android.server.display.DisplayManagerService; import com.android.server.input.InputManagerService; -import com.android.server.pm.ShutdownThread; +import com.android.server.power.PowerManagerService; +import com.android.server.power.ShutdownThread; import android.Manifest; import android.app.ActivityManagerNative; @@ -55,6 +56,7 @@ import android.app.ActivityOptions; import android.app.IActivityManager; import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; +import android.animation.ValueAnimator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -69,42 +71,46 @@ import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Region; -import android.os.BatteryStats; +import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.IRemoteCallback; -import android.os.LocalPowerManager; import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.TokenWatcher; import android.os.Trace; +import android.os.WorkSource; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.FloatMath; import android.util.Log; -import android.util.LogPrinter; +import android.util.SparseArray; import android.util.Pair; import android.util.Slog; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.Choreographer; import android.view.Display; +import android.view.DisplayInfo; import android.view.Gravity; import android.view.IApplicationToken; +import android.view.IDisplayContentChangeListener; +import android.view.IInputFilter; import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindow; @@ -119,8 +125,10 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceSession; import android.view.View; +import android.view.ViewTreeObserver; +import android.view.WindowInfo; import android.view.WindowManager; -import android.view.WindowManagerImpl; +import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerPolicy.FakeWindow; @@ -131,6 +139,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.ScaleAnimation; +import android.view.animation.Transformation; import java.io.BufferedWriter; import java.io.DataInputStream; @@ -151,10 +160,12 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub - implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { + implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs, + DisplayManagerService.WindowManagerFuncs, DisplayManager.DisplayListener { static final String TAG = "WindowManager"; static final boolean DEBUG = false; static final boolean DEBUG_ADD_REMOVE = false; @@ -267,58 +278,30 @@ public class WindowManagerService extends IWindowManager.Stub private static final String SYSTEM_SECURE = "ro.secure"; private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; - private static final String SYSTEM_HEADLESS = "ro.config.headless"; - /** - * Condition waited on by {@link #reenableKeyguard} to know the call to - * the window policy has finished. - * This is set to true only if mKeyguardTokenWatcher.acquired() has - * actually disabled the keyguard. - */ - private boolean mKeyguardDisabled = false; + final private KeyguardDisableHandler mKeyguardDisableHandler; private final boolean mHeadless; - private static final int ALLOW_DISABLE_YES = 1; - private static final int ALLOW_DISABLE_NO = 0; - private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager - private int mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; // sync'd by mKeyguardTokenWatcher - private static final float THUMBNAIL_ANIMATION_DECELERATE_FACTOR = 1.5f; - final TokenWatcher mKeyguardTokenWatcher = new TokenWatcher( - new Handler(), "WindowManagerService.mKeyguardTokenWatcher") { - @Override - public void acquired() { - if (shouldAllowDisableKeyguard()) { - mPolicy.enableKeyguard(false); - mKeyguardDisabled = true; - } else { - Log.v(TAG, "Not disabling keyguard since device policy is enforced"); - } - } - @Override - public void released() { - mPolicy.enableKeyguard(true); - synchronized (mKeyguardTokenWatcher) { - mKeyguardDisabled = false; - mKeyguardTokenWatcher.notifyAll(); - } - } - }; - final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - mPolicy.enableKeyguard(true); - synchronized(mKeyguardTokenWatcher) { - // lazily evaluate this next time we're asked to disable keyguard - mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; - mKeyguardDisabled = false; + final String action = intent.getAction(); + if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { + mKeyguardDisableHandler.sendEmptyMessage( + KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED); + } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { + final int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + mCurrentUserId = newUserId; } } }; + // Current user when multi-user is enabled. Don't show windows of non-current user. + int mCurrentUserId; + final Context mContext; final boolean mHaveInputMethods; @@ -383,11 +366,6 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<AppWindowToken>(); /** - * Z-ordered (bottom-most first) list of all Window objects. - */ - final ArrayList<WindowState> mWindows = new ArrayList<WindowState>(); - - /** * Fake windows added to the window manager. Note: ordered from top to * bottom, opposite of mWindows. */ @@ -451,10 +429,9 @@ public class WindowManagerService extends IWindowManager.Stub Watermark mWatermark; StrictModeFlash mStrictModeFlash; - BlackFrame mBlackFrame; - final float[] mTmpFloats = new float[9]; + boolean mDisplayReady; boolean mSafeMode; boolean mDisplayEnabled = false; boolean mSystemBooted = false; @@ -463,29 +440,8 @@ public class WindowManagerService extends IWindowManager.Stub String mLastANRState; - // This protects the following display size properties, so that - // getDisplaySize() doesn't need to acquire the global lock. This is - // needed because the window manager sometimes needs to use ActivityThread - // while it has its global state locked (for example to load animation - // resources), but the ActivityThread also needs get the current display - // size sometimes when it has its package lock held. - // - // These will only be modified with both mWindowMap and mDisplaySizeLock - // held (in that order) so the window manager doesn't need to acquire this - // lock when needing these values in its normal operation. - final Object mDisplaySizeLock = new Object(); - int mInitialDisplayWidth = 0; - int mInitialDisplayHeight = 0; - int mBaseDisplayWidth = 0; - int mBaseDisplayHeight = 0; - int mCurDisplayWidth = 0; - int mCurDisplayHeight = 0; - int mAppDisplayWidth = 0; - int mAppDisplayHeight = 0; - int mSmallestDisplayWidth = 0; - int mSmallestDisplayHeight = 0; - int mLargestDisplayWidth = 0; - int mLargestDisplayHeight = 0; + /** All DisplayDontents in the world, kept here */ + private SparseArray<DisplayContent> mDisplayContents = new SparseArray<DisplayContent>(); int mRotation = 0; int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -496,13 +452,13 @@ public class WindowManagerService extends IWindowManager.Stub final Rect mSystemDecorRect = new Rect(); int mSystemDecorLayer = 0; + final Rect mScreenRect = new Rect(); - int mPendingLayoutChanges = 0; - boolean mLayoutNeeded = true; boolean mTraversalScheduled = false; boolean mDisplayFrozen = false; boolean mWaitingForConfig = false; boolean mWindowsFreezingScreen = false; + boolean mClientFreezingScreen = false; int mAppsFreezingScreen = 0; int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -512,9 +468,9 @@ public class WindowManagerService extends IWindowManager.Stub // State while inside of layoutAndPlaceSurfacesLocked(). boolean mFocusMayChange; - + Configuration mCurConfiguration = new Configuration(); - + // This is held as long as we have the screen frozen, to give us time to // perform a rotation animation when turning off shows the lock screen which // changes the orientation. @@ -529,7 +485,8 @@ public class WindowManagerService extends IWindowManager.Stub int mNextAppTransitionType = ActivityOptions.ANIM_NONE; String mNextAppTransitionPackage; Bitmap mNextAppTransitionThumbnail; - boolean mNextAppTransitionDelayed; + // Used for thumbnail transitions. True if we're scaling up, false if scaling down + boolean mNextAppTransitionScaleUp; IRemoteCallback mNextAppTransitionCallback; int mNextAppTransitionEnter; int mNextAppTransitionExit; @@ -545,8 +502,6 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<AppWindowToken> mOpeningApps = new ArrayList<AppWindowToken>(); final ArrayList<AppWindowToken> mClosingApps = new ArrayList<AppWindowToken>(); - Display mDisplay; - boolean mIsTouchDevice; final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); @@ -583,10 +538,10 @@ public class WindowManagerService extends IWindowManager.Stub WindowState mWallpaperTarget = null; // If non-null, we are in the middle of animating from one wallpaper target // to another, and this is the lower one in Z-order. - WindowState mLowerWallpaperTarget = null; + private WindowState mLowerWallpaperTarget = null; // If non-null, we are in the middle of animating from one wallpaper target // to another, and this is the higher one in Z-order. - WindowState mUpperWallpaperTarget = null; + private WindowState mUpperWallpaperTarget = null; int mWallpaperAnimLayerAdjustment; float mLastWallpaperX = -1; float mLastWallpaperY = -1; @@ -611,6 +566,8 @@ public class WindowManagerService extends IWindowManager.Stub float mAnimatorDurationScale = 1.0f; final InputManagerService mInputManager; + final DisplayManagerService mDisplayManagerService; + final DisplayManager mDisplayManager; // Who is holding the screen on. Session mHoldingScreenOn; @@ -626,7 +583,7 @@ public class WindowManagerService extends IWindowManager.Stub static final int SET_UPDATE_ROTATION = 1 << 0; static final int SET_WALLPAPER_MAY_CHANGE = 1 << 1; static final int SET_FORCE_HIDING_CHANGED = 1 << 2; - static final int CLEAR_ORIENTATION_CHANGE_COMPLETE = 1 << 3; + static final int SET_ORIENTATION_CHANGE_COMPLETE = 1 << 3; static final int SET_TURN_ON_SCREEN = 1 << 4; boolean mWallpaperForceHidingChanged = false; @@ -641,7 +598,45 @@ public class WindowManagerService extends IWindowManager.Stub private float mButtonBrightness = -1; private boolean mUpdateRotation = false; } - LayoutFields mInnerFields = new LayoutFields(); + final LayoutFields mInnerFields = new LayoutFields(); + + static class AppWindowAnimParams { + AppWindowAnimator mAppAnimator; + ArrayList<WindowStateAnimator> mWinAnimators; + + public AppWindowAnimParams(final AppWindowAnimator appAnimator) { + mAppAnimator = appAnimator; + + final AppWindowToken atoken = appAnimator.mAppToken; + mWinAnimators = new ArrayList<WindowStateAnimator>(); + final int N = atoken.allAppWindows.size(); + for (int i = 0; i < N; i++) { + mWinAnimators.add(atoken.allAppWindows.get(i).mWinAnimator); + } + } + } + + static class LayoutToAnimatorParams { + boolean mParamsModified; + + static final long WALLPAPER_TOKENS_CHANGED = 1 << 0; + long mChanges; + + boolean mAnimationScheduled; + SparseArray<WinAnimatorList> mWinAnimatorLists = new SparseArray<WinAnimatorList>(); + WindowState mWallpaperTarget; + WindowState mLowerWallpaperTarget; + WindowState mUpperWallpaperTarget; + SparseArray<DimAnimator.Parameters> mDimParams = new SparseArray<DimAnimator.Parameters>(); + ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>(); + ArrayList<AppWindowAnimParams> mAppWindowAnimParams = new ArrayList<AppWindowAnimParams>(); + } + /** Params from WindowManagerService to WindowAnimator. Do not modify or read without first + * locking on either mWindowMap or mAnimator and then on mLayoutToAnim */ + final LayoutToAnimatorParams mLayoutToAnim = new LayoutToAnimatorParams(); + + /** The lowest wallpaper target with a detached wallpaper animation on it. */ + WindowState mWindowDetachedWallpaper = null; /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */ @@ -650,33 +645,6 @@ public class WindowManagerService extends IWindowManager.Stub /** Only do a maximum of 6 repeated layouts. After that quit */ private int mLayoutRepeatCount; - private final class AnimationRunnable implements Runnable { - @Override - public void run() { - synchronized(mWindowMap) { - mAnimationScheduled = false; - // Update animations of all applications, including those - // associated with exiting/removed apps - synchronized (mAnimator) { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmAnimate"); - final ArrayList<WindowStateAnimator> winAnimators = mAnimator.mWinAnimators; - winAnimators.clear(); - final int N = mWindows.size(); - for (int i = 0; i < N; i++) { - final WindowStateAnimator winAnimator = mWindows.get(i).mWinAnimator; - if (winAnimator.mSurface != null) { - winAnimators.add(winAnimator); - } - } - mAnimator.animate(); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } - } - } - final AnimationRunnable mAnimationRunnable = new AnimationRunnable(); - boolean mAnimationScheduled; - final WindowAnimator mAnimator; final class DragInputEventReceiver extends InputEventReceiver { @@ -748,6 +716,9 @@ public class WindowManagerService extends IWindowManager.Stub */ boolean mInTouchMode = true; + // Temp regions for intermediary calculations. + private final Region mTempRegion = new Region(); + private ViewServer mViewServer; private ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<WindowChangeListener>(); @@ -768,111 +739,38 @@ public class WindowManagerService extends IWindowManager.Stub // For example, when this flag is true, there will be no wallpaper service. final boolean mOnlyCore; - public static WindowManagerService main(Context context, - PowerManagerService pm, boolean haveInputMethods, boolean allowBootMsgs, - boolean onlyCore) { - WMThread thr = new WMThread(context, pm, haveInputMethods, allowBootMsgs, onlyCore); - thr.start(); - - synchronized (thr) { - while (thr.mService == null) { - try { - thr.wait(); - } catch (InterruptedException e) { - } + public static WindowManagerService main(final Context context, + final PowerManagerService pm, final DisplayManagerService dm, + final Handler uiHandler, final Handler wmHandler, + final boolean haveInputMethods, final boolean showBootMsgs, + final boolean onlyCore) { + final WindowManagerService[] holder = new WindowManagerService[1]; + wmHandler.runWithScissors(new Runnable() { + @Override + public void run() { + holder[0] = new WindowManagerService(context, pm, dm, + uiHandler, haveInputMethods, showBootMsgs, onlyCore); } - return thr.mService; - } + }, 0); + return holder[0]; } - static class WMThread extends Thread { - WindowManagerService mService; - - private final Context mContext; - private final PowerManagerService mPM; - private final boolean mHaveInputMethods; - private final boolean mAllowBootMessages; - private final boolean mOnlyCore; + private void initPolicy(Handler uiHandler) { + uiHandler.runWithScissors(new Runnable() { + @Override + public void run() { + WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper()); - public WMThread(Context context, PowerManagerService pm, - boolean haveInputMethods, boolean allowBootMsgs, boolean onlyCore) { - super("WindowManager"); - mContext = context; - mPM = pm; - mHaveInputMethods = haveInputMethods; - mAllowBootMessages = allowBootMsgs; - mOnlyCore = onlyCore; - } - - @Override - public void run() { - Looper.prepare(); - //Looper.myLooper().setMessageLogging(new LogPrinter( - // android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM)); - WindowManagerService s = new WindowManagerService(mContext, mPM, - mHaveInputMethods, mAllowBootMessages, mOnlyCore); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_DISPLAY); - android.os.Process.setCanSelfBackground(false); - - synchronized (this) { - mService = s; - notifyAll(); - } - - // For debug builds, log event loop stalls to dropbox for analysis. - if (StrictMode.conditionallyEnableDebugLogging()) { - Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper"); - } - - Looper.loop(); - } - } - - static class PolicyThread extends Thread { - private final WindowManagerPolicy mPolicy; - private final WindowManagerService mService; - private final Context mContext; - private final PowerManagerService mPM; - boolean mRunning = false; - - public PolicyThread(WindowManagerPolicy policy, - WindowManagerService service, Context context, - PowerManagerService pm) { - super("WindowManagerPolicy"); - mPolicy = policy; - mService = service; - mContext = context; - mPM = pm; - } - - @Override - public void run() { - Looper.prepare(); - WindowManagerPolicyThread.set(this, Looper.myLooper()); - - //Looper.myLooper().setMessageLogging(new LogPrinter( - // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_FOREGROUND); - android.os.Process.setCanSelfBackground(false); - mPolicy.init(mContext, mService, mService, mPM); - - synchronized (this) { - mRunning = true; - notifyAll(); - } - - // For debug builds, log event loop stalls to dropbox for analysis. - if (StrictMode.conditionallyEnableDebugLogging()) { - Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper"); + mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this); + mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer() + * TYPE_LAYER_MULTIPLIER + + TYPE_LAYER_OFFSET; } - - Looper.loop(); - } + }, 0); } private WindowManagerService(Context context, PowerManagerService pm, + DisplayManagerService displayManager, Handler uiHandler, boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) { mContext = context; mHaveInputMethods = haveInputMethods; @@ -880,7 +778,17 @@ public class WindowManagerService extends IWindowManager.Stub mOnlyCore = onlyCore; mLimitedAlphaCompositing = context.getResources().getBoolean( com.android.internal.R.bool.config_sf_limitedAlpha); - mHeadless = "1".equals(SystemProperties.get(SYSTEM_HEADLESS, "0")); + mDisplayManagerService = displayManager; + mHeadless = displayManager.isHeadless(); + + mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); + mDisplayManager.registerDisplayListener(this, null); + Display[] displays = mDisplayManager.getDisplays(); + for (Display display : displays) { + createDisplayContentLocked(display); + } + + mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy); mPowerManager = pm; mPowerManager.setPolicy(mPolicy); @@ -897,42 +805,37 @@ public class WindowManagerService extends IWindowManager.Stub Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale); mTransitionAnimationScale = Settings.System.getFloat(context.getContentResolver(), Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale); - mAnimatorDurationScale = Settings.System.getFloat(context.getContentResolver(), - Settings.System.ANIMATOR_DURATION_SCALE, mTransitionAnimationScale); + setAnimatorDurationScale(Settings.System.getFloat(context.getContentResolver(), + Settings.System.ANIMATOR_DURATION_SCALE, mTransitionAnimationScale)); // Track changes to DevicePolicyManager state so we can enable/disable keyguard. IntentFilter filter = new IntentFilter(); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + // Track user switching. + filter.addAction(Intent.ACTION_USER_SWITCHED); mContext.registerReceiver(mBroadcastReceiver, filter); mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK - | PowerManager.ON_AFTER_RELEASE, "KEEP_SCREEN_ON_FLAG"); + | PowerManager.ON_AFTER_RELEASE, TAG); mHoldingScreenWakeLock.setReferenceCounted(false); mInputManager = new InputManagerService(context, mInputMonitor); - mAnimator = new WindowAnimator(this, context, mPolicy); - - PolicyThread thr = new PolicyThread(mPolicy, this, context, pm); - thr.start(); + mFxSession = new SurfaceSession(); + mAnimator = new WindowAnimator(this); - synchronized (thr) { - while (!thr.mRunning) { - try { - thr.wait(); - } catch (InterruptedException e) { - } - } - } + initPolicy(uiHandler); mInputManager.start(); // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); - mFxSession = new SurfaceSession(); Surface.openTransaction(); - createWatermark(); - Surface.closeTransaction(); + try { + createWatermarkInTransaction(); + } finally { + Surface.closeTransaction(); + } } public InputManagerService getInputManagerService() { @@ -955,34 +858,31 @@ public class WindowManagerService extends IWindowManager.Stub } private void placeWindowAfter(WindowState pos, WindowState window) { - final int i = mWindows.indexOf(pos); + final WindowList windows = pos.getWindowList(); + final int i = windows.indexOf(pos); if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + window + " at " - + (i+1) + " of " + mWindows.size() + " (after " + pos + ")"); - mWindows.add(i+1, window); + + (i+1) + " of " + windows.size() + " (after " + pos + ")"); + windows.add(i+1, window); mWindowsChanged = true; } private void placeWindowBefore(WindowState pos, WindowState window) { - final int i = mWindows.indexOf(pos); + final WindowList windows = pos.getWindowList(); + final int i = windows.indexOf(pos); if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + window + " at " - + i + " of " + mWindows.size() + " (before " + pos + ")"); - mWindows.add(i, window); + + i + " of " + windows.size() + " (before " + pos + ")"); + windows.add(i, window); mWindowsChanged = true; } //This method finds out the index of a window that has the same app token as //win. used for z ordering the windows in mWindows private int findIdxBasedOnAppTokens(WindowState win) { - //use a local variable to cache mWindows - ArrayList<WindowState> localmWindows = mWindows; - int jmax = localmWindows.size(); - if(jmax == 0) { - return -1; - } - for(int j = (jmax-1); j >= 0; j--) { - WindowState wentry = localmWindows.get(j); + WindowList windows = win.getWindowList(); + for(int j = windows.size() - 1; j >= 0; j--) { + WindowState wentry = windows.get(j); if(wentry.mAppToken == win.mAppToken) { return j; } @@ -990,49 +890,76 @@ public class WindowManagerService extends IWindowManager.Stub return -1; } + /** + * Return the list of Windows from the passed token on the given Display. + * @param token The token with all the windows. + * @param displayContent The display we are interested in. + * @return List of windows from token that are on displayContent. + */ + WindowList getTokenWindowsOnDisplay(WindowToken token, DisplayContent displayContent) { + final WindowList windowList = new WindowList(); + final int count = token.windows.size(); + for (int i = 0; i < count; i++) { + final WindowState win = token.windows.get(i); + if (win.mDisplayContent == displayContent) { + windowList.add(win); + } + } + return windowList; + } + private void addWindowToListInOrderLocked(WindowState win, boolean addToToken) { final IWindow client = win.mClient; final WindowToken token = win.mToken; - final ArrayList<WindowState> localmWindows = mWindows; + final DisplayContent displayContent = win.mDisplayContent; - final int N = localmWindows.size(); + final WindowList windows = win.getWindowList(); + final int N = windows.size(); final WindowState attached = win.mAttachedWindow; int i; + WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent); if (attached == null) { - int tokenWindowsPos = token.windows.size(); + int tokenWindowsPos = 0; + int windowListPos = tokenWindowList.size(); if (token.appWindowToken != null) { - int index = tokenWindowsPos-1; + int index = windowListPos - 1; if (index >= 0) { // If this application has existing windows, we // simply place the new window on top of them... but // keep the starting window on top. if (win.mAttrs.type == TYPE_BASE_APPLICATION) { // Base windows go behind everything else. - placeWindowBefore(token.windows.get(0), win); - tokenWindowsPos = 0; + WindowState lowestWindow = tokenWindowList.get(0); + placeWindowBefore(lowestWindow, win); + tokenWindowsPos = token.windows.indexOf(lowestWindow); } else { AppWindowToken atoken = win.mAppToken; - if (atoken != null && - token.windows.get(index) == atoken.startingWindow) { - placeWindowBefore(token.windows.get(index), win); - tokenWindowsPos--; + WindowState lastWindow = tokenWindowList.get(index); + if (atoken != null && lastWindow == atoken.startingWindow) { + placeWindowBefore(lastWindow, win); + tokenWindowsPos = token.windows.indexOf(lastWindow); } else { - int newIdx = findIdxBasedOnAppTokens(win); - if(newIdx != -1) { - //there is a window above this one associated with the same - //apptoken note that the window could be a floating window - //that was created later or a window at the top of the list of - //windows associated with this token. - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { - Slog.v(TAG, "Adding window " + win + " at " - + (newIdx+1) + " of " + N); - } - localmWindows.add(newIdx+1, win); - mWindowsChanged = true; + int newIdx = findIdxBasedOnAppTokens(win); + //there is a window above this one associated with the same + //apptoken note that the window could be a floating window + //that was created later or a window at the top of the list of + //windows associated with this token. + if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { + Slog.v(TAG, "Adding window " + win + " at " + + (newIdx + 1) + " of " + N); + } + windows.add(newIdx + 1, win); + if (newIdx < 0) { + // No window from token found on win's display. + tokenWindowsPos = 0; + } else { + tokenWindowsPos = token.windows.indexOf(windows.get(newIdx)) + 1; } + mWindowsChanged = true; } } } else { + // No windows from this token on this display if (localLOGV) Slog.v( TAG, "Figuring out where to add app window " + client.asBinder() + " (token=" + token + ")"); @@ -1048,10 +975,11 @@ public class WindowManagerService extends IWindowManager.Stub } // We haven't reached the token yet; if this token - // is not going to the bottom and has windows, we can + // is not going to the bottom and has windows on this display, we can // use it as an anchor for when we do reach the token. - if (!t.sendingToBottom && t.windows.size() > 0) { - pos = t.windows.get(0); + tokenWindowList = getTokenWindowsOnDisplay(t, win.mDisplayContent); + if (!t.sendingToBottom && tokenWindowList.size() > 0) { + pos = tokenWindowList.get(0); } } // We now know the index into the apps. If we found @@ -1061,9 +989,11 @@ public class WindowManagerService extends IWindowManager.Stub // Move behind any windows attached to this one. WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { - final int NC = atoken.windows.size(); + tokenWindowList = + getTokenWindowsOnDisplay(atoken, win.mDisplayContent); + final int NC = tokenWindowList.size(); if (NC > 0) { - WindowState bottom = atoken.windows.get(0); + WindowState bottom = tokenWindowList.get(0); if (bottom.mSubLayer < 0) { pos = bottom; } @@ -1072,12 +1002,13 @@ public class WindowManagerService extends IWindowManager.Stub placeWindowBefore(pos, win); } else { // Continue looking down until we find the first - // token that has windows. + // token that has windows on this display. while (i >= 0) { AppWindowToken t = mAnimatingAppTokens.get(i); - final int NW = t.windows.size(); + tokenWindowList = getTokenWindowsOnDisplay(t, win.mDisplayContent); + final int NW = tokenWindowList.size(); if (NW > 0) { - pos = t.windows.get(NW-1); + pos = tokenWindowList.get(NW-1); break; } i--; @@ -1100,7 +1031,7 @@ public class WindowManagerService extends IWindowManager.Stub // Just search for the start of this layer. final int myLayer = win.mBaseLayer; for (i=0; i<N; i++) { - WindowState w = localmWindows.get(i); + WindowState w = windows.get(i); if (w.mBaseLayer > myLayer) { break; } @@ -1109,7 +1040,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.v(TAG, "Adding window " + win + " at " + i + " of " + N); } - localmWindows.add(i, win); + windows.add(i, win); mWindowsChanged = true; } } @@ -1118,18 +1049,18 @@ public class WindowManagerService extends IWindowManager.Stub // Figure out where window should go, based on layer. final int myLayer = win.mBaseLayer; for (i=N-1; i>=0; i--) { - if (localmWindows.get(i).mBaseLayer <= myLayer) { - i++; + if (windows.get(i).mBaseLayer <= myLayer) { break; } } - if (i < 0) i = 0; + i++; if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + win + " at " + i + " of " + N); - localmWindows.add(i, win); + windows.add(i, win); mWindowsChanged = true; } + if (addToToken) { if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(tokenWindowsPos, win); @@ -1138,12 +1069,12 @@ public class WindowManagerService extends IWindowManager.Stub } else { // Figure out this window's ordering relative to the window // it is attached to. - final int NA = token.windows.size(); + final int NA = tokenWindowList.size(); final int sublayer = win.mSubLayer; int largestSublayer = Integer.MIN_VALUE; WindowState windowWithLargestSublayer = null; for (i=0; i<NA; i++) { - WindowState w = token.windows.get(i); + WindowState w = tokenWindowList.get(i); final int wSublayer = w.mSubLayer; if (wSublayer >= largestSublayer) { largestSublayer = wSublayer; @@ -1157,8 +1088,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(i, win); } - placeWindowBefore( - wSublayer >= 0 ? attached : w, win); + placeWindowBefore(wSublayer >= 0 ? attached : w, win); break; } } else { @@ -1193,6 +1123,10 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mAppToken != null && addToToken) { win.mAppToken.allAppWindows.add(win); } + + if (windows.size() == 1) { + mDisplayManagerService.setDisplayHasContent(win.getDisplayId(), true); + } } /** TODO(cmautner): Is this the same as {@link WindowState#canReceiveKeys()} */ @@ -1226,13 +1160,16 @@ public class WindowManagerService extends IWindowManager.Stub * @return The index+1 in mWindows of the discovered target. */ int findDesiredInputMethodWindowIndexLocked(boolean willMove) { - final ArrayList<WindowState> localmWindows = mWindows; - final int N = localmWindows.size(); + // TODO(multidisplay): Needs some serious rethought when the target and IME are not on the + // same display. Or even when the current IME/target are not on the same screen as the next + // IME/target. For now only look for input windows on the main screen. + WindowList windows = getDefaultWindowListLocked(); + final int N = windows.size(); WindowState w = null; int i = N; while (i > 0) { i--; - w = localmWindows.get(i); + w = windows.get(i); if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG, "Checking window @" + i + " " + w + " fl=0x" + Integer.toHexString(w.mAttrs.flags)); @@ -1247,7 +1184,7 @@ public class WindowManagerService extends IWindowManager.Stub if (!willMove && w.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING && i > 0) { - WindowState wb = localmWindows.get(i-1); + WindowState wb = windows.get(i-1); if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) { i--; w = wb; @@ -1267,12 +1204,13 @@ public class WindowManagerService extends IWindowManager.Stub // the IME shown: when the Dialog is dismissed, we want to keep // the IME above it until it is completely gone so it doesn't drop // behind the dialog or its full-screen scrim. - if (mInputMethodTarget != null && w != null - && mInputMethodTarget.isDisplayedLw() - && mInputMethodTarget.mExiting) { - if (mInputMethodTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) { - w = mInputMethodTarget; - i = localmWindows.indexOf(w); + final WindowState curTarget = mInputMethodTarget; + if (curTarget != null && w != null + && curTarget.isDisplayedLw() + && curTarget.mExiting) { + if (curTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) { + w = curTarget; + i = windows.indexOf(w); if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Current target higher, switching to: " + w); } } @@ -1281,20 +1219,20 @@ public class WindowManagerService extends IWindowManager.Stub + w + " willMove=" + willMove); if (willMove && w != null) { - final WindowState curTarget = mInputMethodTarget; - if (curTarget != null && curTarget.mAppToken != null) { + AppWindowToken token = curTarget == null ? null : curTarget.mAppToken; + if (token != null) { // Now some fun for dealing with window animations that // modify the Z order. We need to look at all windows below // the current target that are in this app, finding the highest // visible one in layering. - AppWindowToken token = curTarget.mAppToken; WindowState highestTarget = null; int highestPos = 0; if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) { - int pos = localmWindows.indexOf(curTarget); + WindowList curWindows = curTarget.getWindowList(); + int pos = curWindows.indexOf(curTarget); while (pos >= 0) { - WindowState win = localmWindows.get(pos); + WindowState win = curWindows.get(pos); if (win.mAppToken != token) { break; } @@ -1339,15 +1277,8 @@ public class WindowManagerService extends IWindowManager.Stub //Slog.i(TAG, "Placing input method @" + (i+1)); if (w != null) { if (willMove) { - if (DEBUG_INPUT_METHOD) { - RuntimeException e = null; - if (!HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - Slog.w(TAG, "Moving IM target from " - + mInputMethodTarget + " to " + w, e); - } + if (DEBUG_INPUT_METHOD) Slog.w(TAG, "Moving IM target from " + curTarget + " to " + + w + (HIDE_STACK_CRAWLS ? "" : " Callers=" + Debug.getCallers(4))); mInputMethodTarget = w; mInputMethodTargetWaitingAnim = false; if (w.mAppToken != null) { @@ -1359,15 +1290,8 @@ public class WindowManagerService extends IWindowManager.Stub return i+1; } if (willMove) { - if (DEBUG_INPUT_METHOD) { - RuntimeException e = null; - if (!HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - Slog.w(TAG, "Moving IM target from " - + mInputMethodTarget + " to null", e); - } + if (DEBUG_INPUT_METHOD) Slog.w(TAG, "Moving IM target from " + curTarget + " to null." + + (HIDE_STACK_CRAWLS ? "" : " Callers=" + Debug.getCallers(4))); mInputMethodTarget = null; setInputMethodAnimLayerAdjustment(0); } @@ -1380,7 +1304,8 @@ public class WindowManagerService extends IWindowManager.Stub win.mTargetAppToken = mInputMethodTarget.mAppToken; if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding input method window " + win + " at " + pos); - mWindows.add(pos, win); + // TODO(multidisplay): IMEs are only supported on the default display. + getDefaultWindowListLocked().add(pos, win); mWindowsChanged = true; moveInputMethodDialogsLocked(pos+1); return; @@ -1418,22 +1343,23 @@ public class WindowManagerService extends IWindowManager.Stub } private int tmpRemoveWindowLocked(int interestingPos, WindowState win) { - int wpos = mWindows.indexOf(win); + WindowList windows = win.getWindowList(); + int wpos = windows.indexOf(win); if (wpos >= 0) { if (wpos < interestingPos) interestingPos--; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Temp removing at " + wpos + ": " + win); - mWindows.remove(wpos); + windows.remove(wpos); mWindowsChanged = true; int NC = win.mChildWindows.size(); while (NC > 0) { NC--; WindowState cw = win.mChildWindows.get(NC); - int cpos = mWindows.indexOf(cw); + int cpos = windows.indexOf(cw); if (cpos >= 0) { if (cpos < interestingPos) interestingPos--; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Temp removing child at " + cpos + ": " + cw); - mWindows.remove(cpos); + windows.remove(cpos); } } } @@ -1445,27 +1371,29 @@ public class WindowManagerService extends IWindowManager.Stub // This is a hack to get all of the child windows added as well // at the right position. Child windows should be rare and // this case should be rare, so it shouldn't be that big a deal. - int wpos = mWindows.indexOf(win); + WindowList windows = win.getWindowList(); + int wpos = windows.indexOf(win); if (wpos >= 0) { - if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "ReAdd removing from " + wpos - + ": " + win); - mWindows.remove(wpos); + if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "ReAdd removing from " + wpos + ": " + win); + windows.remove(wpos); mWindowsChanged = true; reAddWindowLocked(wpos, win); } } - void logWindowList(String prefix) { - int N = mWindows.size(); + void logWindowList(final WindowList windows, String prefix) { + int N = windows.size(); while (N > 0) { N--; - Slog.v(TAG, prefix + "#" + N + ": " + mWindows.get(N)); + Slog.v(TAG, prefix + "#" + N + ": " + windows.get(N)); } } void moveInputMethodDialogsLocked(int pos) { ArrayList<WindowState> dialogs = mInputMethodDialogs; + // TODO(multidisplay): IMEs are only supported on the default display. + WindowList windows = getDefaultWindowListLocked(); final int N = dialogs.size(); if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Removing " + N + " dialogs w/pos=" + pos); for (int i=0; i<N; i++) { @@ -1473,13 +1401,13 @@ public class WindowManagerService extends IWindowManager.Stub } if (DEBUG_INPUT_METHOD) { Slog.v(TAG, "Window list w/pos=" + pos); - logWindowList(" "); + logWindowList(windows, " "); } if (pos >= 0) { final AppWindowToken targetAppToken = mInputMethodTarget.mAppToken; - if (pos < mWindows.size()) { - WindowState wp = mWindows.get(pos); + if (pos < windows.size()) { + WindowState wp = windows.get(pos); if (wp == mInputMethodWindow) { pos++; } @@ -1492,7 +1420,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (DEBUG_INPUT_METHOD) { Slog.v(TAG, "Final window list:"); - logWindowList(" "); + logWindowList(windows, " "); } return; } @@ -1502,7 +1430,7 @@ public class WindowManagerService extends IWindowManager.Stub reAddWindowToListInOrderLocked(win); if (DEBUG_INPUT_METHOD) { Slog.v(TAG, "No IM target, final list:"); - logWindowList(" "); + logWindowList(windows, " "); } } } @@ -1514,6 +1442,9 @@ public class WindowManagerService extends IWindowManager.Stub return false; } + // TODO(multidisplay): IMEs are only supported on the default display. + WindowList windows = getDefaultWindowListLocked(); + int imPos = findDesiredInputMethodWindowIndexLocked(true); if (imPos >= 0) { // In this case, the input method windows are to be placed @@ -1521,9 +1452,9 @@ public class WindowManagerService extends IWindowManager.Stub // First check to see if the input method windows are already // located here, and contiguous. - final int N = mWindows.size(); + final int N = windows.size(); WindowState firstImWin = imPos < N - ? mWindows.get(imPos) : null; + ? windows.get(imPos) : null; // Figure out the actual input method window that should be // at the bottom of their stack. @@ -1539,7 +1470,7 @@ public class WindowManagerService extends IWindowManager.Stub // First find the top IM window. int pos = imPos+1; while (pos < N) { - if (!(mWindows.get(pos)).mIsImWindow) { + if (!(windows.get(pos)).mIsImWindow) { break; } pos++; @@ -1547,7 +1478,7 @@ public class WindowManagerService extends IWindowManager.Stub pos++; // Now there should be no more input method windows above. while (pos < N) { - if ((mWindows.get(pos)).mIsImWindow) { + if ((windows.get(pos)).mIsImWindow) { break; } pos++; @@ -1561,18 +1492,18 @@ public class WindowManagerService extends IWindowManager.Stub if (imWin != null) { if (DEBUG_INPUT_METHOD) { Slog.v(TAG, "Moving IM from " + imPos); - logWindowList(" "); + logWindowList(windows, " "); } imPos = tmpRemoveWindowLocked(imPos, imWin); if (DEBUG_INPUT_METHOD) { Slog.v(TAG, "List after removing with new pos " + imPos + ":"); - logWindowList(" "); + logWindowList(windows, " "); } imWin.mTargetAppToken = mInputMethodTarget.mAppToken; reAddWindowLocked(imPos, imWin); if (DEBUG_INPUT_METHOD) { Slog.v(TAG, "List after moving IM to " + imPos + ":"); - logWindowList(" "); + logWindowList(windows, " "); } if (DN > 0) moveInputMethodDialogsLocked(imPos+1); } else { @@ -1590,17 +1521,17 @@ public class WindowManagerService extends IWindowManager.Stub reAddWindowToListInOrderLocked(imWin); if (DEBUG_INPUT_METHOD) { Slog.v(TAG, "List with no IM target:"); - logWindowList(" "); + logWindowList(windows, " "); } - if (DN > 0) moveInputMethodDialogsLocked(-1);; + if (DN > 0) moveInputMethodDialogsLocked(-1); } else { - moveInputMethodDialogsLocked(-1);; + moveInputMethodDialogsLocked(-1); } } if (needAssignLayers) { - assignLayersLocked(); + assignLayersLocked(windows); } return true; @@ -1631,13 +1562,15 @@ public class WindowManagerService extends IWindowManager.Stub mInnerFields.mWallpaperMayChange = false; int changed = 0; - final int dw = mAppDisplayWidth; - final int dh = mAppDisplayHeight; + // TODO(multidisplay): Wallpapers on main screen only. + final DisplayInfo displayInfo = getDefaultDisplayContentLocked().getDisplayInfo(); + final int dw = displayInfo.appWidth; + final int dh = displayInfo.appHeight; // First find top-most window that has asked to be on top of the // wallpaper; all wallpapers go behind it. - final ArrayList<WindowState> localmWindows = mWindows; - int N = localmWindows.size(); + final WindowList windows = getDefaultWindowListLocked(); + int N = windows.size(); WindowState w = null; WindowState foundW = null; int foundI = 0; @@ -1647,7 +1580,7 @@ public class WindowManagerService extends IWindowManager.Stub int i = N; while (i > 0) { i--; - w = localmWindows.get(i); + w = windows.get(i); if ((w.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER)) { if (topCurW == null) { topCurW = w; @@ -1656,7 +1589,7 @@ public class WindowManagerService extends IWindowManager.Stub continue; } topCurW = null; - if (w != mAnimator.mWindowDetachedWallpaper && w.mAppToken != null) { + if (w != mWindowDetachedWallpaper && w.mAppToken != null) { // If this window's app token is hidden and not animating, // it is of no interest to us. if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) { @@ -1682,7 +1615,7 @@ public class WindowManagerService extends IWindowManager.Stub continue; } break; - } else if (w == mAnimator.mWindowDetachedWallpaper) { + } else if (w == mWindowDetachedWallpaper) { windowDetachedI = i; } } @@ -1741,7 +1674,7 @@ public class WindowManagerService extends IWindowManager.Stub + " old animation: " + oldAnim); } if (foundAnim && oldAnim) { - int oldI = localmWindows.indexOf(oldW); + int oldI = windows.indexOf(oldW); if (DEBUG_WALLPAPER) { Slog.v(TAG, "New i: " + foundI + " old i: " + oldI); } @@ -1760,7 +1693,7 @@ public class WindowManagerService extends IWindowManager.Stub mWallpaperTarget = oldW; foundW = oldW; foundI = oldI; - } + } // Now set the upper and lower wallpaper targets // correctly, and make sure that we are positioning // the wallpaper below the lower. @@ -1825,7 +1758,7 @@ public class WindowManagerService extends IWindowManager.Stub // AND any starting window associated with it, AND below the // maximum layer the policy allows for wallpapers. while (foundI > 0) { - WindowState wb = localmWindows.get(foundI-1); + WindowState wb = windows.get(foundI-1); if (wb.mBaseLayer < maxLayer && wb.mAttachedWindow != foundW && (foundW.mAttachedWindow == null || @@ -1851,7 +1784,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { // Okay i is the position immediately above the wallpaper. Look at // what is below it for later. - foundW = foundI > 0 ? localmWindows.get(foundI-1) : null; + foundW = foundI > 0 ? windows.get(foundI-1) : null; } if (visible) { @@ -1876,7 +1809,7 @@ public class WindowManagerService extends IWindowManager.Stub token.hidden = !visible; // Need to do a layout to ensure the wallpaper now has the // correct size. - mLayoutNeeded = true; + getDefaultDisplayContentLocked().layoutNeeded = true; } int curWallpaperIndex = token.windows.size(); @@ -1901,18 +1834,18 @@ public class WindowManagerService extends IWindowManager.Stub if (wallpaper == foundW) { foundI--; foundW = foundI > 0 - ? localmWindows.get(foundI-1) : null; + ? windows.get(foundI-1) : null; continue; } // The window didn't match... the current wallpaper window, // wherever it is, is in the wrong place, so make sure it is // not in the list. - int oldIndex = localmWindows.indexOf(wallpaper); + int oldIndex = windows.indexOf(wallpaper); if (oldIndex >= 0) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Wallpaper removing at " + oldIndex + ": " + wallpaper); - localmWindows.remove(oldIndex); + windows.remove(oldIndex); mWindowsChanged = true; if (oldIndex < foundI) { foundI--; @@ -1925,7 +1858,7 @@ public class WindowManagerService extends IWindowManager.Stub + " from " + oldIndex + " to " + foundI); } - localmWindows.add(foundI, wallpaper); + windows.add(foundI, wallpaper); mWindowsChanged = true; changed |= ADJUST_WALLPAPER_LAYERS_CHANGED; } @@ -2040,14 +1973,11 @@ public class WindowManagerService extends IWindowManager.Stub } } - // TODO(cmautner): Move to WindowAnimator. - void setWallpaperOffset(final WindowStateAnimator winAnimator, final int left, final int top) { - mH.sendMessage(mH.obtainMessage(H.SET_WALLPAPER_OFFSET, left, top, winAnimator)); - } - void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { - final int dw = mAppDisplayWidth; - final int dh = mAppDisplayHeight; + final DisplayContent displayContent = changingTarget.mDisplayContent; + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final int dw = displayInfo.appWidth; + final int dh = displayInfo.appHeight; WindowState target = mWallpaperTarget; if (target != null) { @@ -2079,19 +2009,8 @@ public class WindowManagerService extends IWindowManager.Stub // TODO(cmautner): Don't move this from here, just lock the WindowAnimator. if (winAnimator.mSurfaceX != wallpaper.mShownFrame.left || winAnimator.mSurfaceY != wallpaper.mShownFrame.top) { - Surface.openTransaction(); - try { - if (SHOW_TRANSACTIONS) logSurface(wallpaper, - "POS " + wallpaper.mShownFrame.left - + ", " + wallpaper.mShownFrame.top, null); - setWallpaperOffset(winAnimator, (int) wallpaper.mShownFrame.left, + winAnimator.setWallpaperOffset((int) wallpaper.mShownFrame.left, (int) wallpaper.mShownFrame.top); - } catch (RuntimeException e) { - Slog.w(TAG, "Error positioning surface of " + wallpaper - + " pos=(" + wallpaper.mShownFrame.left - + "," + wallpaper.mShownFrame.top + ")", e); - } - Surface.closeTransaction(); } // We only want to be synchronous with one wallpaper. sync = false; @@ -2120,8 +2039,10 @@ public class WindowManagerService extends IWindowManager.Stub void updateWallpaperVisibilityLocked() { final boolean visible = isWallpaperVisible(mWallpaperTarget); - final int dw = mAppDisplayWidth; - final int dh = mAppDisplayHeight; + final DisplayContent displayContent = mWallpaperTarget.mDisplayContent; + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final int dw = displayInfo.appWidth; + final int dh = displayInfo.appHeight; int curTokenIndex = mWallpaperTokens.size(); while (curTokenIndex > 0) { @@ -2131,7 +2052,7 @@ public class WindowManagerService extends IWindowManager.Stub token.hidden = !visible; // Need to do a layout to ensure the wallpaper now has the // correct size. - mLayoutNeeded = true; + getDefaultDisplayContentLocked().layoutNeeded = true; } int curWallpaperIndex = token.windows.size(); @@ -2146,12 +2067,12 @@ public class WindowManagerService extends IWindowManager.Stub } } } - + public int addWindow(Session session, IWindow client, int seq, - WindowManager.LayoutParams attrs, int viewVisibility, + WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, InputChannel outInputChannel) { int res = mPolicy.checkAddPermission(attrs); - if (res != WindowManagerImpl.ADD_OKAY) { + if (res != WindowManagerGlobal.ADD_OKAY) { return res; } @@ -2161,13 +2082,13 @@ public class WindowManagerService extends IWindowManager.Stub long origId; synchronized(mWindowMap) { - if (mDisplay == null) { + if (!mDisplayReady) { throw new IllegalStateException("Display has not been initialialized"); } if (mWindowMap.containsKey(client.asBinder())) { Slog.w(TAG, "Window " + client + " is already added"); - return WindowManagerImpl.ADD_DUPLICATE_ADD; + return WindowManagerGlobal.ADD_DUPLICATE_ADD; } if (attrs.type >= FIRST_SUB_WINDOW && attrs.type <= LAST_SUB_WINDOW) { @@ -2175,13 +2096,13 @@ public class WindowManagerService extends IWindowManager.Stub if (attachedWindow == null) { Slog.w(TAG, "Attempted to add window with token that is not a window: " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN; + return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) { Slog.w(TAG, "Attempted to add window with token that is a sub-window: " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN; + return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } } @@ -2192,22 +2113,22 @@ public class WindowManagerService extends IWindowManager.Stub && attrs.type <= LAST_APPLICATION_WINDOW) { Slog.w(TAG, "Attempted to add application window with unknown token " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_APP_TOKEN; + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (attrs.type == TYPE_INPUT_METHOD) { Slog.w(TAG, "Attempted to add input method window with unknown token " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_APP_TOKEN; + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (attrs.type == TYPE_WALLPAPER) { Slog.w(TAG, "Attempted to add wallpaper window with unknown token " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_APP_TOKEN; + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (attrs.type == TYPE_DREAM) { Slog.w(TAG, "Attempted to add Dream window with unknown token " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_APP_TOKEN; + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } token = new WindowToken(this, attrs.token, -1, false); addToken = true; @@ -2217,68 +2138,69 @@ public class WindowManagerService extends IWindowManager.Stub if (atoken == null) { Slog.w(TAG, "Attempted to add window with non-application token " + token + ". Aborting."); - return WindowManagerImpl.ADD_NOT_APP_TOKEN; + return WindowManagerGlobal.ADD_NOT_APP_TOKEN; } else if (atoken.removed) { Slog.w(TAG, "Attempted to add window with exiting application token " + token + ". Aborting."); - return WindowManagerImpl.ADD_APP_EXITING; + return WindowManagerGlobal.ADD_APP_EXITING; } if (attrs.type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) { // No need for this guy! if (localLOGV) Slog.v( TAG, "**** NO NEED TO START: " + attrs.getTitle()); - return WindowManagerImpl.ADD_STARTING_NOT_NEEDED; + return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED; } } else if (attrs.type == TYPE_INPUT_METHOD) { if (token.windowType != TYPE_INPUT_METHOD) { Slog.w(TAG, "Attempted to add input method window with bad token " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_APP_TOKEN; + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (attrs.type == TYPE_WALLPAPER) { if (token.windowType != TYPE_WALLPAPER) { Slog.w(TAG, "Attempted to add wallpaper window with bad token " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_APP_TOKEN; + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (attrs.type == TYPE_DREAM) { if (token.windowType != TYPE_DREAM) { Slog.w(TAG, "Attempted to add Dream window with bad token " + attrs.token + ". Aborting."); - return WindowManagerImpl.ADD_BAD_APP_TOKEN; + return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } + final DisplayContent displayContent = getDisplayContentLocked(displayId); win = new WindowState(this, session, client, token, - attachedWindow, seq, attrs, viewVisibility); + attachedWindow, seq, attrs, viewVisibility, displayContent); if (win.mDeathRecipient == null) { // Client has apparently died, so there is no reason to // continue. Slog.w(TAG, "Adding window client " + client.asBinder() + " that is dead, aborting."); - return WindowManagerImpl.ADD_APP_EXITING; + return WindowManagerGlobal.ADD_APP_EXITING; } mPolicy.adjustWindowParamsLw(win.mAttrs); res = mPolicy.prepareAddWindowLw(win, attrs); - if (res != WindowManagerImpl.ADD_OKAY) { + if (res != WindowManagerGlobal.ADD_OKAY) { return res; } - + if (outInputChannel != null && (attrs.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { String name = win.makeInputChannelName(); InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); win.setInputChannel(inputChannels[0]); inputChannels[1].transferTo(outInputChannel); - + mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); } // From now on, no exceptions or errors allowed! - res = WindowManagerImpl.ADD_OKAY; + res = WindowManagerGlobal.ADD_OKAY; origId = Binder.clearCallingIdentity(); @@ -2319,13 +2241,17 @@ public class WindowManagerService extends IWindowManager.Stub win.mWinAnimator.mEnterAnimationPending = true; - mPolicy.getContentInsetHintLw(attrs, outContentInsets); + if (displayContent.isDefaultDisplay) { + mPolicy.getContentInsetHintLw(attrs, outContentInsets); + } else { + outContentInsets.setEmpty(); + } if (mInTouchMode) { - res |= WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE; + res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; } if (win.mAppToken == null || !win.mAppToken.clientHidden) { - res |= WindowManagerImpl.ADD_FLAG_APP_VISIBLE; + res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; } mInputMonitor.setUpdateInputWindowsNeededLw(); @@ -2343,7 +2269,7 @@ public class WindowManagerService extends IWindowManager.Stub moveInputMethodWindowsIfNeededLocked(false); } - assignLayersLocked(); + assignLayersLocked(displayContent.getWindowList()); // Don't do layout here, the window must call // relayout to be displayed, so we'll do it there. @@ -2357,7 +2283,7 @@ public class WindowManagerService extends IWindowManager.Stub if (localLOGV) Slog.v( TAG, "New client " + client.asBinder() + ": window=" + win); - + if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) { reportNewConfig = true; } @@ -2423,13 +2349,14 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mWinAnimator.applyAnimationLocked(transit, false)) { win.mExiting = true; } + scheduleNotifyWindowTranstionIfNeededLocked(win, transit); } if (win.mExiting || win.mWinAnimator.isAnimating()) { // The exit animation is running... wait for it! //Slog.i(TAG, "*** Running exit animation..."); win.mExiting = true; win.mRemoveOnExit = true; - mLayoutNeeded = true; + win.mDisplayContent.layoutNeeded = true; updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); performLayoutAndPlaceSurfacesLocked(); @@ -2485,7 +2412,12 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win); mWindowMap.remove(win.mClient.asBinder()); - mWindows.remove(win); + + final WindowList windows = win.getWindowList(); + windows.remove(win); + if (windows.isEmpty()) { + mDisplayManagerService.setDisplayHasContent(win.getDisplayId(), false); + } mPendingRemove.remove(win); mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win); @@ -2543,14 +2475,14 @@ public class WindowManagerService extends IWindowManager.Stub } if (!mInLayout) { - assignLayersLocked(); - mLayoutNeeded = true; + assignLayersLocked(windows); + win.mDisplayContent.layoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); if (win.mAppToken != null) { win.mAppToken.updateReportedVisibilityLocked(); } } - + mInputMonitor.updateInputWindowsLw(true /*force*/); } @@ -2610,7 +2542,7 @@ public class WindowManagerService extends IWindowManager.Stub w.mGivenVisibleInsets.scale(w.mGlobalScale); w.mGivenTouchableRegion.scale(w.mGlobalScale); } - mLayoutNeeded = true; + w.mDisplayContent.layoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); } } @@ -2683,6 +2615,80 @@ public class WindowManagerService extends IWindowManager.Stub return null; } + public void setUniverseTransformLocked(WindowState window, float alpha, + float offx, float offy, float dsdx, float dtdx, float dsdy, float dtdy) { + Transformation transform = window.mWinAnimator.mUniverseTransform; + transform.setAlpha(alpha); + Matrix matrix = transform.getMatrix(); + matrix.getValues(mTmpFloats); + mTmpFloats[Matrix.MTRANS_X] = offx; + mTmpFloats[Matrix.MTRANS_Y] = offy; + mTmpFloats[Matrix.MSCALE_X] = dsdx; + mTmpFloats[Matrix.MSKEW_Y] = dtdx; + mTmpFloats[Matrix.MSKEW_X] = dsdy; + mTmpFloats[Matrix.MSCALE_Y] = dtdy; + matrix.setValues(mTmpFloats); + final DisplayInfo displayInfo = window.mDisplayContent.getDisplayInfo(); + final RectF dispRect = new RectF(0, 0, + displayInfo.logicalWidth, displayInfo.logicalHeight); + matrix.mapRect(dispRect); + window.mGivenTouchableRegion.set(0, 0, + displayInfo.logicalWidth, displayInfo.logicalHeight); + window.mGivenTouchableRegion.op((int)dispRect.left, (int)dispRect.top, + (int)dispRect.right, (int)dispRect.bottom, Region.Op.DIFFERENCE); + window.mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; + window.mDisplayContent.layoutNeeded = true; + performLayoutAndPlaceSurfacesLocked(); + } + + public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) { + synchronized (mWindowMap) { + WindowState window = mWindowMap.get(token); + if (window != null) { + scheduleNotifyRectangleOnScreenRequestedIfNeededLocked(window, rectangle, + immediate); + } + } + } + + private void scheduleNotifyRectangleOnScreenRequestedIfNeededLocked(WindowState window, + Rect rectangle, boolean immediate) { + DisplayContent displayContent = window.mDisplayContent; + if (displayContent.mDisplayContentChangeListeners != null + && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) { + mH.obtainMessage(H.NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED, displayContent.getDisplayId(), + immediate? 1 : 0, new Rect(rectangle)).sendToTarget(); + } + } + + private void handleNotifyRectangleOnScreenRequested(int displayId, Rect rectangle, + boolean immediate) { + RemoteCallbackList<IDisplayContentChangeListener> callbacks = null; + synchronized (mWindowMap) { + DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent == null) { + return; + } + callbacks = displayContent.mDisplayContentChangeListeners; + if (callbacks == null) { + return; + } + } + final int callbackCount = callbacks.beginBroadcast(); + try { + for (int i = 0; i < callbackCount; i++) { + try { + callbacks.getBroadcastItem(i).onRectangleOnScreenRequested(displayId, + rectangle, immediate); + } catch (RemoteException re) { + /* ignore */ + } + } + } finally { + callbacks.finishBroadcast(); + } + } + public int relayoutWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, @@ -2729,7 +2735,7 @@ public class WindowManagerService extends IWindowManager.Stub } winAnimator.mSurfaceDestroyDeferred = - (flags&WindowManagerImpl.RELAYOUT_DEFER_SURFACE_DESTROY) != 0; + (flags&WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY) != 0; int attrChanges = 0; int flagChanges = 0; @@ -2772,9 +2778,10 @@ public class WindowManagerService extends IWindowManager.Stub WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) != 0; - boolean focusMayChange = win.mViewVisibility != viewVisibility + final boolean isDefaultDisplay = win.isDefaultDisplay(); + boolean focusMayChange = isDefaultDisplay && (win.mViewVisibility != viewVisibility || ((flagChanges&WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) - || (!win.mRelayoutCalled); + || (!win.mRelayoutCalled)); boolean wallpaperMayMove = win.mViewVisibility != viewVisibility && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0; @@ -2848,7 +2855,7 @@ public class WindowManagerService extends IWindowManager.Stub } } catch (Exception e) { mInputMonitor.updateInputWindowsLw(true /*force*/); - + Slog.w(TAG, "Exception thrown when creating surface for client " + client + " (" + win.mAttrs.getTitle() + ")", e); @@ -2856,7 +2863,7 @@ public class WindowManagerService extends IWindowManager.Stub return 0; } if (toBeDisplayed) { - focusMayChange = true; + focusMayChange = isDefaultDisplay; } if (win.mAttrs.type == TYPE_INPUT_METHOD && mInputMethodWindow == null) { @@ -2893,7 +2900,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) { - focusMayChange = true; + focusMayChange = isDefaultDisplay; win.mExiting = true; } else if (win.mWinAnimator.isAnimating()) { // Currently in a hide animation... turn this into @@ -2911,6 +2918,7 @@ public class WindowManagerService extends IWindowManager.Stub } winAnimator.destroySurfaceLocked(); } + scheduleNotifyWindowTranstionIfNeededLocked(win, transit); } } @@ -2947,15 +2955,17 @@ public class WindowManagerService extends IWindowManager.Stub } } - mLayoutNeeded = true; - win.mGivenInsetsPending = (flags&WindowManagerImpl.RELAYOUT_INSETS_PENDING) != 0; + win.mDisplayContent.layoutNeeded = true; + win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0; if (assignLayers) { - assignLayersLocked(); + assignLayersLocked(win.getWindowList()); } configChanged = updateOrientationFromAppTokensLocked(false); performLayoutAndPlaceSurfacesLocked(); if (toBeDisplayed && win.mIsWallpaper) { - updateWallpaperOffsetLocked(win, mAppDisplayWidth, mAppDisplayHeight, false); + DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); + updateWallpaperOffsetLocked(win, + displayInfo.appWidth, displayInfo.appHeight, false); } if (win.mAppToken != null) { win.mAppToken.updateReportedVisibilityLocked(); @@ -2989,10 +2999,10 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); - return (inTouchMode ? WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE : 0) - | (toBeDisplayed ? WindowManagerImpl.RELAYOUT_RES_FIRST_TIME : 0) - | (surfaceChanged ? WindowManagerImpl.RELAYOUT_RES_SURFACE_CHANGED : 0) - | (animating ? WindowManagerImpl.RELAYOUT_RES_ANIMATING : 0); + return (inTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0) + | (toBeDisplayed ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0) + | (surfaceChanged ? WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED : 0) + | (animating ? WindowManagerGlobal.RELAYOUT_RES_ANIMATING : 0); } public void performDeferredDestroyWindow(Session session, IWindow client) { @@ -3035,20 +3045,118 @@ public class WindowManagerService extends IWindowManager.Stub if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) { adjustWallpaperWindowsLocked(); } - mLayoutNeeded = true; + win.mDisplayContent.layoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); } } Binder.restoreCallingIdentity(origId); } + @Override public float getWindowCompatibilityScale(IBinder windowToken) { + if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, + "getWindowCompatibilityScale()")) { + throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); + } synchronized (mWindowMap) { WindowState windowState = mWindowMap.get(windowToken); return (windowState != null) ? windowState.mGlobalScale : 1.0f; } } + @Override + public WindowInfo getWindowInfo(IBinder token) { + if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, + "getWindowInfo()")) { + throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); + } + synchronized (mWindowMap) { + WindowState window = mWindowMap.get(token); + if (window != null) { + return getWindowInfoForWindowStateLocked(window); + } + return null; + } + } + + @Override + public void getVisibleWindowsForDisplay(int displayId, List<WindowInfo> outInfos) { + if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, + "getWindowInfos()")) { + throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); + } + synchronized (mWindowMap) { + DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent == null) { + return; + } + WindowList windows = displayContent.getWindowList(); + final int windowCount = windows.size(); + for (int i = 0; i < windowCount; i++) { + WindowState window = windows.get(i); + if (window.isVisibleLw() || + window.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) { + WindowInfo info = getWindowInfoForWindowStateLocked(window); + outInfos.add(info); + } + } + } + } + + @Override + public void magnifyDisplay(int displayId, float scale, float offsetX, float offsetY) { + if (!checkCallingPermission( + android.Manifest.permission.MAGNIFY_DISPLAY, "magnifyDisplay()")) { + throw new SecurityException("Requires MAGNIFY_DISPLAY permission"); + } + synchronized (mWindowMap) { + MagnificationSpec spec = getDisplayMagnificationSpecLocked(displayId); + if (spec != null) { + final boolean scaleChanged = spec.mScale != scale; + final boolean offsetChanged = spec.mOffsetX != offsetX || spec.mOffsetY != offsetY; + if (!scaleChanged && !offsetChanged) { + return; + } + spec.initialize(scale, offsetX, offsetY); + // If the offset has changed we need to re-add the input windows + // since the offsets have to be propagated to the input system. + if (offsetChanged) { + // TODO(multidisplay): Input only occurs on the default display. + if (displayId == Display.DEFAULT_DISPLAY) { + mInputMonitor.updateInputWindowsLw(true); + } + } + scheduleAnimationLocked(); + } + } + } + + MagnificationSpec getDisplayMagnificationSpecLocked(int displayId) { + DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + if (displayContent.mMagnificationSpec == null) { + displayContent.mMagnificationSpec = new MagnificationSpec(); + } + return displayContent.mMagnificationSpec; + } + return null; + } + + private WindowInfo getWindowInfoForWindowStateLocked(WindowState window) { + WindowInfo info = WindowInfo.obtain(); + info.token = window.mToken.token; + info.frame.set(window.mFrame); + info.type = window.mAttrs.type; + info.displayId = window.getDisplayId(); + info.compatibilityScale = window.mGlobalScale; + info.visible = window.isVisibleLw() + || info.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND; + info.layer = window.mLayer; + window.getTouchableRegion(mTempRegion); + mTempRegion.getBounds(info.touchableRegion); + return info; + } + private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) { if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" + (lp != null ? lp.packageName : null) @@ -3128,12 +3236,11 @@ public class WindowManagerService extends IWindowManager.Stub a.setDetachWallpaper(true); a.setDuration(duration); return a; - } else { - // For normal animations, the exiting element just holds in place. - Animation a = new AlphaAnimation(1, 1); - a.setDuration(duration); - return a; } + // For normal animations, the exiting element just holds in place. + Animation a = new AlphaAnimation(1, 1); + a.setDuration(duration); + return a; } /** @@ -3170,10 +3277,12 @@ public class WindowManagerService extends IWindowManager.Stub duration = 300; break; } + // TODO(multidisplay): For now assume all app animation is on main display. + final DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); if (enter) { // Entering app zooms out from the center of the initial rect. - float scaleW = mNextAppTransitionStartWidth / (float) mAppDisplayWidth; - float scaleH = mNextAppTransitionStartHeight / (float) mAppDisplayHeight; + float scaleW = mNextAppTransitionStartWidth / (float) displayInfo.appWidth; + float scaleH = mNextAppTransitionStartHeight / (float) displayInfo.appHeight; Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, computePivot(mNextAppTransitionStartX, scaleW), computePivot(mNextAppTransitionStartY, scaleH)); @@ -3193,13 +3302,13 @@ public class WindowManagerService extends IWindowManager.Stub final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext, com.android.internal.R.interpolator.decelerate_cubic); a.setInterpolator(interpolator); - a.initialize(mAppDisplayWidth, mAppDisplayHeight, - mAppDisplayWidth, mAppDisplayHeight); + a.initialize(displayInfo.appWidth, displayInfo.appHeight, + displayInfo.appWidth, displayInfo.appHeight); return a; } private Animation createThumbnailAnimationLocked(int transit, - boolean enter, boolean thumb, boolean delayed) { + boolean enter, boolean thumb, boolean scaleUp) { Animation a; final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; @@ -3209,7 +3318,6 @@ public class WindowManagerService extends IWindowManager.Stub // it is the standard duration for that. Otherwise we use the longer // task transition duration. int duration; - int delayDuration = delayed ? 270 : 0; switch (transit) { case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: @@ -3217,66 +3325,106 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.integer.config_shortAnimTime); break; default: - duration = delayed ? 250 : 300; + duration = 250; break; } + // TOOD(multidisplay): For now assume all app animation is on the main screen. + DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); if (thumb) { // Animation for zooming thumbnail from its initial size to // filling the screen. - float scaleW = mAppDisplayWidth/thumbWidth; - float scaleH = mAppDisplayHeight/thumbHeight; - - Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, - computePivot(mNextAppTransitionStartX, 1/scaleW), - computePivot(mNextAppTransitionStartY, 1/scaleH)); - AnimationSet set = new AnimationSet(true); - Animation alpha = new AlphaAnimation(1, 0); - scale.setDuration(duration); - scale.setInterpolator( - new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); - set.addAnimation(scale); - alpha.setDuration(duration); - set.addAnimation(alpha); - set.setFillBefore(true); - if (delayDuration > 0) { - set.setStartOffset(delayDuration); + if (scaleUp) { + float scaleW = displayInfo.appWidth / thumbWidth; + float scaleH = displayInfo.appHeight / thumbHeight; + + Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, + computePivot(mNextAppTransitionStartX, 1 / scaleW), + computePivot(mNextAppTransitionStartY, 1 / scaleH)); + AnimationSet set = new AnimationSet(true); + Animation alpha = new AlphaAnimation(1, 0); + scale.setDuration(duration); + scale.setInterpolator( + new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + set.addAnimation(scale); + alpha.setDuration(duration); + set.addAnimation(alpha); + set.setFillBefore(true); + a = set; + } else { + float scaleW = displayInfo.appWidth / thumbWidth; + float scaleH = displayInfo.appHeight / thumbHeight; + + Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mNextAppTransitionStartX, 1 / scaleW), + computePivot(mNextAppTransitionStartY, 1 / scaleH)); + AnimationSet set = new AnimationSet(true); + Animation alpha = new AlphaAnimation(1, 1); + scale.setDuration(duration); + scale.setInterpolator( + new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + set.addAnimation(scale); + alpha.setDuration(duration); + set.addAnimation(alpha); + set.setFillBefore(true); + + a = set; } - a = set; } else if (enter) { // Entering app zooms out from the center of the thumbnail. - float scaleW = thumbWidth / mAppDisplayWidth; - float scaleH = thumbHeight / mAppDisplayHeight; - Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, - computePivot(mNextAppTransitionStartX, scaleW), - computePivot(mNextAppTransitionStartY, scaleH)); - scale.setDuration(duration); - scale.setInterpolator( - new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); - scale.setFillBefore(true); - if (delayDuration > 0) { - scale.setStartOffset(delayDuration); + if (scaleUp) { + float scaleW = thumbWidth / displayInfo.appWidth; + float scaleH = thumbHeight / displayInfo.appHeight; + Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mNextAppTransitionStartX, scaleW), + computePivot(mNextAppTransitionStartY, scaleH)); + scale.setDuration(duration); + scale.setInterpolator( + new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + scale.setFillBefore(true); + a = scale; + } else { + // noop animation + a = new AlphaAnimation(1, 1); + a.setDuration(duration); } - a = scale; } else { - if (delayed) { - a = new AlphaAnimation(1, 0); - a.setStartOffset(0); - a.setDuration(delayDuration - 120); - a.setBackgroundColor(0xFF000000); + // Exiting app + if (scaleUp) { + // noop animation + a = new AlphaAnimation(1, 1); + a.setDuration(duration); } else { - a = createExitAnimationLocked(transit, duration); + float scaleW = thumbWidth / displayInfo.appWidth; + float scaleH = thumbHeight / displayInfo.appHeight; + Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH, + computePivot(mNextAppTransitionStartX, scaleW), + computePivot(mNextAppTransitionStartY, scaleH)); + scale.setDuration(duration); + scale.setInterpolator( + new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + scale.setFillBefore(true); + AnimationSet set = new AnimationSet(true); + Animation alpha = new AlphaAnimation(1, 0); + set.addAnimation(scale); + alpha.setDuration(duration); + alpha.setInterpolator(new DecelerateInterpolator( + THUMBNAIL_ANIMATION_DECELERATE_FACTOR)); + set.addAnimation(alpha); + set.setFillBefore(true); + set.setZAdjustment(Animation.ZORDER_TOP); + a = set; } } a.setFillAfter(true); final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext, com.android.internal.R.interpolator.decelerate_quad); a.setInterpolator(interpolator); - a.initialize(mAppDisplayWidth, mAppDisplayHeight, - mAppDisplayWidth, mAppDisplayHeight); + a.initialize(displayInfo.appWidth, displayInfo.appHeight, + displayInfo.appWidth, displayInfo.appHeight); return a; } - private boolean applyAnimationLocked(AppWindowToken wtoken, + private boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp, int transit, boolean enter) { // Only apply an animation if the display isn't frozen. If it is // frozen, there is no reason to animate and it can cause strange @@ -3289,27 +3437,29 @@ public class WindowManagerService extends IWindowManager.Stub a = loadAnimation(mNextAppTransitionPackage, enter ? mNextAppTransitionEnter : mNextAppTransitionExit); if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, - "applyAnimation: wtoken=" + wtoken + "applyAnimation: atoken=" + atoken + " anim=" + a + " nextAppTransition=ANIM_CUSTOM" - + " transit=" + transit + " Callers " + Debug.getCallers(3)); + + " transit=" + transit + " isEntrance=" + enter + + " Callers " + Debug.getCallers(3)); } else if (mNextAppTransitionType == ActivityOptions.ANIM_SCALE_UP) { a = createScaleUpAnimationLocked(transit, enter); initialized = true; if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, - "applyAnimation: wtoken=" + wtoken + "applyAnimation: atoken=" + atoken + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP" - + " transit=" + transit + " Callers " + Debug.getCallers(3)); - } else if (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL || - mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_DELAYED) { - boolean delayed = (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_DELAYED); - a = createThumbnailAnimationLocked(transit, enter, false, delayed); + + " transit=" + transit + " isEntrance=" + enter + + " Callers " + Debug.getCallers(3)); + } else if (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP || + mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN) { + boolean scaleUp = (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP); + a = createThumbnailAnimationLocked(transit, enter, false, scaleUp); initialized = true; - if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) { - String animName = delayed ? "ANIM_THUMBNAIL_DELAYED" : "ANIM_THUMBNAIL"; - Slog.v(TAG, "applyAnimation: wtoken=" + wtoken + String animName = scaleUp ? "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN"; + Slog.v(TAG, "applyAnimation: atoken=" + atoken + " anim=" + a + " nextAppTransition=" + animName - + " transit=" + transit + " Callers " + Debug.getCallers(3)); + + " transit=" + transit + " isEntrance=" + enter + + " Callers " + Debug.getCallers(3)); } } else { int animAttr = 0; @@ -3367,10 +3517,11 @@ public class WindowManagerService extends IWindowManager.Stub } a = animAttr != 0 ? loadAnimation(lp, animAttr) : null; if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, - "applyAnimation: wtoken=" + wtoken + "applyAnimation: atoken=" + atoken + " anim=" + a + " animAttr=0x" + Integer.toHexString(animAttr) - + " transit=" + transit + " Callers " + Debug.getCallers(3)); + + " transit=" + transit + " isEntrance=" + enter + + " Callers " + Debug.getCallers(3)); } if (a != null) { if (DEBUG_ANIM) { @@ -3379,15 +3530,15 @@ public class WindowManagerService extends IWindowManager.Stub e = new RuntimeException(); e.fillInStackTrace(); } - Slog.v(TAG, "Loaded animation " + a + " for " + wtoken, e); + Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e); } - wtoken.mAppAnimator.setAnimation(a, initialized); + atoken.mAppAnimator.setAnimation(a, initialized); } } else { - wtoken.mAppAnimator.clearAnimation(); + atoken.mAppAnimator.clearAnimation(); } - return wtoken.mAppAnimator.animation != null; + return atoken.mAppAnimator.animation != null; } // ------------------------------------------------------------- @@ -3398,14 +3549,14 @@ public class WindowManagerService extends IWindowManager.Stub int v = tokens.size()-1; int m = mAppTokens.size()-1; while (v >= 0 && m >= 0) { - AppWindowToken wtoken = mAppTokens.get(m); - if (wtoken.removed) { + AppWindowToken atoken = mAppTokens.get(m); + if (atoken.removed) { m--; continue; } - if (tokens.get(v) != wtoken.token) { + if (tokens.get(v) != atoken.token) { Slog.w(TAG, "Tokens out of sync: external is " + tokens.get(v) - + " @ " + v + ", internal is " + wtoken.token + " @ " + m); + + " @ " + v + ", internal is " + atoken.token + " @ " + m); } v--; m--; @@ -3415,9 +3566,9 @@ public class WindowManagerService extends IWindowManager.Stub v--; } while (m >= 0) { - AppWindowToken wtoken = mAppTokens.get(m); - if (!wtoken.removed) { - Slog.w(TAG, "Invalid internal token: " + wtoken.token + " @ " + m); + AppWindowToken atoken = mAppTokens.get(m); + if (!atoken.removed) { + Slog.w(TAG, "Invalid internal atoken: " + atoken.token + " @ " + m); } m--; } @@ -3440,7 +3591,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, msg); return false; } - + boolean okToDisplay() { return !mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully(); } @@ -3470,10 +3621,12 @@ public class WindowManagerService extends IWindowManager.Stub mTokenMap.put(token, wtoken); if (type == TYPE_WALLPAPER) { mWallpaperTokens.add(wtoken); + updateLayoutToAnimWallpaperTokens(); } } } + @Override public void removeWindowToken(IBinder token) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "removeWindowToken()")) { @@ -3499,13 +3652,16 @@ public class WindowManagerService extends IWindowManager.Stub } if (win.isVisibleNow()) { - win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false); + win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, + false); + scheduleNotifyWindowTranstionIfNeededLocked(win, + WindowManagerPolicy.TRANSIT_EXIT); changed = true; + win.mDisplayContent.layoutNeeded = true; } } if (changed) { - mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /*updateInputWindows*/); @@ -3515,6 +3671,7 @@ public class WindowManagerService extends IWindowManager.Stub mExitingTokens.add(wtoken); } else if (wtoken.windowType == TYPE_WALLPAPER) { mWallpaperTokens.remove(wtoken); + updateLayoutToAnimWallpaperTokens(); } } @@ -3530,18 +3687,18 @@ public class WindowManagerService extends IWindowManager.Stub * Find the location to insert a new AppWindowToken into the window-ordered app token list. * Note that mAppTokens.size() == mAnimatingAppTokens.size() + 1. * @param addPos The location the token was inserted into in mAppTokens. - * @param wtoken The token to insert. + * @param atoken The token to insert. */ - private void addAppTokenToAnimating(final int addPos, final AppWindowToken wtoken) { + private void addAppTokenToAnimating(final int addPos, final AppWindowToken atoken) { if (addPos == 0 || addPos == mAnimatingAppTokens.size()) { // It was inserted into the beginning or end of mAppTokens. Honor that. - mAnimatingAppTokens.add(addPos, wtoken); + mAnimatingAppTokens.add(addPos, atoken); return; } // Find the item immediately above the mAppTokens insertion point and put the token // immediately below that one in mAnimatingAppTokens. final AppWindowToken aboveAnchor = mAppTokens.get(addPos + 1); - mAnimatingAppTokens.add(mAnimatingAppTokens.indexOf(aboveAnchor), wtoken); + mAnimatingAppTokens.add(mAnimatingAppTokens.indexOf(aboveAnchor), atoken); } @Override @@ -3567,30 +3724,31 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized(mWindowMap) { - AppWindowToken wtoken = findAppWindowToken(token.asBinder()); - if (wtoken != null) { + AppWindowToken atoken = findAppWindowToken(token.asBinder()); + if (atoken != null) { Slog.w(TAG, "Attempted to add existing app token: " + token); return; } - wtoken = new AppWindowToken(this, token); - wtoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; - wtoken.groupId = groupId; - wtoken.appFullscreen = fullscreen; - wtoken.requestedOrientation = requestedOrientation; - if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + wtoken + atoken = new AppWindowToken(this, token); + atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; + atoken.groupId = groupId; + atoken.appFullscreen = fullscreen; + atoken.requestedOrientation = requestedOrientation; + if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken + " at " + addPos); - mAppTokens.add(addPos, wtoken); - addAppTokenToAnimating(addPos, wtoken); - mTokenMap.put(token.asBinder(), wtoken); + mAppTokens.add(addPos, atoken); + addAppTokenToAnimating(addPos, atoken); + mTokenMap.put(token.asBinder(), atoken); // Application tokens start out hidden. - wtoken.hidden = true; - wtoken.hiddenRequested = true; + atoken.hidden = true; + atoken.hiddenRequested = true; //dump(); } } + @Override public void setAppGroupId(IBinder token, int groupId) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppGroupId()")) { @@ -3598,12 +3756,12 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized(mWindowMap) { - AppWindowToken wtoken = findAppWindowToken(token); - if (wtoken == null) { + AppWindowToken atoken = findAppWindowToken(token); + if (atoken == null) { Slog.w(TAG, "Attempted to set group id of non-existing app token: " + token); return; } - wtoken.groupId = groupId; + atoken.groupId = groupId; } } @@ -3617,9 +3775,11 @@ public class WindowManagerService extends IWindowManager.Stub return mLastWindowForcedOrientation; } - int pos = mWindows.size() - 1; + // TODO(multidisplay): Change to the correct display. + final WindowList windows = getDefaultWindowListLocked(); + int pos = windows.size() - 1; while (pos >= 0) { - WindowState wtoken = mWindows.get(pos); + WindowState wtoken = windows.get(pos); pos--; if (wtoken.mAppToken != null) { // We hit an application window. so the orientation will be determined by the @@ -3633,9 +3793,9 @@ public class WindowManagerService extends IWindowManager.Stub if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) || (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){ continue; - } else { - return (mLastWindowForcedOrientation=req); } + + return (mLastWindowForcedOrientation=req); } return (mLastWindowForcedOrientation=ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } @@ -3647,20 +3807,20 @@ public class WindowManagerService extends IWindowManager.Stub boolean haveGroup = false; boolean lastFullscreen = false; for (int pos = mAppTokens.size() - 1; pos >= 0; pos--) { - AppWindowToken wtoken = mAppTokens.get(pos); + AppWindowToken atoken = mAppTokens.get(pos); - if (DEBUG_APP_ORIENTATION) Slog.v(TAG, "Checking app orientation: " + wtoken); + if (DEBUG_APP_ORIENTATION) Slog.v(TAG, "Checking app orientation: " + atoken); // if we're about to tear down this window and not seek for // the behind activity, don't use it for orientation if (!findingBehind - && (!wtoken.hidden && wtoken.hiddenRequested)) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + wtoken + && (!atoken.hidden && atoken.hiddenRequested)) { + if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken + " -- going to hide"); continue; } - if (haveGroup == true && curGroup != wtoken.groupId) { + if (haveGroup == true && curGroup != atoken.groupId) { // If we have hit a new application group, and the bottom // of the previous group didn't explicitly say to use // the orientation behind it, and the last app was @@ -3668,33 +3828,33 @@ public class WindowManagerService extends IWindowManager.Stub // user's orientation. if (lastOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND && lastFullscreen) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + wtoken + if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken + " -- end of group, return " + lastOrientation); return lastOrientation; } } // We ignore any hidden applications on the top. - if (wtoken.hiddenRequested || wtoken.willBeHidden) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + wtoken + if (atoken.hiddenRequested || atoken.willBeHidden) { + if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken + " -- hidden on top"); continue; } if (!haveGroup) { haveGroup = true; - curGroup = wtoken.groupId; - lastOrientation = wtoken.requestedOrientation; - } + curGroup = atoken.groupId; + lastOrientation = atoken.requestedOrientation; + } - int or = wtoken.requestedOrientation; + int or = atoken.requestedOrientation; // If this application is fullscreen, and didn't explicitly say // to use the orientation behind it, then just take whatever // orientation it has and ignores whatever is under it. - lastFullscreen = wtoken.appFullscreen; + lastFullscreen = atoken.appFullscreen; if (lastFullscreen && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + wtoken + if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken + " -- full screen, return " + or); return or; } @@ -3702,7 +3862,7 @@ public class WindowManagerService extends IWindowManager.Stub // then use it. if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + wtoken + if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken + " -- explicitly set, return " + or); return or; } @@ -3712,6 +3872,7 @@ public class WindowManagerService extends IWindowManager.Stub return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } + @Override public Configuration updateOrientationFromAppTokens( Configuration currentConfig, IBinder freezeThisOneIfNeeded) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, @@ -3721,7 +3882,7 @@ public class WindowManagerService extends IWindowManager.Stub Configuration config = null; long ident = Binder.clearCallingIdentity(); - + synchronized(mWindowMap) { config = updateOrientationFromAppTokensLocked(currentConfig, freezeThisOneIfNeeded); @@ -3737,10 +3898,10 @@ public class WindowManagerService extends IWindowManager.Stub if (updateOrientationFromAppTokensLocked(false)) { if (freezeThisOneIfNeeded != null) { - AppWindowToken wtoken = findAppWindowToken( + AppWindowToken atoken = findAppWindowToken( freezeThisOneIfNeeded); - if (wtoken != null) { - startAppFreezingScreenLocked(wtoken, + if (atoken != null) { + startAppFreezingScreenLocked(atoken, ActivityInfo.CONFIG_ORIENTATION); } } @@ -3756,13 +3917,13 @@ public class WindowManagerService extends IWindowManager.Stub if (computeScreenConfigurationLocked(mTempConfiguration)) { if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; - mLayoutNeeded = true; - startFreezingDisplayLocked(false); + getDefaultDisplayContentLocked().layoutNeeded = true; + startFreezingDisplayLocked(false, 0, 0); config = new Configuration(mTempConfiguration); } } } - + return config; } @@ -3773,7 +3934,7 @@ public class WindowManagerService extends IWindowManager.Stub * setNewConfiguration() TO TELL THE WINDOW MANAGER IT CAN UNFREEZE THE * SCREEN. This will typically be done for you if you call * sendNewConfiguration(). - * + * * The orientation is computed from non-application windows first. If none of * the non-application windows specify orientation, the orientation is computed from * application tokens. @@ -3810,6 +3971,7 @@ public class WindowManagerService extends IWindowManager.Stub return req; } + @Override public void setNewConfiguration(Configuration config) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setNewConfiguration()")) { @@ -3822,7 +3984,8 @@ public class WindowManagerService extends IWindowManager.Stub performLayoutAndPlaceSurfacesLocked(); } } - + + @Override public void setAppOrientation(IApplicationToken token, int requestedOrientation) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppOrientation()")) { @@ -3830,16 +3993,17 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized(mWindowMap) { - AppWindowToken wtoken = findAppWindowToken(token.asBinder()); - if (wtoken == null) { + AppWindowToken atoken = findAppWindowToken(token.asBinder()); + if (atoken == null) { Slog.w(TAG, "Attempted to set orientation of non-existing app token: " + token); return; } - wtoken.requestedOrientation = requestedOrientation; + atoken.requestedOrientation = requestedOrientation; } } + @Override public int getAppOrientation(IApplicationToken token) { synchronized(mWindowMap) { AppWindowToken wtoken = findAppWindowToken(token.asBinder()); @@ -3851,6 +4015,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void setFocusedApp(IBinder token, boolean moveFocusNow) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setFocusedApp()")) { @@ -3888,6 +4053,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "prepareAppTransition()")) { @@ -3926,6 +4092,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public int getPendingAppTransition() { return mNextAppTransition; } @@ -3936,6 +4103,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim, IRemoteCallback startedCallback) { synchronized(mWindowMap) { @@ -3953,6 +4121,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth, int startHeight) { synchronized(mWindowMap) { @@ -3970,15 +4139,16 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, - int startY, IRemoteCallback startedCallback, boolean delayed) { + int startY, IRemoteCallback startedCallback, boolean scaleUp) { synchronized(mWindowMap) { if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { - mNextAppTransitionType = delayed - ? ActivityOptions.ANIM_THUMBNAIL_DELAYED : ActivityOptions.ANIM_THUMBNAIL; + mNextAppTransitionType = scaleUp + ? ActivityOptions.ANIM_THUMBNAIL_SCALE_UP : ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; mNextAppTransitionPackage = null; mNextAppTransitionThumbnail = srcThumb; - mNextAppTransitionDelayed = delayed; + mNextAppTransitionScaleUp = scaleUp; mNextAppTransitionStartX = startX; mNextAppTransitionStartY = startY; scheduleAnimationCallback(mNextAppTransitionCallback); @@ -3989,6 +4159,7 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override public void executeAppTransition() { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "executeAppTransition()")) { @@ -4075,7 +4246,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) { Slog.v(TAG, "Removing starting window: " + startingWindow); } - mWindows.remove(startingWindow); + startingWindow.getWindowList().remove(startingWindow); mWindowsChanged = true; if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing starting " + startingWindow + " from " + ttoken); @@ -4117,7 +4288,7 @@ public class WindowManagerService extends IWindowManager.Stub updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/); - mLayoutNeeded = true; + getDefaultDisplayContentLocked().layoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); Binder.restoreCallingIdentity(origId); return; @@ -4263,6 +4434,10 @@ public class WindowManagerService extends IWindowManager.Stub if (applyAnimationLocked(wtoken, lp, transit, visible)) { delayed = runningAppAnimation = true; } + WindowState window = wtoken.findMainWindow(); + if (window != null) { + scheduleNotifyWindowTranstionIfNeededLocked(window, transit); + } changed = true; } @@ -4280,15 +4455,21 @@ public class WindowManagerService extends IWindowManager.Stub if (!runningAppAnimation) { win.mWinAnimator.applyAnimationLocked( WindowManagerPolicy.TRANSIT_ENTER, true); + scheduleNotifyWindowTranstionIfNeededLocked(win, + WindowManagerPolicy.TRANSIT_ENTER); } changed = true; + win.mDisplayContent.layoutNeeded = true; } } else if (win.isVisibleNow()) { if (!runningAppAnimation) { win.mWinAnimator.applyAnimationLocked( WindowManagerPolicy.TRANSIT_EXIT, false); + scheduleNotifyWindowTranstionIfNeededLocked(win, + WindowManagerPolicy.TRANSIT_EXIT); } changed = true; + win.mDisplayContent.layoutNeeded = true; } } @@ -4310,7 +4491,6 @@ public class WindowManagerService extends IWindowManager.Stub + wtoken.hiddenRequested); if (changed) { - mLayoutNeeded = true; mInputMonitor.setUpdateInputWindowsNeededLw(); if (performLayout) { updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, @@ -4438,6 +4618,7 @@ public class WindowManagerService extends IWindowManager.Stub mInnerFields.mOrientationChangeComplete = false; } unfrozeWindows = true; + w.mDisplayContent.layoutNeeded = true; } } if (force || unfrozeWindows) { @@ -4447,7 +4628,6 @@ public class WindowManagerService extends IWindowManager.Stub } if (unfreezeSurfaceNow) { if (unfrozeWindows) { - mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); } stopFreezingDisplayLocked(); @@ -4472,7 +4652,7 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.mAppAnimator.freezingScreen = true; mAppsFreezingScreen++; if (mAppsFreezingScreen == 1) { - startFreezingDisplayLocked(false); + startFreezingDisplayLocked(false, 0, 0); mH.removeMessages(H.APP_FREEZE_TIMEOUT); mH.sendMessageDelayed(mH.obtainMessage(H.APP_FREEZE_TIMEOUT), 5000); @@ -4612,14 +4792,14 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<NW; i++) { WindowState win = token.windows.get(i); if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing app window " + win); - mWindows.remove(win); + win.getWindowList().remove(win); int j = win.mChildWindows.size(); while (j > 0) { j--; WindowState cwin = win.mChildWindows.get(j); if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing child window " + cwin); - mWindows.remove(cwin); + cwin.getWindowList().remove(cwin); } } return NW > 0; @@ -4638,19 +4818,22 @@ public class WindowManagerService extends IWindowManager.Stub } void dumpWindowsLocked() { - for (int i=mWindows.size()-1; i>=0; i--) { - Slog.v(TAG, " #" + i + ": " + mWindows.get(i)); + int i = 0; + final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); + Slog.v(TAG, " #" + i++ + ": " + w); } } - private int findWindowOffsetLocked(int tokenPos) { - final int NW = mWindows.size(); + private int findWindowOffsetLocked(WindowList windows, int tokenPos) { + final int NW = windows.size(); if (tokenPos >= mAnimatingAppTokens.size()) { int i = NW; while (i > 0) { i--; - WindowState win = mWindows.get(i); + WindowState win = windows.get(i); if (win.getAppToken() != null) { return i+1; } @@ -4660,7 +4843,7 @@ public class WindowManagerService extends IWindowManager.Stub while (tokenPos > 0) { // Find the first app token below the new position that has // a window displayed. - final AppWindowToken wtoken = mAnimatingAppTokens.get(tokenPos-1); + final AppWindowToken wtoken = mAppTokens.get(tokenPos-1); if (DEBUG_REORDER) Slog.v(TAG, "Looking for lower windows @ " + tokenPos + " -- " + wtoken.token); if (wtoken.sendingToBottom) { @@ -4679,7 +4862,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState cwin = win.mChildWindows.get(j); if (cwin.mSubLayer >= 0) { for (int pos=NW-1; pos>=0; pos--) { - if (mWindows.get(pos) == cwin) { + if (windows.get(pos) == cwin) { if (DEBUG_REORDER) Slog.v(TAG, "Found child win @" + (pos+1)); return pos+1; @@ -4688,7 +4871,7 @@ public class WindowManagerService extends IWindowManager.Stub } } for (int pos=NW-1; pos>=0; pos--) { - if (mWindows.get(pos) == win) { + if (windows.get(pos) == win) { if (DEBUG_REORDER) Slog.v(TAG, "Found win @" + (pos+1)); return pos+1; } @@ -4701,6 +4884,7 @@ public class WindowManagerService extends IWindowManager.Stub } private final int reAddWindowLocked(int index, WindowState win) { + final WindowList windows = win.getWindowList(); final int NCW = win.mChildWindows.size(); boolean added = false; for (int j=0; j<NCW; j++) { @@ -4709,31 +4893,35 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at " + index + ": " + cwin); win.mRebuilding = false; - mWindows.add(index, win); + windows.add(index, win); index++; added = true; } if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at " + index + ": " + cwin); cwin.mRebuilding = false; - mWindows.add(index, cwin); + windows.add(index, cwin); index++; } if (!added) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at " + index + ": " + win); win.mRebuilding = false; - mWindows.add(index, win); + windows.add(index, win); index++; } mWindowsChanged = true; return index; } - private final int reAddAppWindowsLocked(int index, WindowToken token) { + private final int reAddAppWindowsLocked(final DisplayContent displayContent, int index, + WindowToken token) { final int NW = token.windows.size(); for (int i=0; i<NW; i++) { - index = reAddWindowLocked(index, token.windows.get(i)); + final WindowState win = token.windows.get(i); + if (win.mDisplayContent == displayContent) { + index = reAddWindowLocked(index, win); + } } return index; } @@ -4779,12 +4967,20 @@ public class WindowManagerService extends IWindowManager.Stub if (tmpRemoveAppWindowsLocked(wtoken)) { if (DEBUG_REORDER) Slog.v(TAG, "Adding windows back in:"); if (DEBUG_REORDER) dumpWindowsLocked(); - reAddAppWindowsLocked(findWindowOffsetLocked(index), wtoken); + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while(iterator.hasNext()) { + final DisplayContent displayContent = iterator.next(); + final WindowList windows = displayContent.getWindowList(); + final int pos = findWindowOffsetLocked(windows, index); + final int newPos = reAddAppWindowsLocked(displayContent, pos, wtoken); + if (pos != newPos) { + displayContent.layoutNeeded = true; + } + } if (DEBUG_REORDER) Slog.v(TAG, "Final window list:"); if (DEBUG_REORDER) dumpWindowsLocked(); updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); - mLayoutNeeded = true; mInputMonitor.setUpdateInputWindowsNeededLw(); performLayoutAndPlaceSurfacesLocked(); mInputMonitor.updateInputWindowsLw(false /*force*/); @@ -4818,19 +5014,28 @@ public class WindowManagerService extends IWindowManager.Stub // First remove all of the windows from the list. tmpRemoveAppWindowsLocked(wtoken); - // Where to start adding? - int pos = findWindowOffsetLocked(tokenPos); - // And now add them back at the correct place. - pos = reAddAppWindowsLocked(pos, wtoken); + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while (iterator.hasNext()) { + final DisplayContent displayContent = iterator.next(); + final WindowList windows = displayContent.getWindowList(); + final int pos = findWindowOffsetLocked(windows, tokenPos); + final int newPos = reAddAppWindowsLocked(displayContent, pos, wtoken); + if (pos != newPos) { + displayContent.layoutNeeded = true; + } - if (updateFocusAndLayout) { - mInputMonitor.setUpdateInputWindowsNeededLw(); - if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + if (updateFocusAndLayout && !updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/)) { - assignLayersLocked(); + assignLayersLocked(windows); } - mLayoutNeeded = true; + } + + if (updateFocusAndLayout) { + mInputMonitor.setUpdateInputWindowsNeededLw(); + + // Note that the above updateFocusedWindowLocked conditional used to sit here. + if (!mInLayout) { performLayoutAndPlaceSurfacesLocked(); } @@ -4849,23 +5054,33 @@ public class WindowManagerService extends IWindowManager.Stub } } - // Where to start adding? - int pos = findWindowOffsetLocked(tokenPos); - // And now add them back at the correct place. - for (i=0; i<N; i++) { - WindowToken token = mTokenMap.get(tokens.get(i)); - if (token != null) { - pos = reAddAppWindowsLocked(pos, token); + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while (iterator.hasNext()) { + final DisplayContent displayContent = iterator.next(); + final WindowList windows = displayContent.getWindowList(); + // Where to start adding? + int pos = findWindowOffsetLocked(windows, tokenPos); + for (i=0; i<N; i++) { + WindowToken token = mTokenMap.get(tokens.get(i)); + if (token != null) { + final int newPos = reAddAppWindowsLocked(displayContent, pos, token); + if (newPos != pos) { + displayContent.layoutNeeded = true; + } + pos = newPos; + } + } + if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/)) { + assignLayersLocked(windows); } } mInputMonitor.setUpdateInputWindowsNeededLw(); - if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, - false /*updateInputWindows*/)) { - assignLayersLocked(); - } - mLayoutNeeded = true; + + // Note that the above updateFocusedWindowLocked used to sit here. + performLayoutAndPlaceSurfacesLocked(); mInputMonitor.updateInputWindowsLw(false /*force*/); @@ -4946,59 +5161,69 @@ public class WindowManagerService extends IWindowManager.Stub // Misc IWindowSession methods // ------------------------------------------------------------- - private boolean shouldAllowDisableKeyguard() - { - // We fail safe and prevent disabling keyguard in the unlikely event this gets - // called before DevicePolicyManagerService has started. - if (mAllowDisableKeyguard == ALLOW_DISABLE_UNKNOWN) { - DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( - Context.DEVICE_POLICY_SERVICE); - if (dpm != null) { - mAllowDisableKeyguard = dpm.getPasswordQuality(null) - == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED ? - ALLOW_DISABLE_YES : ALLOW_DISABLE_NO; + @Override + public void startFreezingScreen(int exitAnim, int enterAnim) { + if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN, + "startFreezingScreen()")) { + throw new SecurityException("Requires FREEZE_SCREEN permission"); + } + + synchronized(mWindowMap) { + if (!mClientFreezingScreen) { + mClientFreezingScreen = true; + final long origId = Binder.clearCallingIdentity(); + try { + startFreezingDisplayLocked(false, exitAnim, enterAnim); + mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT); + mH.sendMessageDelayed(mH.obtainMessage(H.CLIENT_FREEZE_TIMEOUT), + 5000); + } finally { + Binder.restoreCallingIdentity(origId); + } } } - return mAllowDisableKeyguard == ALLOW_DISABLE_YES; } + @Override + public void stopFreezingScreen() { + if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN, + "stopFreezingScreen()")) { + throw new SecurityException("Requires FREEZE_SCREEN permission"); + } + + synchronized(mWindowMap) { + if (mClientFreezingScreen) { + mClientFreezingScreen = false; + final long origId = Binder.clearCallingIdentity(); + try { + stopFreezingDisplayLocked(); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + } + + @Override public void disableKeyguard(IBinder token, String tag) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } - synchronized (mKeyguardTokenWatcher) { - mKeyguardTokenWatcher.acquire(token, tag); - } + mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage( + KeyguardDisableHandler.KEYGUARD_DISABLE, new Pair<IBinder, String>(token, tag))); } + @Override public void reenableKeyguard(IBinder token) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires DISABLE_KEYGUARD permission"); } - synchronized (mKeyguardTokenWatcher) { - mKeyguardTokenWatcher.release(token); - - if (!mKeyguardTokenWatcher.isAcquired()) { - // If we are the last one to reenable the keyguard wait until - // we have actually finished reenabling until returning. - // It is possible that reenableKeyguard() can be called before - // the previous disableKeyguard() is handled, in which case - // neither mKeyguardTokenWatcher.acquired() or released() would - // be called. In that case mKeyguardDisabled will be false here - // and we have nothing to wait for. - while (mKeyguardDisabled) { - try { - mKeyguardTokenWatcher.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - } + mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage( + KeyguardDisableHandler.KEYGUARD_REENABLE, token)); } /** @@ -5044,8 +5269,9 @@ public class WindowManagerService extends IWindowManager.Stub public void closeSystemDialogs(String reason) { synchronized(mWindowMap) { - for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = mWindows.get(i); + final AllWindowsIterator iterator = new AllWindowsIterator(); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); if (w.mHasSurface) { try { w.mClient.closeSystemDialogs(reason); @@ -5095,7 +5321,7 @@ public class WindowManagerService extends IWindowManager.Stub mTransitionAnimationScale = fixScale(scales[1]); } if (scales.length >= 3) { - mAnimatorDurationScale = fixScale(scales[2]); + setAnimatorDurationScale(fixScale(scales[2])); } } @@ -5103,6 +5329,11 @@ public class WindowManagerService extends IWindowManager.Stub mH.obtainMessage(H.PERSIST_ANIMATION_SCALE).sendToTarget(); } + private void setAnimatorDurationScale(float scale) { + mAnimatorDurationScale = scale; + ValueAnimator.setDurationScale(scale); + } + public float getAnimationScale(int which) { switch (which) { case 0: return mWindowAnimationScale; @@ -5148,17 +5379,20 @@ public class WindowManagerService extends IWindowManager.Stub // Called by window manager policy. Not exposed externally. @Override - public void shutdown() { - ShutdownThread.shutdown(mContext, true); + public void shutdown(boolean confirm) { + ShutdownThread.shutdown(mContext, confirm); } // Called by window manager policy. Not exposed externally. @Override - public void rebootSafeMode() { - ShutdownThread.rebootSafeMode(mContext, true); + public void rebootSafeMode(boolean confirm) { + ShutdownThread.rebootSafeMode(mContext, confirm); } - public void setInputFilter(InputFilter filter) { + public void setInputFilter(IInputFilter filter) { + if (!checkCallingPermission(android.Manifest.permission.FILTER_EVENTS, "setInputFilter()")) { + throw new SecurityException("Requires FILTER_EVENTS permission"); + } mInputManager.setInputFilter(filter); } @@ -5247,9 +5481,11 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_enableWallpaperService) && !mOnlyCore; boolean haveKeyguard = true; - final int N = mWindows.size(); + // TODO(multidisplay): Expand to all displays? + final WindowList windows = getDefaultWindowListLocked(); + final int N = windows.size(); for (int i=0; i<N; i++) { - WindowState w = mWindows.get(i); + WindowState w = windows.get(i); if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD) { // Only if there is a keyguard attached to the window manager // will we consider ourselves as having a keyguard. If it @@ -5401,8 +5637,9 @@ public class WindowManagerService extends IWindowManager.Stub // the background..) if (on) { boolean isVisible = false; - for (int i = mWindows.size() - 1; i >= 0; i--) { - final WindowState ws = mWindows.get(i); + final AllWindowsIterator iterator = new AllWindowsIterator(); + while (iterator.hasNext()) { + final WindowState ws = iterator.next(); if (ws.mSession.mPid == pid && ws.isVisibleLw()) { isVisible = true; break; @@ -5417,8 +5654,10 @@ public class WindowManagerService extends IWindowManager.Stub ">>> OPEN TRANSACTION showStrictModeViolation"); Surface.openTransaction(); try { + // TODO(multi-display): support multiple displays if (mStrictModeFlash == null) { - mStrictModeFlash = new StrictModeFlash(mDisplay, mFxSession); + mStrictModeFlash = new StrictModeFlash( + getDefaultDisplayContentLocked().getDisplay(), mFxSession); } mStrictModeFlash.setVisibility(on); } finally { @@ -5438,10 +5677,11 @@ public class WindowManagerService extends IWindowManager.Stub * In portrait mode, it grabs the upper region of the screen based on the vertical dimension * of the target image. * + * @param displayId the Display to take a screenshot of. * @param width the width of the target bitmap * @param height the height of the target bitmap */ - public Bitmap screenshotApplications(IBinder appToken, int width, int height) { + public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height) { if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, "screenshotApplications()")) { throw new SecurityException("Requires READ_FRAME_BUFFER permission"); @@ -5459,8 +5699,10 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { long ident = Binder.clearCallingIdentity(); - dw = mCurDisplayWidth; - dh = mCurDisplayHeight; + final DisplayContent displayContent = getDisplayContentLocked(displayId); + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + dw = displayInfo.logicalWidth; + dh = displayInfo.logicalHeight; int aboveAppLayer = mPolicy.windowTypeToLayerLw( WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER @@ -5474,8 +5716,9 @@ public class WindowManagerService extends IWindowManager.Stub // Figure out the part of the screen that is actually the app. boolean including = false; - for (int i=mWindows.size()-1; i>=0; i--) { - WindowState ws = mWindows.get(i); + final WindowList windows = displayContent.getWindowList(); + for (int i = windows.size() - 1; i >= 0; i--) { + WindowState ws = windows.get(i); if (!ws.mHasSurface) { continue; } @@ -5527,7 +5770,7 @@ public class WindowManagerService extends IWindowManager.Stub } // The screenshot API does not apply the current screen rotation. - rot = mDisplay.getRotation(); + rot = getDefaultDisplayContentLocked().getDisplay().getRotation(); int fw = frame.width(); int fh = frame.height(); @@ -5562,10 +5805,11 @@ public class WindowManagerService extends IWindowManager.Stub } if (DEBUG_SCREENSHOT) { Slog.i(TAG, "Screenshot: " + dw + "x" + dh + " from 0 to " + maxLayer); - for (int i=0; i<mWindows.size(); i++) { - Slog.i(TAG, mWindows.get(i) + ": " + mWindows.get(i).mLayer - + " animLayer=" + mWindows.get(i).mWinAnimator.mAnimLayer - + " surfaceLayer=" + mWindows.get(i).mWinAnimator.mSurfaceLayer); + for (int i = 0; i < windows.size(); i++) { + WindowState win = windows.get(i); + Slog.i(TAG, win + ": " + win.mLayer + + " animLayer=" + win.mWinAnimator.mAnimLayer + + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer); } } rawss = Surface.screenshot(dw, dh, 0, maxLayer); @@ -5675,7 +5919,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { changed = updateRotationUncheckedLocked(false); if (!changed || forceRelayout) { - mLayoutNeeded = true; + getDefaultDisplayContentLocked().layoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); } } @@ -5687,6 +5931,7 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } + // TODO(multidisplay): Rotate any display? /** * Updates the current rotation. * @@ -5701,8 +5946,9 @@ public class WindowManagerService extends IWindowManager.Stub return false; } - if (mAnimator.mScreenRotationAnimation != null && - mAnimator.mScreenRotationAnimation.isAnimating()) { + ScreenRotationAnimation screenRotationAnimation = + mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY); + if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { // Rotation updates cannot be performed while the previous rotation change // animation is still in progress. Skip this update. We will try updating // again after the animation is finished and the display is unfrozen. @@ -5752,60 +5998,70 @@ public class WindowManagerService extends IWindowManager.Stub mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); mH.sendMessageDelayed(mH.obtainMessage(H.WINDOW_FREEZE_TIMEOUT), 2000); mWaitingForConfig = true; - mLayoutNeeded = true; - startFreezingDisplayLocked(inTransaction); - mInputManager.setDisplayOrientation(0, rotation, - mDisplay != null ? mDisplay.getExternalRotation() : Surface.ROTATION_0); + getDefaultDisplayContentLocked().layoutNeeded = true; + startFreezingDisplayLocked(inTransaction, 0, 0); + // startFreezingDisplayLocked can reset the ScreenRotationAnimation. + screenRotationAnimation = + mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY); // We need to update our screen size information to match the new // rotation. Note that this is redundant with the later call to // sendNewConfiguration() that must be called after this function // returns... however we need to do the screen size part of that - // before then so we have the correct size to use when initializiation + // before then so we have the correct size to use when initializing // the rotation animation for the new rotation. computeScreenConfigurationLocked(null); + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); if (!inTransaction) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, - ">>> OPEN TRANSACTION setRotationUnchecked"); + if (SHOW_TRANSACTIONS) { + Slog.i(TAG, ">>> OPEN TRANSACTION setRotationUnchecked"); + } Surface.openTransaction(); } try { // NOTE: We disable the rotation in the emulator because // it doesn't support hardware OpenGL emulation yet. - if (CUSTOM_SCREEN_ROTATION && mAnimator.mScreenRotationAnimation != null - && mAnimator.mScreenRotationAnimation.hasScreenshot()) { - if (mAnimator.mScreenRotationAnimation.setRotation(rotation, mFxSession, + if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null + && screenRotationAnimation.hasScreenshot()) { + if (screenRotationAnimation.setRotationInTransaction( + rotation, mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, - mCurDisplayWidth, mCurDisplayHeight)) { - scheduleAnimationLocked(); + displayInfo.logicalWidth, displayInfo.logicalHeight)) { + updateLayoutToAnimationLocked(); } } - Surface.setOrientation(0, rotation); + + mDisplayManagerService.performTraversalInTransactionFromWindowManager(); } finally { if (!inTransaction) { Surface.closeTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION setRotationUnchecked"); + if (SHOW_LIGHT_TRANSACTIONS) { + Slog.i(TAG, "<<< CLOSE TRANSACTION setRotationUnchecked"); + } } } - rebuildBlackFrame(); - - for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = mWindows.get(i); + final WindowList windows = displayContent.getWindowList(); + for (int i = windows.size() - 1; i >= 0; i--) { + WindowState w = windows.get(i); if (w.mHasSurface) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Set mOrientationChanging of " + w); w.mOrientationChanging = true; mInnerFields.mOrientationChangeComplete = false; } } + for (int i=mRotationWatchers.size()-1; i>=0; i--) { try { mRotationWatchers.get(i).onRotationChanged(rotation); } catch (RemoteException e) { } } + + scheduleNotifyRotationChangedIfNeededLocked(displayContent, rotation); + return true; } @@ -5864,7 +6120,9 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { final int rotation = getRotation(); - if (mInitialDisplayWidth < mInitialDisplayHeight) { + // TODO(multidisplay): Assume that such devices physical keys are on the main screen. + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + if (displayContent.mInitialDisplayWidth < displayContent.mInitialDisplayHeight) { // On devices with a natural orientation of portrait switch (rotation) { default: @@ -5875,7 +6133,7 @@ public class WindowManagerService extends IWindowManager.Stub case Surface.ROTATION_180: return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; case Surface.ROTATION_270: - return Gravity.LEFT | Gravity.BOTTOM; + return Gravity.START | Gravity.BOTTOM; } } else { // On devices with a natural orientation of landscape @@ -5886,7 +6144,7 @@ public class WindowManagerService extends IWindowManager.Stub case Surface.ROTATION_90: return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; case Surface.ROTATION_180: - return Gravity.LEFT | Gravity.BOTTOM; + return Gravity.START | Gravity.BOTTOM; case Surface.ROTATION_270: return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; } @@ -6000,10 +6258,13 @@ public class WindowManagerService extends IWindowManager.Stub boolean result = true; - WindowState[] windows; + WindowList windows = new WindowList(); synchronized (mWindowMap) { //noinspection unchecked - windows = mWindows.toArray(new WindowState[mWindows.size()]); + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while(iterator.hasNext()) { + windows.addAll(iterator.next().getWindowList()); + } } BufferedWriter out = null; @@ -6013,9 +6274,9 @@ public class WindowManagerService extends IWindowManager.Stub OutputStream clientStream = client.getOutputStream(); out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024); - final int count = windows.length; + final int count = windows.size(); for (int i = 0; i < count; i++) { - final WindowState w = windows[i]; + final WindowState w = windows.get(i); out.write(Integer.toHexString(System.identityHashCode(w))); out.write(' '); out.append(w.mAttrs.getTitle()); @@ -6039,6 +6300,7 @@ public class WindowManagerService extends IWindowManager.Stub return result; } + // TODO(multidisplay): Extend to multiple displays. /** * Returns the focused window in the following format: * windowHashCodeInHexadecimal windowName @@ -6180,6 +6442,110 @@ public class WindowManagerService extends IWindowManager.Stub return success; } + public void addDisplayContentChangeListener(int displayId, + IDisplayContentChangeListener listener) { + if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, + "addDisplayContentChangeListener()")) { + throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission"); + } + synchronized(mWindowMap) { + DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent.mDisplayContentChangeListeners == null) { + displayContent.mDisplayContentChangeListeners = + new RemoteCallbackList<IDisplayContentChangeListener>(); + displayContent.mDisplayContentChangeListeners.register(listener); + } + } + } + + public void removeDisplayContentChangeListener(int displayId, + IDisplayContentChangeListener listener) { + if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, + "removeDisplayContentChangeListener()")) { + throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission"); + } + synchronized(mWindowMap) { + DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent.mDisplayContentChangeListeners != null) { + displayContent.mDisplayContentChangeListeners.unregister(listener); + if (displayContent.mDisplayContentChangeListeners + .getRegisteredCallbackCount() == 0) { + displayContent.mDisplayContentChangeListeners = null; + } + } + } + } + + void scheduleNotifyWindowTranstionIfNeededLocked(WindowState window, int transition) { + DisplayContent displayContent = window.mDisplayContent; + if (displayContent.mDisplayContentChangeListeners != null) { + WindowInfo info = getWindowInfoForWindowStateLocked(window); + mH.obtainMessage(H.NOTIFY_WINDOW_TRANSITION, transition, 0, info).sendToTarget(); + } + } + + private void handleNotifyWindowTranstion(int transition, WindowInfo info) { + RemoteCallbackList<IDisplayContentChangeListener> callbacks = null; + synchronized (mWindowMap) { + DisplayContent displayContent = getDisplayContentLocked(info.displayId); + if (displayContent == null) { + return; + } + callbacks = displayContent.mDisplayContentChangeListeners; + if (callbacks == null) { + return; + } + } + final int callbackCount = callbacks.beginBroadcast(); + try { + for (int i = 0; i < callbackCount; i++) { + try { + callbacks.getBroadcastItem(i).onWindowTransition(info.displayId, + transition, info); + } catch (RemoteException re) { + /* ignore */ + } + } + } finally { + callbacks.finishBroadcast(); + } + } + + private void scheduleNotifyRotationChangedIfNeededLocked(DisplayContent displayContent, + int rotation) { + if (displayContent.mDisplayContentChangeListeners != null + && displayContent.mDisplayContentChangeListeners.getRegisteredCallbackCount() > 0) { + mH.obtainMessage(H.NOTIFY_ROTATION_CHANGED, displayContent.getDisplayId(), + rotation).sendToTarget(); + } + } + + private void handleNotifyRotationChanged(int displayId, int rotation) { + RemoteCallbackList<IDisplayContentChangeListener> callbacks = null; + synchronized (mWindowMap) { + DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent == null) { + return; + } + callbacks = displayContent.mDisplayContentChangeListeners; + if (callbacks == null) { + return; + } + } + try { + final int watcherCount = callbacks.beginBroadcast(); + for (int i = 0; i < watcherCount; i++) { + try { + callbacks.getBroadcastItem(i).onRotationChanged(rotation); + } catch (RemoteException re) { + /* ignore */ + } + } + } finally { + callbacks.finishBroadcast(); + } + } + public void addWindowChangeListener(WindowChangeListener listener) { synchronized(mWindowMap) { mWindowChangeListeners.add(listener); @@ -6224,15 +6590,14 @@ public class WindowManagerService extends IWindowManager.Stub private WindowState findWindow(int hashCode) { if (hashCode == -1) { + // TODO(multidisplay): Extend to multiple displays. return getFocusedWindow(); } synchronized (mWindowMap) { - final ArrayList<WindowState> windows = mWindows; - final int count = windows.size(); - - for (int i = 0; i < count; i++) { - WindowState w = windows.get(i); + final AllWindowsIterator iterator = new AllWindowsIterator(); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); if (System.identityHashCode(w) == hashCode) { return w; } @@ -6274,25 +6639,27 @@ public class WindowManagerService extends IWindowManager.Stub return config; } - private void adjustDisplaySizeRanges(int rotation, int dw, int dh) { + private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) { + // TODO: Multidisplay: for now only use with default display. final int width = mPolicy.getConfigDisplayWidth(dw, dh, rotation); - if (width < mSmallestDisplayWidth) { - mSmallestDisplayWidth = width; + if (width < displayInfo.smallestNominalAppWidth) { + displayInfo.smallestNominalAppWidth = width; } - if (width > mLargestDisplayWidth) { - mLargestDisplayWidth = width; + if (width > displayInfo.largestNominalAppWidth) { + displayInfo.largestNominalAppWidth = width; } final int height = mPolicy.getConfigDisplayHeight(dw, dh, rotation); - if (height < mSmallestDisplayHeight) { - mSmallestDisplayHeight = height; + if (height < displayInfo.smallestNominalAppHeight) { + displayInfo.smallestNominalAppHeight = height; } - if (height > mLargestDisplayHeight) { - mLargestDisplayHeight = height; + if (height > displayInfo.largestNominalAppHeight) { + displayInfo.largestNominalAppHeight = height; } } private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh) { + // TODO: Multidisplay: for now only use with default display. // Get the app screen size at this rotation. int w = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation); int h = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation); @@ -6370,8 +6737,10 @@ public class WindowManagerService extends IWindowManager.Stub return curLayout; } - private void computeSizeRangesAndScreenLayout(boolean rotated, int dw, int dh, - float density, Configuration outConfig) { + private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated, + int dw, int dh, float density, Configuration outConfig) { + // TODO: Multidisplay: for now only use with default display. + // We need to determine the smallest width that will occur under normal // operation. To this, start with the base screen size and compute the // width under the different possible rotations. We need to un-rotate @@ -6384,26 +6753,28 @@ public class WindowManagerService extends IWindowManager.Stub unrotDw = dw; unrotDh = dh; } - mSmallestDisplayWidth = 1<<30; - mSmallestDisplayHeight = 1<<30; - mLargestDisplayWidth = 0; - mLargestDisplayHeight = 0; - adjustDisplaySizeRanges(Surface.ROTATION_0, unrotDw, unrotDh); - adjustDisplaySizeRanges(Surface.ROTATION_90, unrotDh, unrotDw); - adjustDisplaySizeRanges(Surface.ROTATION_180, unrotDw, unrotDh); - adjustDisplaySizeRanges(Surface.ROTATION_270, unrotDh, unrotDw); + displayInfo.smallestNominalAppWidth = 1<<30; + displayInfo.smallestNominalAppHeight = 1<<30; + displayInfo.largestNominalAppWidth = 0; + displayInfo.largestNominalAppHeight = 0; + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh); + adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw); int sl = Configuration.SCREENLAYOUT_SIZE_XLARGE | Configuration.SCREENLAYOUT_LONG_YES; sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh); sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw); sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh); sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw); - outConfig.smallestScreenWidthDp = (int)(mSmallestDisplayWidth / density); - outConfig.screenLayout = sl; + outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density); + outConfig.screenLayout = + sl|(outConfig.screenLayout&Configuration.SCREENLAYOUT_LAYOUTDIR_MASK); } private int reduceCompatConfigWidthSize(int curSize, int rotation, DisplayMetrics dm, int dw, int dh) { + // TODO: Multidisplay: for now only use with default display. dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation); dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation); float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); @@ -6415,9 +6786,10 @@ public class WindowManagerService extends IWindowManager.Stub } private int computeCompatSmallestWidth(boolean rotated, DisplayMetrics dm, int dw, int dh) { + // TODO: Multidisplay: for now only use with default display. mTmpDisplayMetrics.setTo(dm); - dm = mTmpDisplayMetrics; - int unrotDw, unrotDh; + final DisplayMetrics tmpDm = mTmpDisplayMetrics; + final int unrotDw, unrotDh; if (rotated) { unrotDw = dh; unrotDh = dw; @@ -6425,79 +6797,75 @@ public class WindowManagerService extends IWindowManager.Stub unrotDw = dw; unrotDh = dh; } - int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, dm, unrotDw, unrotDh); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, dm, unrotDh, unrotDw); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, dm, unrotDw, unrotDh); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, dm, unrotDh, unrotDw); + int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, tmpDm, unrotDw, unrotDh); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, tmpDm, unrotDh, unrotDw); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, tmpDm, unrotDw, unrotDh); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, tmpDm, unrotDh, unrotDw); return sw; } boolean computeScreenConfigurationLocked(Configuration config) { - if (mDisplay == null) { + if (!mDisplayReady) { return false; } + // TODO(multidisplay): For now, apply Configuration to main screen only. + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + // Use the effective "visual" dimensions based on current rotation final boolean rotated = (mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270); - final int realdw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; - final int realdh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight; - - synchronized(mDisplaySizeLock) { - if (mAltOrientation) { - mCurDisplayWidth = realdw; - mCurDisplayHeight = realdh; - if (realdw > realdh) { - // Turn landscape into portrait. - int maxw = (int)(realdh/1.3f); - if (maxw < realdw) { - mCurDisplayWidth = maxw; - } - } else { - // Turn portrait into landscape. - int maxh = (int)(realdw/1.3f); - if (maxh < realdh) { - mCurDisplayHeight = maxh; - } + final int realdw = rotated ? + displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth; + final int realdh = rotated ? + displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight; + int dw = realdw; + int dh = realdh; + + if (mAltOrientation) { + if (realdw > realdh) { + // Turn landscape into portrait. + int maxw = (int)(realdh/1.3f); + if (maxw < realdw) { + dw = maxw; } } else { - mCurDisplayWidth = realdw; - mCurDisplayHeight = realdh; + // Turn portrait into landscape. + int maxh = (int)(realdw/1.3f); + if (maxh < realdh) { + dh = maxh; + } } } - final int dw = mCurDisplayWidth; - final int dh = mCurDisplayHeight; - if (config != null) { - int orientation = Configuration.ORIENTATION_SQUARE; - if (dw < dh) { - orientation = Configuration.ORIENTATION_PORTRAIT; - } else if (dw > dh) { - orientation = Configuration.ORIENTATION_LANDSCAPE; - } - config.orientation = orientation; + config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT : + Configuration.ORIENTATION_LANDSCAPE; } - // Update real display metrics. - mDisplay.getMetricsWithSize(mRealDisplayMetrics, mCurDisplayWidth, mCurDisplayHeight); - // Update application display metrics. - final DisplayMetrics dm = mDisplayMetrics; final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation); final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation); - synchronized(mDisplaySizeLock) { - mAppDisplayWidth = appWidth; - mAppDisplayHeight = appHeight; - mAnimator.setDisplayDimensions(mCurDisplayWidth, mCurDisplayHeight, - mAppDisplayWidth, mAppDisplayHeight); + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + synchronized(displayContent.mDisplaySizeLock) { + displayInfo.rotation = mRotation; + displayInfo.logicalWidth = dw; + displayInfo.logicalHeight = dh; + displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity; + displayInfo.appWidth = appWidth; + displayInfo.appHeight = appHeight; + displayInfo.getLogicalMetrics(mRealDisplayMetrics, null); + displayInfo.getAppMetrics(mDisplayMetrics, null); + mDisplayManagerService.setDisplayInfoOverrideFromWindowManager( + displayContent.getDisplayId(), displayInfo); + + mAnimator.setDisplayDimensions(dw, dh, appWidth, appHeight); } if (false) { - Slog.i(TAG, "Set app display size: " + mAppDisplayWidth - + " x " + mAppDisplayHeight); + Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight); } - mDisplay.getMetricsWithSize(dm, mAppDisplayWidth, mAppDisplayHeight); + final DisplayMetrics dm = mDisplayMetrics; mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm, mCompatDisplayMetrics); @@ -6506,11 +6874,12 @@ public class WindowManagerService extends IWindowManager.Stub / dm.density); config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) / dm.density); - computeSizeRangesAndScreenLayout(rotated, dw, dh, dm.density, config); + computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, dm.density, config); config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh); + config.densityDpi = displayContent.mBaseDisplayDensity; // Update the configuration based on available input devices, lid switch, // and platform configuration. @@ -6638,8 +7007,12 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { try { if (mDragState == null) { - Surface surface = new Surface(session, callerPid, "drag surface", 0, + // TODO(multi-display): support other displays + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final Display display = displayContent.getDisplay(); + Surface surface = new Surface(session, "drag surface", width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN); + surface.setLayerStack(display.getLayerStack()); if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG " + surface + ": CREATE"); outSurface.copyFrom(surface); @@ -6720,8 +7093,11 @@ public class WindowManagerService extends IWindowManager.Stub } } - // TODO: Put this on the IWindowManagerService and guard with a permission. - public IBinder getFocusedWindowClientToken() { + public IBinder getFocusedWindowToken() { + if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO, + "getFocusedWindowToken()")) { + throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission."); + } synchronized (mWindowMap) { WindowState windowState = getFocusedWindowLocked(); if (windowState != null) { @@ -6731,18 +7107,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - // TODO: This is a workaround - remove when 6623031 is fixed. - public boolean getWindowFrame(IBinder token, Rect outBounds) { - synchronized (mWindowMap) { - WindowState windowState = mWindowMap.get(token); - if (windowState != null) { - outBounds.set(windowState.getFrameLw()); - return true; - } - } - return false; - } - private WindowState getFocusedWindow() { synchronized (mWindowMap) { return getFocusedWindowLocked(); @@ -6790,45 +7154,53 @@ public class WindowManagerService extends IWindowManager.Stub } public void displayReady() { + displayReady(Display.DEFAULT_DISPLAY); + synchronized(mWindowMap) { - if (mDisplay != null) { - throw new IllegalStateException("Display already initialized"); - } - WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); - mDisplay = wm.getDefaultDisplay(); + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final Display display = displayContent.getDisplay(); + readForcedDisplaySizeAndDensityLocked(displayContent); + + mDisplayReady = true; mIsTouchDevice = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN); - synchronized(mDisplaySizeLock) { - mInitialDisplayWidth = mDisplay.getRawWidth(); - mInitialDisplayHeight = mDisplay.getRawHeight(); - int rot = mDisplay.getRotation(); - if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { - // If the screen is currently rotated, we need to swap the - // initial width and height to get the true natural values. - int tmp = mInitialDisplayWidth; - mInitialDisplayWidth = mInitialDisplayHeight; - mInitialDisplayHeight = tmp; - } - mBaseDisplayWidth = mCurDisplayWidth = mAppDisplayWidth = mInitialDisplayWidth; - mBaseDisplayHeight = mCurDisplayHeight = mAppDisplayHeight = mInitialDisplayHeight; - mAnimator.setDisplayDimensions(mCurDisplayWidth, mCurDisplayHeight, - mAppDisplayWidth, mAppDisplayHeight); - } - mInputManager.setDisplaySize(Display.DEFAULT_DISPLAY, - mDisplay.getRawWidth(), mDisplay.getRawHeight(), - mDisplay.getRawExternalWidth(), mDisplay.getRawExternalHeight()); - mInputManager.setDisplayOrientation(Display.DEFAULT_DISPLAY, - mDisplay.getRotation(), mDisplay.getExternalRotation()); - mPolicy.setInitialDisplaySize(mDisplay, mInitialDisplayWidth, mInitialDisplayHeight); + + final DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); + mAnimator.setDisplayDimensions( + displayInfo.logicalWidth, displayInfo.logicalHeight, + displayInfo.appWidth, displayInfo.appHeight); + + mPolicy.setInitialDisplaySize(displayContent.getDisplay(), + displayContent.mInitialDisplayWidth, + displayContent.mInitialDisplayHeight, + displayContent.mInitialDisplayDensity); } try { mActivityManager.updateConfiguration(null); } catch (RemoteException e) { } - - synchronized (mWindowMap) { - readForcedDisplaySizeLocked(); + } + + public void displayReady(int displayId) { + synchronized(mWindowMap) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + final DisplayInfo displayInfo; + mAnimator.addDisplayLocked(displayId); + synchronized(displayContent.mDisplaySizeLock) { + // Bootstrap the default logical display from the display manager. + displayInfo = displayContent.getDisplayInfo(); + DisplayInfo newDisplayInfo = mDisplayManagerService.getDisplayInfo(displayId); + if (newDisplayInfo != null) { + displayInfo.copyFrom(newDisplayInfo); + } + displayContent.mInitialDisplayWidth = displayInfo.logicalWidth; + displayContent.mInitialDisplayHeight = displayInfo.logicalHeight; + displayContent.mInitialDisplayDensity = displayInfo.logicalDensityDpi; + displayContent.mBaseDisplayWidth = displayContent.mInitialDisplayWidth; + displayContent.mBaseDisplayHeight = displayContent.mInitialDisplayHeight; + displayContent.mBaseDisplayDensity = displayContent.mInitialDisplayDensity; + } } } @@ -6836,14 +7208,13 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.systemReady(); } + // TODO(multidisplay): Call isScreenOn for each display. private void sendScreenStatusToClientsLocked() { - final ArrayList<WindowState> windows = mWindows; - final int count = windows.size(); - boolean on = mPowerManager.isScreenOn(); - for (int i = count - 1; i >= 0; i--) { - WindowState win = mWindows.get(i); + final boolean on = mPowerManager.isScreenOn(); + final AllWindowsIterator iterator = new AllWindowsIterator(); + while (iterator.hasNext()) { try { - win.mClient.dispatchScreenState(on); + iterator.next().mClient.dispatchScreenState(on); } catch (RemoteException e) { // Ignored } @@ -6864,7 +7235,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int REPORT_APPLICATION_TOKEN_WINDOWS = 8; public static final int REPORT_APPLICATION_TOKEN_DRAWN = 9; public static final int WINDOW_FREEZE_TIMEOUT = 11; - public static final int HOLD_SCREEN_CHANGED = 12; + public static final int APP_TRANSITION_TIMEOUT = 13; public static final int PERSIST_ANIMATION_SCALE = 14; public static final int FORCE_GC = 15; @@ -6877,17 +7248,22 @@ public class WindowManagerService extends IWindowManager.Stub public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22; public static final int BOOT_TIMEOUT = 23; public static final int WAITING_FOR_DRAWN_TIMEOUT = 24; - public static final int BULK_UPDATE_PARAMETERS = 25; + public static final int UPDATE_ANIM_PARAMETERS = 25; public static final int SHOW_STRICT_MODE_VIOLATION = 26; public static final int DO_ANIMATION_CALLBACK = 27; + public static final int NOTIFY_ROTATION_CHANGED = 28; + public static final int NOTIFY_WINDOW_TRANSITION = 29; + public static final int NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 30; + + public static final int DO_DISPLAY_ADDED = 31; + public static final int DO_DISPLAY_REMOVED = 32; + public static final int DO_DISPLAY_CHANGED = 33; + + public static final int CLIENT_FREEZE_TIMEOUT = 34; public static final int ANIMATOR_WHAT_OFFSET = 100000; public static final int SET_TRANSPARENT_REGION = ANIMATOR_WHAT_OFFSET + 1; - public static final int SET_WALLPAPER_OFFSET = ANIMATOR_WHAT_OFFSET + 2; - public static final int SET_DIM_PARAMETERS = ANIMATOR_WHAT_OFFSET + 3; - public static final int CLEAR_PENDING_ACTIONS = ANIMATOR_WHAT_OFFSET + 4; - - private Session mLastReportedHold; + public static final int CLEAR_PENDING_ACTIONS = ANIMATOR_WHAT_OFFSET + 2; public H() { } @@ -7122,12 +7498,14 @@ public class WindowManagerService extends IWindowManager.Stub } break; case WINDOW_FREEZE_TIMEOUT: { + // TODO(multidisplay): Can non-default displays rotate? synchronized (mWindowMap) { Slog.w(TAG, "Window freeze timeout expired."); - int i = mWindows.size(); + final WindowList windows = getDefaultWindowListLocked(); + int i = windows.size(); while (i > 0) { i--; - WindowState w = mWindows.get(i); + WindowState w = windows.get(i); if (w.mOrientationChanging) { w.mOrientationChanging = false; Slog.w(TAG, "Force clearing orientation change: " + w); @@ -7138,33 +7516,6 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case HOLD_SCREEN_CHANGED: { - Session oldHold; - Session newHold; - synchronized (mWindowMap) { - oldHold = mLastReportedHold; - newHold = (Session)msg.obj; - mLastReportedHold = newHold; - } - - if (oldHold != newHold) { - try { - if (oldHold != null) { - mBatteryStats.noteStopWakelock(oldHold.mUid, -1, - "window", - BatteryStats.WAKE_TYPE_WINDOW); - } - if (newHold != null) { - mBatteryStats.noteStartWakelock(newHold.mUid, -1, - "window", - BatteryStats.WAKE_TYPE_WINDOW); - } - } catch (RemoteException e) { - } - } - break; - } - case APP_TRANSITION_TIMEOUT: { synchronized (mWindowMap) { if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { @@ -7191,18 +7542,22 @@ public class WindowManagerService extends IWindowManager.Stub } case FORCE_GC: { - synchronized(mWindowMap) { - if (mAnimationScheduled) { - // If we are animating, don't do the gc now but - // delay a bit so we don't interrupt the animation. - mH.sendMessageDelayed(mH.obtainMessage(H.FORCE_GC), - 2000); - return; - } - // If we are currently rotating the display, it will - // schedule a new message when done. - if (mDisplayFrozen) { - return; + synchronized (mWindowMap) { + synchronized (mAnimator) { + // Since we're holding both mWindowMap and mAnimator we don't need to + // hold mAnimator.mLayoutToAnim. + if (mAnimator.mAnimating || mLayoutToAnim.mAnimationScheduled) { + // If we are animating, don't do the gc now but + // delay a bit so we don't interrupt the animation. + mH.sendMessageDelayed(mH.obtainMessage(H.FORCE_GC), + 2000); + return; + } + // If we are currently rotating the display, it will + // schedule a new message when done. + if (mDisplayFrozen) { + return; + } } } Runtime.getRuntime().gc(); @@ -7232,6 +7587,16 @@ public class WindowManagerService extends IWindowManager.Stub break; } + case CLIENT_FREEZE_TIMEOUT: { + synchronized (mWindowMap) { + if (mClientFreezingScreen) { + mClientFreezingScreen = false; + stopFreezingDisplayLocked(); + } + } + break; + } + case SEND_NEW_CONFIGURATION: { removeMessages(SEND_NEW_CONFIGURATION); sendNewConfiguration(); @@ -7306,42 +7671,10 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case BULK_UPDATE_PARAMETERS: { + case UPDATE_ANIM_PARAMETERS: { // Used to send multiple changes from the animation side to the layout side. synchronized (mWindowMap) { - boolean doRequest = false; - // TODO(cmautner): As the number of bits grows, use masks of bit groups to - // eliminate unnecessary tests. - if ((msg.arg1 & LayoutFields.SET_UPDATE_ROTATION) != 0) { - mInnerFields.mUpdateRotation = true; - doRequest = true; - } - if ((msg.arg1 & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) { - mInnerFields.mWallpaperMayChange = true; - doRequest = true; - } - if ((msg.arg1 & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) { - mInnerFields.mWallpaperForceHidingChanged = true; - doRequest = true; - } - if ((msg.arg1 & LayoutFields.CLEAR_ORIENTATION_CHANGE_COMPLETE) != 0) { - mInnerFields.mOrientationChangeComplete = false; - } else { - mInnerFields.mOrientationChangeComplete = true; - if (mWindowsFreezingScreen) { - doRequest = true; - } - } - if ((msg.arg1 & LayoutFields.SET_TURN_ON_SCREEN) != 0) { - mTurnOnScreen = true; - } - - mPendingLayoutChanges |= msg.arg2; - if (mPendingLayoutChanges != 0) { - doRequest = true; - } - - if (doRequest) { + if (copyAnimToLayoutParamsLocked()) { mH.sendEmptyMessage(CLEAR_PENDING_ACTIONS); performLayoutAndPlaceSurfacesLocked(); } @@ -7363,33 +7696,58 @@ public class WindowManagerService extends IWindowManager.Stub break; } - case SET_WALLPAPER_OFFSET: { - final WindowStateAnimator winAnimator = (WindowStateAnimator) msg.obj; - winAnimator.setWallpaperOffset(msg.arg1, msg.arg2); + case CLEAR_PENDING_ACTIONS: { + mAnimator.clearPendingActions(); + break; + } - scheduleAnimationLocked(); + case DO_ANIMATION_CALLBACK: { + try { + ((IRemoteCallback)msg.obj).sendResult(null); + } catch (RemoteException e) { + } break; } - case SET_DIM_PARAMETERS: { - mAnimator.mDimParams = (DimAnimator.Parameters) msg.obj; + case NOTIFY_ROTATION_CHANGED: { + final int displayId = msg.arg1; + final int rotation = msg.arg2; + handleNotifyRotationChanged(displayId, rotation); + break; + } - scheduleAnimationLocked(); + case NOTIFY_WINDOW_TRANSITION: { + final int transition = msg.arg1; + WindowInfo info = (WindowInfo) msg.obj; + handleNotifyWindowTranstion(transition, info); break; } - case CLEAR_PENDING_ACTIONS: { - mAnimator.clearPendingActions(); + case NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: { + final int displayId = msg.arg1; + final boolean immediate = (msg.arg2 == 1); + Rect rectangle = (Rect) msg.obj; + handleNotifyRectangleOnScreenRequested(displayId, rectangle, immediate); break; } - case DO_ANIMATION_CALLBACK: { - try { - ((IRemoteCallback)msg.obj).sendResult(null); - } catch (RemoteException e) { + case DO_DISPLAY_ADDED: + synchronized (mWindowMap) { + handleDisplayAddedLocked(msg.arg1); + } + break; + + case DO_DISPLAY_REMOVED: + synchronized (mWindowMap) { + handleDisplayRemovedLocked(msg.arg1); + } + break; + + case DO_DISPLAY_CHANGED: + synchronized (mWindowMap) { + handleDisplayChangedLocked(msg.arg1); } break; - } } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG, "handleMessage: exit"); @@ -7416,13 +7774,13 @@ public class WindowManagerService extends IWindowManager.Stub // The focus for the client is the window immediately below // where we would place the input method window. int idx = findDesiredInputMethodWindowIndexLocked(false); - WindowState imFocus; if (idx > 0) { - imFocus = mWindows.get(idx-1); + // TODO(multidisplay): IMEs are only supported on the default display. + WindowState imFocus = getDefaultWindowListLocked().get(idx-1); if (DEBUG_INPUT_METHOD) { Slog.i(TAG, "Desired input method target: " + imFocus); - Slog.i(TAG, "Current focus: " + this.mCurrentFocus); - Slog.i(TAG, "Last focus: " + this.mLastFocus); + Slog.i(TAG, "Current focus: " + mCurrentFocus); + Slog.i(TAG, "Last focus: " + mLastFocus); } if (imFocus != null) { // This may be a starting window, in which case we still want @@ -7452,143 +7810,148 @@ public class WindowManagerService extends IWindowManager.Stub imFocus.mSession.mClient.asBinder() == client.asBinder()) { return true; } - - // Okay, how about this... what is the current focus? - // It seems in some cases we may not have moved the IM - // target window, such as when it was in a pop-up window, - // so let's also look at the current focus. (An example: - // go to Gmail, start searching so the keyboard goes up, - // press home. Sometimes the IME won't go down.) - // Would be nice to fix this more correctly, but it's - // way at the end of a release, and this should be good enough. - if (mCurrentFocus != null && mCurrentFocus.mSession.mClient != null && - mCurrentFocus.mSession.mClient.asBinder() == client.asBinder()) { - return true; - } } } + + // Okay, how about this... what is the current focus? + // It seems in some cases we may not have moved the IM + // target window, such as when it was in a pop-up window, + // so let's also look at the current focus. (An example: + // go to Gmail, start searching so the keyboard goes up, + // press home. Sometimes the IME won't go down.) + // Would be nice to fix this more correctly, but it's + // way at the end of a release, and this should be good enough. + if (mCurrentFocus != null && mCurrentFocus.mSession.mClient != null + && mCurrentFocus.mSession.mClient.asBinder() == client.asBinder()) { + return true; + } } return false; } - public void getDisplaySize(Point size) { - synchronized(mDisplaySizeLock) { - size.x = mAppDisplayWidth; - size.y = mAppDisplayHeight; + public void getInitialDisplaySize(int displayId, Point size) { + // TODO(cmautner): Access to DisplayContent should be locked on mWindowMap. Doing that + // could lead to deadlock since this is called from ActivityManager. + final DisplayContent displayContent = getDisplayContentLocked(displayId); + synchronized(displayContent.mDisplaySizeLock) { + size.x = displayContent.mInitialDisplayWidth; + size.y = displayContent.mInitialDisplayHeight; } } - public void getRealDisplaySize(Point size) { - synchronized(mDisplaySizeLock) { - size.x = mCurDisplayWidth; - size.y = mCurDisplayHeight; + public void setForcedDisplaySize(int displayId, int width, int height) { + 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); + + 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); + } + } + + private void readForcedDisplaySizeAndDensityLocked(final DisplayContent displayContent) { + final String sizeStr = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.DISPLAY_SIZE_FORCED); + if (sizeStr != null && sizeStr.length() > 0) { + final int pos = sizeStr.indexOf(','); + if (pos > 0 && sizeStr.lastIndexOf(',') == pos) { + int width, height; + try { + width = Integer.parseInt(sizeStr.substring(0, pos)); + height = Integer.parseInt(sizeStr.substring(pos+1)); + synchronized(displayContent.mDisplaySizeLock) { + if (displayContent.mBaseDisplayWidth != width + || displayContent.mBaseDisplayHeight != height) { + Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height); + displayContent.mBaseDisplayWidth = width; + displayContent.mBaseDisplayHeight = height; + } + } + } catch (NumberFormatException ex) { + } + } } - } - - public void getInitialDisplaySize(Point size) { - synchronized(mDisplaySizeLock) { - size.x = mInitialDisplayWidth; - size.y = mInitialDisplayHeight; + final String densityStr = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.DISPLAY_DENSITY_FORCED); + if (densityStr != null && densityStr.length() > 0) { + int density; + try { + density = Integer.parseInt(densityStr); + synchronized(displayContent.mDisplaySizeLock) { + if (displayContent.mBaseDisplayDensity != density) { + Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density); + displayContent.mBaseDisplayDensity = density; + } + } + } catch (NumberFormatException ex) { + } } } - public int getMaximumSizeDimension() { - synchronized(mDisplaySizeLock) { - // Do this based on the raw screen size, until we are smarter. - return mBaseDisplayWidth > mBaseDisplayHeight - ? mBaseDisplayWidth : mBaseDisplayHeight; + private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) { + Slog.i(TAG, "Using new display size: " + width + "x" + height); + + synchronized(displayContent.mDisplaySizeLock) { + displayContent.mBaseDisplayWidth = width; + displayContent.mBaseDisplayHeight = height; } + reconfigureDisplayLocked(displayContent); } - public void getCurrentSizeRange(Point smallestSize, Point largestSize) { - synchronized(mDisplaySizeLock) { - smallestSize.x = mSmallestDisplayWidth; - smallestSize.y = mSmallestDisplayHeight; - largestSize.x = mLargestDisplayWidth; - largestSize.y = mLargestDisplayHeight; + public void clearForcedDisplaySize(int displayId) { + synchronized(mWindowMap) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + setForcedDisplaySizeLocked(displayContent, displayContent.mInitialDisplayWidth, + displayContent.mInitialDisplayHeight); + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.DISPLAY_SIZE_FORCED, ""); } } - public void setForcedDisplaySize(int longDimen, int shortDimen) { + public void setForcedDisplayDensity(int displayId, int density) { synchronized(mWindowMap) { - int width, height; - if (mInitialDisplayWidth < mInitialDisplayHeight) { - width = shortDimen < mInitialDisplayWidth - ? shortDimen : mInitialDisplayWidth; - height = longDimen < mInitialDisplayHeight - ? longDimen : mInitialDisplayHeight; - } else { - width = longDimen < mInitialDisplayWidth - ? longDimen : mInitialDisplayWidth; - height = shortDimen < mInitialDisplayHeight - ? shortDimen : mInitialDisplayHeight; - } - setForcedDisplaySizeLocked(width, height); - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DISPLAY_SIZE_FORCED, width + "," + height); + final DisplayContent displayContent = getDisplayContentLocked(displayId); + setForcedDisplayDensityLocked(displayContent, density); + Settings.Global.putString(mContext.getContentResolver(), + Settings.Global.DISPLAY_DENSITY_FORCED, Integer.toString(density)); } } - private void rebuildBlackFrame() { - if (mBlackFrame != null) { - mBlackFrame.kill(); - mBlackFrame = null; - } - if (mBaseDisplayWidth < mInitialDisplayWidth - || mBaseDisplayHeight < mInitialDisplayHeight) { - int initW, initH, baseW, baseH; - final boolean rotated = (mRotation == Surface.ROTATION_90 - || mRotation == Surface.ROTATION_270); - if (rotated) { - initW = mInitialDisplayHeight; - initH = mInitialDisplayWidth; - baseW = mBaseDisplayHeight; - baseH = mBaseDisplayWidth; - } else { - initW = mInitialDisplayWidth; - initH = mInitialDisplayHeight; - baseW = mBaseDisplayWidth; - baseH = mBaseDisplayHeight; - } - Rect outer = new Rect(0, 0, initW, initH); - Rect inner = new Rect(0, 0, baseW, baseH); - try { - mBlackFrame = new BlackFrame(mFxSession, outer, inner, MASK_LAYER); - } catch (Surface.OutOfResourcesException e) { - } + private void setForcedDisplayDensityLocked(DisplayContent displayContent, int density) { + Slog.i(TAG, "Using new display density: " + density); + + synchronized(displayContent.mDisplaySizeLock) { + displayContent.mBaseDisplayDensity = density; } + reconfigureDisplayLocked(displayContent); } - private void readForcedDisplaySizeLocked() { - final String str = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.DISPLAY_SIZE_FORCED); - if (str == null || str.length() == 0) { - return; - } - final int pos = str.indexOf(','); - if (pos <= 0 || str.lastIndexOf(',') != pos) { - return; - } - int width, height; - try { - width = Integer.parseInt(str.substring(0, pos)); - height = Integer.parseInt(str.substring(pos+1)); - } catch (NumberFormatException ex) { - return; + public void clearForcedDisplayDensity(int displayId) { + synchronized(mWindowMap) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + setForcedDisplayDensityLocked(displayContent, displayContent.mInitialDisplayDensity); + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.DISPLAY_DENSITY_FORCED, ""); } - setForcedDisplaySizeLocked(width, height); } - private void setForcedDisplaySizeLocked(int width, int height) { - Slog.i(TAG, "Using new display size: " + width + "x" + height); - - synchronized(mDisplaySizeLock) { - mBaseDisplayWidth = width; - mBaseDisplayHeight = height; - } - mPolicy.setInitialDisplaySize(mDisplay, mBaseDisplayWidth, mBaseDisplayHeight); + private void reconfigureDisplayLocked(DisplayContent displayContent) { + // TODO: Multidisplay: for now only use with default display. + mPolicy.setInitialDisplaySize(displayContent.getDisplay(), + displayContent.mBaseDisplayWidth, + displayContent.mBaseDisplayHeight, + displayContent.mBaseDisplayDensity); - mLayoutNeeded = true; + displayContent.layoutNeeded = true; boolean configChanged = updateOrientationFromAppTokensLocked(false); mTempConfiguration.setToDefaults(); @@ -7601,23 +7964,13 @@ public class WindowManagerService extends IWindowManager.Stub if (configChanged) { mWaitingForConfig = true; - startFreezingDisplayLocked(false); + startFreezingDisplayLocked(false, 0, 0); mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } - rebuildBlackFrame(); - performLayoutAndPlaceSurfacesLocked(); } - public void clearForcedDisplaySize() { - synchronized(mWindowMap) { - setForcedDisplaySizeLocked(mInitialDisplayWidth, mInitialDisplayHeight); - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DISPLAY_SIZE_FORCED, ""); - } - } - public boolean hasSystemNavBar() { return mPolicy.hasSystemNavBar(); } @@ -7660,9 +8013,17 @@ public class WindowManagerService extends IWindowManager.Stub } final void rebuildAppWindowListLocked() { - int NW = mWindows.size(); + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while (iterator.hasNext()) { + rebuildAppWindowListLocked(iterator.next()); + } + } + + private void rebuildAppWindowListLocked(final DisplayContent displayContent) { + final WindowList windows = displayContent.getWindowList(); + int NW = windows.size(); int i; - int lastWallpaper = -1; + int lastBelow = -1; int numRemoved = 0; if (mRebuildTmp.length < NW) { @@ -7672,9 +8033,9 @@ public class WindowManagerService extends IWindowManager.Stub // First remove all existing app windows. i=0; while (i < NW) { - WindowState w = mWindows.get(i); + WindowState w = windows.get(i); if (w.mAppToken != null) { - WindowState win = mWindows.remove(i); + WindowState win = windows.remove(i); win.mRebuilding = true; mRebuildTmp[numRemoved] = win; mWindowsChanged = true; @@ -7683,17 +8044,19 @@ public class WindowManagerService extends IWindowManager.Stub NW--; numRemoved++; continue; - } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER - && lastWallpaper == i-1) { - lastWallpaper = i; + } else if (lastBelow == i-1) { + if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER + || w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) { + lastBelow = i; + } } i++; } - // The wallpaper window(s) typically live at the bottom of the stack, - // so skip them before adding app tokens. - lastWallpaper++; - i = lastWallpaper; + // Keep whatever windows were below the app windows still below, + // by skipping them. + lastBelow++; + i = lastBelow; // First add all of the exiting app tokens... these are no longer // in the main app list, but still have windows shown. We put them @@ -7701,16 +8064,16 @@ public class WindowManagerService extends IWindowManager.Stub // will care about them. int NT = mExitingAppTokens.size(); for (int j=0; j<NT; j++) { - i = reAddAppWindowsLocked(i, mExitingAppTokens.get(j)); + i = reAddAppWindowsLocked(displayContent, i, mExitingAppTokens.get(j)); } // And add in the still active app tokens in Z order. NT = mAnimatingAppTokens.size(); for (int j=0; j<NT; j++) { - i = reAddAppWindowsLocked(i, mAnimatingAppTokens.get(j)); + i = reAddAppWindowsLocked(displayContent, i, mAnimatingAppTokens.get(j)); } - i -= lastWallpaper; + i -= lastBelow; if (i != numRemoved) { Slog.w(TAG, "Rebuild removed " + numRemoved + " windows but added " + i); @@ -7733,8 +8096,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - private final void assignLayersLocked() { - int N = mWindows.size(); + private final void assignLayersLocked(WindowList windows) { + int N = windows.size(); int curBaseLayer = 0; int curLayer = 0; int i; @@ -7746,7 +8109,7 @@ public class WindowManagerService extends IWindowManager.Stub } for (i=0; i<N; i++) { - final WindowState w = mWindows.get(i); + final WindowState w = windows.get(i); final WindowStateAnimator winAnimator = w.mWinAnimator; boolean layerChanged = false; int oldLayer = w.mLayer; @@ -7779,9 +8142,9 @@ public class WindowManagerService extends IWindowManager.Stub if (winAnimator.mAnimLayer != oldLayer) { layerChanged = true; } - if (layerChanged && mAnimator.isDimming(winAnimator)) { + if (layerChanged && mAnimator.isDimmingLocked(winAnimator)) { // Force an animation pass just to update the mDimAnimator layer. - scheduleAnimationLocked(); + updateLayoutToAnimationLocked(); } if (DEBUG_LAYERS) Slog.v(TAG, "Assign layer " + w + ": " + winAnimator.mAnimLayer); @@ -7808,7 +8171,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (mDisplay == null) { + if (!mDisplayReady) { // Not yet initialized, nothing to do. return; } @@ -7839,35 +8202,13 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RuntimeException e) { Log.wtf(TAG, "Unhandled exception while force removing for memory", e); } - + try { performLayoutAndPlaceSurfacesLockedInner(recoveringMemory); - final int N = mPendingRemove.size(); - if (N > 0) { - if (mPendingRemoveTmp.length < N) { - mPendingRemoveTmp = new WindowState[N+10]; - } - mPendingRemove.toArray(mPendingRemoveTmp); - mPendingRemove.clear(); - for (int i=0; i<N; i++) { - WindowState w = mPendingRemoveTmp[i]; - removeWindowInnerLocked(w.mSession, w); - } - - mInLayout = false; - assignLayersLocked(); - mLayoutNeeded = true; - // XXX this recursion seems broken! - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - performLayoutAndPlaceSurfacesLocked(); - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout"); - - } else { - mInLayout = false; - } + mInLayout = false; - if (mLayoutNeeded) { + if (needsLayout()) { if (++mLayoutRepeatCount < 6) { requestTraversalLocked(); } else { @@ -7890,32 +8231,41 @@ public class WindowManagerService extends IWindowManager.Stub Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } - private final void performLayoutLockedInner(boolean initial, boolean updateInputWindows) { - if (!mLayoutNeeded) { + private final void performLayoutLockedInner(final DisplayContent displayContent, + boolean initial, boolean updateInputWindows) { + if (!displayContent.layoutNeeded) { return; } - - mLayoutNeeded = false; - - final int dw = mCurDisplayWidth; - final int dh = mCurDisplayHeight; + displayContent.layoutNeeded = false; + WindowList windows = displayContent.getWindowList(); + boolean isDefaultDisplay = displayContent.isDefaultDisplay; + + DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; final int NFW = mFakeWindows.size(); for (int i=0; i<NFW; i++) { mFakeWindows.get(i).layout(dw, dh); } - final int N = mWindows.size(); + final int N = windows.size(); int i; if (DEBUG_LAYOUT) { Slog.v(TAG, "-------------------------------------"); Slog.v(TAG, "performLayout: needed=" - + mLayoutNeeded + " dw=" + dw + " dh=" + dh); + + displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh); + } + + WindowStateAnimator universeBackground = null; + + mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation); + if (isDefaultDisplay) { + // Not needed on non-default displays. + mSystemDecorLayer = mPolicy.getSystemDecorRectLw(mSystemDecorRect); + mScreenRect.set(0, 0, dw, dh); } - - mPolicy.beginLayoutLw(dw, dh, mRotation); - mSystemDecorLayer = mPolicy.getSystemDecorRectLw(mSystemDecorRect); int seq = mLayoutSeq+1; if (seq < 0) seq = 0; @@ -7925,7 +8275,7 @@ public class WindowManagerService extends IWindowManager.Stub // to another window). int topAttached = -1; for (i = N-1; i >= 0; i--) { - final WindowState win = mWindows.get(i); + final WindowState win = windows.get(i); // Don't do layout of a window if it is not visible, or // soon won't be visible, to avoid wasting time and funky @@ -7950,13 +8300,14 @@ public class WindowManagerService extends IWindowManager.Stub + (atoken != null && atoken.hiddenRequested) + " mAttachedHidden=" + win.mAttachedHidden); } - + // If this view is GONE, then skip it -- keep the current // frame, and let the caller know so they can ignore it // if they want. (We do the normal layout for INVISIBLE // windows, since that means "perform layout as normal, // just don't display"). - if (!gone || !win.mHaveFrame || win.mLayoutNeeded) { + if (!gone || !win.mHaveFrame || win.mLayoutNeeded + || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) { if (!win.mLayoutAttached) { if (initial) { //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial"); @@ -7974,6 +8325,16 @@ public class WindowManagerService extends IWindowManager.Stub if (topAttached < 0) topAttached = i; } } + if (win.mViewVisibility == View.VISIBLE + && win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND + && universeBackground == null) { + universeBackground = win.mWinAnimator; + } + } + + if (mAnimator.mUniverseBackground != universeBackground) { + mFocusMayChange = true; + mAnimator.mUniverseBackground = universeBackground; } // Now perform layout of attached windows, which usually @@ -7981,7 +8342,7 @@ public class WindowManagerService extends IWindowManager.Stub // XXX does not deal with windows that are attached to windows // that are themselves attached. for (i = topAttached; i >= 0; i--) { - final WindowState win = mWindows.get(i); + final WindowState win = windows.get(i); if (win.mLayoutAttached) { if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + win @@ -8042,10 +8403,10 @@ public class WindowManagerService extends IWindowManager.Stub /** * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. - * + * @param windows List of windows on default display. * @return bitmap indicating if another pass through layout must be made. */ - public int handleAppTransitionReadyLocked() { + public int handleAppTransitionReadyLocked(WindowList windows) { int changes = 0; int i; int NN = mOpeningApps.size(); @@ -8200,15 +8561,23 @@ public class WindowManagerService extends IWindowManager.Stub NN = mOpeningApps.size(); for (i=0; i<NN; i++) { AppWindowToken wtoken = mOpeningApps.get(i); + final AppWindowAnimator appAnimator = wtoken.mAppAnimator; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken); - wtoken.mAppAnimator.clearThumbnail(); + appAnimator.clearThumbnail(); wtoken.reportedVisible = false; wtoken.inPendingTransaction = false; - wtoken.mAppAnimator.animation = null; + appAnimator.animation = null; setTokenVisibilityLocked(wtoken, animLp, true, transit, false); wtoken.updateReportedVisibilityLocked(); wtoken.waitingToShow = false; - mAnimator.mAnimating |= wtoken.mAppAnimator.showAllWindowsLocked(); + + appAnimator.mAllAppWinAnimators.clear(); + final int N = wtoken.allAppWindows.size(); + for (int j = 0; j < N; j++) { + appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator); + } + mAnimator.mAnimating |= appAnimator.showAllWindowsLocked(); + if (animLp != null) { int layer = -1; for (int j=0; j<wtoken.windows.size(); j++) { @@ -8248,9 +8617,14 @@ public class WindowManagerService extends IWindowManager.Stub Rect dirty = new Rect(0, 0, mNextAppTransitionThumbnail.getWidth(), mNextAppTransitionThumbnail.getHeight()); try { - Surface surface = new Surface(mFxSession, Process.myPid(), - "thumbnail anim", 0, dirty.width(), dirty.height(), + // TODO(multi-display): support other displays + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final Display display = displayContent.getDisplay(); + Surface surface = new Surface(mFxSession, + "thumbnail anim", + dirty.width(), dirty.height(), PixelFormat.TRANSLUCENT, Surface.HIDDEN); + surface.setLayerStack(display.getLayerStack()); topOpeningApp.mAppAnimator.thumbnail = surface; if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL " + surface + ": CREATE"); @@ -8262,7 +8636,7 @@ public class WindowManagerService extends IWindowManager.Stub drawSurface.release(); topOpeningApp.mAppAnimator.thumbnailLayer = topOpeningLayer; Animation anim = createThumbnailAnimationLocked( - transit, true, true, mNextAppTransitionDelayed); + transit, true, true, mNextAppTransitionScaleUp); topOpeningApp.mAppAnimator.thumbnailAnimation = anim; anim.restrictDuration(MAX_ANIMATION_DURATION); anim.scaleCurrentDuration(mTransitionAnimationScale); @@ -8286,14 +8660,16 @@ public class WindowManagerService extends IWindowManager.Stub // This has changed the visibility of windows, so perform // a new layout to get them all up-to-date. - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT + changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; - mLayoutNeeded = true; - if (!moveInputMethodWindowsIfNeededLocked(true)) { - assignLayersLocked(); + getDefaultDisplayContentLocked().layoutNeeded = true; + + // TODO(multidisplay): IMEs are only supported on the default display. + if (windows == getDefaultWindowListLocked() + && !moveInputMethodWindowsIfNeededLocked(true)) { + assignLayersLocked(windows); } - updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, - false /*updateInputWindows*/); + updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, false /*updateInputWindows*/); mFocusMayChange = false; } @@ -8302,7 +8678,6 @@ public class WindowManagerService extends IWindowManager.Stub /** * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. - * * @return bitmap indicating if another pass through layout must be made. */ private int handleAnimatingStoppedAndTransitionLocked() { @@ -8473,17 +8848,18 @@ public class WindowManagerService extends IWindowManager.Stub //Slog.i(TAG, "DIM BEHIND: " + w); mInnerFields.mDimming = true; final WindowStateAnimator winAnimator = w.mWinAnimator; - if (!mAnimator.isDimming(winAnimator)) { + if (!mAnimator.isDimmingLocked(winAnimator)) { final int width, height; if (attrs.type == WindowManager.LayoutParams.TYPE_BOOT_PROGRESS) { - width = mCurDisplayWidth; - height = mCurDisplayHeight; + final DisplayInfo displayInfo = w.mDisplayContent.getDisplayInfo(); + width = displayInfo.logicalWidth; + height = displayInfo.logicalHeight; } else { width = innerDw; height = innerDh; } - mAnimator.startDimming(winAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount, - width, height); + startDimmingLocked( + winAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount, width, height); } } } @@ -8510,22 +8886,13 @@ public class WindowManagerService extends IWindowManager.Stub } // "Something has changed! Let's make it correct now." - private final void performLayoutAndPlaceSurfacesLockedInner( - boolean recoveringMemory) { + private final void performLayoutAndPlaceSurfacesLockedInner(boolean recoveringMemory) { if (DEBUG_WINDOW_TRACE) { Slog.v(TAG, "performLayoutAndPlaceSurfacesLockedInner: entry. Called by " + Debug.getCallers(3)); } - if (mDisplay == null) { - Slog.i(TAG, "skipping performLayoutAndPlaceSurfacesLockedInner with no mDisplay"); - return; - } final long currentTime = SystemClock.uptimeMillis(); - final int dw = mCurDisplayWidth; - final int dh = mCurDisplayHeight; - final int innerDw = mAppDisplayWidth; - final int innerDh = mAppDisplayHeight; int i; @@ -8550,193 +8917,238 @@ public class WindowManagerService extends IWindowManager.Stub mInnerFields.mButtonBrightness = -1; mTransactionSequence++; + final DisplayContent defaultDisplay = getDefaultDisplayContentLocked(); + final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo(); + final int defaultDw = defaultInfo.logicalWidth; + final int defaultDh = defaultInfo.logicalHeight; + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces"); - Surface.openTransaction(); - - if (mWatermark != null) { - mWatermark.positionSurface(dw, dh); - } - if (mStrictModeFlash != null) { - mStrictModeFlash.positionSurface(dw, dh); - } - try { - int repeats = 0; - do { - repeats++; - if (repeats > 6) { - Slog.w(TAG, "Animation repeat aborted after too many iterations"); - mLayoutNeeded = false; - break; - } + if (mWatermark != null) { + mWatermark.positionSurface(defaultDw, defaultDh); + } + if (mStrictModeFlash != null) { + mStrictModeFlash.positionSurface(defaultDw, defaultDh); + } - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner", - mPendingLayoutChanges); + // Give the display manager a chance to adjust properties + // like display rotation if it needs to. + mDisplayManagerService.performTraversalInTransactionFromWindowManager(); - if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { - if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { - assignLayersLocked(); - mLayoutNeeded = true; + boolean focusDisplayed = false; + boolean updateAllDrawn = false; + + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while (iterator.hasNext()) { + final DisplayContent displayContent = iterator.next(); + WindowList windows = displayContent.getWindowList(); + DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final int displayId = displayContent.getDisplayId(); + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; + final int innerDw = displayInfo.appWidth; + final int innerDh = displayInfo.appHeight; + final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); + + int repeats = 0; + do { + repeats++; + if (repeats > 6) { + Slog.w(TAG, "Animation repeat aborted after too many iterations"); + displayContent.layoutNeeded = false; + break; } - } - if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { - if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout"); - if (updateOrientationFromAppTokensLocked(true)) { - mLayoutNeeded = true; - mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner", + displayContent.pendingLayoutChanges); + + if (isDefaultDisplay && ((displayContent.pendingLayoutChanges + & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) + && ((adjustWallpaperWindowsLocked() + & ADJUST_WALLPAPER_LAYERS_CHANGED) != 0)) { + assignLayersLocked(windows); + displayContent.layoutNeeded = true; } - } - if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { - mLayoutNeeded = true; - } + if (isDefaultDisplay && (displayContent.pendingLayoutChanges + & WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) { + if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout"); + if (updateOrientationFromAppTokensLocked(true)) { + displayContent.layoutNeeded = true; + mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); + } + } - // FIRST LOOP: Perform a layout, if needed. - if (repeats < 4) { - performLayoutLockedInner(repeats == 1, false /*updateInputWindows*/); - } else { - Slog.w(TAG, "Layout repeat skipped after too many iterations"); - } + if ((displayContent.pendingLayoutChanges + & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { + displayContent.layoutNeeded = true; + } - // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think - // it is animating. - mPendingLayoutChanges = 0; - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("loop number " + mLayoutRepeatCount, - mPendingLayoutChanges); - mPolicy.beginAnimationLw(dw, dh); - for (i = mWindows.size() - 1; i >= 0; i--) { - WindowState w = mWindows.get(i); - if (w.mHasSurface) { - mPolicy.animatingWindowLw(w, w.mAttrs); + // FIRST LOOP: Perform a layout, if needed. + if (repeats < 4) { + performLayoutLockedInner(displayContent, repeats == 1, + false /*updateInputWindows*/); + } else { + Slog.w(TAG, "Layout repeat skipped after too many iterations"); } - } - mPendingLayoutChanges |= mPolicy.finishAnimationLw(); - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after finishAnimationLw", - mPendingLayoutChanges); - } while (mPendingLayoutChanges != 0); - final boolean someoneLosingFocus = !mLosingFocus.isEmpty(); + // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think + // it is animating. + displayContent.pendingLayoutChanges = 0; - mInnerFields.mObscured = false; - mInnerFields.mDimming = false; - mInnerFields.mSyswin = false; + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("loop number " + + mLayoutRepeatCount, displayContent.pendingLayoutChanges); - boolean focusDisplayed = false; - boolean updateAllDrawn = false; - final int N = mWindows.size(); - for (i=N-1; i>=0; i--) { - WindowState w = mWindows.get(i); + if (isDefaultDisplay) { + mPolicy.beginPostLayoutPolicyLw(dw, dh); + for (i = windows.size() - 1; i >= 0; i--) { + WindowState w = windows.get(i); + if (w.mHasSurface) { + mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs); + } + } + displayContent.pendingLayoutChanges |= mPolicy.finishPostLayoutPolicyLw(); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats( + "after finishPostLayoutPolicyLw", displayContent.pendingLayoutChanges); + } + } while (displayContent.pendingLayoutChanges != 0); - final boolean obscuredChanged = w.mObscured != mInnerFields.mObscured; + mInnerFields.mObscured = false; + mInnerFields.mDimming = false; + mInnerFields.mSyswin = false; - // Update effect. - w.mObscured = mInnerFields.mObscured; - if (!mInnerFields.mObscured) { - handleNotObscuredLocked(w, currentTime, innerDw, innerDh); - } + // Only used if default window + final boolean someoneLosingFocus = !mLosingFocus.isEmpty(); - if (obscuredChanged && (mWallpaperTarget == w) && w.isVisibleLw()) { - // This is the wallpaper target and its obscured state - // changed... make sure the current wallaper's visibility - // has been updated accordingly. - updateWallpaperVisibilityLocked(); - } + final int N = windows.size(); + for (i=N-1; i>=0; i--) { + WindowState w = windows.get(i); - final WindowStateAnimator winAnimator = w.mWinAnimator; + final boolean obscuredChanged = w.mObscured != mInnerFields.mObscured; - // If the window has moved due to its containing - // content frame changing, then we'd like to animate - // it. - if (w.mHasSurface && w.shouldAnimateMove()) { - // Frame has moved, containing content frame - // has also moved, and we're not currently animating... - // let's do something. - Animation a = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.window_move_from_decor); - winAnimator.setAnimation(a); - winAnimator.mAnimDw = w.mLastFrame.left - w.mFrame.left; - winAnimator.mAnimDh = w.mLastFrame.top - w.mFrame.top; - } + // Update effect. + w.mObscured = mInnerFields.mObscured; + if (!mInnerFields.mObscured) { + handleNotObscuredLocked(w, currentTime, innerDw, innerDh); + } - //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); - w.mContentChanged = false; + if (isDefaultDisplay && obscuredChanged && (mWallpaperTarget == w) + && w.isVisibleLw()) { + // This is the wallpaper target and its obscured state + // changed... make sure the current wallaper's visibility + // has been updated accordingly. + updateWallpaperVisibilityLocked(); + } - // Moved from updateWindowsAndWallpaperLocked(). - if (w.mHasSurface) { - // Take care of the window being ready to display. - if (winAnimator.commitFinishDrawingLocked(currentTime)) { - if ((w.mAttrs.flags - & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { - if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG, - "First draw done in potential wallpaper target " + w); - mInnerFields.mWallpaperMayChange = true; - mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - debugLayoutRepeats("updateWindowsAndWallpaperLocked 1", - mPendingLayoutChanges); - } + final WindowStateAnimator winAnimator = w.mWinAnimator; + + // If the window has moved due to its containing + // content frame changing, then we'd like to animate + // it. + if (w.mHasSurface && w.shouldAnimateMove()) { + // Frame has moved, containing content frame + // has also moved, and we're not currently animating... + // let's do something. + Animation a = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.window_move_from_decor); + winAnimator.setAnimation(a); + winAnimator.mAnimDw = w.mLastFrame.left - w.mFrame.left; + winAnimator.mAnimDh = w.mLastFrame.top - w.mFrame.top; + try { + w.mClient.moved(w.mFrame.left, w.mFrame.top); + } catch (RemoteException e) { } } - winAnimator.setSurfaceBoundaries(recoveringMemory); + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); + w.mContentChanged = false; - final AppWindowToken atoken = w.mAppToken; - if (DEBUG_STARTING_WINDOW && atoken != null && w == atoken.startingWindow) { - Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" - + w.isOnScreen() + " allDrawn=" + atoken.allDrawn - + " freezingScreen=" + atoken.mAppAnimator.freezingScreen); - } - if (atoken != null && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) { - if (atoken.lastTransactionSequence != mTransactionSequence) { - atoken.lastTransactionSequence = mTransactionSequence; - atoken.numInterestingWindows = atoken.numDrawnWindows = 0; - atoken.startingDisplayed = false; - } - if ((w.isOnScreen() || winAnimator.mAttrType - == WindowManager.LayoutParams.TYPE_BASE_APPLICATION) - && !w.mExiting && !w.mDestroying) { - if (WindowManagerService.DEBUG_VISIBILITY || - WindowManagerService.DEBUG_ORIENTATION) { - Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw() - + ", isAnimating=" + winAnimator.isAnimating()); - if (!w.isDrawnLw()) { - Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurface - + " pv=" + w.mPolicyVisibility - + " mDrawState=" + winAnimator.mDrawState - + " ah=" + w.mAttachedHidden - + " th=" + atoken.hiddenRequested - + " a=" + winAnimator.mAnimating); + // Moved from updateWindowsAndWallpaperLocked(). + if (w.mHasSurface) { + // Take care of the window being ready to display. + final boolean committed = + winAnimator.commitFinishDrawingLocked(currentTime); + if (isDefaultDisplay && committed) { + if ((w.mAttrs.flags + & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG, + "First draw done in potential wallpaper target " + w); + mInnerFields.mWallpaperMayChange = true; + displayContent.pendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + debugLayoutRepeats( + "wallpaper and commitFinishDrawingLocked true", + displayContent.pendingLayoutChanges); } } - if (w != atoken.startingWindow) { - if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) { - atoken.numInterestingWindows++; - if (w.isDrawnLw()) { - atoken.numDrawnWindows++; - if (WindowManagerService.DEBUG_VISIBILITY || - WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG, - "tokenMayBeDrawn: " + atoken - + " freezingScreen=" + atoken.mAppAnimator.freezingScreen - + " mAppFreezing=" + w.mAppFreezing); - updateAllDrawn = true; + } + + winAnimator.setSurfaceBoundariesLocked(recoveringMemory); + + final AppWindowToken atoken = w.mAppToken; + if (DEBUG_STARTING_WINDOW && atoken != null && w == atoken.startingWindow) { + Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + + w.isOnScreen() + " allDrawn=" + atoken.allDrawn + + " freezingScreen=" + atoken.mAppAnimator.freezingScreen); + } + if (atoken != null + && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) { + if (atoken.lastTransactionSequence != mTransactionSequence) { + atoken.lastTransactionSequence = mTransactionSequence; + atoken.numInterestingWindows = atoken.numDrawnWindows = 0; + atoken.startingDisplayed = false; + } + if ((w.isOnScreen() || winAnimator.mAttrType + == WindowManager.LayoutParams.TYPE_BASE_APPLICATION) + && !w.mExiting && !w.mDestroying) { + if (WindowManagerService.DEBUG_VISIBILITY || + WindowManagerService.DEBUG_ORIENTATION) { + Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw() + + ", isAnimating=" + winAnimator.isAnimating()); + if (!w.isDrawnLw()) { + Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurface + + " pv=" + w.mPolicyVisibility + + " mDrawState=" + winAnimator.mDrawState + + " ah=" + w.mAttachedHidden + + " th=" + atoken.hiddenRequested + + " a=" + winAnimator.mAnimating); } } - } else if (w.isDrawnLw()) { - atoken.startingDisplayed = true; + if (w != atoken.startingWindow) { + if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) { + atoken.numInterestingWindows++; + if (w.isDrawnLw()) { + atoken.numDrawnWindows++; + if (WindowManagerService.DEBUG_VISIBILITY || + WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG, + "tokenMayBeDrawn: " + atoken + + " freezingScreen=" + atoken.mAppAnimator.freezingScreen + + " mAppFreezing=" + w.mAppFreezing); + updateAllDrawn = true; + } + } + } else if (w.isDrawnLw()) { + atoken.startingDisplayed = true; + } } } } - } - if (someoneLosingFocus && w == mCurrentFocus && w.isDisplayedLw()) { - focusDisplayed = true; + if (isDefaultDisplay && someoneLosingFocus && (w == mCurrentFocus) + && w.isDisplayedLw()) { + focusDisplayed = true; + } + + updateResizingWindows(w); } - updateResizingWindows(w); + if (!mInnerFields.mDimming && mAnimator.isDimmingLocked(displayId)) { + stopDimmingLocked(displayId); + } } if (updateAllDrawn) { @@ -8746,23 +9158,21 @@ public class WindowManagerService extends IWindowManager.Stub if (focusDisplayed) { mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS); } - - if (!mInnerFields.mDimming && mAnimator.isDimming()) { - mAnimator.stopDimming(); - } } catch (RuntimeException e) { Log.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { Surface.closeTransaction(); } + final WindowList defaultWindows = defaultDisplay.getWindowList(); + // If we are ready to perform an app transition, check through // all of the app tokens to be shown and see if they are ready // to go. if (mAppTransitionReady) { - mPendingLayoutChanges |= handleAppTransitionReadyLocked(); + defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows); if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked", - mPendingLayoutChanges); + defaultDisplay.pendingLayoutChanges); } mInnerFields.mAdjResult = 0; @@ -8774,22 +9184,22 @@ public class WindowManagerService extends IWindowManager.Stub // reflects the correct Z-order, but the window list may now // be out of sync with it. So here we will just rebuild the // entire app window list. Fun! - mPendingLayoutChanges |= handleAnimatingStoppedAndTransitionLocked(); + defaultDisplay.pendingLayoutChanges |= handleAnimatingStoppedAndTransitionLocked(); if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAnimStopAndXitionLock", - mPendingLayoutChanges); + defaultDisplay.pendingLayoutChanges); } - if (mInnerFields.mWallpaperForceHidingChanged && mPendingLayoutChanges == 0 && - !mAppTransitionReady) { + if (mInnerFields.mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0 + && !mAppTransitionReady) { // At this point, there was a window with a wallpaper that // was force hiding other windows behind it, but now it // is going away. This may be simple -- just animate // away the wallpaper and its window -- or it may be // hard -- the wallpaper now needs to be shown behind // something that was hidden. - mPendingLayoutChanges |= animateAwayWallpaperLocked(); + defaultDisplay.pendingLayoutChanges |= animateAwayWallpaperLocked(); if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animateAwayWallpaperLocked", - mPendingLayoutChanges); + defaultDisplay.pendingLayoutChanges); } mInnerFields.mWallpaperForceHidingChanged = false; @@ -8802,26 +9212,27 @@ public class WindowManagerService extends IWindowManager.Stub if ((mInnerFields.mAdjResult&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper layer changed: assigning layers + relayout"); - mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - assignLayersLocked(); + defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; + assignLayersLocked(defaultWindows); } else if ((mInnerFields.mAdjResult&ADJUST_WALLPAPER_VISIBILITY_CHANGED) != 0) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper visibility changed: relayout"); - mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; } if (mFocusMayChange) { mFocusMayChange = false; if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, false /*updateInputWindows*/)) { - mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; + defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; mInnerFields.mAdjResult = 0; } } - if (mLayoutNeeded) { - mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("mLayoutNeeded", mPendingLayoutChanges); + if (needsLayout()) { + defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("mLayoutNeeded", + defaultDisplay.pendingLayoutChanges); } if (!mResizingWindows.isEmpty()) { @@ -8847,9 +9258,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION && winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING) Slog.i( TAG, "Resizing " + win + " WITH DRAW PENDING"); - win.mClient.resized((int)winAnimator.mSurfaceW, - (int)winAnimator.mSurfaceH, - win.mLastContentInsets, win.mLastVisibleInsets, + win.mClient.resized(win.mFrame, win.mLastContentInsets, win.mLastVisibleInsets, winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING, configChanged ? win.mConfiguration : null); win.mContentInsetsChanged = false; @@ -8899,6 +9308,7 @@ public class WindowManagerService extends IWindowManager.Stub mExitingTokens.remove(i); if (token.windowType == TYPE_WALLPAPER) { mWallpaperTokens.remove(token); + updateLayoutToAnimWallpaperTokens(); } } } @@ -8930,41 +9340,40 @@ public class WindowManagerService extends IWindowManager.Stub mRelayoutWhileAnimating.clear(); } - if (wallpaperDestroyed) { - mLayoutNeeded |= adjustWallpaperWindowsLocked() != 0; + if (wallpaperDestroyed && (adjustWallpaperWindowsLocked() != 0)) { + getDefaultDisplayContentLocked().layoutNeeded = true; } - if (mPendingLayoutChanges != 0) { - mLayoutNeeded = true; + + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while (iterator.hasNext()) { + DisplayContent displayContent = iterator.next(); + if (displayContent.pendingLayoutChanges != 0) { + displayContent.layoutNeeded = true; + } } // Finally update all input windows now that the window changes have stabilized. mInputMonitor.updateInputWindowsLw(true /*force*/); - setHoldScreenLocked(mInnerFields.mHoldScreen != null); + setHoldScreenLocked(mInnerFields.mHoldScreen); if (!mDisplayFrozen) { if (mInnerFields.mScreenBrightness < 0 || mInnerFields.mScreenBrightness > 1.0f) { - mPowerManager.setScreenBrightnessOverride(-1); + mPowerManager.setScreenBrightnessOverrideFromWindowManager(-1); } else { - mPowerManager.setScreenBrightnessOverride((int) - (mInnerFields.mScreenBrightness * PowerManager.BRIGHTNESS_ON)); + mPowerManager.setScreenBrightnessOverrideFromWindowManager( + toBrightnessOverride(mInnerFields.mScreenBrightness)); } if (mInnerFields.mButtonBrightness < 0 || mInnerFields.mButtonBrightness > 1.0f) { - mPowerManager.setButtonBrightnessOverride(-1); + mPowerManager.setButtonBrightnessOverrideFromWindowManager(-1); } else { - mPowerManager.setButtonBrightnessOverride((int) - (mInnerFields.mButtonBrightness * PowerManager.BRIGHTNESS_ON)); + mPowerManager.setButtonBrightnessOverrideFromWindowManager( + toBrightnessOverride(mInnerFields.mButtonBrightness)); } } - if (mInnerFields.mHoldScreen != mHoldingScreenOn) { - mHoldingScreenOn = mInnerFields.mHoldScreen; - Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, mInnerFields.mHoldScreen); - mH.sendMessage(m); - } if (mTurnOnScreen) { if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!"); - mPowerManager.userActivity(SystemClock.uptimeMillis(), false, - LocalPowerManager.BUTTON_EVENT, true); + mPowerManager.wakeUp(SystemClock.uptimeMillis()); mTurnOnScreen = false; } @@ -8977,24 +9386,49 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (mInnerFields.mOrientationChangeComplete && !mLayoutNeeded && - !mInnerFields.mUpdateRotation) { + if (mInnerFields.mOrientationChangeComplete && !defaultDisplay.layoutNeeded + && !mInnerFields.mUpdateRotation) { checkDrawnWindowsLocked(); } + final int N = mPendingRemove.size(); + if (N > 0) { + if (mPendingRemoveTmp.length < N) { + mPendingRemoveTmp = new WindowState[N+10]; + } + mPendingRemove.toArray(mPendingRemoveTmp); + mPendingRemove.clear(); + DisplayContentList displayList = new DisplayContentList(); + for (i = 0; i < N; i++) { + WindowState w = mPendingRemoveTmp[i]; + removeWindowInnerLocked(w.mSession, w); + if (!displayList.contains(w.mDisplayContent)) { + displayList.add(w.mDisplayContent); + } + } + + for (DisplayContent displayContent : displayList) { + assignLayersLocked(displayContent.getWindowList()); + displayContent.layoutNeeded = true; + } + } + // Check to see if we are now in a state where the screen should // be enabled, because the window obscured flags have changed. enableScreenIfNeededLocked(); - scheduleAnimationLocked(); + updateLayoutToAnimationLocked(); if (DEBUG_WINDOW_TRACE) { - Slog.e(TAG, "performLayoutAndPlaceSurfacesLockedInner exit: mPendingLayoutChanges=" - + Integer.toHexString(mPendingLayoutChanges) + " mLayoutNeeded=" + mLayoutNeeded - + " animating=" + mAnimator.mAnimating); + Slog.e(TAG, "performLayoutAndPlaceSurfacesLockedInner exit: animating=" + + mAnimator.mAnimating); } } + private int toBrightnessOverride(float value) { + return (int)(value * PowerManager.BRIGHTNESS_ON); + } + void checkDrawnWindowsLocked() { if (mWaitingForDrawn.size() > 0) { for (int j=mWaitingForDrawn.size()-1; j>=0; j--) { @@ -9040,13 +9474,17 @@ public class WindowManagerService extends IWindowManager.Stub } } - /** - * Must be called with the main window manager lock held. - */ - void setHoldScreenLocked(boolean holding) { - boolean state = mHoldingScreenWakeLock.isHeld(); - if (holding != state) { - if (holding) { + void setHoldScreenLocked(final Session newHoldScreen) { + final boolean hold = newHoldScreen != null; + + if (hold && mHoldingScreenOn != newHoldScreen) { + mHoldingScreenWakeLock.setWorkSource(new WorkSource(newHoldScreen.mUid)); + } + mHoldingScreenOn = newHoldScreen; + + final boolean state = mHoldingScreenWakeLock.isHeld(); + if (hold != state) { + if (hold) { mPolicy.screenOnStartedLw(); mHoldingScreenWakeLock.acquire(); } else { @@ -9056,6 +9494,13 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public void requestTraversal() { + synchronized (mWindowMap) { + requestTraversalLocked(); + } + } + void requestTraversalLocked() { if (!mTraversalScheduled) { mTraversalScheduled = true; @@ -9063,11 +9508,133 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** Note that Locked in this case is on mLayoutToAnim */ void scheduleAnimationLocked() { - if (!mAnimationScheduled) { - mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationRunnable, null); - mAnimationScheduled = true; + final LayoutToAnimatorParams layoutToAnim = mLayoutToAnim; + if (!layoutToAnim.mAnimationScheduled) { + layoutToAnim.mAnimationScheduled = true; + mChoreographer.postCallback( + Choreographer.CALLBACK_ANIMATION, mAnimator.mAnimationRunnable, null); + } + } + + void updateLayoutToAnimationLocked() { + final LayoutToAnimatorParams layoutToAnim = mLayoutToAnim; + synchronized (layoutToAnim) { + // Copy local params to transfer params. + SparseArray<WinAnimatorList> allWinAnimatorLists = layoutToAnim.mWinAnimatorLists; + allWinAnimatorLists.clear(); + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while (iterator.hasNext()) { + final DisplayContent displayContent = iterator.next(); + WinAnimatorList winAnimatorList = new WinAnimatorList(); + final WindowList windows = displayContent.getWindowList(); + int N = windows.size(); + for (int i = 0; i < N; i++) { + final WindowStateAnimator winAnimator = windows.get(i).mWinAnimator; + if (winAnimator.mSurface != null) { + winAnimatorList.add(winAnimator); + } + } + allWinAnimatorLists.put(displayContent.getDisplayId(), winAnimatorList); + } + + layoutToAnim.mWallpaperTarget = mWallpaperTarget; + layoutToAnim.mLowerWallpaperTarget = mLowerWallpaperTarget; + layoutToAnim.mUpperWallpaperTarget = mUpperWallpaperTarget; + + final ArrayList<AppWindowAnimParams> paramList = layoutToAnim.mAppWindowAnimParams; + paramList.clear(); + int N = mAnimatingAppTokens.size(); + for (int i = 0; i < N; i++) { + paramList.add(new AppWindowAnimParams(mAnimatingAppTokens.get(i).mAppAnimator)); + } + + layoutToAnim.mParamsModified = true; + scheduleAnimationLocked(); + } + } + + void updateLayoutToAnimWallpaperTokens() { + synchronized(mLayoutToAnim) { + mLayoutToAnim.mWallpaperTokens = new ArrayList<WindowToken>(mWallpaperTokens); + mLayoutToAnim.mChanges |= LayoutToAnimatorParams.WALLPAPER_TOKENS_CHANGED; + } + } + + void setAnimDimParams(int displayId, DimAnimator.Parameters params) { + synchronized (mLayoutToAnim) { + mLayoutToAnim.mDimParams.put(displayId, params); + scheduleAnimationLocked(); + } + } + + void startDimmingLocked(final WindowStateAnimator winAnimator, final float target, + final int width, final int height) { + setAnimDimParams(winAnimator.mWin.getDisplayId(), + new DimAnimator.Parameters(winAnimator, width, height, target)); + } + + void stopDimmingLocked(int displayId) { + setAnimDimParams(displayId, null); + } + + private boolean needsLayout() { + DisplayContentsIterator iterator = new DisplayContentsIterator(); + while (iterator.hasNext()) { + if (iterator.next().layoutNeeded) { + return true; + } + } + return false; + } + + private boolean copyAnimToLayoutParamsLocked() { + boolean doRequest = false; + final WindowAnimator.AnimatorToLayoutParams animToLayout = mAnimator.mAnimToLayout; + synchronized (animToLayout) { + animToLayout.mUpdateQueued = false; + final int bulkUpdateParams = animToLayout.mBulkUpdateParams; + // TODO(cmautner): As the number of bits grows, use masks of bit groups to + // eliminate unnecessary tests. + if ((bulkUpdateParams & LayoutFields.SET_UPDATE_ROTATION) != 0) { + mInnerFields.mUpdateRotation = true; + doRequest = true; + } + if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) { + mInnerFields.mWallpaperMayChange = true; + doRequest = true; + } + if ((bulkUpdateParams & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) { + mInnerFields.mWallpaperForceHidingChanged = true; + doRequest = true; + } + if ((bulkUpdateParams & LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE) == 0) { + mInnerFields.mOrientationChangeComplete = false; + } else { + mInnerFields.mOrientationChangeComplete = true; + if (mWindowsFreezingScreen) { + doRequest = true; + } + } + if ((bulkUpdateParams & LayoutFields.SET_TURN_ON_SCREEN) != 0) { + mTurnOnScreen = true; + } + + SparseIntArray pendingLayouts = animToLayout.mPendingLayoutChanges; + final int count = pendingLayouts.size(); + if (count > 0) { + doRequest = true; + } + for (int i = 0; i < count; ++i) { + final DisplayContent displayContent = + getDisplayContentLocked(pendingLayouts.keyAt(i)); + displayContent.pendingLayoutChanges |= pendingLayouts.valueAt(i); + } + + mWindowDetachedWallpaper = animToLayout.mWindowDetachedWallpaper; } + return doRequest; } boolean reclaimSomeSurfaceMemoryLocked(WindowStateAnimator winAnimator, String operation, @@ -9088,10 +9655,11 @@ public class WindowManagerService extends IWindowManager.Stub // There was some problem... first, do a sanity check of the // window list to make sure we haven't left any dangling surfaces // around. - int N = mWindows.size(); + + AllWindowsIterator iterator = new AllWindowsIterator(); Slog.i(TAG, "Out of memory for surface! Looking for leaks..."); - for (int i=0; i<N; i++) { - WindowState ws = mWindows.get(i); + while (iterator.hasNext()) { + WindowState ws = iterator.next(); WindowStateAnimator wsa = ws.mWinAnimator; if (wsa.mSurface != null) { if (!mSessions.contains(wsa.mSession)) { @@ -9106,8 +9674,6 @@ public class WindowManagerService extends IWindowManager.Stub wsa.mSurface = null; ws.mHasSurface = false; mForceRemoves.add(ws); - i--; - N--; leakedSurface = true; } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) { Slog.w(TAG, "LEAKED SURFACE (app token hidden): " @@ -9126,8 +9692,13 @@ public class WindowManagerService extends IWindowManager.Stub if (!leakedSurface) { Slog.w(TAG, "No leaked surfaces; killing applicatons!"); SparseIntArray pidCandidates = new SparseIntArray(); - for (int i=0; i<N; i++) { - WindowStateAnimator wsa = mWindows.get(i).mWinAnimator; + iterator = new AllWindowsIterator(); + while (iterator.hasNext()) { + WindowState ws = iterator.next(); + if (mForceRemoves.contains(ws)) { + continue; + } + WindowStateAnimator wsa = ws.mWinAnimator; if (wsa.mSurface != null) { pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid); } @@ -9187,28 +9758,31 @@ public class WindowManagerService extends IWindowManager.Stub mLosingFocus.remove(newFocus); int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus); + // TODO(multidisplay): Focused windows on default display only. + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final WindowState imWindow = mInputMethodWindow; if (newFocus != imWindow && oldFocus != imWindow) { if (moveInputMethodWindowsIfNeededLocked( mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES)) { - mLayoutNeeded = true; + getDefaultDisplayContentLocked().layoutNeeded = true; } if (mode == UPDATE_FOCUS_PLACING_SURFACES) { - performLayoutLockedInner(true /*initial*/, updateInputWindows); + performLayoutLockedInner(displayContent, true /*initial*/, updateInputWindows); focusChanged &= ~WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) { // Client will do the layout, but we need to assign layers // for handleNewWindowLocked() below. - assignLayersLocked(); + assignLayersLocked(displayContent.getWindowList()); } } - if ((focusChanged&WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { + if ((focusChanged & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { // The change in focus caused us to need to do a layout. Okay. - mLayoutNeeded = true; + getDefaultDisplayContentLocked().layoutNeeded = true; if (mode == UPDATE_FOCUS_PLACING_SURFACES) { - performLayoutLockedInner(true /*initial*/, updateInputWindows); + performLayoutLockedInner(displayContent, true /*initial*/, updateInputWindows); } } @@ -9232,12 +9806,19 @@ public class WindowManagerService extends IWindowManager.Stub WindowState result = null; WindowState win; + if (mAnimator.mUniverseBackground != null + && mAnimator.mUniverseBackground.mWin.canReceiveKeys()) { + return mAnimator.mUniverseBackground.mWin; + } + int nextAppIndex = mAppTokens.size()-1; WindowToken nextApp = nextAppIndex >= 0 ? mAppTokens.get(nextAppIndex) : null; - for (int i = mWindows.size() - 1; i >= 0; i--) { - win = mWindows.get(i); + // TODO(multidisplay): IMEs are only supported on the default display. + WindowList windows = getDefaultWindowListLocked(); + for (int i = windows.size() - 1; i >= 0; i--) { + win = windows.get(i); if (localLOGV || DEBUG_FOCUS) Slog.v( TAG, "Looking for focus: " + i @@ -9295,12 +9876,13 @@ public class WindowManagerService extends IWindowManager.Stub return result; } - private void startFreezingDisplayLocked(boolean inTransaction) { + private void startFreezingDisplayLocked(boolean inTransaction, + int exitAnim, int enterAnim) { if (mDisplayFrozen) { return; } - if (mDisplay == null || !mPolicy.isScreenOnFully()) { + if (!mDisplayReady || !mPolicy.isScreenOnFully()) { // No need to freeze the screen before the system is ready or if // the screen is off. return; @@ -9326,20 +9908,22 @@ public class WindowManagerService extends IWindowManager.Stub } if (CUSTOM_SCREEN_ROTATION) { - if (mAnimator.mScreenRotationAnimation != null) { - mAnimator.mScreenRotationAnimation.kill(); - mAnimator.mScreenRotationAnimation = null; + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final int displayId = displayContent.getDisplayId(); + ScreenRotationAnimation screenRotationAnimation = + mAnimator.getScreenRotationAnimationLocked(displayId); + if (screenRotationAnimation != null) { + screenRotationAnimation.kill(); } - mAnimator.mScreenRotationAnimation = new ScreenRotationAnimation(mContext, - mFxSession, inTransaction, mCurDisplayWidth, mCurDisplayHeight, - mDisplay.getRotation()); - - if (!mAnimator.mScreenRotationAnimation.hasScreenshot()) { - Surface.freezeDisplay(0); - } - } else { - Surface.freezeDisplay(0); + // TODO(multidisplay): rotation on main screen only. + final Display display = displayContent.getDisplay(); + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + screenRotationAnimation = new ScreenRotationAnimation(mContext, + display, mFxSession, inTransaction, displayInfo.logicalWidth, + displayInfo.logicalHeight, display.getRotation(), + exitAnim, enterAnim); + mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation); } } @@ -9348,41 +9932,52 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen) { + if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen + || mClientFreezingScreen) { if (DEBUG_ORIENTATION) Slog.d(TAG, "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig + ", mAppsFreezingScreen=" + mAppsFreezingScreen - + ", mWindowsFreezingScreen=" + mWindowsFreezingScreen); + + ", mWindowsFreezingScreen=" + mWindowsFreezingScreen + + ", mClientFreezingScreen=" + mClientFreezingScreen); return; } mDisplayFrozen = false; mH.removeMessages(H.APP_FREEZE_TIMEOUT); + mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT); if (PROFILE_ORIENTATION) { Debug.stopMethodTracing(); } boolean updateRotation = false; - - if (CUSTOM_SCREEN_ROTATION && mAnimator.mScreenRotationAnimation != null - && mAnimator.mScreenRotationAnimation.hasScreenshot()) { + + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final int displayId = displayContent.getDisplayId(); + ScreenRotationAnimation screenRotationAnimation = + mAnimator.getScreenRotationAnimationLocked(displayId); + if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null + && screenRotationAnimation.hasScreenshot()) { if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation"); - if (mAnimator.mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, - mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) { - scheduleAnimationLocked(); + // TODO(multidisplay): rotation on main screen only. + DisplayInfo displayInfo = displayContent.getDisplayInfo(); + if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, + mTransitionAnimationScale, displayInfo.logicalWidth, + displayInfo.logicalHeight)) { + updateLayoutToAnimationLocked(); } else { - mAnimator.mScreenRotationAnimation.kill(); - mAnimator.mScreenRotationAnimation = null; + screenRotationAnimation.kill(); + screenRotationAnimation = null; + mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation); updateRotation = true; } } else { - if (mAnimator.mScreenRotationAnimation != null) { - mAnimator.mScreenRotationAnimation.kill(); - mAnimator.mScreenRotationAnimation = null; + if (screenRotationAnimation != null) { + screenRotationAnimation.kill(); + screenRotationAnimation = null; + mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation); } updateRotation = true; } - Surface.unfreezeDisplay(0); mInputMonitor.thawInputDispatchingLw(); @@ -9434,7 +10029,7 @@ public class WindowManagerService extends IWindowManager.Stub return val; } - void createWatermark() { + void createWatermarkInTransaction() { if (mWatermark != null) { return; } @@ -9448,7 +10043,8 @@ public class WindowManagerService extends IWindowManager.Stub if (line != null) { String[] toks = line.split("%"); if (toks != null && toks.length > 0) { - mWatermark = new Watermark(mRealDisplayMetrics, mFxSession, toks); + mWatermark = new Watermark(getDefaultDisplayContentLocked().getDisplay(), + mRealDisplayMetrics, mFxSession, toks); } } } catch (FileNotFoundException e) { @@ -9478,11 +10074,13 @@ public class WindowManagerService extends IWindowManager.Stub } } + // TOOD(multidisplay): StatusBar on multiple screens? void updateStatusBarVisibilityLocked(int visibility) { mInputManager.setSystemUiVisibility(visibility); - final int N = mWindows.size(); + final WindowList windows = getDefaultWindowListLocked(); + final int N = windows.size(); for (int i = 0; i < N; i++) { - WindowState ws = mWindows.get(i); + WindowState ws = windows.get(i); try { int curValue = ws.mSystemUiVisibility; int diff = curValue ^ visibility; @@ -9549,6 +10147,8 @@ public class WindowManagerService extends IWindowManager.Stub // It is assumed that this method is called only by InputMethodManagerService. public void saveLastInputMethodWindowForTransition() { synchronized (mWindowMap) { + // TODO(multidisplay): Pass in the displayID. + DisplayContent displayContent = getDefaultDisplayContentLocked(); if (mInputMethodWindow != null) { mPolicy.setLastInputMethodWindowLw(mInputMethodWindow, mInputMethodTarget); } @@ -9699,10 +10299,12 @@ public class WindowManagerService extends IWindowManager.Stub void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll, ArrayList<WindowState> windows) { - for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = mWindows.get(i); + int j = 0; + final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); if (windows == null || windows.contains(w)) { - pw.print(" Window #"); pw.print(i); pw.print(' '); + pw.print(" Window #"); pw.print(j++); pw.print(' '); pw.print(w); pw.println(":"); w.dump(pw, " ", dumpAll || windows != null); } @@ -9809,28 +10411,12 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(": "); pw.println(pair.second); } } - pw.println(); - if (mDisplay != null) { - pw.print(" Display: init="); pw.print(mInitialDisplayWidth); pw.print("x"); - pw.print(mInitialDisplayHeight); - if (mInitialDisplayWidth != mBaseDisplayWidth - || mInitialDisplayHeight != mBaseDisplayHeight) { - pw.print(" base="); - pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight); - } - final int rawWidth = mDisplay.getRawWidth(); - final int rawHeight = mDisplay.getRawHeight(); - if (rawWidth != mCurDisplayWidth || rawHeight != mCurDisplayHeight) { - pw.print(" raw="); pw.print(rawWidth); pw.print("x"); pw.print(rawHeight); - } - pw.print(" cur="); - pw.print(mCurDisplayWidth); pw.print("x"); pw.print(mCurDisplayHeight); - pw.print(" app="); - pw.print(mAppDisplayWidth); pw.print("x"); pw.print(mAppDisplayHeight); - pw.print(" rng="); pw.print(mSmallestDisplayWidth); - pw.print("x"); pw.print(mSmallestDisplayHeight); - pw.print("-"); pw.print(mLargestDisplayWidth); - pw.print("x"); pw.println(mLargestDisplayHeight); + pw.println(" DisplayContents"); + if (mDisplayReady) { + DisplayContentsIterator dCIterator = new DisplayContentsIterator(); + while (dCIterator.hasNext()) { + dCIterator.next().dump(" ", pw); + } } else { pw.println(" NO DISPLAY"); } @@ -9847,7 +10433,8 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mLayoutSeq="); pw.println(mLayoutSeq); if (dumpAll) { pw.print(" mSystemDecorRect="); pw.print(mSystemDecorRect.toShortString()); - pw.print(" mSystemDecorLayer="); pw.println(mSystemDecorLayer); + pw.print(" mSystemDecorLayer="); pw.print(mSystemDecorLayer); + pw.print(" mScreenRecr="); pw.println(mScreenRect.toShortString()); if (mLastStatusBarVisibility != 0) { pw.print(" mLastStatusBarVisibility=0x"); pw.println(Integer.toHexString(mLastStatusBarVisibility)); @@ -9871,21 +10458,28 @@ public class WindowManagerService extends IWindowManager.Stub } pw.print(" mSystemBooted="); pw.print(mSystemBooted); pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled); - pw.print(" mLayoutNeeded="); pw.print(mLayoutNeeded); - pw.print("mTransactionSequence="); pw.println(mTransactionSequence); + if (needsLayout()) { + pw.print(" layoutNeeded on displays="); + DisplayContentsIterator dcIterator = new DisplayContentsIterator(); + while (dcIterator.hasNext()) { + final DisplayContent displayContent = dcIterator.next(); + if (displayContent.layoutNeeded) { + pw.print(displayContent.getDisplayId()); + } + } + pw.println(); + } + pw.print(" mTransactionSequence="); pw.println(mTransactionSequence); pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen); - pw.print(" mWindowsFreezingScreen="); pw.print(mWindowsFreezingScreen); - pw.print(" mAppsFreezingScreen="); pw.print(mAppsFreezingScreen); - pw.print(" mWaitingForConfig="); pw.println(mWaitingForConfig); + pw.print(" windows="); pw.print(mWindowsFreezingScreen); + pw.print(" client="); pw.print(mClientFreezingScreen); + pw.print(" apps="); pw.print(mAppsFreezingScreen); + pw.print(" waitingForConfig="); pw.println(mWaitingForConfig); pw.print(" mRotation="); pw.print(mRotation); pw.print(" mAltOrientation="); pw.println(mAltOrientation); pw.print(" mLastWindowForcedOrientation="); pw.print(mLastWindowForcedOrientation); pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation); pw.print(" mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount); - if (mAnimator.mScreenRotationAnimation != null) { - pw.println(" mScreenRotationAnimation:"); - mAnimator.mScreenRotationAnimation.printTo(" ", pw); - } pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); pw.print(" mTransitionWindowAnimationScale="); pw.print(mTransitionAnimationScale); pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale); @@ -9916,15 +10510,15 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mNextAppTransitionStartHeight="); pw.println(mNextAppTransitionStartHeight); break; - case ActivityOptions.ANIM_THUMBNAIL: - case ActivityOptions.ANIM_THUMBNAIL_DELAYED: + case ActivityOptions.ANIM_THUMBNAIL_SCALE_UP: + case ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN: pw.print(" mNextAppTransitionThumbnail="); pw.print(mNextAppTransitionThumbnail); pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX); pw.print(" mNextAppTransitionStartY="); pw.println(mNextAppTransitionStartY); - pw.print(" mNextAppTransitionDelayed="); pw.println(mNextAppTransitionDelayed); + pw.print(" mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp); break; } if (mNextAppTransitionCallback != null) { @@ -9934,17 +10528,18 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition); pw.print(" mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation); pw.println(" Window Animator:"); - mAnimator.dump(pw, " ", dumpAll); + mAnimator.dumpLocked(pw, " ", dumpAll); } } boolean dumpWindows(PrintWriter pw, String name, String[] args, int opti, boolean dumpAll) { - ArrayList<WindowState> windows = new ArrayList<WindowState>(); + WindowList windows = new WindowList(); if ("visible".equals(name)) { synchronized(mWindowMap) { - for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = mWindows.get(i); + final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); if (w.mWinAnimator.mSurfaceShown) { windows.add(w); } @@ -9959,8 +10554,9 @@ public class WindowManagerService extends IWindowManager.Stub } catch (RuntimeException e) { } synchronized(mWindowMap) { - for (int i=mWindows.size()-1; i>=0; i--) { - WindowState w = mWindows.get(i); + final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR); + while (iterator.hasNext()) { + final WindowState w = iterator.next(); if (name != null) { if (w.mAttrs.getTitle().toString().contains(name)) { windows.add(w); @@ -10133,7 +10729,6 @@ public class WindowManagerService extends IWindowManager.Stub // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection). public void monitor() { synchronized (mWindowMap) { } - synchronized (mKeyguardTokenWatcher) { } } public interface OnHardKeyboardStatusChangeListener { @@ -10147,8 +10742,155 @@ public class WindowManagerService extends IWindowManager.Stub } } - void bulkSetParameters(final int bulkUpdateParams, int pendingLayoutChanges) { - mH.sendMessage(mH.obtainMessage(H.BULK_UPDATE_PARAMETERS, bulkUpdateParams, - pendingLayoutChanges)); + public void createDisplayContentLocked(final Display display) { + if (display == null) { + throw new IllegalArgumentException("getDisplayContent: display must not be null"); + } + final DisplayContent displayContent = new DisplayContent(display); + mDisplayContents.put(display.getDisplayId(), displayContent); + } + + public DisplayContent getDisplayContentLocked(final int displayId) { + DisplayContent displayContent = mDisplayContents.get(displayId); + if (displayContent == null) { + displayContent = new DisplayContent(mDisplayManager.getDisplay(displayId)); + mDisplayContents.put(displayId, displayContent); + } + return displayContent; + } + + class DisplayContentsIterator implements Iterator<DisplayContent> { + private int cur; + + @Override + public boolean hasNext() { + return cur < mDisplayContents.size(); + } + + @Override + public DisplayContent next() { + if (hasNext()) { + return mDisplayContents.valueAt(cur++); + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new IllegalArgumentException("AllDisplayContentIterator.remove not implemented"); + } + } + + final static boolean REVERSE_ITERATOR = true; + class AllWindowsIterator implements Iterator<WindowState> { + private DisplayContent mDisplayContent; + private DisplayContentsIterator mDisplayContentsIterator; + private WindowList mWindowList; + private int mWindowListIndex; + private boolean mReverse; + + AllWindowsIterator() { + mDisplayContentsIterator = new DisplayContentsIterator(); + mDisplayContent = mDisplayContentsIterator.next(); + mWindowList = mDisplayContent.getWindowList(); + } + + AllWindowsIterator(boolean reverse) { + this(); + mReverse = reverse; + mWindowListIndex = reverse ? mWindowList.size() - 1 : 0; + } + + @Override + public boolean hasNext() { + if (mReverse) { + return mWindowListIndex >= 0; + } + return mWindowListIndex < mWindowList.size(); + } + + @Override + public WindowState next() { + if (hasNext()) { + WindowState win = mWindowList.get(mWindowListIndex); + if (mReverse) { + mWindowListIndex--; + if (mWindowListIndex < 0 && mDisplayContentsIterator.hasNext()) { + mDisplayContent = mDisplayContentsIterator.next(); + mWindowList = mDisplayContent.getWindowList(); + mWindowListIndex = mWindowList.size() - 1; + } + } else { + mWindowListIndex++; + if (mWindowListIndex >= mWindowList.size() + && mDisplayContentsIterator.hasNext()) { + mDisplayContent = mDisplayContentsIterator.next(); + mWindowList = mDisplayContent.getWindowList(); + mWindowListIndex = 0; + } + } + return win; + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new IllegalArgumentException("AllWindowsIterator.remove not implemented"); + } + } + + public DisplayContent getDefaultDisplayContentLocked() { + return getDisplayContentLocked(Display.DEFAULT_DISPLAY); + } + + public WindowList getDefaultWindowListLocked() { + return getDefaultDisplayContentLocked().getWindowList(); + } + + public DisplayInfo getDefaultDisplayInfoLocked() { + return getDefaultDisplayContentLocked().getDisplayInfo(); + } + + public WindowList getWindowListLocked(final Display display) { + return getDisplayContentLocked(display.getDisplayId()).getWindowList(); + } + + @Override + public void onDisplayAdded(int displayId) { + mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_ADDED, displayId, 0)); + } + + private void handleDisplayAddedLocked(int displayId) { + createDisplayContentLocked(mDisplayManager.getDisplay(displayId)); + displayReady(displayId); + } + + @Override + public void onDisplayRemoved(int displayId) { + mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_REMOVED, displayId, 0)); + } + + private void handleDisplayRemovedLocked(int displayId) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + mDisplayContents.delete(displayId); + WindowList windows = displayContent.getWindowList(); + for (int i = windows.size() - 1; i >= 0; --i) { + final WindowState win = windows.get(i); + removeWindowLocked(win.mSession, win); + } + mAnimator.removeDisplayLocked(displayId); + } + + @Override + public void onDisplayChanged(int displayId) { + mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_CHANGED, displayId, 0)); + } + + private void handleDisplayChangedLocked(int displayId) { + final DisplayContent displayContent = getDisplayContentLocked(displayId); + if (displayContent != null) { + displayContent.updateDisplayInfo(); + } } } diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index a00e8a5..b62028e 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -22,6 +22,9 @@ import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import com.android.server.input.InputWindowHandle; @@ -33,8 +36,11 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Slog; +import android.view.DisplayInfo; import android.view.Gravity; import android.view.IApplicationToken; import android.view.IWindow; @@ -47,6 +53,9 @@ import android.view.WindowManagerPolicy; import java.io.PrintWriter; import java.util.ArrayList; +class WindowList extends ArrayList<WindowState> { +} + /** * A window in the window manager. */ @@ -251,15 +260,22 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean mHasSurface = false; + DisplayContent mDisplayContent; + + // UserId and appId of the owner. Don't display windows of non-current user. + final int mOwnerUid; + WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int seq, WindowManager.LayoutParams a, - int viewVisibility) { + int viewVisibility, final DisplayContent displayContent) { mService = service; mSession = s; mClient = c; mToken = token; + mOwnerUid = s.mUid; mAttrs.copyFrom(a); mViewVisibility = viewVisibility; + mDisplayContent = displayContent; mPolicy = mService.mPolicy; mContext = mService.mContext; DeathRecipient deathRecipient = new DeathRecipient(); @@ -317,9 +333,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { mIsFloatingLayer = mIsImWindow || mIsWallpaper; } - mWinAnimator = new WindowStateAnimator(service, this, mAttachedWindow); - mWinAnimator.mAlpha = a.alpha; - WindowState appWin = this; while (appWin.mAttachedWindow != null) { appWin = appWin.mAttachedWindow; @@ -335,6 +348,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { mRootToken = appToken; mAppToken = appToken.appWindowToken; + mWinAnimator = new WindowStateAnimator(this); + mWinAnimator.mAlpha = a.alpha; + mRequestedWidth = 0; mRequestedHeight = 0; mLastRequestedWidth = 0; @@ -343,7 +359,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { mYOffset = 0; mLayer = 0; mInputWindowHandle = new InputWindowHandle( - mAppToken != null ? mAppToken.mInputApplicationHandle : null, this); + mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, + displayContent.getDisplayId()); } void attach() { @@ -479,8 +496,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { } if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) { - mService.updateWallpaperOffsetLocked(this, - mService.mAppDisplayWidth, mService.mAppDisplayHeight, false); + final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); + mService.updateWallpaperOffsetLocked(this, displayInfo.appWidth, displayInfo.appHeight, + false); } if (WindowManagerService.localLOGV) { @@ -496,6 +514,21 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } + MagnificationSpec getWindowMagnificationSpecLocked() { + MagnificationSpec spec = mDisplayContent.mMagnificationSpec; + if (spec != null && !spec.isNop()) { + if (mAttachedWindow != null) { + if (!mPolicy.canMagnifyWindow(mAttachedWindow.mAttrs)) { + return null; + } + } + if (!mPolicy.canMagnifyWindow(mAttrs)) { + return null; + } + } + return spec; + } + @Override public Rect getFrameLw() { return mFrame; @@ -544,6 +577,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) { int index = -1; WindowState ws = this; + WindowList windows = getWindowList(); while (true) { if ((ws.mAttrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY) != 0) { @@ -558,20 +592,22 @@ final class WindowState implements WindowManagerPolicy.WindowState { // look behind it. // First, we may need to determine the starting position. if (index < 0) { - index = mService.mWindows.indexOf(ws); + index = windows.indexOf(ws); } index--; if (index < 0) { return false; } - ws = mService.mWindows.get(index); + ws = windows.get(index); } } + @Override public int getSystemUiVisibility() { return mSystemUiVisibility; } + @Override public int getSurfaceLayer() { return mLayer; } @@ -579,7 +615,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { public IApplicationToken getAppToken() { return mAppToken != null ? mAppToken.appToken : null; } - + + public int getDisplayId() { + return mDisplayContent.getDisplayId(); + } + public long getInputDispatchingTimeoutNanos() { return mAppToken != null ? mAppToken.inputDispatchingTimeoutNanos @@ -766,7 +806,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mViewVisibility == View.GONE || !mRelayoutCalled || (atoken == null && mRootToken.hidden) - || (atoken != null && (atoken.hiddenRequested || atoken.hidden)) + || (atoken != null && atoken.hiddenRequested) || mAttachedHidden || mExiting || mDestroying; } @@ -884,6 +924,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { } boolean showLw(boolean doAnimation, boolean requestAnim) { + if (isOtherUsersAppWindow()) { + Slog.w(TAG, "Current user " + mService.mCurrentUserId + " trying to display " + + this + ", type " + mAttrs.type + ", belonging to " + mOwnerUid); + return false; + } if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { // Already showing. return false; @@ -907,7 +952,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true); } if (requestAnim) { - mService.scheduleAnimationLocked(); + mService.updateLayoutToAnimationLocked(); } return true; } @@ -950,7 +995,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } if (requestAnim) { - mService.scheduleAnimationLocked(); + mService.updateLayoutToAnimationLocked(); } return true; } @@ -960,6 +1005,22 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mClient.asBinder().isBinderAlive(); } + @Override + public boolean isDefaultDisplay() { + return mDisplayContent.isDefaultDisplay; + } + + boolean isOtherUsersAppWindow() { + final int type = mAttrs.type; + if ((UserHandle.getUserId(mOwnerUid) != mService.mCurrentUserId) + && (mOwnerUid != Process.SYSTEM_UID) + && (type >= TYPE_BASE_APPLICATION) && (type <= LAST_APPLICATION_WINDOW) + && (type != TYPE_APPLICATION_STARTING)) { + return true; + } + return false; + } + private static void applyInsets(Region outRegion, Rect frame, Rect inset) { outRegion.set( frame.left + inset.left, frame.top + inset.top, @@ -988,8 +1049,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } + WindowList getWindowList() { + return mDisplayContent.getWindowList(); + } + void dump(PrintWriter pw, String prefix, boolean dumpAll) { - pw.print(prefix); pw.print("mSession="); pw.print(mSession); + pw.print(prefix); pw.print("mDisplayId="); pw.print(mDisplayContent.getDisplayId()); + pw.print(" mSession="); pw.print(mSession); pw.print(" mClient="); pw.println(mClient.asBinder()); pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs); pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth); @@ -1060,11 +1126,15 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (mTouchableInsets != 0 || mGivenInsetsPending) { pw.print(prefix); pw.print("mTouchableInsets="); pw.print(mTouchableInsets); pw.print(" mGivenInsetsPending="); pw.println(mGivenInsetsPending); + Region region = new Region(); + getTouchableRegion(region); + pw.print(prefix); pw.print("touchable region="); pw.println(region); } pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration); } pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface); - pw.print(" mShownFrame="); mShownFrame.printShortString(pw); pw.println(); + pw.print(" mShownFrame="); mShownFrame.printShortString(pw); + pw.print(" isReadyForDisplay()="); pw.println(isReadyForDisplay()); if (dumpAll) { pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw); pw.print(" last="); mLastFrame.printShortString(pw); diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index 03e52fe..000a191 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -3,9 +3,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; - -import static com.android.server.wm.WindowManagerService.LayoutFields.CLEAR_ORIENTATION_CHANGE_COMPLETE; +import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_TURN_ON_SCREEN; import android.content.Context; @@ -17,6 +15,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.Debug; import android.util.Slog; +import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceSession; import android.view.WindowManager; @@ -31,6 +30,16 @@ import com.android.server.wm.WindowManagerService.H; import java.io.PrintWriter; import java.util.ArrayList; +class WinAnimatorList extends ArrayList<WindowStateAnimator> { + public WinAnimatorList() { + super(); + } + + public WinAnimatorList(WinAnimatorList other) { + super(other); + } +} + /** * Keep track of animations and surface operations for a single WindowState. **/ @@ -48,13 +57,20 @@ class WindowStateAnimator { static final String TAG = "WindowStateAnimator"; + // Unchanging local convenience fields. final WindowManagerService mService; final WindowState mWin; - final WindowState mAttachedWindow; + final WindowStateAnimator mAttachedWinAnimator; final WindowAnimator mAnimator; + final AppWindowAnimator mAppAnimator; final Session mSession; final WindowManagerPolicy mPolicy; final Context mContext; + final boolean mIsWallpaper; + + // If this is a universe background window, this is the transformation + // it is applying to the rest of the universe. + final Transformation mUniverseTransform = new Transformation(); // Currently running animation. boolean mAnimating; @@ -138,19 +154,28 @@ class WindowStateAnimator { int mAttrFlags; int mAttrType; - public WindowStateAnimator(final WindowManagerService service, final WindowState win, - final WindowState attachedWindow) { + final int mLayerStack; + + public WindowStateAnimator(final WindowState win) { + final WindowManagerService service = win.mService; + mService = service; + mAnimator = service.mAnimator; + mPolicy = service.mPolicy; + mContext = service.mContext; + final DisplayInfo displayInfo = win.mDisplayContent.getDisplayInfo(); + mAnimDw = displayInfo.appWidth; + mAnimDh = displayInfo.appHeight; + mWin = win; - mAttachedWindow = attachedWindow; - mAnimator = mService.mAnimator; + mAttachedWinAnimator = win.mAttachedWindow == null + ? null : win.mAttachedWindow.mWinAnimator; + mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator; mSession = win.mSession; - mPolicy = mService.mPolicy; - mContext = mService.mContext; mAttrFlags = win.mAttrs.flags; mAttrType = win.mAttrs.type; - mAnimDw = service.mAppDisplayWidth; - mAnimDh = service.mAppDisplayHeight; + mIsWallpaper = win.mIsWallpaper; + mLayerStack = win.mDisplayContent.getDisplay().getLayerStack(); } public void setAnimation(Animation anim) { @@ -177,20 +202,17 @@ class WindowStateAnimator { /** Is the window or its container currently animating? */ boolean isAnimating() { - final WindowState attached = mAttachedWindow; - final AppWindowToken atoken = mWin.mAppToken; return mAnimation != null - || (attached != null && attached.mWinAnimator.mAnimation != null) - || (atoken != null && - (atoken.mAppAnimator.animation != null - || atoken.inPendingTransaction)); + || (mAttachedWinAnimator != null && mAttachedWinAnimator.mAnimation != null) + || (mAppAnimator != null && + (mAppAnimator.animation != null + || mAppAnimator.mAppToken.inPendingTransaction)); } /** Is the window animating the DummyAnimation? */ boolean isDummyAnimation() { - final AppWindowToken atoken = mWin.mAppToken; - return atoken != null - && atoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation; + return mAppAnimator != null + && mAppAnimator.animation == AppWindowAnimator.sDummyAnimation; } /** Is this window currently animating? */ @@ -213,7 +235,7 @@ class WindowStateAnimator { } mTransformation.clear(); final boolean more = mAnimation.getTransformation(currentTime, mTransformation); - if (DEBUG_ANIM) Slog.v( + if (false && DEBUG_ANIM) Slog.v( TAG, "Stepped animation in " + this + ": more=" + more + ", xform=" + mTransformation); return more; @@ -240,8 +262,9 @@ class WindowStateAnimator { " scale=" + mService.mWindowAnimationScale); mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mAnimDw, mAnimDh); - mAnimDw = mService.mAppDisplayWidth; - mAnimDh = mService.mAppDisplayHeight; + final DisplayInfo displayInfo = mWin.mDisplayContent.getDisplayInfo(); + mAnimDw = displayInfo.appWidth; + mAnimDh = displayInfo.appHeight; mAnimation.setStartTime(currentTime); mLocalAnimating = true; mAnimating = true; @@ -257,8 +280,8 @@ class WindowStateAnimator { //WindowManagerService.this.dump(); } mHasLocalTransformation = false; - if ((!mLocalAnimating || mAnimationIsEntrance) && mWin.mAppToken != null - && mWin.mAppToken.mAppAnimator.animation != null) { + if ((!mLocalAnimating || mAnimationIsEntrance) && mAppAnimator != null + && mAppAnimator.animation != null) { // When our app token is animating, we kind-of pretend like // we are as well. Note the mLocalAnimating mAnimationIsEntrance // part of this check means that we will only do this if @@ -305,7 +328,7 @@ class WindowStateAnimator { mAnimLayer = mWin.mLayer; if (mWin.mIsImWindow) { mAnimLayer += mService.mInputMethodAnimLayerAdjustment; - } else if (mWin.mIsWallpaper) { + } else if (mIsWallpaper) { mAnimLayer += mService.mWallpaperAnimLayerAdjustment; } if (DEBUG_LAYERS) Slog.v(TAG, "Stepping win " + this @@ -318,7 +341,7 @@ class WindowStateAnimator { + mWin.mPolicyVisibilityAfterAnim); } mWin.mPolicyVisibility = mWin.mPolicyVisibilityAfterAnim; - mService.mLayoutNeeded = true; + mWin.mDisplayContent.layoutNeeded = true; if (!mWin.mPolicyVisibility) { if (mService.mCurrentFocus == mWin) { mService.mFocusMayChange = true; @@ -342,9 +365,10 @@ class WindowStateAnimator { } finishExit(); - mAnimator.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; + final int displayId = mWin.mDisplayContent.getDisplayId(); + mAnimator.setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM); if (WindowManagerService.DEBUG_LAYOUT_REPEATS) mService.debugLayoutRepeats( - "WindowStateAnimator", mAnimator.mPendingLayoutChanges); + "WindowStateAnimator", mAnimator.mPendingLayoutChanges.get(displayId)); if (mWin.mAppToken != null) { mWin.mAppToken.updateReportedVisibilityLocked(); @@ -460,22 +484,14 @@ class WindowStateAnimator { private final Point mSize = new Point(); private final Rect mWindowCrop = new Rect(); private boolean mShown = false; - private String mName = "Not named"; - - public SurfaceTrace(SurfaceSession s, - int pid, int display, int w, int h, int format, int flags) throws - OutOfResourcesException { - super(s, pid, display, w, h, format, flags); - mSize.set(w, h); - Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by " - + Debug.getCallers(3)); - } + private int mLayerStack; + private String mName; public SurfaceTrace(SurfaceSession s, - int pid, String name, int display, int w, int h, int format, int flags) + String name, int w, int h, int format, int flags) throws OutOfResourcesException { - super(s, pid, name, display, w, h, format, flags); - mName = name; + super(s, name, w, h, format, flags); + mName = name != null ? name : "Not named"; mSize.set(w, h); Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by " + Debug.getCallers(3)); @@ -534,6 +550,13 @@ class WindowStateAnimator { } @Override + public void setLayerStack(int layerStack) { + super.setLayerStack(layerStack); + mLayerStack = layerStack; + Slog.v(SURFACE_TAG, "setLayerStack: " + this + ". Called by " + Debug.getCallers(3)); + } + + @Override public void hide() { super.hide(); mShown = false; @@ -574,7 +597,7 @@ class WindowStateAnimator { @Override public String toString() { return "Surface " + Integer.toHexString(System.identityHashCode(this)) + " " - + mName + ": shown=" + mShown + " layer=" + mLayer + + mName + " (" + mLayerStack + "): shown=" + mShown + " layer=" + mLayer + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y + " " + mSize.x + "x" + mSize.y + " crop=" + mWindowCrop.toShortString(); @@ -592,7 +615,7 @@ class WindowStateAnimator { mService.makeWindowFreezingScreenIfNeededLocked(mWin); - int flags = 0; + int flags = Surface.HIDDEN; final WindowManager.LayoutParams attrs = mWin.mAttrs; if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { @@ -636,14 +659,14 @@ class WindowStateAnimator { } if (DEBUG_SURFACE_TRACE) { mSurface = new SurfaceTrace( - mSession.mSurfaceSession, mSession.mPid, + mSession.mSurfaceSession, attrs.getTitle().toString(), - 0, w, h, format, flags); + w, h, format, flags); } else { mSurface = new Surface( - mSession.mSurfaceSession, mSession.mPid, + mSession.mSurfaceSession, attrs.getTitle().toString(), - 0, w, h, format, flags); + w, h, format, flags); } mWin.mHasSurface = true; if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG, @@ -685,14 +708,10 @@ class WindowStateAnimator { mSurfaceY = mWin.mFrame.top + mWin.mYOffset; mSurface.setPosition(mSurfaceX, mSurfaceY); mSurfaceLayer = mAnimLayer; + mSurface.setLayerStack(mLayerStack); mSurface.setLayer(mAnimLayer); mSurface.setAlpha(0); mSurfaceShown = false; - mSurface.hide(); - if ((mWin.mAttrs.flags&WindowManager.LayoutParams.FLAG_DITHER) != 0) { - if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "DITHER", null); - mSurface.setFlags(Surface.SURFACE_DITHER, Surface.SURFACE_DITHER); - } } catch (RuntimeException e) { Slog.w(TAG, "Error creating surface in " + w, e); mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true); @@ -799,31 +818,28 @@ class WindowStateAnimator { void computeShownFrameLocked() { final boolean selfTransformation = mHasLocalTransformation; Transformation attachedTransformation = - (mAttachedWindow != null && mAttachedWindow.mWinAnimator.mHasLocalTransformation) - ? mAttachedWindow.mWinAnimator.mTransformation : null; - final AppWindowAnimator appAnimator = - mWin.mAppToken == null ? null : mWin.mAppToken.mAppAnimator; - Transformation appTransformation = (appAnimator != null && appAnimator.hasTransformation) - ? appAnimator.transformation : null; + (mAttachedWinAnimator != null && mAttachedWinAnimator.mHasLocalTransformation) + ? mAttachedWinAnimator.mTransformation : null; + Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation) + ? mAppAnimator.transformation : null; // Wallpapers are animated based on the "real" window they // are currently targeting. - if (mWin.mAttrs.type == TYPE_WALLPAPER && mService.mLowerWallpaperTarget == null - && mService.mWallpaperTarget != null) { - if (mService.mWallpaperTarget.mWinAnimator.mHasLocalTransformation && - mService.mWallpaperTarget.mWinAnimator.mAnimation != null && - !mService.mWallpaperTarget.mWinAnimator.mAnimation.getDetachWallpaper()) { - attachedTransformation = mService.mWallpaperTarget.mWinAnimator.mTransformation; + if (mIsWallpaper && mAnimator.mLowerWallpaperTarget == null + && mAnimator.mWallpaperTarget != null) { + final WindowStateAnimator wallpaperAnimator = mAnimator.mWallpaperTarget.mWinAnimator; + if (wallpaperAnimator.mHasLocalTransformation && + wallpaperAnimator.mAnimation != null && + !wallpaperAnimator.mAnimation.getDetachWallpaper()) { + attachedTransformation = wallpaperAnimator.mTransformation; if (WindowManagerService.DEBUG_WALLPAPER && attachedTransformation != null) { Slog.v(TAG, "WP target attached xform: " + attachedTransformation); } } - final AppWindowAnimator wpAppAnimator = mService.mWallpaperTarget.mAppToken == null - ? null : mService.mWallpaperTarget.mAppToken.mAppAnimator; - if (wpAppAnimator != null && - wpAppAnimator.hasTransformation && - wpAppAnimator.animation != null && - !wpAppAnimator.animation.getDetachWallpaper()) { + final AppWindowAnimator wpAppAnimator = mAnimator.mWpAppAnimator; + if (wpAppAnimator != null && wpAppAnimator.hasTransformation + && wpAppAnimator.animation != null + && !wpAppAnimator.animation.getDetachWallpaper()) { appTransformation = wpAppAnimator.transformation; if (WindowManagerService.DEBUG_WALLPAPER && appTransformation != null) { Slog.v(TAG, "WP target app xform: " + appTransformation); @@ -831,8 +847,11 @@ class WindowStateAnimator { } } - final boolean screenAnimation = mService.mAnimator.mScreenRotationAnimation != null - && mService.mAnimator.mScreenRotationAnimation.isAnimating(); + final int displayId = mWin.getDisplayId(); + final ScreenRotationAnimation screenRotationAnimation = + mAnimator.getScreenRotationAnimationLocked(displayId); + final boolean screenAnimation = + screenRotationAnimation != null && screenRotationAnimation.isAnimating(); if (selfTransformation || attachedTransformation != null || appTransformation != null || screenAnimation) { // cache often used attributes locally @@ -870,9 +889,16 @@ class WindowStateAnimator { if (appTransformation != null) { tmpMatrix.postConcat(appTransformation.getMatrix()); } + if (mAnimator.mUniverseBackground != null) { + tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix()); + } if (screenAnimation) { - tmpMatrix.postConcat( - mService.mAnimator.mScreenRotationAnimation.getEnterTransformation().getMatrix()); + tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix()); + } + MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked(); + if (spec != null && !spec.isNop()) { + tmpMatrix.postScale(spec.mScale, spec.mScale); + tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY); } // "convert" it into SurfaceFlinger's format @@ -913,24 +939,27 @@ class WindowStateAnimator { if (appTransformation != null) { mShownAlpha *= appTransformation.getAlpha(); } + if (mAnimator.mUniverseBackground != null) { + mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha(); + } if (screenAnimation) { - mShownAlpha *= - mService.mAnimator.mScreenRotationAnimation.getEnterTransformation().getAlpha(); + mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha(); } } else { //Slog.i(TAG, "Not applying alpha transform"); } - if (WindowManagerService.localLOGV && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v( - TAG, "computeShownFrameLocked: Animating " + this + - " mAlpha=" + mAlpha + - " self=" + (selfTransformation ? mTransformation.getAlpha() : "null") + - " attached=" + (attachedTransformation == null ? "null" : attachedTransformation.getAlpha()) + - " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha()) + - " screen=" + (screenAnimation ? mService.mAnimator.mScreenRotationAnimation.getEnterTransformation().getAlpha() - : "null")); + if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV) + && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v( + TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha + + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null") + + " attached=" + (attachedTransformation == null ? + "null" : attachedTransformation.getAlpha()) + + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha()) + + " screen=" + (screenAnimation ? + screenRotationAnimation.getEnterTransformation().getAlpha() : "null")); return; - } else if (mWin.mIsWallpaper && + } else if (mIsWallpaper && (mAnimator.mPendingActions & WindowAnimator.WALLPAPER_ACTION_PENDING) != 0) { return; } @@ -938,53 +967,113 @@ class WindowStateAnimator { if (WindowManagerService.localLOGV) Slog.v( TAG, "computeShownFrameLocked: " + this + " not attached, mAlpha=" + mAlpha); - mWin.mShownFrame.set(mWin.mFrame); - if (mWin.mXOffset != 0 || mWin.mYOffset != 0) { - mWin.mShownFrame.offset(mWin.mXOffset, mWin.mYOffset); - } - mShownAlpha = mAlpha; - mHaveMatrix = false; - mDsDx = mWin.mGlobalScale; - mDtDx = 0; - mDsDy = 0; - mDtDy = mWin.mGlobalScale; + + final boolean applyUniverseTransformation = (mAnimator.mUniverseBackground != null + && mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND + && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer); + MagnificationSpec spec = mWin.getWindowMagnificationSpecLocked(); + if (applyUniverseTransformation || spec != null) { + final Rect frame = mWin.mFrame; + final float tmpFloats[] = mService.mTmpFloats; + final Matrix tmpMatrix = mWin.mTmpMatrix; + + tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale); + tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset); + + if (applyUniverseTransformation) { + tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix()); + } + + if (spec != null && !spec.isNop()) { + tmpMatrix.postScale(spec.mScale, spec.mScale); + tmpMatrix.postTranslate(spec.mOffsetX, spec.mOffsetY); + } + + tmpMatrix.getValues(tmpFloats); + + mHaveMatrix = true; + mDsDx = tmpFloats[Matrix.MSCALE_X]; + mDtDx = tmpFloats[Matrix.MSKEW_Y]; + mDsDy = tmpFloats[Matrix.MSKEW_X]; + mDtDy = tmpFloats[Matrix.MSCALE_Y]; + float x = tmpFloats[Matrix.MTRANS_X]; + float y = tmpFloats[Matrix.MTRANS_Y]; + int w = frame.width(); + int h = frame.height(); + mWin.mShownFrame.set(x, y, x + w, y + h); + + mShownAlpha = mAlpha; + if (applyUniverseTransformation) { + mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha(); + } + } else { + mWin.mShownFrame.set(mWin.mFrame); + if (mWin.mXOffset != 0 || mWin.mYOffset != 0) { + mWin.mShownFrame.offset(mWin.mXOffset, mWin.mYOffset); + } + mShownAlpha = mAlpha; + mHaveMatrix = false; + mDsDx = mWin.mGlobalScale; + mDtDx = 0; + mDsDy = 0; + mDtDy = mWin.mGlobalScale; + } + } + + void applyDecorRect(final Rect decorRect) { + final WindowState w = mWin; + // Compute the offset of the window in relation to the decor rect. + final int offX = w.mXOffset + w.mFrame.left; + final int offY = w.mYOffset + w.mFrame.top; + // Initialize the decor rect to the entire frame. + w.mSystemDecorRect.set(0, 0, w.mFrame.width(), w.mFrame.height()); + // Intersect with the decor rect, offsetted by window position. + w.mSystemDecorRect.intersect(decorRect.left-offX, decorRect.top-offY, + decorRect.right-offX, decorRect.bottom-offY); + // If size compatibility is being applied to the window, the + // surface is scaled relative to the screen. Also apply this + // scaling to the crop rect. We aren't using the standard rect + // scale function because we want to round things to make the crop + // always round to a larger rect to ensure we don't crop too + // much and hide part of the window that should be seen. + if (w.mEnforceSizeCompat && w.mInvGlobalScale != 1.0f) { + final float scale = w.mInvGlobalScale; + w.mSystemDecorRect.left = (int) (w.mSystemDecorRect.left * scale - 0.5f); + w.mSystemDecorRect.top = (int) (w.mSystemDecorRect.top * scale - 0.5f); + w.mSystemDecorRect.right = (int) ((w.mSystemDecorRect.right+1) * scale - 0.5f); + w.mSystemDecorRect.bottom = (int) ((w.mSystemDecorRect.bottom+1) * scale - 0.5f); + } } void updateSurfaceWindowCrop(final boolean recoveringMemory) { final WindowState w = mWin; + DisplayInfo displayInfo = w.mDisplayContent.getDisplayInfo(); // Need to recompute a new system decor rect each time. if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { // Currently can't do this cropping for scaled windows. We'll // just keep the crop rect the same as the source surface. w.mSystemDecorRect.set(0, 0, w.mRequestedWidth, w.mRequestedHeight); + } else if (!w.isDefaultDisplay()) { + // On a different display is easy, just use the entire display. + w.mSystemDecorRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); } else if (w.mLayer >= mService.mSystemDecorLayer) { // Above the decor layer is easy, just use the entire window. + // Unless we have a universe background... in which case all the + // windows need to be cropped by the screen, so they don't cover + // the universe background. + if (mAnimator.mUniverseBackground == null) { + w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), + w.mCompatFrame.height()); + } else { + applyDecorRect(mService.mScreenRect); + } + } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) { + // The universe background isn't cropped. w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height()); } else { - final Rect decorRect = mService.mSystemDecorRect; - // Compute the offset of the window in relation to the decor rect. - final int offX = w.mXOffset + w.mFrame.left; - final int offY = w.mYOffset + w.mFrame.top; - // Initialize the decor rect to the entire frame. - w.mSystemDecorRect.set(0, 0, w.mFrame.width(), w.mFrame.height()); - // Intersect with the decor rect, offsetted by window position. - w.mSystemDecorRect.intersect(decorRect.left-offX, decorRect.top-offY, - decorRect.right-offX, decorRect.bottom-offY); - // If size compatibility is being applied to the window, the - // surface is scaled relative to the screen. Also apply this - // scaling to the crop rect. We aren't using the standard rect - // scale function because we want to round things to make the crop - // always round to a larger rect to ensure we don't crop too - // much and hide part of the window that should be seen. - if (w.mEnforceSizeCompat && w.mInvGlobalScale != 1.0f) { - final float scale = w.mInvGlobalScale; - w.mSystemDecorRect.left = (int) (w.mSystemDecorRect.left * scale - 0.5f); - w.mSystemDecorRect.top = (int) (w.mSystemDecorRect.top * scale - 0.5f); - w.mSystemDecorRect.right = (int) ((w.mSystemDecorRect.right+1) * scale - 0.5f); - w.mSystemDecorRect.bottom = (int) ((w.mSystemDecorRect.bottom+1) * scale - 0.5f); - } + applyDecorRect(mService.mSystemDecorRect); } if (!w.mSystemDecorRect.equals(w.mLastSystemDecorRect)) { @@ -1003,7 +1092,7 @@ class WindowStateAnimator { } } - void setSurfaceBoundaries(final boolean recoveringMemory) { + void setSurfaceBoundariesLocked(final boolean recoveringMemory) { final WindowState w = mWin; int width, height; if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { @@ -1053,11 +1142,13 @@ class WindowStateAnimator { "SIZE " + width + "x" + height, null); mSurfaceResized = true; mSurface.setSize(width, height); - mAnimator.mPendingLayoutChanges |= - WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + final int displayId = w.mDisplayContent.getDisplayId(); + mAnimator.setPendingLayoutChanges(displayId, + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) { - mAnimator.startDimming(this, w.mExiting ? 0 : w.mAttrs.dimAmount, - mService.mAppDisplayWidth, mService.mAppDisplayHeight); + final DisplayInfo displayInfo = mWin.mDisplayContent.getDisplayInfo(); + mService.startDimmingLocked(this, w.mExiting ? 0 : w.mAttrs.dimAmount, + displayInfo.appWidth, displayInfo.appHeight); } } catch (RuntimeException e) { // If something goes wrong with the surface (such @@ -1090,9 +1181,9 @@ class WindowStateAnimator { computeShownFrameLocked(); - setSurfaceBoundaries(recoveringMemory); + setSurfaceBoundariesLocked(recoveringMemory); - if (mWin.mIsWallpaper && !mWin.mWallpaperVisible) { + if (mIsWallpaper && !mWin.mWallpaperVisible) { // Wallpaper is no longer visible and there is no wp target => hide it. hide(); } else if (w.mAttachedHidden || !w.isReadyForDisplay()) { @@ -1151,7 +1242,7 @@ class WindowStateAnimator { + " during relayout"); if (showSurfaceRobustlyLocked()) { mLastHidden = false; - if (w.mIsWallpaper) { + if (mIsWallpaper) { mService.dispatchWallpaperVisibility(w, true); } } else { @@ -1169,8 +1260,8 @@ class WindowStateAnimator { } } } else { - if (DEBUG_ANIM) { - // Slog.v(TAG, "prepareSurface: No changes in animation for " + mWin); + if (DEBUG_ANIM && isAnimating()) { + Slog.v(TAG, "prepareSurface: No changes in animation for " + this); } displayed = true; } @@ -1178,7 +1269,7 @@ class WindowStateAnimator { if (displayed) { if (w.mOrientationChanging) { if (!w.isDrawnLw()) { - mAnimator.mBulkUpdateParams |= CLEAR_ORIENTATION_CHANGE_COMPLETE; + mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE; if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation continue waiting for draw in " + w); } else { @@ -1239,6 +1330,11 @@ class WindowStateAnimator { // This must be called while inside a transaction. boolean performShowLocked() { + if (mWin.isOtherUsersAppWindow()) { + Slog.w(TAG, "Current user " + mService.mCurrentUserId + " trying to display " + + this + ", type " + mWin.mAttrs.type + ", belonging to " + mWin.mOwnerUid); + return false; + } if (DEBUG_VISIBILITY || (DEBUG_STARTING_WINDOW && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING)) { RuntimeException e = null; @@ -1258,7 +1354,7 @@ class WindowStateAnimator { + (mWin.mAppToken != null ? mWin.mAppToken.hidden : false) + " animating=" + mAnimating + " tok animating=" - + (mWin.mAppToken != null ? mWin.mAppToken.mAppAnimator.animating : false), e); + + (mAppAnimator != null ? mAppAnimator.animating : false), e); } if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) { if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION) @@ -1274,7 +1370,7 @@ class WindowStateAnimator { + (mWin.mAppToken != null ? mWin.mAppToken.hidden : false) + " animating=" + mAnimating + " tok animating=" - + (mWin.mAppToken != null ? mWin.mAppToken.mAppAnimator.animating : false)); + + (mAppAnimator != null ? mAppAnimator.animating : false)); } mService.enableScreenIfNeededLocked(); @@ -1286,7 +1382,7 @@ class WindowStateAnimator { if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this); mDrawState = HAS_DRAWN; - mService.scheduleAnimationLocked(); + mService.updateLayoutToAnimationLocked(); int i = mWin.mChildWindows.size(); while (i > 0) { @@ -1301,7 +1397,7 @@ class WindowStateAnimator { // do a layout. If called from within the transaction // loop, this will cause it to restart with a new // layout. - mService.mLayoutNeeded = true; + c.mDisplayContent.layoutNeeded = true; } } } @@ -1370,8 +1466,8 @@ class WindowStateAnimator { } else { transit = WindowManagerPolicy.TRANSIT_SHOW; } - applyAnimationLocked(transit, true); + mService.scheduleNotifyWindowTranstionIfNeededLocked(mWin, transit); } // TODO(cmautner): Move back to WindowState? @@ -1483,6 +1579,11 @@ class WindowStateAnimator { pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized); pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred); } + if (mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) { + pw.print(prefix); pw.print("mUniverseTransform="); + mUniverseTransform.printShortString(pw); + pw.println(); + } if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha); pw.print(" mAlpha="); pw.print(mAlpha); |
