diff options
Diffstat (limited to 'services/java')
95 files changed, 21770 insertions, 11967 deletions
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index b8c44d9..32ac8e1 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -34,9 +34,9 @@ import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.WorkSource; import android.text.TextUtils; import android.text.format.Time; -import android.util.EventLog; import android.util.Slog; import android.util.TimeUtils; @@ -50,6 +50,7 @@ import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.Map; import java.util.TimeZone; @@ -89,6 +90,7 @@ class AlarmManagerService extends IAlarmManager.Stub { private int mDescriptor; private int mBroadcastRefCount = 0; private PowerManager.WakeLock mWakeLock; + private LinkedList<PendingIntent> mInFlight = new LinkedList<PendingIntent>(); private final AlarmThread mWaitThread = new AlarmThread(); private final AlarmHandler mHandler = new AlarmHandler(); private ClockReceiver mClockReceiver; @@ -477,7 +479,8 @@ class AlarmManagerService extends IAlarmManager.Stub { : bs.filterStats.entrySet()) { pw.print(" "); pw.print(fe.getValue().count); pw.print(" alarms: "); - pw.println(fe.getKey().getIntent().toShortString(false, true, false)); + pw.println(fe.getKey().getIntent().toShortString( + false, true, false, true)); } } } @@ -667,10 +670,12 @@ class AlarmManagerService extends IAlarmManager.Stub { Intent.EXTRA_ALARM_COUNT, alarm.count), mResultReceiver, mHandler); - // we have an active broadcast so stay awake. + // we have an active broadcast so stay awake. if (mBroadcastRefCount == 0) { + setWakelockWorkSource(alarm.operation); mWakeLock.acquire(); } + mInFlight.add(alarm.operation); mBroadcastRefCount++; BroadcastStats bs = getStatsLocked(alarm.operation); @@ -699,7 +704,22 @@ class AlarmManagerService extends IAlarmManager.Stub { } } } - + + void setWakelockWorkSource(PendingIntent pi) { + try { + final int uid = ActivityManagerNative.getDefault() + .getUidForIntentSender(pi.getTarget()); + if (uid >= 0) { + mWakeLock.setWorkSource(new WorkSource(uid)); + return; + } + } catch (Exception e) { + } + + // Something went wrong; fall back to attributing the lock to the OS + mWakeLock.setWorkSource(null); + } + private class AlarmHandler extends Handler { public static final int ALARM_EVENT = 1; public static final int MINUTE_CHANGE_EVENT = 2; @@ -875,9 +895,20 @@ class AlarmManagerService extends IAlarmManager.Stub { fs.count++; } } + mInFlight.removeFirst(); mBroadcastRefCount--; if (mBroadcastRefCount == 0) { mWakeLock.release(); + } else { + // the next of our alarms is now in flight. reattribute the wakelock. + final PendingIntent nowInFlight = mInFlight.peekFirst(); + if (nowInFlight != null) { + setWakelockWorkSource(nowInFlight); + } else { + // should never happen + Slog.e(TAG, "Alarm wakelock still held but sent queue empty"); + mWakeLock.setWorkSource(null); + } } } } diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 4f81178..7e71b08 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -24,67 +24,36 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.Intent.FilterComparison; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; -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.util.SparseArray; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; -import com.android.internal.os.AtomicFile; -import com.android.internal.util.FastXmlSerializer; import com.android.internal.widget.IRemoteViewsAdapterConnection; -import com.android.internal.widget.IRemoteViewsFactory; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -import java.io.File; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; -import java.util.Set; + +/** + * Redirects calls to this service to the instance of the service for the appropriate user. + */ class AppWidgetService extends IAppWidgetService.Stub { private static final String TAG = "AppWidgetService"; - private static final String SETTINGS_FILENAME = "appwidgets.xml"; - private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes - /* * When identifying a Host or Provider based on the calling process, use the uid field. * When identifying a Host or Provider based on a package manager broadcast, use the @@ -125,11 +94,9 @@ class AppWidgetService extends IAppWidgetService.Stub * globally and may lead us to leak AppWidgetService instances (if there were more than one). */ static class ServiceConnectionProxy implements ServiceConnection { - private final Pair<Integer, Intent.FilterComparison> mKey; private final IBinder mConnectionCb; ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) { - mKey = key; mConnectionCb = connectionCb; } public void onServiceConnected(ComponentName name, IBinder service) { @@ -155,13 +122,6 @@ class AppWidgetService extends IAppWidgetService.Stub } } - // Manages active connections to RemoteViewsServices - private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> - mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>(); - // Manages persistent references to RemoteViewsServices from different App Widgets - private final HashMap<FilterComparison, HashSet<Integer>> - mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>(); - Context mContext; Locale mLocale; PackageManager mPackageManager; @@ -171,35 +131,32 @@ class AppWidgetService extends IAppWidgetService.Stub final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>(); ArrayList<Host> mHosts = new ArrayList<Host>(); boolean mSafeMode; - boolean mStateLoaded; - // These are for debugging only -- widgets are going missing in some rare instances - ArrayList<Provider> mDeletedProviders = new ArrayList<Provider>(); - ArrayList<Host> mDeletedHosts = new ArrayList<Host>(); + + private final SparseArray<AppWidgetServiceImpl> mAppWidgetServices; AppWidgetService(Context context) { mContext = context; - mPackageManager = context.getPackageManager(); - mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5); + AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0); + mAppWidgetServices.append(0, primary); } public void systemReady(boolean safeMode) { mSafeMode = safeMode; - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - } + mAppWidgetServices.get(0).systemReady(safeMode); // Register for the boot completed broadcast, so we can send the - // ENABLE broacasts. If we try to send them now, they time out, + // 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, 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.registerReceiver(mBroadcastReceiver, new IntentFilter( + Intent.ACTION_CONFIGURATION_CHANGED), null, null); // Register for broadcasts about package install, etc., so we can // update the provider list. @@ -214,844 +171,123 @@ class AppWidgetService extends IAppWidgetService.Stub sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); mContext.registerReceiver(mBroadcastReceiver, sdFilter); - } - - private void ensureStateLoadedLocked() { - if (!mStateLoaded) { - loadAppWidgetList(); - loadStateLocked(); - mStateLoaded = true; - } - } - - private void dumpProvider(Provider p, int index, PrintWriter pw) { - AppWidgetProviderInfo info = p.info; - pw.print(" ["); pw.print(index); pw.print("] provider "); - pw.print(info.provider.flattenToShortString()); - pw.println(':'); - pw.print(" min=("); pw.print(info.minWidth); - pw.print("x"); pw.print(info.minHeight); - pw.print(") minResize=("); pw.print(info.minResizeWidth); - pw.print("x"); pw.print(info.minResizeHeight); - pw.print(") updatePeriodMillis="); - pw.print(info.updatePeriodMillis); - pw.print(" resizeMode="); - pw.print(info.resizeMode); - pw.print(" autoAdvanceViewId="); - pw.print(info.autoAdvanceViewId); - pw.print(" initialLayout=#"); - pw.print(Integer.toHexString(info.initialLayout)); - pw.print(" zombie="); pw.println(p.zombie); - } - - private void dumpHost(Host host, int index, PrintWriter pw) { - pw.print(" ["); pw.print(index); pw.print("] hostId="); - pw.print(host.hostId); pw.print(' '); - pw.print(host.packageName); pw.print('/'); - pw.print(host.uid); pw.println(':'); - pw.print(" callbacks="); pw.println(host.callbacks); - pw.print(" instances.size="); pw.print(host.instances.size()); - pw.print(" zombie="); pw.println(host.zombie); - } - - private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) { - pw.print(" ["); pw.print(index); pw.print("] id="); - pw.println(id.appWidgetId); - pw.print(" hostId="); - pw.print(id.host.hostId); pw.print(' '); - pw.print(id.host.packageName); pw.print('/'); - pw.println(id.host.uid); - if (id.provider != null) { - pw.print(" provider="); - pw.println(id.provider.info.provider.flattenToShortString()); - } - if (id.host != null) { - pw.print(" host.callbacks="); pw.println(id.host.callbacks); - } - if (id.views != null) { - pw.print(" views="); pw.println(id.views); - } - } - - @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 from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - synchronized (mAppWidgetIds) { - int N = mInstalledProviders.size(); - pw.println("Providers:"); - for (int i=0; i<N; i++) { - dumpProvider(mInstalledProviders.get(i), i, pw); - } - - N = mAppWidgetIds.size(); - pw.println(" "); - pw.println("AppWidgetIds:"); - for (int i=0; i<N; i++) { - dumpAppWidgetId(mAppWidgetIds.get(i), i, pw); - } - - N = mHosts.size(); - pw.println(" "); - pw.println("Hosts:"); - for (int i=0; i<N; i++) { - dumpHost(mHosts.get(i), i, pw); - } - - N = mDeletedProviders.size(); - pw.println(" "); - pw.println("Deleted Providers:"); - for (int i=0; i<N; i++) { - dumpProvider(mDeletedProviders.get(i), i, pw); - } - - N = mDeletedHosts.size(); - pw.println(" "); - pw.println("Deleted Hosts:"); - for (int i=0; i<N; i++) { - dumpHost(mDeletedHosts.get(i), i, pw); - } - } - } - - public int allocateAppWidgetId(String packageName, int hostId) { - int callingUid = enforceCallingUid(packageName); - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int appWidgetId = mNextAppWidgetId++; - - Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); - - AppWidgetId id = new AppWidgetId(); - id.appWidgetId = appWidgetId; - id.host = host; - - host.instances.add(id); - mAppWidgetIds.add(id); - saveStateLocked(); - - return appWidgetId; - } - } - - public void deleteAppWidgetId(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null) { - deleteAppWidgetLocked(id); - saveStateLocked(); - } - } - } - - public void deleteHost(int hostId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int callingUid = getCallingUid(); - Host host = lookupHostLocked(callingUid, hostId); - if (host != null) { - deleteHostLocked(host); - saveStateLocked(); - } - } - } - - public void deleteAllHosts() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int callingUid = getCallingUid(); - final int N = mHosts.size(); - boolean changed = false; - for (int i=N-1; i>=0; i--) { - Host host = mHosts.get(i); - if (host.uid == callingUid) { - deleteHostLocked(host); - changed = true; - } - } - if (changed) { - saveStateLocked(); - } - } - } - - void deleteHostLocked(Host host) { - final int N = host.instances.size(); - for (int i=N-1; i>=0; i--) { - AppWidgetId id = host.instances.get(i); - deleteAppWidgetLocked(id); - } - host.instances.clear(); - mHosts.remove(host); - mDeletedHosts.add(host); - // it's gone or going away, abruptly drop the callback connection - host.callbacks = null; - } - - void deleteAppWidgetLocked(AppWidgetId id) { - // We first unbind all services that are bound to this id - unbindAppWidgetRemoteViewsServicesLocked(id); - - Host host = id.host; - host.instances.remove(id); - pruneHostLocked(host); - - mAppWidgetIds.remove(id); - - Provider p = id.provider; - if (p != null) { - p.instances.remove(id); - if (!p.zombie) { - // send the broacast saying that this appWidgetId has been deleted - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); - intent.setComponent(p.info.provider); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); - mContext.sendBroadcast(intent); - if (p.instances.size() == 0) { - // cancel the future updates - cancelBroadcasts(p); - - // 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); - } - } - } - } - - void cancelBroadcasts(Provider p) { - if (p.broadcast != null) { - mAlarmManager.cancel(p.broadcast); - long token = Binder.clearCallingIdentity(); - try { - p.broadcast.cancel(); - } finally { - Binder.restoreCallingIdentity(token); - } - p.broadcast = null; - } - } - - public void bindAppWidgetId(int appWidgetId, ComponentName provider) { - mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, - "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider); - - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - if (id.provider != null) { - throw new IllegalArgumentException("appWidgetId " + appWidgetId + " already bound to " - + id.provider.info.provider); - } - Provider p = lookupProviderLocked(provider); - if (p == null) { - throw new IllegalArgumentException("not a appwidget provider: " + provider); - } - if (p.zombie) { - throw new IllegalArgumentException("can't bind to a 3rd party provider in" - + " safe mode: " + provider); - } - - id.provider = p; - p.instances.add(id); - int instancesSize = p.instances.size(); - if (instancesSize == 1) { - // tell the provider that it's ready - sendEnableIntentLocked(p); - } - - // send an update now -- We need this update now, and just for this appWidgetId. - // It's less critical when the next one happens, so when we schdule the next one, - // we add updatePeriodMillis to its start time. That time will have some slop, - // but that's okay. - sendUpdateIntentLocked(p, new int[] { appWidgetId }); - - // schedule the future updates - registerForBroadcastsLocked(p, getAppWidgetIds(p)); - saveStateLocked(); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Binds to a specific RemoteViewsService - public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - final ComponentName componentName = intent.getComponent(); - try { - final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, - PackageManager.GET_PERMISSIONS); - if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { - throw new SecurityException("Selected service does not require " - + android.Manifest.permission.BIND_REMOTEVIEWS - + ": " + componentName); - } - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException("Unknown component " + componentName); - } - - // If there is already a connection made for this service intent, then disconnect from - // that first. (This does not allow multiple connections to the same service under - // the same key) - ServiceConnectionProxy conn = null; - FilterComparison fc = new FilterComparison(intent); - Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc); - if (mBoundRemoteViewsServices.containsKey(key)) { - conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - mBoundRemoteViewsServices.remove(key); - } - - // Bind to the RemoteViewsService (which will trigger a callback to the - // RemoteViewsAdapter.onServiceConnected()) - final long token = Binder.clearCallingIdentity(); - try { - conn = new ServiceConnectionProxy(key, connection); - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - mBoundRemoteViewsServices.put(key, conn); - } finally { - Binder.restoreCallingIdentity(token); - } - - // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine - // when we can call back to the RemoteViewsService later to destroy associated - // factories. - incrementAppWidgetServiceRefCount(appWidgetId, fc); - } - } - - // Unbinds from a specific RemoteViewsService - public void unbindRemoteViewsService(int appWidgetId, Intent intent) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - // Unbind from the RemoteViewsService (which will trigger a callback to the bound - // RemoteViewsAdapter) - Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, - new FilterComparison(intent)); - if (mBoundRemoteViewsServices.containsKey(key)) { - // We don't need to use the appWidgetId until after we are sure there is something - // to unbind. Note that this may mask certain issues with apps calling unbind() - // more than necessary. - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - - ServiceConnectionProxy conn = - (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - mBoundRemoteViewsServices.remove(key); - } else { - Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound"); - } - } - } - - // Unbinds from a RemoteViewsService when we delete an app widget - private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { - int appWidgetId = id.appWidgetId; - // Unbind all connections to Services bound to this AppWidgetId - Iterator<Pair<Integer, Intent.FilterComparison>> it = - mBoundRemoteViewsServices.keySet().iterator(); - while (it.hasNext()) { - final Pair<Integer, Intent.FilterComparison> key = it.next(); - if (key.first.intValue() == appWidgetId) { - final ServiceConnectionProxy conn = (ServiceConnectionProxy) - mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - it.remove(); - } - } - - // Check if we need to destroy any services (if no other app widgets are - // referencing the same service) - decrementAppWidgetServiceRefCount(appWidgetId); - } - - // Destroys the cached factory on the RemoteViewsService's side related to the specified intent - private void destroyRemoteViewsService(final Intent intent) { - final ServiceConnection conn = new ServiceConnection() { + IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(new BroadcastReceiver() { @Override - public void onServiceConnected(ComponentName name, IBinder service) { - final IRemoteViewsFactory cb = - IRemoteViewsFactory.Stub.asInterface(service); - try { - cb.onDestroy(intent); - } catch (RemoteException e) { - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } - mContext.unbindService(this); + public void onReceive(Context context, Intent intent) { + onUserRemoved(intent.getIntExtra(Intent.EXTRA_USERID, -1)); } - @Override - public void onServiceDisconnected(android.content.ComponentName name) { - // Do nothing - } - }; - - // Bind to the service and remove the static intent->factory mapping in the - // RemoteViewsService. - final long token = Binder.clearCallingIdentity(); - try { - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - // Adds to the ref-count for a given RemoteViewsService intent - private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) { - HashSet<Integer> appWidgetIds = null; - if (mRemoteViewsServicesAppWidgets.containsKey(fc)) { - appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc); - } else { - appWidgetIds = new HashSet<Integer>(); - mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds); - } - appWidgetIds.add(appWidgetId); + }, userFilter); } - // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if - // the ref-count reaches zero. - private void decrementAppWidgetServiceRefCount(int appWidgetId) { - Iterator<FilterComparison> it = - mRemoteViewsServicesAppWidgets.keySet().iterator(); - while (it.hasNext()) { - final FilterComparison key = it.next(); - final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key); - if (ids.remove(appWidgetId)) { - // If we have removed the last app widget referencing this service, then we - // should destroy it and remove it from this set - if (ids.isEmpty()) { - destroyRemoteViewsService(key.getIntent()); - it.remove(); - } - } - } - } - - public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null && id.provider != null && !id.provider.zombie) { - return id.provider.info; - } - return null; - } - } - - public RemoteViews getAppWidgetViews(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null) { - return id.views; - } - return null; - } - } - - public List<AppWidgetProviderInfo> getInstalledProviders() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - final int N = mInstalledProviders.size(); - ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (!p.zombie) { - result.add(p.info); - } - } - return result; - } - } - - public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - updateAppWidgetInstanceLocked(id, views); - } - } - } - - public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - updateAppWidgetInstanceLocked(id, views, true); - } - } + @Override + public int allocateAppWidgetId(String packageName, int hostId) throws RemoteException { + return getImplForUser().allocateAppWidgetId(packageName, hostId); } - - public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); - } - } + + @Override + public void deleteAppWidgetId(int appWidgetId) throws RemoteException { + getImplForUser().deleteAppWidgetId(appWidgetId); } - public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Provider p = lookupProviderLocked(provider); - if (p == null) { - Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider); - return; - } - ArrayList<AppWidgetId> instances = p.instances; - final int callingUid = getCallingUid(); - final int N = instances.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = instances.get(i); - if (canAccessAppWidgetId(id, callingUid)) { - updateAppWidgetInstanceLocked(id, views); - } - } - } + @Override + public void deleteHost(int hostId) throws RemoteException { + getImplForUser().deleteHost(hostId); } - void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { - updateAppWidgetInstanceLocked(id, views, false); + @Override + public void deleteAllHosts() throws RemoteException { + getImplForUser().deleteAllHosts(); } - void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { - // allow for stale appWidgetIds and other badness - // lookup also checks that the calling process can access the appWidgetId - // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) - if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - - // We do not want to save this RemoteViews - if (!isPartialUpdate) id.views = views; - - // is anyone listening? - if (id.host.callbacks != null) { - try { - // the lock is held, but this is a oneway call - id.host.callbacks.updateAppWidget(id.appWidgetId, views); - } catch (RemoteException e) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this instance. - id.host.callbacks = null; - } - } - } + @Override + public void bindAppWidgetId(int appWidgetId, ComponentName provider) throws RemoteException { + getImplForUser().bindAppWidgetId(appWidgetId, provider); } - void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { - // allow for stale appWidgetIds and other badness - // lookup also checks that the calling process can access the appWidgetId - // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) - if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - // is anyone listening? - if (id.host.callbacks != null) { - try { - // the lock is held, but this is a oneway call - id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); - } catch (RemoteException e) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this instance. - id.host.callbacks = null; - } - } - - // If the host is unavailable, then we call the associated - // RemoteViewsFactory.onDataSetChanged() directly - if (id.host.callbacks == null) { - Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet(); - for (FilterComparison key : keys) { - if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) { - Intent intent = key.getIntent(); - - final ServiceConnection conn = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - IRemoteViewsFactory cb = - IRemoteViewsFactory.Stub.asInterface(service); - try { - cb.onDataSetChangedAsync(); - } catch (RemoteException e) { - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } - mContext.unbindService(this); - } - @Override - public void onServiceDisconnected(android.content.ComponentName name) { - // Do nothing - } - }; - - // Bind to the service and call onDataSetChanged() - final long token = Binder.clearCallingIdentity(); - try { - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - } - } + @Override + public boolean bindAppWidgetIdIfAllowed( + String packageName, int appWidgetId, ComponentName provider) throws RemoteException { + return getImplForUser().bindAppWidgetIdIfAllowed(packageName, appWidgetId, provider); } - public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, - List<RemoteViews> updatedViews) { - int callingUid = enforceCallingUid(packageName); - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); - host.callbacks = callbacks; - - updatedViews.clear(); - - ArrayList<AppWidgetId> instances = host.instances; - int N = instances.size(); - int[] updatedIds = new int[N]; - for (int i=0; i<N; i++) { - AppWidgetId id = instances.get(i); - updatedIds[i] = id.appWidgetId; - updatedViews.add(id.views); - } - return updatedIds; - } + @Override + public boolean hasBindAppWidgetPermission(String packageName) throws RemoteException { + return getImplForUser().hasBindAppWidgetPermission(packageName); } - public void stopListening(int hostId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Host host = lookupHostLocked(getCallingUid(), hostId); - if (host != null) { - host.callbacks = null; - pruneHostLocked(host); - } - } + @Override + public void setBindAppWidgetPermission(String packageName, boolean permission) + throws RemoteException { + getImplForUser().setBindAppWidgetPermission(packageName, permission); } - boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) { - if (id.host.uid == callingUid) { - // Apps hosting the AppWidget have access to it. - return true; - } - if (id.provider != null && id.provider.uid == callingUid) { - // Apps providing the AppWidget have access to it (if the appWidgetId has been bound) - return true; - } - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) - == PackageManager.PERMISSION_GRANTED) { - // Apps that can bind have access to all appWidgetIds. - return true; - } - // Nobody else can access it. - return false; + @Override + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) + throws RemoteException { + getImplForUser().bindRemoteViewsService(appWidgetId, intent, connection); } - AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) { - int callingUid = getCallingUid(); - final int N = mAppWidgetIds.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = mAppWidgetIds.get(i); - if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) { - return id; - } - } - return null; + @Override + public int[] startListening(IAppWidgetHost host, String packageName, int hostId, + List<RemoteViews> updatedViews) throws RemoteException { + return getImplForUser().startListening(host, packageName, hostId, updatedViews); } - Provider lookupProviderLocked(ComponentName provider) { - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.info.provider.equals(provider)) { - return p; - } - } - return null; - } + public void onUserRemoved(int userId) { + AppWidgetServiceImpl impl = mAppWidgetServices.get(userId); + if (userId < 1) return; - Host lookupHostLocked(int uid, int hostId) { - final int N = mHosts.size(); - for (int i=0; i<N; i++) { - Host h = mHosts.get(i); - if (h.uid == uid && h.hostId == hostId) { - return h; - } + if (impl == null) { + AppWidgetServiceImpl.getSettingsFile(userId).delete(); + } else { + impl.onUserRemoved(); } - return null; } - Host lookupOrAddHostLocked(int uid, String packageName, int hostId) { - final int N = mHosts.size(); - for (int i=0; i<N; i++) { - Host h = mHosts.get(i); - if (h.hostId == hostId && h.packageName.equals(packageName)) { - return h; - } + private AppWidgetServiceImpl getImplForUser() { + final int userId = Binder.getOrigCallingUser(); + AppWidgetServiceImpl service = mAppWidgetServices.get(userId); + if (service == null) { + Slog.e(TAG, "Unable to find AppWidgetServiceImpl for the current user"); + // TODO: Verify that it's a valid user + service = new AppWidgetServiceImpl(mContext, userId); + service.systemReady(mSafeMode); + // Assume that BOOT_COMPLETED was received, as this is a non-primary user. + service.sendInitialBroadcasts(); + mAppWidgetServices.append(userId, service); } - Host host = new Host(); - host.packageName = packageName; - host.uid = uid; - host.hostId = hostId; - mHosts.add(host); - return host; - } - void pruneHostLocked(Host host) { - if (host.instances.size() == 0 && host.callbacks == null) { - mHosts.remove(host); - } + return service; } - void loadAppWidgetList() { - PackageManager pm = mPackageManager; - - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - for (int i=0; i<N; i++) { - ResolveInfo ri = broadcastReceivers.get(i); - addProviderLocked(ri); - } + @Override + public int[] getAppWidgetIds(ComponentName provider) throws RemoteException { + return getImplForUser().getAppWidgetIds(provider); } - boolean addProviderLocked(ResolveInfo ri) { - if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - return false; - } - if (!ri.activityInfo.isEnabled()) { - return false; - } - Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, - ri.activityInfo.name), ri); - if (p != null) { - mInstalledProviders.add(p); - return true; - } else { - return false; - } + @Override + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) throws RemoteException { + return getImplForUser().getAppWidgetInfo(appWidgetId); } - void removeProviderLocked(int index, Provider p) { - int N = p.instances.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = p.instances.get(i); - // Call back with empty RemoteViews - updateAppWidgetInstanceLocked(id, null); - // Stop telling the host about updates for this from now on - cancelBroadcasts(p); - // clear out references to this appWidgetId - id.host.instances.remove(id); - mAppWidgetIds.remove(id); - id.provider = null; - pruneHostLocked(id.host); - id.host = null; - } - p.instances.clear(); - mInstalledProviders.remove(index); - mDeletedProviders.add(p); - // no need to send the DISABLE broadcast, since the receiver is gone anyway - cancelBroadcasts(p); + @Override + public RemoteViews getAppWidgetViews(int appWidgetId) throws RemoteException { + return getImplForUser().getAppWidgetViews(appWidgetId); } - void sendEnableIntentLocked(Provider p) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); + @Override + public void updateAppWidgetExtras(int appWidgetId, Bundle extras) { + getImplForUser().updateAppWidgetExtras(appWidgetId, extras); } - void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { - if (appWidgetIds != null && appWidgetIds.length > 0) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); - } + @Override + public Bundle getAppWidgetExtras(int appWidgetId) { + return getImplForUser().getAppWidgetExtras(appWidgetId); } - void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { - if (p.info.updatePeriodMillis > 0) { - // if this is the first instance, set the alarm. otherwise, - // rely on the fact that we've already set it and that - // PendingIntent.getBroadcast will update the extras. - boolean alreadyRegistered = p.broadcast != null; - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); - intent.setComponent(p.info.provider); - long token = Binder.clearCallingIdentity(); - try { - p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } finally { - Binder.restoreCallingIdentity(token); - } - if (!alreadyRegistered) { - long period = p.info.updatePeriodMillis; - if (period < MIN_UPDATE_PERIOD) { - period = MIN_UPDATE_PERIOD; - } - mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + period, period, p.broadcast); - } - } - } - static int[] getAppWidgetIds(Provider p) { int instancesSize = p.instances.size(); int appWidgetIds[] = new int[instancesSize]; @@ -1060,570 +296,71 @@ class AppWidgetService extends IAppWidgetService.Stub } return appWidgetIds; } - - public int[] getAppWidgetIds(ComponentName provider) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Provider p = lookupProviderLocked(provider); - if (p != null && getCallingUid() == p.uid) { - return getAppWidgetIds(p); - } else { - return new int[0]; - } - } - } - - private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { - Provider p = null; - - ActivityInfo activityInfo = ri.activityInfo; - XmlResourceParser parser = null; - try { - parser = activityInfo.loadXmlMetaData(mPackageManager, - AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); - if (parser == null) { - Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER + " meta-data for " - + "AppWidget provider '" + component + '\''); - return null; - } - - AttributeSet attrs = Xml.asAttributeSet(parser); - - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && type != XmlPullParser.START_TAG) { - // drain whitespace, comments, etc. - } - - String nodeName = parser.getName(); - if (!"appwidget-provider".equals(nodeName)) { - Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for" - + " AppWidget provider '" + component + '\''); - return null; - } - - p = new Provider(); - AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo(); - info.provider = component; - p.uid = activityInfo.applicationInfo.uid; - - Resources res = mPackageManager.getResourcesForApplication( - activityInfo.applicationInfo); - - TypedArray sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.AppWidgetProviderInfo); - - // These dimensions has to be resolved in the application's context. - // We simply send back the raw complex data, which will be - // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}. - TypedValue value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth); - info.minWidth = value != null ? value.data : 0; - value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight); - info.minHeight = value != null ? value.data : 0; - value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth); - info.minResizeWidth = value != null ? value.data : info.minWidth; - value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight); - info.minResizeHeight = value != null ? value.data : info.minHeight; - info.updatePeriodMillis = sa.getInt( - com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); - info.initialLayout = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); - String className = sa.getString( - com.android.internal.R.styleable.AppWidgetProviderInfo_configure); - if (className != null) { - info.configure = new ComponentName(component.getPackageName(), className); - } - info.label = activityInfo.loadLabel(mPackageManager).toString(); - info.icon = ri.getIconResource(); - info.previewImage = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); - info.autoAdvanceViewId = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); - info.resizeMode = sa.getInt( - com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, - AppWidgetProviderInfo.RESIZE_NONE); - - sa.recycle(); - } catch (Exception e) { - // Ok to catch Exception here, because anything going wrong because - // of what a client process passes to us should not be fatal for the - // system process. - Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e); - return null; - } finally { - if (parser != null) parser.close(); - } - return p; + @Override + public List<AppWidgetProviderInfo> getInstalledProviders() throws RemoteException { + return getImplForUser().getInstalledProviders(); } - int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException { - PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0); - if (pkgInfo == null || pkgInfo.applicationInfo == null) { - throw new PackageManager.NameNotFoundException(); - } - return pkgInfo.applicationInfo.uid; + @Override + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) + throws RemoteException { + getImplForUser().notifyAppWidgetViewDataChanged(appWidgetIds, viewId); } - int enforceCallingUid(String packageName) throws IllegalArgumentException { - int callingUid = getCallingUid(); - int packageUid; - try { - packageUid = getUidForPackage(packageName); - } catch (PackageManager.NameNotFoundException ex) { - throw new IllegalArgumentException("packageName and uid don't match packageName=" - + packageName); - } - if (callingUid != packageUid) { - throw new IllegalArgumentException("packageName and uid don't match packageName=" - + packageName); - } - return callingUid; + @Override + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) + throws RemoteException { + getImplForUser().partiallyUpdateAppWidgetIds(appWidgetIds, views); } - void sendInitialBroadcasts() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.instances.size() > 0) { - sendEnableIntentLocked(p); - int[] appWidgetIds = getAppWidgetIds(p); - sendUpdateIntentLocked(p, appWidgetIds); - registerForBroadcastsLocked(p, appWidgetIds); - } - } - } + @Override + public void stopListening(int hostId) throws RemoteException { + getImplForUser().stopListening(hostId); } - // only call from initialization -- it assumes that the data structures are all empty - void loadStateLocked() { - AtomicFile file = savedStateFile(); - try { - FileInputStream stream = file.openRead(); - readStateFromFileLocked(stream); - - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - Slog.w(TAG, "Failed to close state FileInputStream " + e); - } - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "Failed to read state: " + e); - } + @Override + public void unbindRemoteViewsService(int appWidgetId, Intent intent) throws RemoteException { + getImplForUser().unbindRemoteViewsService(appWidgetId, intent); } - void saveStateLocked() { - AtomicFile file = savedStateFile(); - FileOutputStream stream; - try { - stream = file.startWrite(); - if (writeStateToFileLocked(stream)) { - file.finishWrite(stream); - } else { - file.failWrite(stream); - Slog.w(TAG, "Failed to save state, restoring backup."); - } - } catch (IOException e) { - Slog.w(TAG, "Failed open state file for write: " + e); - } + @Override + public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) throws RemoteException { + getImplForUser().updateAppWidgetIds(appWidgetIds, views); } - boolean writeStateToFileLocked(FileOutputStream stream) { - int N; - - try { - XmlSerializer out = new FastXmlSerializer(); - out.setOutput(stream, "utf-8"); - out.startDocument(null, true); - out.startTag(null, "gs"); - - int providerIndex = 0; - N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.instances.size() > 0) { - out.startTag(null, "p"); - out.attribute(null, "pkg", p.info.provider.getPackageName()); - out.attribute(null, "cl", p.info.provider.getClassName()); - out.endTag(null, "p"); - p.tag = providerIndex; - providerIndex++; - } - } - - N = mHosts.size(); - for (int i=0; i<N; i++) { - Host host = mHosts.get(i); - out.startTag(null, "h"); - out.attribute(null, "pkg", host.packageName); - out.attribute(null, "id", Integer.toHexString(host.hostId)); - out.endTag(null, "h"); - host.tag = i; - } - - N = mAppWidgetIds.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = mAppWidgetIds.get(i); - out.startTag(null, "g"); - out.attribute(null, "id", Integer.toHexString(id.appWidgetId)); - out.attribute(null, "h", Integer.toHexString(id.host.tag)); - if (id.provider != null) { - out.attribute(null, "p", Integer.toHexString(id.provider.tag)); - } - out.endTag(null, "g"); - } - - out.endTag(null, "gs"); - - out.endDocument(); - return true; - } catch (IOException e) { - Slog.w(TAG, "Failed to write state: " + e); - return false; - } + @Override + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) + throws RemoteException { + getImplForUser().updateAppWidgetProvider(provider, views); } - void readStateFromFileLocked(FileInputStream stream) { - boolean success = false; - - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(stream, null); - - int type; - int providerIndex = 0; - HashMap<Integer,Provider> loadedProviders = new HashMap<Integer, Provider>(); - do { - type = parser.next(); - if (type == XmlPullParser.START_TAG) { - String tag = parser.getName(); - if ("p".equals(tag)) { - // TODO: do we need to check that this package has the same signature - // as before? - String pkg = parser.getAttributeValue(null, "pkg"); - String cl = parser.getAttributeValue(null, "cl"); - - final PackageManager packageManager = mContext.getPackageManager(); - try { - packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); - } catch (PackageManager.NameNotFoundException e) { - String[] pkgs = packageManager.currentToCanonicalPackageNames( - new String[] { pkg }); - pkg = pkgs[0]; - } - - Provider p = lookupProviderLocked(new ComponentName(pkg, cl)); - if (p == null && mSafeMode) { - // if we're in safe mode, make a temporary one - p = new Provider(); - p.info = new AppWidgetProviderInfo(); - p.info.provider = new ComponentName(pkg, cl); - p.zombie = true; - mInstalledProviders.add(p); - } - if (p != null) { - // if it wasn't uninstalled or something - loadedProviders.put(providerIndex, p); - } - providerIndex++; - } - else if ("h".equals(tag)) { - Host host = new Host(); - - // TODO: do we need to check that this package has the same signature - // as before? - host.packageName = parser.getAttributeValue(null, "pkg"); - try { - host.uid = getUidForPackage(host.packageName); - } catch (PackageManager.NameNotFoundException ex) { - host.zombie = true; - } - if (!host.zombie || mSafeMode) { - // In safe mode, we don't discard the hosts we don't recognize - // so that they're not pruned from our list. Otherwise, we do. - host.hostId = Integer.parseInt( - parser.getAttributeValue(null, "id"), 16); - mHosts.add(host); - } - } - else if ("g".equals(tag)) { - AppWidgetId id = new AppWidgetId(); - id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16); - if (id.appWidgetId >= mNextAppWidgetId) { - mNextAppWidgetId = id.appWidgetId + 1; - } - - String providerString = parser.getAttributeValue(null, "p"); - if (providerString != null) { - // there's no provider if it hasn't been bound yet. - // maybe we don't have to save this, but it brings the system - // to the state it was in. - int pIndex = Integer.parseInt(providerString, 16); - id.provider = loadedProviders.get(pIndex); - if (false) { - Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider " - + pIndex + " which is " + id.provider); - } - if (id.provider == null) { - // This provider is gone. We just let the host figure out - // that this happened when it fails to load it. - continue; - } - } - - int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16); - id.host = mHosts.get(hIndex); - if (id.host == null) { - // This host is gone. - continue; - } - - if (id.provider != null) { - id.provider.instances.add(id); - } - id.host.instances.add(id); - mAppWidgetIds.add(id); - } - } - } while (type != XmlPullParser.END_DOCUMENT); - success = true; - } catch (NullPointerException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (NumberFormatException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (XmlPullParserException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (IOException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (IndexOutOfBoundsException e) { - Slog.w(TAG, "failed parsing " + e); - } - - if (success) { - // delete any hosts that didn't manage to get connected (should happen) - // if it matters, they'll be reconnected. - for (int i=mHosts.size()-1; i>=0; i--) { - pruneHostLocked(mHosts.get(i)); - } - } else { - // failed reading, clean up - Slog.w(TAG, "Failed to read state, clearing widgets and hosts."); - - mAppWidgetIds.clear(); - mHosts.clear(); - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - mInstalledProviders.get(i).instances.clear(); - } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + // Dump the state of all the app widget providers + for (int i = 0; i < mAppWidgetServices.size(); i++) { + AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); + service.dump(fd, pw, args); } } - AtomicFile savedStateFile() { - return new AtomicFile(new File("/data/system/" + SETTINGS_FILENAME)); - } - BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - //Slog.d(TAG, "received " + action); + // Slog.d(TAG, "received " + action); if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { - sendInitialBroadcasts(); + getImplForUser().sendInitialBroadcasts(); } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - Locale revised = Locale.getDefault(); - if (revised == null || mLocale == null || - !(revised.equals(mLocale))) { - mLocale = revised; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int N = mInstalledProviders.size(); - for (int i=N-1; i>=0; i--) { - Provider p = mInstalledProviders.get(i); - String pkgName = p.info.provider.getPackageName(); - updateProvidersForPackageLocked(pkgName); - } - saveStateLocked(); - } + for (int i = 0; i < mAppWidgetServices.size(); i++) { + AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); + service.onConfigurationChanged(); } } else { - boolean added = false; - boolean changed = false; - String pkgList[] = null; - if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = true; - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = false; - } else { - Uri uri = intent.getData(); - if (uri == null) { - return; - } - String pkgName = uri.getSchemeSpecificPart(); - if (pkgName == null) { - return; - } - pkgList = new String[] { pkgName }; - added = Intent.ACTION_PACKAGE_ADDED.equals(action); - changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); - } - if (pkgList == null || pkgList.length == 0) { - return; - } - if (added || changed) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Bundle extras = intent.getExtras(); - if (changed || (extras != null && - extras.getBoolean(Intent.EXTRA_REPLACING, false))) { - for (String pkgName : pkgList) { - // The package was just upgraded - updateProvidersForPackageLocked(pkgName); - } - } else { - // The package was just added - for (String pkgName : pkgList) { - addProvidersForPackageLocked(pkgName); - } - } - saveStateLocked(); - } - } else { - Bundle extras = intent.getExtras(); - if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { - // The package is being updated. We'll receive a PACKAGE_ADDED shortly. - } else { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (String pkgName : pkgList) { - removeProvidersForPackageLocked(pkgName); - saveStateLocked(); - } - } - } + for (int i = 0; i < mAppWidgetServices.size(); i++) { + AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); + service.onBroadcastReceived(intent); } } } }; - - void addProvidersForPackageLocked(String pkgName) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.setPackage(pkgName); - List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - for (int i=0; i<N; i++) { - ResolveInfo ri = broadcastReceivers.get(i); - ActivityInfo ai = ri.activityInfo; - if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - continue; - } - if (pkgName.equals(ai.packageName)) { - addProviderLocked(ri); - } - } - } - - void updateProvidersForPackageLocked(String pkgName) { - HashSet<String> keep = new HashSet<String>(); - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.setPackage(pkgName); - List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - // add the missing ones and collect which ones to keep - int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - for (int i=0; i<N; i++) { - ResolveInfo ri = broadcastReceivers.get(i); - ActivityInfo ai = ri.activityInfo; - if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - continue; - } - if (pkgName.equals(ai.packageName)) { - ComponentName component = new ComponentName(ai.packageName, ai.name); - Provider p = lookupProviderLocked(component); - if (p == null) { - if (addProviderLocked(ri)) { - keep.add(ai.name); - } - } else { - Provider parsed = parseProviderInfoXml(component, ri); - if (parsed != null) { - keep.add(ai.name); - // Use the new AppWidgetProviderInfo. - p.info = parsed.info; - // If it's enabled - final int M = p.instances.size(); - if (M > 0) { - int[] appWidgetIds = getAppWidgetIds(p); - // Reschedule for the new updatePeriodMillis (don't worry about handling - // it specially if updatePeriodMillis didn't change because we just sent - // an update, and the next one will be updatePeriodMillis from now). - cancelBroadcasts(p); - registerForBroadcastsLocked(p, appWidgetIds); - // If it's currently showing, call back with the new AppWidgetProviderInfo. - for (int j=0; j<M; j++) { - AppWidgetId id = p.instances.get(j); - id.views = null; - if (id.host != null && id.host.callbacks != null) { - try { - id.host.callbacks.providerChanged(id.appWidgetId, p.info); - } catch (RemoteException ex) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this - // instance. - id.host.callbacks = null; - } - } - } - // Now that we've told the host, push out an update. - sendUpdateIntentLocked(p, appWidgetIds); - } - } - } - } - } - - // prune the ones we don't want to keep - N = mInstalledProviders.size(); - for (int i=N-1; i>=0; i--) { - Provider p = mInstalledProviders.get(i); - if (pkgName.equals(p.info.provider.getPackageName()) - && !keep.contains(p.info.provider.getClassName())) { - removeProviderLocked(i, p); - } - } - } - - void removeProvidersForPackageLocked(String pkgName) { - 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); - } - } - - // Delete the hosts for this package too - // - // By now, we have removed any AppWidgets that were in any hosts here, - // so we don't need to worry about sending DISABLE broadcasts to them. - N = mHosts.size(); - for (int i=N-1; i>=0; i--) { - Host host = mHosts.get(i); - if (pkgName.equals(host.packageName)) { - deleteHostLocked(host); - } - } - } } - diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java new file mode 100644 index 0000000..a0b8c531 --- /dev/null +++ b/services/java/com/android/server/AppWidgetServiceImpl.java @@ -0,0 +1,1746 @@ +/* + * 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; + +import android.app.AlarmManager; +import android.app.AppGlobals; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.Intent.FilterComparison; +import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserId; +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.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; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +class AppWidgetServiceImpl { + + private static final String TAG = "AppWidgetServiceImpl"; + private static final String SETTINGS_FILENAME = "appwidgets.xml"; + private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes + + /* + * When identifying a Host or Provider based on the calling process, use the uid field. When + * identifying a Host or Provider based on a package manager broadcast, use the package given. + */ + + static class Provider { + int uid; + AppWidgetProviderInfo info; + ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>(); + PendingIntent broadcast; + boolean zombie; // if we're in safe mode, don't prune this just because nobody references it + + int tag; // for use while saving state (the index) + } + + static class Host { + int uid; + int hostId; + String packageName; + ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>(); + IAppWidgetHost callbacks; + boolean zombie; // if we're in safe mode, don't prune this just because nobody references it + + int tag; // for use while saving state (the index) + } + + static class AppWidgetId { + int appWidgetId; + Provider provider; + RemoteViews views; + Bundle extras; + Host host; + } + + /** + * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This + * needs to be a static inner class since a reference to the ServiceConnection is held globally + * and may lead us to leak AppWidgetService instances (if there were more than one). + */ + static class ServiceConnectionProxy implements ServiceConnection { + private final IBinder mConnectionCb; + + ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) { + mConnectionCb = connectionCb; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub + .asInterface(mConnectionCb); + try { + cb.onServiceConnected(service); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void onServiceDisconnected(ComponentName name) { + disconnect(); + } + + public void disconnect() { + final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub + .asInterface(mConnectionCb); + try { + cb.onServiceDisconnected(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + // Manages active connections to RemoteViewsServices + private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> mBoundRemoteViewsServices = new HashMap<Pair<Integer, FilterComparison>, ServiceConnection>(); + // Manages persistent references to RemoteViewsServices from different App Widgets + private final HashMap<FilterComparison, HashSet<Integer>> mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>(); + + Context mContext; + Locale mLocale; + IPackageManager mPm; + AlarmManager mAlarmManager; + ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>(); + int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1; + final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>(); + ArrayList<Host> mHosts = new ArrayList<Host>(); + // set of package names + HashSet<String> mPackagesWithBindWidgetPermission = new HashSet<String>(); + boolean mSafeMode; + int mUserId; + boolean mStateLoaded; + + // These are for debugging only -- widgets are going missing in some rare instances + ArrayList<Provider> mDeletedProviders = new ArrayList<Provider>(); + ArrayList<Host> mDeletedHosts = new ArrayList<Host>(); + + AppWidgetServiceImpl(Context context, int userId) { + mContext = context; + mPm = AppGlobals.getPackageManager(); + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mUserId = userId; + } + + public void systemReady(boolean safeMode) { + mSafeMode = safeMode; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + } + } + + void onConfigurationChanged() { + Locale revised = Locale.getDefault(); + if (revised == null || mLocale == null || !(revised.equals(mLocale))) { + mLocale = revised; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + String pkgName = p.info.provider.getPackageName(); + updateProvidersForPackageLocked(pkgName); + } + saveStateLocked(); + } + } + } + + void onBroadcastReceived(Intent intent) { + final String action = intent.getAction(); + boolean added = false; + boolean changed = false; + String pkgList[] = null; + if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + added = true; + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + added = false; + } else { + Uri uri = intent.getData(); + if (uri == null) { + return; + } + String pkgName = uri.getSchemeSpecificPart(); + if (pkgName == null) { + return; + } + pkgList = new String[] { pkgName }; + added = Intent.ACTION_PACKAGE_ADDED.equals(action); + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); + } + if (pkgList == null || pkgList.length == 0) { + return; + } + if (added || changed) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Bundle extras = intent.getExtras(); + if (changed + || (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false))) { + for (String pkgName : pkgList) { + // The package was just upgraded + updateProvidersForPackageLocked(pkgName); + } + } else { + // The package was just added + for (String pkgName : pkgList) { + addProvidersForPackageLocked(pkgName); + } + } + saveStateLocked(); + } + } else { + Bundle extras = intent.getExtras(); + if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { + // The package is being updated. We'll receive a PACKAGE_ADDED shortly. + } else { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (String pkgName : pkgList) { + removeProvidersForPackageLocked(pkgName); + saveStateLocked(); + } + } + } + } + } + + private void dumpProvider(Provider p, int index, PrintWriter pw) { + AppWidgetProviderInfo info = p.info; + pw.print(" ["); pw.print(index); pw.print("] provider "); + pw.print(info.provider.flattenToShortString()); + pw.println(':'); + pw.print(" min=("); pw.print(info.minWidth); + pw.print("x"); pw.print(info.minHeight); + pw.print(") minResize=("); pw.print(info.minResizeWidth); + pw.print("x"); pw.print(info.minResizeHeight); + pw.print(") updatePeriodMillis="); + pw.print(info.updatePeriodMillis); + pw.print(" resizeMode="); + pw.print(info.resizeMode); + pw.print(" autoAdvanceViewId="); + pw.print(info.autoAdvanceViewId); + pw.print(" initialLayout=#"); + pw.print(Integer.toHexString(info.initialLayout)); + pw.print(" zombie="); pw.println(p.zombie); + } + + private void dumpHost(Host host, int index, PrintWriter pw) { + pw.print(" ["); pw.print(index); pw.print("] hostId="); + pw.print(host.hostId); pw.print(' '); + pw.print(host.packageName); pw.print('/'); + pw.print(host.uid); pw.println(':'); + pw.print(" callbacks="); pw.println(host.callbacks); + pw.print(" instances.size="); pw.print(host.instances.size()); + pw.print(" zombie="); pw.println(host.zombie); + } + + private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) { + pw.print(" ["); pw.print(index); pw.print("] id="); + pw.println(id.appWidgetId); + pw.print(" hostId="); + pw.print(id.host.hostId); pw.print(' '); + pw.print(id.host.packageName); pw.print('/'); + pw.println(id.host.uid); + if (id.provider != null) { + pw.print(" provider="); + pw.println(id.provider.info.provider.flattenToShortString()); + } + if (id.host != null) { + pw.print(" host.callbacks="); pw.println(id.host.callbacks); + } + if (id.views != null) { + pw.print(" views="); pw.println(id.views); + } + } + + 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 from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mAppWidgetIds) { + int N = mInstalledProviders.size(); + pw.println("Providers:"); + for (int i=0; i<N; i++) { + dumpProvider(mInstalledProviders.get(i), i, pw); + } + + N = mAppWidgetIds.size(); + pw.println(" "); + pw.println("AppWidgetIds:"); + for (int i=0; i<N; i++) { + dumpAppWidgetId(mAppWidgetIds.get(i), i, pw); + } + + N = mHosts.size(); + pw.println(" "); + pw.println("Hosts:"); + for (int i=0; i<N; i++) { + dumpHost(mHosts.get(i), i, pw); + } + + N = mDeletedProviders.size(); + pw.println(" "); + pw.println("Deleted Providers:"); + for (int i=0; i<N; i++) { + dumpProvider(mDeletedProviders.get(i), i, pw); + } + + N = mDeletedHosts.size(); + pw.println(" "); + pw.println("Deleted Hosts:"); + for (int i=0; i<N; i++) { + dumpHost(mDeletedHosts.get(i), i, pw); + } + } + } + + private void ensureStateLoadedLocked() { + if (!mStateLoaded) { + loadAppWidgetList(); + loadStateLocked(); + mStateLoaded = true; + } + } + + public int allocateAppWidgetId(String packageName, int hostId) { + int callingUid = enforceCallingUid(packageName); + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int appWidgetId = mNextAppWidgetId++; + + Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); + + AppWidgetId id = new AppWidgetId(); + id.appWidgetId = appWidgetId; + id.host = host; + + host.instances.add(id); + mAppWidgetIds.add(id); + + saveStateLocked(); + + return appWidgetId; + } + } + + public void deleteAppWidgetId(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null) { + deleteAppWidgetLocked(id); + saveStateLocked(); + } + } + } + + public void deleteHost(int hostId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int callingUid = Binder.getCallingUid(); + Host host = lookupHostLocked(callingUid, hostId); + if (host != null) { + deleteHostLocked(host); + saveStateLocked(); + } + } + } + + public void deleteAllHosts() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int callingUid = Binder.getCallingUid(); + final int N = mHosts.size(); + boolean changed = false; + for (int i = N - 1; i >= 0; i--) { + Host host = mHosts.get(i); + if (host.uid == callingUid) { + deleteHostLocked(host); + changed = true; + } + } + if (changed) { + saveStateLocked(); + } + } + } + + void deleteHostLocked(Host host) { + final int N = host.instances.size(); + for (int i = N - 1; i >= 0; i--) { + AppWidgetId id = host.instances.get(i); + deleteAppWidgetLocked(id); + } + host.instances.clear(); + mHosts.remove(host); + mDeletedHosts.add(host); + // it's gone or going away, abruptly drop the callback connection + host.callbacks = null; + } + + void deleteAppWidgetLocked(AppWidgetId id) { + // We first unbind all services that are bound to this id + unbindAppWidgetRemoteViewsServicesLocked(id); + + Host host = id.host; + host.instances.remove(id); + pruneHostLocked(host); + + mAppWidgetIds.remove(id); + + Provider p = id.provider; + if (p != null) { + p.instances.remove(id); + if (!p.zombie) { + // send the broacast saying that this appWidgetId has been deleted + 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); + if (p.instances.size() == 0) { + // cancel the future updates + cancelBroadcasts(p); + + // 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); + } + } + } + } + + void cancelBroadcasts(Provider p) { + if (p.broadcast != null) { + mAlarmManager.cancel(p.broadcast); + long token = Binder.clearCallingIdentity(); + try { + p.broadcast.cancel(); + } finally { + Binder.restoreCallingIdentity(token); + } + p.broadcast = null; + } + } + + private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider) { + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + if (id.provider != null) { + throw new IllegalArgumentException("appWidgetId " + appWidgetId + + " already bound to " + id.provider.info.provider); + } + Provider p = lookupProviderLocked(provider); + if (p == null) { + throw new IllegalArgumentException("not a appwidget provider: " + provider); + } + if (p.zombie) { + throw new IllegalArgumentException("can't bind to a 3rd party provider in" + + " safe mode: " + provider); + } + + id.provider = p; + p.instances.add(id); + int instancesSize = p.instances.size(); + if (instancesSize == 1) { + // tell the provider that it's ready + sendEnableIntentLocked(p); + } + + // send an update now -- We need this update now, and just for this appWidgetId. + // It's less critical when the next one happens, so when we schedule the next one, + // we add updatePeriodMillis to its start time. That time will have some slop, + // but that's okay. + sendUpdateIntentLocked(p, new int[] { appWidgetId }); + + // schedule the future updates + registerForBroadcastsLocked(p, getAppWidgetIds(p)); + saveStateLocked(); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + public void bindAppWidgetId(int appWidgetId, ComponentName provider) { + mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, + "bindAppWidgetId appWidgetId=" + appWidgetId + " provider=" + provider); + bindAppWidgetIdImpl(appWidgetId, provider); + } + + public boolean bindAppWidgetIdIfAllowed( + String packageName, int appWidgetId, ComponentName provider) { + try { + mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, null); + } catch (SecurityException se) { + if (!callerHasBindAppWidgetPermission(packageName)) { + return false; + } + } + bindAppWidgetIdImpl(appWidgetId, provider); + return true; + } + + private boolean callerHasBindAppWidgetPermission(String packageName) { + int callingUid = Binder.getCallingUid(); + try { + if (!UserId.isSameApp(callingUid, getUidForPackage(packageName))) { + return false; + } + } catch (Exception e) { + return false; + } + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + return mPackagesWithBindWidgetPermission.contains(packageName); + } + } + + public boolean hasBindAppWidgetPermission(String packageName) { + mContext.enforceCallingPermission( + android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS, + "hasBindAppWidgetPermission packageName=" + packageName); + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + return mPackagesWithBindWidgetPermission.contains(packageName); + } + } + + public void setBindAppWidgetPermission(String packageName, boolean permission) { + mContext.enforceCallingPermission( + android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS, + "setBindAppWidgetPermission packageName=" + packageName); + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + if (permission) { + mPackagesWithBindWidgetPermission.add(packageName); + } else { + mPackagesWithBindWidgetPermission.remove(packageName); + } + } + saveStateLocked(); + } + + // Binds to a specific RemoteViewsService + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + final ComponentName componentName = intent.getComponent(); + try { + final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, + PackageManager.GET_PERMISSIONS); + if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { + throw new SecurityException("Selected service does not require " + + android.Manifest.permission.BIND_REMOTEVIEWS + ": " + componentName); + } + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown component " + componentName); + } + + // If there is already a connection made for this service intent, then disconnect from + // that first. (This does not allow multiple connections to the same service under + // the same key) + ServiceConnectionProxy conn = null; + FilterComparison fc = new FilterComparison(intent); + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc); + if (mBoundRemoteViewsServices.containsKey(key)) { + conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); + conn.disconnect(); + mContext.unbindService(conn); + mBoundRemoteViewsServices.remove(key); + } + + int userId = UserId.getUserId(id.provider.uid); + // Bind to the RemoteViewsService (which will trigger a callback to the + // RemoteViewsAdapter.onServiceConnected()) + final long token = Binder.clearCallingIdentity(); + try { + conn = new ServiceConnectionProxy(key, connection); + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId); + mBoundRemoteViewsServices.put(key, conn); + } finally { + Binder.restoreCallingIdentity(token); + } + + // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine + // when we can call back to the RemoteViewsService later to destroy associated + // factories. + incrementAppWidgetServiceRefCount(appWidgetId, fc); + } + } + + // Unbinds from a specific RemoteViewsService + public void unbindRemoteViewsService(int appWidgetId, Intent intent) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + // Unbind from the RemoteViewsService (which will trigger a callback to the bound + // RemoteViewsAdapter) + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, new FilterComparison( + intent)); + if (mBoundRemoteViewsServices.containsKey(key)) { + // We don't need to use the appWidgetId until after we are sure there is something + // to unbind. Note that this may mask certain issues with apps calling unbind() + // more than necessary. + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + + ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices + .get(key); + conn.disconnect(); + mContext.unbindService(conn); + mBoundRemoteViewsServices.remove(key); + } else { + Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound"); + } + } + } + + // Unbinds from a RemoteViewsService when we delete an app widget + private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { + int appWidgetId = id.appWidgetId; + // Unbind all connections to Services bound to this AppWidgetId + Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet() + .iterator(); + while (it.hasNext()) { + final Pair<Integer, Intent.FilterComparison> key = it.next(); + if (key.first.intValue() == appWidgetId) { + final ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices + .get(key); + conn.disconnect(); + mContext.unbindService(conn); + it.remove(); + } + } + + // Check if we need to destroy any services (if no other app widgets are + // referencing the same service) + decrementAppWidgetServiceRefCount(id); + } + + // Destroys the cached factory on the RemoteViewsService's side related to the specified intent + private void destroyRemoteViewsService(final Intent intent, AppWidgetId id) { + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service); + try { + cb.onDestroy(intent); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (RuntimeException e) { + e.printStackTrace(); + } + mContext.unbindService(this); + } + + @Override + public void onServiceDisconnected(android.content.ComponentName name) { + // Do nothing + } + }; + + int userId = UserId.getUserId(id.provider.uid); + // Bind to the service and remove the static intent->factory mapping in the + // RemoteViewsService. + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Adds to the ref-count for a given RemoteViewsService intent + private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) { + HashSet<Integer> appWidgetIds = null; + if (mRemoteViewsServicesAppWidgets.containsKey(fc)) { + appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc); + } else { + appWidgetIds = new HashSet<Integer>(); + mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds); + } + appWidgetIds.add(appWidgetId); + } + + // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if + // the ref-count reaches zero. + private void decrementAppWidgetServiceRefCount(AppWidgetId id) { + Iterator<FilterComparison> it = mRemoteViewsServicesAppWidgets.keySet().iterator(); + while (it.hasNext()) { + final FilterComparison key = it.next(); + final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key); + if (ids.remove(id.appWidgetId)) { + // If we have removed the last app widget referencing this service, then we + // should destroy it and remove it from this set + if (ids.isEmpty()) { + destroyRemoteViewsService(key.getIntent(), id); + it.remove(); + } + } + } + } + + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null && id.provider != null && !id.provider.zombie) { + return id.provider.info; + } + return null; + } + } + + public RemoteViews getAppWidgetViews(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null) { + return id.views; + } + return null; + } + } + + public List<AppWidgetProviderInfo> getInstalledProviders() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + final int N = mInstalledProviders.size(); + ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (!p.zombie) { + result.add(p.info); + } + } + return result; + } + } + + public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views); + } + } + } + + public void updateAppWidgetExtras(int appWidgetId, Bundle extras) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + + if (id == null) { + return; + } + Provider p = id.provider; + id.extras = extras; + + // send the broacast saying that this appWidgetId has been deleted + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_EXTRAS_CHANGED); + intent.setComponent(p.info.provider); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_EXTRAS, extras); + mContext.sendBroadcast(intent, mUserId); + } + } + + public Bundle getAppWidgetExtras(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null && id.extras != null) { + return id.extras; + } else { + return Bundle.EMPTY; + } + } + } + + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views, true); + } + } + } + + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); + } + } + } + + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Provider p = lookupProviderLocked(provider); + if (p == null) { + Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider); + return; + } + ArrayList<AppWidgetId> instances = p.instances; + final int callingUid = Binder.getCallingUid(); + final int N = instances.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = instances.get(i); + if (canAccessAppWidgetId(id, callingUid)) { + updateAppWidgetInstanceLocked(id, views); + } + } + } + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { + updateAppWidgetInstanceLocked(id, views, false); + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + + // We do not want to save this RemoteViews + if (!isPartialUpdate) + id.views = views; + + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.updateAppWidget(id.appWidgetId, views); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + } + } + + void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + + // If the host is unavailable, then we call the associated + // RemoteViewsFactory.onDataSetChanged() directly + if (id.host.callbacks == null) { + Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet(); + for (FilterComparison key : keys) { + if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) { + Intent intent = key.getIntent(); + + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IRemoteViewsFactory cb = IRemoteViewsFactory.Stub + .asInterface(service); + try { + cb.onDataSetChangedAsync(); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (RuntimeException e) { + e.printStackTrace(); + } + mContext.unbindService(this); + } + + @Override + public void onServiceDisconnected(android.content.ComponentName name) { + // Do nothing + } + }; + + int userId = UserId.getUserId(id.provider.uid); + // Bind to the service and call onDataSetChanged() + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + } + } + } + + public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, + List<RemoteViews> updatedViews) { + int callingUid = enforceCallingUid(packageName); + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); + host.callbacks = callbacks; + + updatedViews.clear(); + + ArrayList<AppWidgetId> instances = host.instances; + int N = instances.size(); + int[] updatedIds = new int[N]; + for (int i = 0; i < N; i++) { + AppWidgetId id = instances.get(i); + updatedIds[i] = id.appWidgetId; + updatedViews.add(id.views); + } + return updatedIds; + } + } + + public void stopListening(int hostId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Host host = lookupHostLocked(Binder.getCallingUid(), hostId); + if (host != null) { + host.callbacks = null; + pruneHostLocked(host); + } + } + } + + boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) { + if (id.host.uid == callingUid) { + // Apps hosting the AppWidget have access to it. + return true; + } + if (id.provider != null && id.provider.uid == callingUid) { + // Apps providing the AppWidget have access to it (if the appWidgetId has been bound) + return true; + } + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) == PackageManager.PERMISSION_GRANTED) { + // Apps that can bind have access to all appWidgetIds. + return true; + } + // Nobody else can access it. + return false; + } + + AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) { + int callingUid = Binder.getCallingUid(); + final int N = mAppWidgetIds.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = mAppWidgetIds.get(i); + if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) { + return id; + } + } + return null; + } + + Provider lookupProviderLocked(ComponentName provider) { + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.info.provider.equals(provider)) { + return p; + } + } + return null; + } + + Host lookupHostLocked(int uid, int hostId) { + final int N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host h = mHosts.get(i); + if (h.uid == uid && h.hostId == hostId) { + return h; + } + } + return null; + } + + Host lookupOrAddHostLocked(int uid, String packageName, int hostId) { + final int N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host h = mHosts.get(i); + if (h.hostId == hostId && h.packageName.equals(packageName)) { + return h; + } + } + Host host = new Host(); + host.packageName = packageName; + host.uid = uid; + host.hostId = hostId; + mHosts.add(host); + return host; + } + + void pruneHostLocked(Host host) { + if (host.instances.size() == 0 && host.callbacks == null) { + mHosts.remove(host); + } + } + + void loadAppWidgetList() { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + try { + List<ResolveInfo> broadcastReceivers = mPm.queryIntentReceivers(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.GET_META_DATA, mUserId); + + final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + for (int i = 0; i < N; i++) { + ResolveInfo ri = broadcastReceivers.get(i); + addProviderLocked(ri); + } + } catch (RemoteException re) { + // Shouldn't happen, local call + } + } + + boolean addProviderLocked(ResolveInfo ri) { + if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + return false; + } + if (!ri.activityInfo.isEnabled()) { + return false; + } + Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, + ri.activityInfo.name), ri); + if (p != null) { + mInstalledProviders.add(p); + return true; + } else { + return false; + } + } + + void removeProviderLocked(int index, Provider p) { + int N = p.instances.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = p.instances.get(i); + // Call back with empty RemoteViews + updateAppWidgetInstanceLocked(id, null); + // Stop telling the host about updates for this from now on + cancelBroadcasts(p); + // clear out references to this appWidgetId + id.host.instances.remove(id); + mAppWidgetIds.remove(id); + id.provider = null; + pruneHostLocked(id.host); + id.host = null; + } + p.instances.clear(); + mInstalledProviders.remove(index); + mDeletedProviders.add(p); + // no need to send the DISABLE broadcast, since the receiver is gone anyway + cancelBroadcasts(p); + } + + void sendEnableIntentLocked(Provider p) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent, mUserId); + } + + void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { + if (appWidgetIds != null && appWidgetIds.length > 0) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent, mUserId); + } + } + + void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { + if (p.info.updatePeriodMillis > 0) { + // if this is the first instance, set the alarm. otherwise, + // rely on the fact that we've already set it and that + // PendingIntent.getBroadcast will update the extras. + boolean alreadyRegistered = p.broadcast != null; + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + intent.setComponent(p.info.provider); + long token = Binder.clearCallingIdentity(); + try { + p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } finally { + Binder.restoreCallingIdentity(token); + } + if (!alreadyRegistered) { + long period = p.info.updatePeriodMillis; + if (period < MIN_UPDATE_PERIOD) { + period = MIN_UPDATE_PERIOD; + } + mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock + .elapsedRealtime() + + period, period, p.broadcast); + } + } + } + + static int[] getAppWidgetIds(Provider p) { + int instancesSize = p.instances.size(); + int appWidgetIds[] = new int[instancesSize]; + for (int i = 0; i < instancesSize; i++) { + appWidgetIds[i] = p.instances.get(i).appWidgetId; + } + return appWidgetIds; + } + + public int[] getAppWidgetIds(ComponentName provider) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Provider p = lookupProviderLocked(provider); + if (p != null && Binder.getCallingUid() == p.uid) { + return getAppWidgetIds(p); + } else { + return new int[0]; + } + } + } + + private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { + Provider p = null; + + ActivityInfo activityInfo = ri.activityInfo; + XmlResourceParser parser = null; + try { + parser = activityInfo.loadXmlMetaData(mContext.getPackageManager(), + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); + if (parser == null) { + Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER + + " meta-data for " + "AppWidget provider '" + component + '\''); + return null; + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + // drain whitespace, comments, etc. + } + + String nodeName = parser.getName(); + if (!"appwidget-provider".equals(nodeName)) { + Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for" + + " AppWidget provider '" + component + '\''); + return null; + } + + p = new Provider(); + AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo(); + info.provider = component; + p.uid = activityInfo.applicationInfo.uid; + + Resources res = mContext.getPackageManager() + .getResourcesForApplication(activityInfo.applicationInfo); + + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AppWidgetProviderInfo); + + // These dimensions has to be resolved in the application's context. + // We simply send back the raw complex data, which will be + // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}. + TypedValue value = sa + .peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth); + info.minWidth = value != null ? value.data : 0; + value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight); + info.minHeight = value != null ? value.data : 0; + value = sa.peekValue( + com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth); + info.minResizeWidth = value != null ? value.data : info.minWidth; + value = sa.peekValue( + com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight); + info.minResizeHeight = value != null ? value.data : info.minHeight; + info.updatePeriodMillis = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); + info.initialLayout = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); + String className = sa + .getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure); + if (className != null) { + info.configure = new ComponentName(component.getPackageName(), className); + } + info.label = activityInfo.loadLabel(mContext.getPackageManager()).toString(); + info.icon = ri.getIconResource(); + info.previewImage = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); + info.autoAdvanceViewId = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); + info.resizeMode = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, + AppWidgetProviderInfo.RESIZE_NONE); + + sa.recycle(); + } catch (Exception e) { + // Ok to catch Exception here, because anything going wrong because + // of what a client process passes to us should not be fatal for the + // system process. + Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e); + return null; + } finally { + if (parser != null) + parser.close(); + } + return p; + } + + int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException { + PackageInfo pkgInfo = null; + try { + pkgInfo = mPm.getPackageInfo(packageName, 0, mUserId); + } catch (RemoteException re) { + // Shouldn't happen, local call + } + if (pkgInfo == null || pkgInfo.applicationInfo == null) { + throw new PackageManager.NameNotFoundException(); + } + return pkgInfo.applicationInfo.uid; + } + + int enforceCallingUid(String packageName) throws IllegalArgumentException { + int callingUid = Binder.getCallingUid(); + int packageUid; + try { + packageUid = getUidForPackage(packageName); + } catch (PackageManager.NameNotFoundException ex) { + throw new IllegalArgumentException("packageName and uid don't match packageName=" + + packageName); + } + if (!UserId.isSameApp(callingUid, packageUid)) { + throw new IllegalArgumentException("packageName and uid don't match packageName=" + + packageName); + } + return callingUid; + } + + void sendInitialBroadcasts() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.instances.size() > 0) { + sendEnableIntentLocked(p); + int[] appWidgetIds = getAppWidgetIds(p); + sendUpdateIntentLocked(p, appWidgetIds); + registerForBroadcastsLocked(p, appWidgetIds); + } + } + } + } + + // only call from initialization -- it assumes that the data structures are all empty + void loadStateLocked() { + AtomicFile file = savedStateFile(); + try { + FileInputStream stream = file.openRead(); + readStateFromFileLocked(stream); + + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + Slog.w(TAG, "Failed to close state FileInputStream " + e); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "Failed to read state: " + e); + } + } + + void saveStateLocked() { + AtomicFile file = savedStateFile(); + FileOutputStream stream; + try { + stream = file.startWrite(); + if (writeStateToFileLocked(stream)) { + file.finishWrite(stream); + } else { + file.failWrite(stream); + Slog.w(TAG, "Failed to save state, restoring backup."); + } + } catch (IOException e) { + Slog.w(TAG, "Failed open state file for write: " + e); + } + } + + boolean writeStateToFileLocked(FileOutputStream stream) { + int N; + + try { + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(stream, "utf-8"); + out.startDocument(null, true); + out.startTag(null, "gs"); + + int providerIndex = 0; + N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.instances.size() > 0) { + out.startTag(null, "p"); + out.attribute(null, "pkg", p.info.provider.getPackageName()); + out.attribute(null, "cl", p.info.provider.getClassName()); + out.endTag(null, "p"); + p.tag = providerIndex; + providerIndex++; + } + } + + N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host host = mHosts.get(i); + out.startTag(null, "h"); + out.attribute(null, "pkg", host.packageName); + out.attribute(null, "id", Integer.toHexString(host.hostId)); + out.endTag(null, "h"); + host.tag = i; + } + + N = mAppWidgetIds.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = mAppWidgetIds.get(i); + out.startTag(null, "g"); + out.attribute(null, "id", Integer.toHexString(id.appWidgetId)); + out.attribute(null, "h", Integer.toHexString(id.host.tag)); + if (id.provider != null) { + out.attribute(null, "p", Integer.toHexString(id.provider.tag)); + } + out.endTag(null, "g"); + } + + Iterator<String> it = mPackagesWithBindWidgetPermission.iterator(); + while (it.hasNext()) { + out.startTag(null, "b"); + out.attribute(null, "packageName", it.next()); + out.endTag(null, "b"); + } + + out.endTag(null, "gs"); + + out.endDocument(); + return true; + } catch (IOException e) { + Slog.w(TAG, "Failed to write state: " + e); + return false; + } + } + + void readStateFromFileLocked(FileInputStream stream) { + boolean success = false; + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + + int type; + int providerIndex = 0; + HashMap<Integer, Provider> loadedProviders = new HashMap<Integer, Provider>(); + do { + type = parser.next(); + if (type == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if ("p".equals(tag)) { + // TODO: do we need to check that this package has the same signature + // as before? + String pkg = parser.getAttributeValue(null, "pkg"); + String cl = parser.getAttributeValue(null, "cl"); + + final PackageManager packageManager = mContext.getPackageManager(); + try { + packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); + } catch (PackageManager.NameNotFoundException e) { + String[] pkgs = packageManager + .currentToCanonicalPackageNames(new String[] { pkg }); + pkg = pkgs[0]; + } + + Provider p = lookupProviderLocked(new ComponentName(pkg, cl)); + if (p == null && mSafeMode) { + // if we're in safe mode, make a temporary one + p = new Provider(); + p.info = new AppWidgetProviderInfo(); + p.info.provider = new ComponentName(pkg, cl); + p.zombie = true; + mInstalledProviders.add(p); + } + if (p != null) { + // if it wasn't uninstalled or something + loadedProviders.put(providerIndex, p); + } + providerIndex++; + } else if ("h".equals(tag)) { + Host host = new Host(); + + // TODO: do we need to check that this package has the same signature + // as before? + host.packageName = parser.getAttributeValue(null, "pkg"); + try { + host.uid = getUidForPackage(host.packageName); + } catch (PackageManager.NameNotFoundException ex) { + host.zombie = true; + } + if (!host.zombie || mSafeMode) { + // In safe mode, we don't discard the hosts we don't recognize + // so that they're not pruned from our list. Otherwise, we do. + host.hostId = Integer + .parseInt(parser.getAttributeValue(null, "id"), 16); + mHosts.add(host); + } + } else if ("b".equals(tag)) { + String packageName = parser.getAttributeValue(null, "packageName"); + if (packageName != null) { + mPackagesWithBindWidgetPermission.add(packageName); + } + } else if ("g".equals(tag)) { + AppWidgetId id = new AppWidgetId(); + id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16); + if (id.appWidgetId >= mNextAppWidgetId) { + mNextAppWidgetId = id.appWidgetId + 1; + } + + String providerString = parser.getAttributeValue(null, "p"); + if (providerString != null) { + // there's no provider if it hasn't been bound yet. + // maybe we don't have to save this, but it brings the system + // to the state it was in. + int pIndex = Integer.parseInt(providerString, 16); + id.provider = loadedProviders.get(pIndex); + if (false) { + Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider " + + pIndex + " which is " + id.provider); + } + if (id.provider == null) { + // This provider is gone. We just let the host figure out + // that this happened when it fails to load it. + continue; + } + } + + int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16); + id.host = mHosts.get(hIndex); + if (id.host == null) { + // This host is gone. + continue; + } + + if (id.provider != null) { + id.provider.instances.add(id); + } + id.host.instances.add(id); + mAppWidgetIds.add(id); + } + } + } while (type != XmlPullParser.END_DOCUMENT); + success = true; + } catch (NullPointerException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (NumberFormatException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (XmlPullParserException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (IOException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (IndexOutOfBoundsException e) { + Slog.w(TAG, "failed parsing " + e); + } + + if (success) { + // delete any hosts that didn't manage to get connected (should happen) + // if it matters, they'll be reconnected. + for (int i = mHosts.size() - 1; i >= 0; i--) { + pruneHostLocked(mHosts.get(i)); + } + } else { + // failed reading, clean up + Slog.w(TAG, "Failed to read state, clearing widgets and hosts."); + + mAppWidgetIds.clear(); + mHosts.clear(); + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + mInstalledProviders.get(i).instances.clear(); + } + } + } + + static File getSettingsFile(int userId) { + return new File("/data/system/users/" + userId + "/" + SETTINGS_FILENAME); + } + + AtomicFile savedStateFile() { + File dir = new File("/data/system/users/" + mUserId); + File settingsFile = getSettingsFile(mUserId); + if (!dir.exists()) { + dir.mkdirs(); + if (mUserId == 0) { + // Migrate old data + File oldFile = new File("/data/system/" + SETTINGS_FILENAME); + // Method doesn't throw an exception on failure. Ignore any errors + // in moving the file (like non-existence) + oldFile.renameTo(settingsFile); + } + } + return new AtomicFile(settingsFile); + } + + void onUserRemoved() { + // prune the ones we don't want to keep + int N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + cancelBroadcasts(p); + } + getSettingsFile(mUserId).delete(); + } + + void addProvidersForPackageLocked(String pkgName) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.setPackage(pkgName); + List<ResolveInfo> broadcastReceivers; + try { + broadcastReceivers = mPm.queryIntentReceivers(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.GET_META_DATA, mUserId); + } catch (RemoteException re) { + // Shouldn't happen, local call + return; + } + final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + for (int i = 0; i < N; i++) { + ResolveInfo ri = broadcastReceivers.get(i); + ActivityInfo ai = ri.activityInfo; + if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + continue; + } + if (pkgName.equals(ai.packageName)) { + addProviderLocked(ri); + } + } + } + + void updateProvidersForPackageLocked(String pkgName) { + HashSet<String> keep = new HashSet<String>(); + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.setPackage(pkgName); + List<ResolveInfo> broadcastReceivers; + try { + broadcastReceivers = mPm.queryIntentReceivers(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.GET_META_DATA, mUserId); + } catch (RemoteException re) { + // Shouldn't happen, local call + return; + } + + // add the missing ones and collect which ones to keep + int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + for (int i = 0; i < N; i++) { + ResolveInfo ri = broadcastReceivers.get(i); + ActivityInfo ai = ri.activityInfo; + if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + continue; + } + if (pkgName.equals(ai.packageName)) { + ComponentName component = new ComponentName(ai.packageName, ai.name); + Provider p = lookupProviderLocked(component); + if (p == null) { + if (addProviderLocked(ri)) { + keep.add(ai.name); + } + } else { + Provider parsed = parseProviderInfoXml(component, ri); + if (parsed != null) { + keep.add(ai.name); + // Use the new AppWidgetProviderInfo. + p.info = parsed.info; + // If it's enabled + final int M = p.instances.size(); + if (M > 0) { + int[] appWidgetIds = getAppWidgetIds(p); + // Reschedule for the new updatePeriodMillis (don't worry about handling + // it specially if updatePeriodMillis didn't change because we just sent + // an update, and the next one will be updatePeriodMillis from now). + cancelBroadcasts(p); + registerForBroadcastsLocked(p, appWidgetIds); + // If it's currently showing, call back with the new + // AppWidgetProviderInfo. + for (int j = 0; j < M; j++) { + AppWidgetId id = p.instances.get(j); + id.views = null; + if (id.host != null && id.host.callbacks != null) { + try { + id.host.callbacks.providerChanged(id.appWidgetId, p.info); + } catch (RemoteException ex) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this + // instance. + id.host.callbacks = null; + } + } + } + // Now that we've told the host, push out an update. + sendUpdateIntentLocked(p, appWidgetIds); + } + } + } + } + } + + // prune the ones we don't want to keep + N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + if (pkgName.equals(p.info.provider.getPackageName()) + && !keep.contains(p.info.provider.getClassName())) { + removeProviderLocked(i, p); + } + } + } + + void removeProvidersForPackageLocked(String pkgName) { + 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); + } + } + + // Delete the hosts for this package too + // + // By now, we have removed any AppWidgets that were in any hosts here, + // so we don't need to worry about sending DISABLE broadcasts to them. + N = mHosts.size(); + for (int i = N - 1; i >= 0; i--) { + Host host = mHosts.get(i); + if (pkgName.equals(host.packageName)) { + deleteHostLocked(host); + } + } + } +} diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 4d5e0a6..a3768c6 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -140,6 +140,8 @@ class BackupManagerService extends IBackupManager.Stub { static final int BACKUP_FILE_VERSION = 1; static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production + static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup"; + // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR; @@ -199,9 +201,9 @@ class BackupManagerService extends IBackupManager.Stub { BackupHandler mBackupHandler; PendingIntent mRunBackupIntent, mRunInitIntent; BroadcastReceiver mRunBackupReceiver, mRunInitReceiver; - // map UIDs to the set of backup client services within that UID's app set - final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants - = new SparseArray<HashSet<ApplicationInfo>>(); + // map UIDs to the set of participating packages under that UID + final SparseArray<HashSet<String>> mBackupParticipants + = new SparseArray<HashSet<String>>(); // set of backup services that have pending changes class BackupRequest { public String packageName; @@ -235,6 +237,10 @@ class BackupManagerService extends IBackupManager.Stub { volatile long mLastBackupPass; volatile long mNextBackupPass; + // For debugging, we maintain a progress trace of operations during backup + static final boolean DEBUG_BACKUP_TRACE = true; + final List<String> mBackupTrace = new ArrayList<String>(); + // A similar synchronization mechanism around clearing apps' data for restore final Object mClearDataLock = new Object(); volatile boolean mClearingData; @@ -652,6 +658,23 @@ class BackupManagerService extends IBackupManager.Stub { } } + // ----- Debug-only backup operation trace ----- + void addBackupTrace(String s) { + if (DEBUG_BACKUP_TRACE) { + synchronized (mBackupTrace) { + mBackupTrace.add(s); + } + } + } + + void clearBackupTrace() { + if (DEBUG_BACKUP_TRACE) { + synchronized (mBackupTrace) { + mBackupTrace.clear(); + } + } + } + // ----- Main service implementation ----- public BackupManagerService(Context context) { @@ -937,7 +960,6 @@ class BackupManagerService extends IBackupManager.Stub { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addDataScheme("package"); mContext.registerReceiver(mBroadcastReceiver, filter); // Register for events related to sdcard installation. @@ -1216,12 +1238,13 @@ class BackupManagerService extends IBackupManager.Stub { // Enqueue a new backup of every participant synchronized (mBackupParticipants) { - int N = mBackupParticipants.size(); + final int N = mBackupParticipants.size(); for (int i=0; i<N; i++) { - int uid = mBackupParticipants.keyAt(i); - HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); - for (ApplicationInfo app: participants) { - dataChangedImpl(app.packageName); + HashSet<String> participants = mBackupParticipants.valueAt(i); + if (participants != null) { + for (String packageName : participants) { + dataChangedImpl(packageName); + } } } } @@ -1279,8 +1302,7 @@ class BackupManagerService extends IBackupManager.Stub { Bundle extras = intent.getExtras(); String pkgList[] = null; if (Intent.ACTION_PACKAGE_ADDED.equals(action) || - Intent.ACTION_PACKAGE_REMOVED.equals(action) || - Intent.ACTION_PACKAGE_REPLACED.equals(action)) { + Intent.ACTION_PACKAGE_REMOVED.equals(action)) { Uri uri = intent.getData(); if (uri == null) { return; @@ -1289,14 +1311,8 @@ class BackupManagerService extends IBackupManager.Stub { if (pkgName != null) { pkgList = new String[] { pkgName }; } - if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { - // use the existing "add with replacement" logic - if (MORE_DEBUG) Slog.d(TAG, "PACKAGE_REPLACED, updating package " + pkgName); - added = replacing = true; - } else { - added = Intent.ACTION_PACKAGE_ADDED.equals(action); - replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); - } + added = Intent.ACTION_PACKAGE_ADDED.equals(action); + replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { added = true; pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); @@ -1308,20 +1324,23 @@ class BackupManagerService extends IBackupManager.Stub { if (pkgList == null || pkgList.length == 0) { return; } + + final int uid = extras.getInt(Intent.EXTRA_UID); if (added) { synchronized (mBackupParticipants) { if (replacing) { - updatePackageParticipantsLocked(pkgList); - } else { - addPackageParticipantsLocked(pkgList); + // This is the package-replaced case; we just remove the entry + // under the old uid and fall through to re-add. + removePackageParticipantsLocked(pkgList, uid); } + addPackageParticipantsLocked(pkgList); } } else { if (replacing) { // The package is being updated. We'll receive a PACKAGE_ADDED shortly. } else { synchronized (mBackupParticipants) { - removePackageParticipantsLocked(pkgList); + removePackageParticipantsLocked(pkgList, uid); } } } @@ -1368,12 +1387,12 @@ class BackupManagerService extends IBackupManager.Stub { for (PackageInfo pkg : targetPkgs) { if (packageName == null || pkg.packageName.equals(packageName)) { int uid = pkg.applicationInfo.uid; - HashSet<ApplicationInfo> set = mBackupParticipants.get(uid); + HashSet<String> set = mBackupParticipants.get(uid); if (set == null) { - set = new HashSet<ApplicationInfo>(); + set = new HashSet<String>(); mBackupParticipants.put(uid, set); } - set.add(pkg.applicationInfo); + set.add(pkg.packageName); if (MORE_DEBUG) Slog.v(TAG, "Agent found; added"); // If we've never seen this app before, schedule a backup for it @@ -1387,63 +1406,36 @@ class BackupManagerService extends IBackupManager.Stub { } // Remove the given packages' entries from our known active set. - void removePackageParticipantsLocked(String[] packageNames) { + void removePackageParticipantsLocked(String[] packageNames, int oldUid) { if (packageNames == null) { Slog.w(TAG, "removePackageParticipants with null list"); return; } - if (DEBUG) Slog.v(TAG, "removePackageParticipantsLocked: #" + packageNames.length); - List<PackageInfo> knownPackages = allAgentPackages(); + if (DEBUG) Slog.v(TAG, "removePackageParticipantsLocked: uid=" + oldUid + + " #" + packageNames.length); for (String pkg : packageNames) { - removePackageParticipantsLockedInner(pkg, knownPackages); + // Known previous UID, so we know which package set to check + HashSet<String> set = mBackupParticipants.get(oldUid); + if (set != null && set.contains(pkg)) { + removePackageFromSetLocked(set, pkg); + if (set.isEmpty()) { + if (MORE_DEBUG) Slog.v(TAG, " last one of this uid; purging set"); + mBackupParticipants.remove(oldUid); + } + } } } - private void removePackageParticipantsLockedInner(String packageName, - List<PackageInfo> allPackages) { - if (MORE_DEBUG) { - Slog.v(TAG, "removePackageParticipantsLockedInner (" + packageName - + ") removing from " + allPackages.size() + " entries"); - for (PackageInfo p : allPackages) { - Slog.v(TAG, " - " + p.packageName); - } - } - for (PackageInfo pkg : allPackages) { - if (packageName == null || pkg.packageName.equals(packageName)) { - /* - int uid = -1; - try { - PackageInfo info = mPackageManager.getPackageInfo(packageName, 0); - uid = info.applicationInfo.uid; - } catch (NameNotFoundException e) { - // we don't know this package name, so just skip it for now - continue; - } - */ - final int uid = pkg.applicationInfo.uid; - if (MORE_DEBUG) Slog.i(TAG, " found pkg " + packageName + " uid=" + uid); - - HashSet<ApplicationInfo> set = mBackupParticipants.get(uid); - if (set != null) { - // Find the existing entry with the same package name, and remove it. - // We can't just remove(app) because the instances are different. - for (ApplicationInfo entry: set) { - if (MORE_DEBUG) Slog.i(TAG, " checking against " + entry.packageName); - if (entry.packageName.equals(pkg)) { - if (MORE_DEBUG) Slog.v(TAG, " removing participant " + pkg); - set.remove(entry); - removeEverBackedUp(pkg.packageName); - break; - } - } - if (set.size() == 0) { - mBackupParticipants.delete(uid); - } - } else { - if (MORE_DEBUG) Slog.i(TAG, " ... not found in uid mapping"); - } - } + private void removePackageFromSetLocked(final HashSet<String> set, + final String packageName) { + if (set.contains(packageName)) { + // Found it. Remove this one package from the bookkeeping, and + // if it's the last participating app under this uid we drop the + // (now-empty) set as well. + if (MORE_DEBUG) Slog.v(TAG, " removing participant " + packageName); + removeEverBackedUp(packageName); + set.remove(packageName); } } @@ -1474,24 +1466,6 @@ class BackupManagerService extends IBackupManager.Stub { return packages; } - // Reset the given package's known backup participants. Unlike add/remove, the update - // action cannot be passed a null package name. - void updatePackageParticipantsLocked(String[] packageNames) { - if (packageNames == null) { - Slog.e(TAG, "updatePackageParticipants called with null package list"); - return; - } - if (DEBUG) Slog.v(TAG, "updatePackageParticipantsLocked: #" + packageNames.length); - - if (packageNames.length > 0) { - List<PackageInfo> allApps = allAgentPackages(); - for (String packageName : packageNames) { - removePackageParticipantsLockedInner(packageName, allApps); - addPackageParticipantsLockedInner(packageName, allApps); - } - } - } - // Called from the backup task: record that the given app has been successfully // backed up at least once void logBackupComplete(String packageName) { @@ -1612,6 +1586,7 @@ class BackupManagerService extends IBackupManager.Stub { mAgentConnectLock.wait(5000); } catch (InterruptedException e) { // just bail + if (DEBUG) Slog.w(TAG, "Interrupted: " + e); return null; } } @@ -1621,6 +1596,7 @@ class BackupManagerService extends IBackupManager.Stub { Slog.w(TAG, "Timeout waiting for agent " + app); return null; } + if (DEBUG) Slog.i(TAG, "got agent " + mConnectedAgent); agent = mConnectedAgent; } } catch (RemoteException e) { @@ -1650,7 +1626,8 @@ class BackupManagerService extends IBackupManager.Stub { synchronized(mClearDataLock) { mClearingData = true; try { - mActivityManager.clearApplicationUserData(packageName, observer); + mActivityManager.clearApplicationUserData(packageName, observer, + Binder.getOrigCallingUser()); } catch (RemoteException e) { // can't happen because the activity manager is in this process } @@ -1814,6 +1791,8 @@ class BackupManagerService extends IBackupManager.Stub { mCurrentState = BackupState.INITIAL; mFinished = false; + + addBackupTrace("STATE => INITIAL"); } // Main entry point: perform one chunk of work, updating the state as appropriate @@ -1842,11 +1821,25 @@ class BackupManagerService extends IBackupManager.Stub { // We're starting a backup pass. Initialize the transport and send // the PM metadata blob if we haven't already. void beginBackup() { + if (DEBUG_BACKUP_TRACE) { + clearBackupTrace(); + StringBuilder b = new StringBuilder(256); + b.append("beginBackup: ["); + for (BackupRequest req : mOriginalQueue) { + b.append(' '); + b.append(req.packageName); + } + b.append(" ]"); + addBackupTrace(b.toString()); + } + mStatus = BackupConstants.TRANSPORT_OK; // Sanity check: if the queue is empty we have no work to do. if (mOriginalQueue.isEmpty()) { Slog.w(TAG, "Backup begun with an empty queue - nothing to do."); + addBackupTrace("queue empty at begin"); + executeNextState(BackupState.FINAL); return; } @@ -1859,13 +1852,17 @@ class BackupManagerService extends IBackupManager.Stub { File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL); try { - EventLog.writeEvent(EventLogTags.BACKUP_START, mTransport.transportDirName()); + final String transportName = mTransport.transportDirName(); + EventLog.writeEvent(EventLogTags.BACKUP_START, transportName); // If we haven't stored package manager metadata yet, we must init the transport. if (mStatus == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) { Slog.i(TAG, "Initializing (wiping) backup state and transport storage"); + addBackupTrace("initializing transport " + transportName); resetBackupState(mStateDir); // Just to make sure. mStatus = mTransport.initializeDevice(); + + addBackupTrace("transport.initializeDevice() == " + mStatus); if (mStatus == BackupConstants.TRANSPORT_OK) { EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); } else { @@ -1884,6 +1881,7 @@ class BackupManagerService extends IBackupManager.Stub { mPackageManager, allAgentPackages()); mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL, IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport); + addBackupTrace("PMBA invoke: " + mStatus); } if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) { @@ -1894,11 +1892,13 @@ class BackupManagerService extends IBackupManager.Stub { } } catch (Exception e) { Slog.e(TAG, "Error in backup thread", e); + addBackupTrace("Exception in backup thread: " + e); mStatus = BackupConstants.TRANSPORT_ERROR; } finally { // If we've succeeded so far, invokeAgentForBackup() will have run the PM // metadata and its completion/timeout callback will continue the state // machine chain. If it failed that won't happen; we handle that now. + addBackupTrace("exiting prelim: " + mStatus); if (mStatus != BackupConstants.TRANSPORT_OK) { // if things went wrong at this point, we need to // restage everything and try again later. @@ -1912,11 +1912,12 @@ class BackupManagerService extends IBackupManager.Stub { // if that was warranted. Now we process the single next thing in the queue. void invokeNextAgent() { mStatus = BackupConstants.TRANSPORT_OK; + addBackupTrace("invoke q=" + mQueue.size()); // Sanity check that we have work to do. If not, skip to the end where // we reestablish the wakelock invariants etc. if (mQueue.isEmpty()) { - Slog.e(TAG, "Running queue but it's empty!"); + if (DEBUG) Slog.i(TAG, "queue now empty"); executeNextState(BackupState.FINAL); return; } @@ -1926,6 +1927,7 @@ class BackupManagerService extends IBackupManager.Stub { mQueue.remove(0); Slog.d(TAG, "starting agent for backup of " + request); + addBackupTrace("launch agent for " + request.packageName); // Verify that the requested app exists; it might be something that // requested a backup but was then uninstalled. The request was @@ -1935,12 +1937,23 @@ class BackupManagerService extends IBackupManager.Stub { try { mCurrentPackage = mPackageManager.getPackageInfo(request.packageName, PackageManager.GET_SIGNATURES); + if (mCurrentPackage.applicationInfo.backupAgentName == null) { + // The manifest has changed but we had a stale backup request pending. + // This won't happen again because the app won't be requesting further + // backups. + Slog.i(TAG, "Package " + request.packageName + + " no longer supports backup; skipping"); + addBackupTrace("skipping - no agent, completion is noop"); + executeNextState(BackupState.RUNNING_QUEUE); + return; + } IBackupAgent agent = null; try { mWakelock.setWorkSource(new WorkSource(mCurrentPackage.applicationInfo.uid)); agent = bindToAgentSynchronous(mCurrentPackage.applicationInfo, IApplicationThread.BACKUP_MODE_INCREMENTAL); + addBackupTrace("agent bound; a? = " + (agent != null)); if (agent != null) { mStatus = invokeAgentForBackup(request.packageName, agent, mTransport); // at this point we'll either get a completion callback from the @@ -1954,14 +1967,17 @@ class BackupManagerService extends IBackupManager.Stub { // Try for the next one. Slog.d(TAG, "error in bind/backup", ex); mStatus = BackupConstants.AGENT_ERROR; + addBackupTrace("agent SE"); } } catch (NameNotFoundException e) { Slog.d(TAG, "Package does not exist; skipping"); + addBackupTrace("no such package"); + mStatus = BackupConstants.AGENT_UNKNOWN; } finally { mWakelock.setWorkSource(null); // If there was an agent error, no timeout/completion handling will occur. - // That means we need to deal with the next state ourselves. + // That means we need to direct to the next state ourselves. if (mStatus != BackupConstants.TRANSPORT_OK) { BackupState nextState = BackupState.RUNNING_QUEUE; @@ -1973,18 +1989,26 @@ class BackupManagerService extends IBackupManager.Stub { dataChangedImpl(request.packageName); mStatus = BackupConstants.TRANSPORT_OK; if (mQueue.isEmpty()) nextState = BackupState.FINAL; - } else if (mStatus != BackupConstants.TRANSPORT_OK) { + } else if (mStatus == BackupConstants.AGENT_UNKNOWN) { + // Failed lookup of the app, so we couldn't bring up an agent, but + // we're otherwise fine. Just drop it and go on to the next as usual. + mStatus = BackupConstants.TRANSPORT_OK; + } else { // Transport-level failure means we reenqueue everything revertAndEndBackup(); nextState = BackupState.FINAL; } executeNextState(nextState); + } else { + addBackupTrace("expecting completion/timeout callback"); } } } void finalizeBackup() { + addBackupTrace("finishing"); + // Either backup was successful, in which case we of course do not need // this pass's journal any more; or it failed, in which case we just // re-enqueued all of these packages in the current active journal. @@ -1997,6 +2021,7 @@ class BackupManagerService extends IBackupManager.Stub { // done a backup, we can now record what the current backup dataset token // is. if ((mCurrentToken == 0) && (mStatus == BackupConstants.TRANSPORT_OK)) { + addBackupTrace("success; recording token"); try { mCurrentToken = mTransport.getCurrentRestoreSet(); } catch (RemoteException e) {} // can't happen @@ -2012,11 +2037,13 @@ class BackupManagerService extends IBackupManager.Stub { // Make sure we back up everything and perform the one-time init clearMetadata(); if (DEBUG) Slog.d(TAG, "Server requires init; rerunning"); + addBackupTrace("init required; rerunning"); backupNow(); } } // Only once we're entirely finished do we release the wakelock + clearBackupTrace(); Slog.i(TAG, "Backup pass finished."); mWakelock.release(); } @@ -2031,7 +2058,8 @@ class BackupManagerService extends IBackupManager.Stub { // handler in case it doesn't get back to us. int invokeAgentForBackup(String packageName, IBackupAgent agent, IBackupTransport transport) { - if (DEBUG) Slog.d(TAG, "processOneBackup doBackup() on " + packageName); + if (DEBUG) Slog.d(TAG, "invokeAgentForBackup on " + packageName); + addBackupTrace("invoking " + packageName); mSavedStateName = new File(mStateDir, packageName); mBackupDataName = new File(mDataDir, packageName + ".data"); @@ -2071,10 +2099,13 @@ class BackupManagerService extends IBackupManager.Stub { ParcelFileDescriptor.MODE_TRUNCATE); // Initiate the target's backup pass + addBackupTrace("setting timeout"); prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this); + addBackupTrace("calling agent doBackup()"); agent.doBackup(mSavedState, mBackupData, mNewState, token, mBackupManagerBinder); } catch (Exception e) { Slog.e(TAG, "Error invoking for backup on " + packageName); + addBackupTrace("exception: " + e); EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, e.toString()); agentErrorCleanup(); @@ -2085,6 +2116,7 @@ class BackupManagerService extends IBackupManager.Stub { // either be a callback from the agent, at which point we'll process its data // for transport, or a timeout. Either way the next phase will happen in // response to the TimeoutHandler interface callbacks. + addBackupTrace("invoke success"); return BackupConstants.TRANSPORT_OK; } @@ -2096,6 +2128,7 @@ class BackupManagerService extends IBackupManager.Stub { + mCurrentPackage.packageName); mBackupHandler.removeMessages(MSG_TIMEOUT); clearAgentState(); + addBackupTrace("operation complete"); ParcelFileDescriptor backupData = null; mStatus = BackupConstants.TRANSPORT_OK; @@ -2105,6 +2138,7 @@ class BackupManagerService extends IBackupManager.Stub { if (mStatus == BackupConstants.TRANSPORT_OK) { backupData = ParcelFileDescriptor.open(mBackupDataName, ParcelFileDescriptor.MODE_READ_ONLY); + addBackupTrace("sending data to transport"); mStatus = mTransport.performBackup(mCurrentPackage, backupData); } @@ -2113,11 +2147,15 @@ class BackupManagerService extends IBackupManager.Stub { // hold off on finishBackup() until the end, which implies holding off on // renaming *all* the output state files (see below) until that happens. + addBackupTrace("data delivered: " + mStatus); if (mStatus == BackupConstants.TRANSPORT_OK) { + addBackupTrace("finishing op on transport"); mStatus = mTransport.finishBackup(); + addBackupTrace("finished: " + mStatus); } } else { if (DEBUG) Slog.i(TAG, "no backup data written; not calling transport"); + addBackupTrace("no data to send"); } // After successful transport, delete the now-stale data @@ -2165,12 +2203,14 @@ class BackupManagerService extends IBackupManager.Stub { Slog.e(TAG, "Timeout backing up " + mCurrentPackage.packageName); EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName, "timeout"); + addBackupTrace("timeout of " + mCurrentPackage.packageName); agentErrorCleanup(); dataChangedImpl(mCurrentPackage.packageName); } void revertAndEndBackup() { if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything"); + addBackupTrace("transport error; reverting"); for (BackupRequest request : mOriginalQueue) { dataChangedImpl(request.packageName); } @@ -2199,6 +2239,7 @@ class BackupManagerService extends IBackupManager.Stub { // If this was a pseudopackage there's no associated Activity Manager state if (mCurrentPackage.applicationInfo != null) { + addBackupTrace("unbinding " + mCurrentPackage.packageName); try { // unbind even on timeout, just in case mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo); } catch (RemoteException e) {} @@ -2206,6 +2247,7 @@ class BackupManagerService extends IBackupManager.Stub { } void restartBackupAlarm() { + addBackupTrace("setting backup trigger"); synchronized (mQueueLock) { try { startBackupAlarmsLocked(mTransport.requestBackupTime()); @@ -2216,6 +2258,7 @@ class BackupManagerService extends IBackupManager.Stub { void executeNextState(BackupState nextState) { if (MORE_DEBUG) Slog.i(TAG, " => executing next step on " + this + " nextState=" + nextState); + addBackupTrace("executeNextState => " + nextState); mCurrentState = nextState; Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this); mBackupHandler.sendMessage(msg); @@ -2246,14 +2289,16 @@ class BackupManagerService extends IBackupManager.Stub { ParcelFileDescriptor mPipe; int mToken; boolean mSendApk; + boolean mWriteManifest; FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe, - int token, boolean sendApk) throws IOException { + int token, boolean sendApk, boolean writeManifest) throws IOException { mPackage = pack; mAgent = agent; mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor()); mToken = token; mSendApk = sendApk; + mWriteManifest = writeManifest; } @Override @@ -2262,12 +2307,14 @@ class BackupManagerService extends IBackupManager.Stub { BackupDataOutput output = new BackupDataOutput( mPipe.getFileDescriptor()); - if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName); - writeAppManifest(mPackage, mManifestFile, mSendApk); - FullBackup.backupToTar(mPackage.packageName, null, null, - mFilesDir.getAbsolutePath(), - mManifestFile.getAbsolutePath(), - output); + if (mWriteManifest) { + if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName); + writeAppManifest(mPackage, mManifestFile, mSendApk); + FullBackup.backupToTar(mPackage.packageName, null, null, + mFilesDir.getAbsolutePath(), + mManifestFile.getAbsolutePath(), + output); + } if (mSendApk) { writeApkToBackup(mPackage, output); @@ -2354,10 +2401,13 @@ class BackupManagerService extends IBackupManager.Stub { } } - // Cull any packages that have indicated that backups are not permitted. + // Cull any packages that have indicated that backups are not permitted, as well + // as any explicit mention of the 'special' shared-storage agent package (we + // handle that one at the end). for (int i = 0; i < packagesToBackup.size(); ) { PackageInfo pkg = packagesToBackup.get(i); - if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) { + if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0 + || pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) { packagesToBackup.remove(i); } else { i++; @@ -2437,6 +2487,16 @@ class BackupManagerService extends IBackupManager.Stub { return; } + // Shared storage if requested + if (mIncludeShared) { + try { + pkg = mPackageManager.getPackageInfo(SHARED_BACKUP_AGENT_PACKAGE, 0); + packagesToBackup.add(pkg); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Unable to find shared-storage backup handler"); + } + } + // Now back up the app data via the agent mechanism int N = packagesToBackup.size(); for (int i = 0; i < N; i++) { @@ -2444,15 +2504,12 @@ class BackupManagerService extends IBackupManager.Stub { backupOnePackage(pkg, out); } - // Shared storage if requested - if (mIncludeShared) { - backupSharedStorage(); - } - // Done! finalizeBackup(out); } catch (RemoteException e) { Slog.e(TAG, "App died during full backup"); + } catch (Exception e) { + Slog.e(TAG, "Internal exception during full backup", e); } finally { tearDown(pkg); try { @@ -2554,19 +2611,21 @@ class BackupManagerService extends IBackupManager.Stub { if (agent != null) { ParcelFileDescriptor[] pipes = null; try { - pipes = ParcelFileDescriptor.createPipe(); + pipes = ParcelFileDescriptor.createPipe(); ApplicationInfo app = pkg.applicationInfo; + final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); final boolean sendApk = mIncludeApks + && !isSharedStorage && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0) && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); - sendOnBackupPackage(pkg.packageName); + sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName); final int token = generateToken(); FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1], - token, sendApk); + token, sendApk, !isSharedStorage); pipes[1].close(); // the runner has dup'd it pipes[1] = null; Thread t = new Thread(runner); @@ -2641,33 +2700,6 @@ class BackupManagerService extends IBackupManager.Stub { } } - private void backupSharedStorage() throws RemoteException { - PackageInfo pkg = null; - try { - pkg = mPackageManager.getPackageInfo("com.android.sharedstoragebackup", 0); - IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo, - IApplicationThread.BACKUP_MODE_FULL); - if (agent != null) { - sendOnBackupPackage("Shared storage"); - - final int token = generateToken(); - prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL, null); - agent.doFullBackup(mOutputFile, token, mBackupManagerBinder); - if (!waitUntilOperationComplete(token)) { - Slog.e(TAG, "Full backup failed on shared storage"); - } else { - if (DEBUG) Slog.d(TAG, "Full shared storage backup success"); - } - } else { - Slog.e(TAG, "Could not bind to shared storage backup agent"); - } - } catch (NameNotFoundException e) { - Slog.e(TAG, "Shared storage backup package not found"); - } finally { - tearDown(pkg); - } - } - private void finalizeBackup(OutputStream out) { try { // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes. @@ -2893,7 +2925,7 @@ class BackupManagerService extends IBackupManager.Stub { // Are we able to restore shared-storage data? if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - mPackagePolicies.put("com.android.sharedstoragebackup", RestorePolicy.ACCEPT); + mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT); } FileInputStream rawInStream = null; @@ -3771,7 +3803,7 @@ class BackupManagerService extends IBackupManager.Stub { info.path, 0, FullBackup.SHARED_PREFIX.length())) { // File in shared storage. !!! TODO: implement this. info.path = info.path.substring(FullBackup.SHARED_PREFIX.length()); - info.packageName = "com.android.sharedstoragebackup"; + info.packageName = SHARED_BACKUP_AGENT_PACKAGE; info.domain = FullBackup.SHARED_STORAGE_TOKEN; if (DEBUG) Slog.i(TAG, "File in shared storage: " + info.path); } else if (FullBackup.APPS_PREFIX.regionMatches(0, @@ -4612,6 +4644,8 @@ class BackupManagerService extends IBackupManager.Stub { mTransport.clearBackupData(mPackage); } catch (RemoteException e) { // can't happen; the transport is local + } catch (Exception e) { + Slog.e(TAG, "Transport threw attempting to clear data for " + mPackage); } finally { try { // TODO - need to handle failures @@ -4689,11 +4723,11 @@ class BackupManagerService extends IBackupManager.Stub { } private void dataChangedImpl(String packageName) { - HashSet<ApplicationInfo> targets = dataChangedTargets(packageName); + HashSet<String> targets = dataChangedTargets(packageName); dataChangedImpl(packageName, targets); } - private void dataChangedImpl(String packageName, HashSet<ApplicationInfo> targets) { + private void dataChangedImpl(String packageName, HashSet<String> targets) { // Record that we need a backup pass for the caller. Since multiple callers // may share a uid, we need to note all candidates within that uid and schedule // a backup pass for each of them. @@ -4707,25 +4741,23 @@ class BackupManagerService extends IBackupManager.Stub { synchronized (mQueueLock) { // Note that this client has made data changes that need to be backed up - for (ApplicationInfo app : targets) { - // validate the caller-supplied package name against the known set of - // packages associated with this uid - if (app.packageName.equals(packageName)) { - // Add the caller to the set of pending backups. If there is - // one already there, then overwrite it, but no harm done. - BackupRequest req = new BackupRequest(packageName); - if (mPendingBackups.put(app.packageName, req) == null) { - // Journal this request in case of crash. The put() - // operation returned null when this package was not already - // in the set; we want to avoid touching the disk redundantly. - writeToJournalLocked(packageName); + if (targets.contains(packageName)) { + // Add the caller to the set of pending backups. If there is + // one already there, then overwrite it, but no harm done. + BackupRequest req = new BackupRequest(packageName); + if (mPendingBackups.put(packageName, req) == null) { + if (DEBUG) Slog.d(TAG, "Now staging backup of " + packageName); + + // Journal this request in case of crash. The put() + // operation returned null when this package was not already + // in the set; we want to avoid touching the disk redundantly. + writeToJournalLocked(packageName); - if (MORE_DEBUG) { - int numKeys = mPendingBackups.size(); - Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:"); - for (BackupRequest b : mPendingBackups.values()) { - Slog.d(TAG, " + " + b); - } + if (MORE_DEBUG) { + int numKeys = mPendingBackups.size(); + Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:"); + for (BackupRequest b : mPendingBackups.values()) { + Slog.d(TAG, " + " + b); } } } @@ -4734,7 +4766,7 @@ class BackupManagerService extends IBackupManager.Stub { } // Note: packageName is currently unused, but may be in the future - private HashSet<ApplicationInfo> dataChangedTargets(String packageName) { + private HashSet<String> dataChangedTargets(String packageName) { // If the caller does not hold the BACKUP permission, it can only request a // backup of its own data. if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(), @@ -4746,11 +4778,11 @@ class BackupManagerService extends IBackupManager.Stub { // a caller with full permission can ask to back up any participating app // !!! TODO: allow backup of ANY app? - HashSet<ApplicationInfo> targets = new HashSet<ApplicationInfo>(); + HashSet<String> targets = new HashSet<String>(); synchronized (mBackupParticipants) { int N = mBackupParticipants.size(); for (int i = 0; i < N; i++) { - HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i); + HashSet<String> s = mBackupParticipants.valueAt(i); if (s != null) { targets.addAll(s); } @@ -4777,7 +4809,7 @@ class BackupManagerService extends IBackupManager.Stub { // ----- IBackupManager binder interface ----- public void dataChanged(final String packageName) { - final HashSet<ApplicationInfo> targets = dataChangedTargets(packageName); + final HashSet<String> targets = dataChangedTargets(packageName); if (targets == null) { Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'" + " uid=" + Binder.getCallingUid()); @@ -4804,7 +4836,7 @@ class BackupManagerService extends IBackupManager.Stub { // If the caller does not hold the BACKUP permission, it can only request a // wipe of its own backed-up data. - HashSet<ApplicationInfo> apps; + HashSet<String> apps; if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(), Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) { apps = mBackupParticipants.get(Binder.getCallingUid()); @@ -4812,30 +4844,27 @@ class BackupManagerService extends IBackupManager.Stub { // a caller with full permission can ask to back up any participating app // !!! TODO: allow data-clear of ANY app? if (DEBUG) Slog.v(TAG, "Privileged caller, allowing clear of other apps"); - apps = new HashSet<ApplicationInfo>(); + apps = new HashSet<String>(); int N = mBackupParticipants.size(); for (int i = 0; i < N; i++) { - HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i); + HashSet<String> s = mBackupParticipants.valueAt(i); if (s != null) { apps.addAll(s); } } } - // now find the given package in the set of candidate apps - for (ApplicationInfo app : apps) { - if (app.packageName.equals(packageName)) { - if (DEBUG) Slog.v(TAG, "Found the app - running clear process"); - // found it; fire off the clear request - synchronized (mQueueLock) { - long oldId = Binder.clearCallingIdentity(); - mWakelock.acquire(); - Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR, - new ClearParams(getTransport(mCurrentTransport), info)); - mBackupHandler.sendMessage(msg); - Binder.restoreCallingIdentity(oldId); - } - break; + // Is the given app an available participant? + if (apps.contains(packageName)) { + if (DEBUG) Slog.v(TAG, "Found the app - running clear process"); + // found it; fire off the clear request + synchronized (mQueueLock) { + long oldId = Binder.clearCallingIdentity(); + mWakelock.acquire(); + Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR, + new ClearParams(getTransport(mCurrentTransport), info)); + mBackupHandler.sendMessage(msg); + Binder.restoreCallingIdentity(oldId); } } } @@ -5685,6 +5714,8 @@ class BackupManagerService extends IBackupManager.Stub { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + long identityToken = Binder.clearCallingIdentity(); try { dumpInternal(pw); @@ -5694,16 +5725,6 @@ class BackupManagerService extends IBackupManager.Stub { } private void dumpInternal(PrintWriter pw) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump Backup Manager service from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() - + " without permission " - + android.Manifest.permission.DUMP); - return; - } - synchronized (mQueueLock) { pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled") + " / " + (!mProvisioned ? "not " : "") + "provisioned / " @@ -5736,15 +5757,26 @@ class BackupManagerService extends IBackupManager.Stub { pw.println(" " + s); } + if (DEBUG_BACKUP_TRACE) { + synchronized (mBackupTrace) { + if (!mBackupTrace.isEmpty()) { + pw.println("Most recent backup trace:"); + for (String s : mBackupTrace) { + pw.println(" " + s); + } + } + } + } + int N = mBackupParticipants.size(); pw.println("Participants:"); for (int i=0; i<N; i++) { int uid = mBackupParticipants.keyAt(i); pw.print(" uid: "); pw.println(uid); - HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); - for (ApplicationInfo app: participants) { - pw.println(" " + app.packageName); + HashSet<String> participants = mBackupParticipants.valueAt(i); + for (String app: participants) { + pw.println(" " + app); } } diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java index 6665614..22874e6 100644 --- a/services/java/com/android/server/BootReceiver.java +++ b/services/java/com/android/server/BootReceiver.java @@ -20,13 +20,13 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.net.Downloads; import android.os.Build; import android.os.DropBoxManager; import android.os.FileObserver; import android.os.FileUtils; import android.os.RecoverySystem; import android.os.SystemProperties; +import android.provider.Downloads; import android.util.Slog; import java.io.File; @@ -39,8 +39,10 @@ import java.io.IOException; public class BootReceiver extends BroadcastReceiver { private static final String TAG = "BootReceiver"; - // Maximum size of a logged event (files get truncated if they're longer) - private static final int LOG_SIZE = 65536; + // Maximum size of a logged event (files get truncated if they're longer). + // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg. + private static final int LOG_SIZE = + SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536; private static final File TOMBSTONE_DIR = new File("/data/tombstones"); @@ -76,9 +78,8 @@ public class BootReceiver extends BroadcastReceiver { }.start(); } - private void removeOldUpdatePackages(Context ctx) { - Downloads.ByUri.removeAllDownloadsByPackage( - ctx, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); + private void removeOldUpdatePackages(Context context) { + Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS); } private void logBootEvents(Context ctx) throws IOException { diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java index 062ab74..8a6a550 100644 --- a/services/java/com/android/server/ClipboardService.java +++ b/services/java/com/android/server/ClipboardService.java @@ -18,12 +18,14 @@ package com.android.server; import android.app.ActivityManagerNative; import android.app.IActivityManager; +import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipDescription; import android.content.IClipboard; import android.content.IOnPrimaryClipChangedListener; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -34,8 +36,10 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.UserId; import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import java.util.HashSet; @@ -43,18 +47,31 @@ import java.util.HashSet; * Implementation of the clipboard for copy and paste. */ public class ClipboardService extends IClipboard.Stub { + + private static final String TAG = "ClipboardService"; + private final Context mContext; private final IActivityManager mAm; private final PackageManager mPm; private final IBinder mPermissionOwner; - private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners - = new RemoteCallbackList<IOnPrimaryClipChangedListener>(); + private class PerUserClipboard { + final int userId; + + final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners + = new RemoteCallbackList<IOnPrimaryClipChangedListener>(); + + ClipData primaryClip; + + final HashSet<String> activePermissionOwners + = new HashSet<String>(); - private ClipData mPrimaryClip; + PerUserClipboard(int userId) { + this.userId = userId; + } + } - private final HashSet<String> mActivePermissionOwners - = new HashSet<String>(); + private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>(); /** * Instantiates the clipboard. @@ -70,6 +87,19 @@ public class ClipboardService extends IClipboard.Stub { Slog.w("clipboard", "AM dead", e); } mPermissionOwner = permOwner; + + // Remove the clipboard if a user is removed + IntentFilter userFilter = new IntentFilter(); + 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_REMOVED.equals(action)) { + removeClipboard(intent.getIntExtra(Intent.EXTRA_USERID, 0)); + } + } + }, userFilter); } @Override @@ -84,6 +114,28 @@ public class ClipboardService extends IClipboard.Stub { } + private PerUserClipboard getClipboard() { + return getClipboard(UserId.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); + mClipboards.put(userId, puc); + } + return puc; + } + } + + private void removeClipboard(int userId) { + synchronized (mClipboards) { + mClipboards.remove(userId); + } + } + public void setPrimaryClip(ClipData clip) { synchronized (this) { if (clip != null && clip.getItemCount() <= 0) { @@ -91,56 +143,59 @@ public class ClipboardService extends IClipboard.Stub { } checkDataOwnerLocked(clip, Binder.getCallingUid()); clearActiveOwnersLocked(); - mPrimaryClip = clip; - final int n = mPrimaryClipListeners.beginBroadcast(); + PerUserClipboard clipboard = getClipboard(); + clipboard.primaryClip = clip; + final int n = clipboard.primaryClipListeners.beginBroadcast(); for (int i = 0; i < n; i++) { try { - mPrimaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged(); + clipboard.primaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged(); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing // the dead object for us. } } - mPrimaryClipListeners.finishBroadcast(); + clipboard.primaryClipListeners.finishBroadcast(); } } public ClipData getPrimaryClip(String pkg) { synchronized (this) { addActiveOwnerLocked(Binder.getCallingUid(), pkg); - return mPrimaryClip; + return getClipboard().primaryClip; } } public ClipDescription getPrimaryClipDescription() { synchronized (this) { - return mPrimaryClip != null ? mPrimaryClip.getDescription() : null; + PerUserClipboard clipboard = getClipboard(); + return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null; } } public boolean hasPrimaryClip() { synchronized (this) { - return mPrimaryClip != null; + return getClipboard().primaryClip != null; } } public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { synchronized (this) { - mPrimaryClipListeners.register(listener); + getClipboard().primaryClipListeners.register(listener); } } public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { synchronized (this) { - mPrimaryClipListeners.unregister(listener); + getClipboard().primaryClipListeners.unregister(listener); } } public boolean hasClipboardText() { synchronized (this) { - if (mPrimaryClip != null) { - CharSequence text = mPrimaryClip.getItemAt(0).getText(); + PerUserClipboard clipboard = getClipboard(); + if (clipboard.primaryClip != null) { + CharSequence text = clipboard.primaryClip.getItemAt(0).getText(); return text != null && text.length() > 0; } return false; @@ -152,7 +207,6 @@ public class ClipboardService extends IClipboard.Stub { return; } long ident = Binder.clearCallingIdentity(); - boolean allowed = false; try { // This will throw SecurityException for us. mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -204,19 +258,20 @@ public class ClipboardService extends IClipboard.Stub { PackageInfo pi; try { pi = mPm.getPackageInfo(pkg, 0); - if (pi.applicationInfo.uid != uid) { + if (!UserId.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); } - if (mPrimaryClip != null && !mActivePermissionOwners.contains(pkg)) { - final int N = mPrimaryClip.getItemCount(); + PerUserClipboard clipboard = getClipboard(); + if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) { + final int N = clipboard.primaryClip.getItemCount(); for (int i=0; i<N; i++) { - grantItemLocked(mPrimaryClip.getItemAt(i), pkg); + grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg); } - mActivePermissionOwners.add(pkg); + clipboard.activePermissionOwners.add(pkg); } } @@ -243,13 +298,14 @@ public class ClipboardService extends IClipboard.Stub { } private final void clearActiveOwnersLocked() { - mActivePermissionOwners.clear(); - if (mPrimaryClip == null) { + PerUserClipboard clipboard = getClipboard(); + clipboard.activePermissionOwners.clear(); + if (clipboard.primaryClip == null) { return; } - final int N = mPrimaryClip.getItemCount(); + final int N = clipboard.primaryClip.getItemCount(); for (int i=0; i<N; i++) { - revokeItemLocked(mPrimaryClip.getItemAt(i)); + revokeItemLocked(clipboard.primaryClip.getItemAt(i)); } } } diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java new file mode 100644 index 0000000..9a25d2e --- /dev/null +++ b/services/java/com/android/server/CommonTimeManagementService.java @@ -0,0 +1,377 @@ +/* + * 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 java.io.FileDescriptor; +import java.io.PrintWriter; +import java.net.InetAddress; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.INetworkManagementEventObserver; +import android.net.InterfaceConfiguration; +import android.net.NetworkInfo; +import android.os.Binder; +import android.os.CommonTimeConfig; +import android.os.Handler; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.util.Log; + +/** + * @hide + * <p>CommonTimeManagementService manages the configuration of the native Common Time service, + * reconfiguring the native service as appropriate in response to changes in network configuration. + */ +class CommonTimeManagementService extends Binder { + /* + * Constants and globals. + */ + private static final String TAG = CommonTimeManagementService.class.getSimpleName(); + private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000; + private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable"; + private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi"; + private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio"; + private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout"; + private static final boolean AUTO_DISABLE; + private static final boolean ALLOW_WIFI; + private static final byte BASE_SERVER_PRIO; + private static final int NO_INTERFACE_TIMEOUT; + private static final InterfaceScoreRule[] IFACE_SCORE_RULES; + + static { + int tmp; + AUTO_DISABLE = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1)); + ALLOW_WIFI = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0)); + tmp = SystemProperties.getInt(SERVER_PRIO_PROP, 1); + NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000); + + if (tmp < 1) + BASE_SERVER_PRIO = 1; + else + if (tmp > 30) + BASE_SERVER_PRIO = 30; + else + BASE_SERVER_PRIO = (byte)tmp; + + if (ALLOW_WIFI) { + IFACE_SCORE_RULES = new InterfaceScoreRule[] { + new InterfaceScoreRule("wlan", (byte)1), + new InterfaceScoreRule("eth", (byte)2), + }; + } else { + IFACE_SCORE_RULES = new InterfaceScoreRule[] { + new InterfaceScoreRule("eth", (byte)2), + }; + } + }; + + /* + * Internal state + */ + private final Context mContext; + private INetworkManagementService mNetMgr; + private CommonTimeConfig mCTConfig; + private String mCurIface; + private Handler mReconnectHandler = new Handler(); + private Handler mNoInterfaceHandler = new Handler(); + private Object mLock = new Object(); + private boolean mDetectedAtStartup = false; + private byte mEffectivePrio = BASE_SERVER_PRIO; + + /* + * Callback handler implementations. + */ + private INetworkManagementEventObserver mIfaceObserver = + new INetworkManagementEventObserver.Stub() { + + public void interfaceStatusChanged(String iface, boolean up) { + reevaluateServiceState(); + } + public void interfaceLinkStateChanged(String iface, boolean up) { + reevaluateServiceState(); + } + public void interfaceAdded(String iface) { + reevaluateServiceState(); + } + public void interfaceRemoved(String iface) { + reevaluateServiceState(); + } + public void limitReached(String limitName, String iface) { } + }; + + private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + reevaluateServiceState(); + } + }; + + private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener = + new CommonTimeConfig.OnServerDiedListener() { + public void onServerDied() { + scheduleTimeConfigReconnect(); + } + }; + + private Runnable mReconnectRunnable = new Runnable() { + public void run() { connectToTimeConfig(); } + }; + + private Runnable mNoInterfaceRunnable = new Runnable() { + public void run() { handleNoInterfaceTimeout(); } + }; + + /* + * Public interface (constructor, systemReady and dump) + */ + public CommonTimeManagementService(Context context) { + mContext = context; + } + + void systemReady() { + if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) { + Log.i(TAG, "No common time service detected on this platform. " + + "Common time services will be unavailable."); + return; + } + + mDetectedAtStartup = true; + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + mNetMgr = INetworkManagementService.Stub.asInterface(b); + + // Network manager is running along-side us, so we should never receiver a remote exception + // while trying to register this observer. + try { + mNetMgr.registerObserver(mIfaceObserver); + } + catch (RemoteException e) { } + + // Register with the connectivity manager for connectivity changed intents. + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + mContext.registerReceiver(mConnectivityMangerObserver, filter); + + // Connect to the common time config service and apply the initial configuration. + connectToTimeConfig(); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println(String.format( + "Permission Denial: can't dump CommonTimeManagement service from from " + + "pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid())); + return; + } + + if (!mDetectedAtStartup) { + pw.println("Native Common Time service was not detected at startup. " + + "Service is unavailable"); + return; + } + + synchronized (mLock) { + pw.println("Current Common Time Management Service Config:"); + pw.println(String.format(" Native service : %s", + (null == mCTConfig) ? "reconnecting" + : "alive")); + pw.println(String.format(" Bound interface : %s", + (null == mCurIface ? "unbound" : mCurIface))); + pw.println(String.format(" Allow WiFi : %s", ALLOW_WIFI ? "yes" : "no")); + pw.println(String.format(" Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no")); + pw.println(String.format(" Server Priority : %d", mEffectivePrio)); + pw.println(String.format(" No iface timeout : %d", NO_INTERFACE_TIMEOUT)); + } + } + + /* + * Inner helper classes + */ + private static class InterfaceScoreRule { + public final String mPrefix; + public final byte mScore; + public InterfaceScoreRule(String prefix, byte score) { + mPrefix = prefix; + mScore = score; + } + }; + + /* + * Internal implementation + */ + private void cleanupTimeConfig() { + mReconnectHandler.removeCallbacks(mReconnectRunnable); + mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable); + if (null != mCTConfig) { + mCTConfig.release(); + mCTConfig = null; + } + } + + private void connectToTimeConfig() { + // Get access to the common time service configuration interface. If we catch a remote + // exception in the process (service crashed or no running for w/e reason), schedule an + // attempt to reconnect in the future. + cleanupTimeConfig(); + try { + synchronized (mLock) { + mCTConfig = new CommonTimeConfig(); + mCTConfig.setServerDiedListener(mCTServerDiedListener); + mCurIface = mCTConfig.getInterfaceBinding(); + mCTConfig.setAutoDisable(AUTO_DISABLE); + mCTConfig.setMasterElectionPriority(mEffectivePrio); + } + + if (NO_INTERFACE_TIMEOUT >= 0) + mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT); + + reevaluateServiceState(); + } + catch (RemoteException e) { + scheduleTimeConfigReconnect(); + } + } + + private void scheduleTimeConfigReconnect() { + cleanupTimeConfig(); + Log.w(TAG, String.format("Native service died, will reconnect in %d mSec", + NATIVE_SERVICE_RECONNECT_TIMEOUT)); + mReconnectHandler.postDelayed(mReconnectRunnable, + NATIVE_SERVICE_RECONNECT_TIMEOUT); + } + + private void handleNoInterfaceTimeout() { + if (null != mCTConfig) { + Log.i(TAG, "Timeout waiting for interface to come up. " + + "Forcing networkless master mode."); + if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode()) + scheduleTimeConfigReconnect(); + } + } + + private void reevaluateServiceState() { + String bindIface = null; + byte bestScore = -1; + try { + // Check to see if this interface is suitable to use for time synchronization. + // + // TODO : This selection algorithm needs to be enhanced for use with mobile devices. In + // particular, the choice of whether to a wireless interface or not should not be an all + // or nothing thing controlled by properties. It would probably be better if the + // platform had some concept of public wireless networks vs. home or friendly wireless + // networks (something a user would configure in settings or when a new interface is + // added). Then this algorithm could pick only wireless interfaces which were flagged + // as friendly, and be dormant when on public wireless networks. + // + // Another issue which needs to be dealt with is the use of driver supplied interface + // name to determine the network type. The fact that the wireless interface on a device + // is named "wlan0" is just a matter of convention; its not a 100% rule. For example, + // there are devices out there where the wireless is name "tiwlan0", not "wlan0". The + // internal network management interfaces in Android have all of the information needed + // to make a proper classification, there is just no way (currently) to fetch an + // interface's type (available from the ConnectionManager) as well as its address + // (available from either the java.net interfaces or from the NetworkManagment service). + // Both can enumerate interfaces, but that is no way to correlate their results (no + // common shared key; although using the interface name in the connection manager would + // be a good start). Until this gets resolved, we resort to substring searching for + // tags like wlan and eth. + // + String ifaceList[] = mNetMgr.listInterfaces(); + if (null != ifaceList) { + for (String iface : ifaceList) { + + byte thisScore = -1; + for (InterfaceScoreRule r : IFACE_SCORE_RULES) { + if (iface.contains(r.mPrefix)) { + thisScore = r.mScore; + break; + } + } + + if (thisScore <= bestScore) + continue; + + InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface); + if (null == config) + continue; + + if (config.isActive()) { + bindIface = iface; + bestScore = thisScore; + } + } + } + } + catch (RemoteException e) { + // Bad news; we should not be getting remote exceptions from the connectivity manager + // since it is running in SystemServer along side of us. It probably does not matter + // what we do here, but go ahead and unbind the common time service in this case, just + // so we have some defined behavior. + bindIface = null; + } + + boolean doRebind = true; + synchronized (mLock) { + if ((null != bindIface) && (null == mCurIface)) { + Log.e(TAG, String.format("Binding common time service to %s.", bindIface)); + mCurIface = bindIface; + } else + if ((null == bindIface) && (null != mCurIface)) { + Log.e(TAG, "Unbinding common time service."); + mCurIface = null; + } else + if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) { + Log.e(TAG, String.format("Switching common time service binding from %s to %s.", + mCurIface, bindIface)); + mCurIface = bindIface; + } else { + doRebind = false; + } + } + + if (doRebind && (null != mCTConfig)) { + byte newPrio = (bestScore > 0) + ? (byte)(bestScore * BASE_SERVER_PRIO) + : BASE_SERVER_PRIO; + if (newPrio != mEffectivePrio) { + mEffectivePrio = newPrio; + mCTConfig.setMasterElectionPriority(mEffectivePrio); + } + + int res = mCTConfig.setNetworkBinding(mCurIface); + if (res != CommonTimeConfig.SUCCESS) + scheduleTimeConfigReconnect(); + + else if (NO_INTERFACE_TIMEOUT >= 0) { + mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable); + if (null == mCurIface) + mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT); + } + } + } +} diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index b7dc4a2..dd650bf 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -865,14 +865,39 @@ private NetworkStateTracker makeWimaxStateTracker() { @Override public NetworkQuotaInfo getActiveNetworkQuotaInfo() { enforceAccessPermission(); - final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork); - if (state != null) { - try { - return mPolicyManager.getNetworkQuotaInfo(state); - } catch (RemoteException e) { + + final long token = Binder.clearCallingIdentity(); + try { + final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork); + if (state != null) { + try { + return mPolicyManager.getNetworkQuotaInfo(state); + } catch (RemoteException e) { + } } + return null; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean isActiveNetworkMetered() { + enforceAccessPermission(); + + final long token = Binder.clearCallingIdentity(); + try { + final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork); + if (state != null) { + try { + return mPolicyManager.isNetworkMetered(state); + } catch (RemoteException e) { + } + } + return false; + } finally { + Binder.restoreCallingIdentity(token); } - return null; } public boolean setRadios(boolean turnOn) { @@ -992,11 +1017,15 @@ private NetworkStateTracker makeWimaxStateTracker() { NetworkInfo ni = network.getNetworkInfo(); if (ni.isAvailable() == false) { - if (DBG) log("special network not available"); if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) { + if (DBG) log("special network not available ni=" + ni.getTypeName()); return Phone.APN_TYPE_NOT_AVAILABLE; } else { // else make the attempt anyway - probably giving REQUEST_STARTED below + if (DBG) { + log("special network not available, but try anyway ni=" + + ni.getTypeName()); + } } } @@ -1337,7 +1366,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.removeRoute(ifaceName, r); } catch (Exception e) { // never crash - catch them all - if (DBG) loge("Exception trying to remove a route: " + e); + if (VDBG) loge("Exception trying to remove a route: " + e); return false; } } else { @@ -1349,7 +1378,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.removeSecondaryRoute(ifaceName, r); } catch (Exception e) { // never crash - catch them all - if (DBG) loge("Exception trying to remove a route: " + e); + if (VDBG) loge("Exception trying to remove a route: " + e); return false; } } @@ -1390,9 +1419,7 @@ private NetworkStateTracker makeWimaxStateTracker() { private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() { @Override public void onUidRulesChanged(int uid, int uidRules) { - // only someone like NPMS should only be calling us - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - + // caller is NPMS, since we only register with them if (LOGD_RULES) { log("onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")"); } @@ -1411,9 +1438,7 @@ private NetworkStateTracker makeWimaxStateTracker() { @Override public void onMeteredIfacesChanged(String[] meteredIfaces) { - // only someone like NPMS should only be calling us - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - + // caller is NPMS, since we only register with them if (LOGD_RULES) { log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")"); } @@ -1425,6 +1450,27 @@ private NetworkStateTracker makeWimaxStateTracker() { } } } + + @Override + public void onRestrictBackgroundChanged(boolean restrictBackground) { + // caller is NPMS, since we only register with them + if (LOGD_RULES) { + log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")"); + } + + // kick off connectivity change broadcast for active network, since + // global background policy change is radical. + final int networkType = mActiveDefaultNetwork; + if (isNetworkTypeValid(networkType)) { + final NetworkStateTracker tracker = mNetTrackers[networkType]; + if (tracker != null) { + final NetworkInfo info = tracker.getNetworkInfo(); + if (info != null && info.isConnected()) { + sendConnectedBroadcast(info); + } + } + } + } }; /** @@ -2158,8 +2204,9 @@ private NetworkStateTracker makeWimaxStateTracker() { String dnsString = dns.getHostAddress(); if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) { changed = true; - SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress()); + SystemProperties.set("net.dns" + j + "." + pid, dns.getHostAddress()); } + j++; } return changed; } @@ -2375,15 +2422,15 @@ private NetworkStateTracker makeWimaxStateTracker() { } // Connectivity state changed: - // [31-13] Reserved for future use - // [12-9] Network subtype (for mobile network, as defined + // [31-14] Reserved for future use + // [13-10] Network subtype (for mobile network, as defined // by TelephonyManager) - // [8-3] Detailed state ordinal (as defined by + // [9-4] Detailed state ordinal (as defined by // NetworkInfo.DetailedState) - // [2-0] Network type (as defined by ConnectivityManager) - int eventLogParam = (info.getType() & 0x7) | - ((info.getDetailedState().ordinal() & 0x3f) << 3) | - (info.getSubtype() << 9); + // [3-0] Network type (as defined by ConnectivityManager) + int eventLogParam = (info.getType() & 0xf) | + ((info.getDetailedState().ordinal() & 0x3f) << 4) | + (info.getSubtype() << 10); EventLog.writeEvent(EventLogTags.CONNECTIVITY_STATE_CHANGED, eventLogParam); diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java index 3112b50..fc76277 100644 --- a/services/java/com/android/server/CountryDetectorService.java +++ b/services/java/com/android/server/CountryDetectorService.java @@ -212,6 +212,8 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run @SuppressWarnings("unused") @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + if (!DEBUG) return; try { final Printer p = new PrintWriterPrinter(fout); diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index d8e3d59..eb33060 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -451,7 +451,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public DevicePolicyManagerService(Context context) { mContext = context; mMonitor = new MyPackageMonitor(); - mMonitor.register(context, true); + mMonitor.register(context, null, true); mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM"); IntentFilter filter = new IntentFilter(); diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 16eeb7b..0ed5189 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -25,6 +25,8 @@ import android.content.Intent; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; import android.os.Binder; +import android.os.Environment; +import android.os.FileObserver; import android.os.Handler; import android.os.Message; import android.os.Process; @@ -90,6 +92,7 @@ public class DeviceStorageMonitorService extends Binder { private Intent mStorageFullIntent; private Intent mStorageNotFullIntent; private CachePackageDataObserver mClearCacheObserver; + private final CacheFileDeletedObserver mCacheFileDeletedObserver; private static final int _TRUE = 1; private static final int _FALSE = 0; private long mMemLowThreshold; @@ -323,6 +326,9 @@ public class DeviceStorageMonitorService extends Binder { mMemLowThreshold = getMemThreshold(); mMemFullThreshold = getMemFullThreshold(); checkMemory(true); + + mCacheFileDeletedObserver = new CacheFileDeletedObserver(); + mCacheFileDeletedObserver.startWatching(); } @@ -336,7 +342,9 @@ public class DeviceStorageMonitorService extends Binder { //log the event to event log with the amount of free storage(in bytes) left on the device EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem); // Pack up the values and broadcast them to everyone - Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE); + Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated() + ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS + : Intent.ACTION_MANAGE_PACKAGE_STORAGE); lowMemIntent.putExtra("memory", mFreeMem); lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); NotificationManager mNotificationMgr = @@ -416,4 +424,15 @@ public class DeviceStorageMonitorService extends Binder { public boolean isMemoryLow() { return mLowMemFlag; } + + public static class CacheFileDeletedObserver extends FileObserver { + public CacheFileDeletedObserver() { + super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE); + } + + @Override + public void onEvent(int event, String path) { + EventLogTags.writeCacheFileDeleted(path); + } + } } diff --git a/services/java/com/android/server/DiskStatsService.java b/services/java/com/android/server/DiskStatsService.java index 8ef974a..ac25dc5 100644 --- a/services/java/com/android/server/DiskStatsService.java +++ b/services/java/com/android/server/DiskStatsService.java @@ -34,6 +34,8 @@ import java.io.PrintWriter; * statistics about the status of the disk. */ public class DiskStatsService extends Binder { + private static final String TAG = "DiskStatsService"; + private final Context mContext; public DiskStatsService(Context context) { @@ -42,7 +44,7 @@ public class DiskStatsService extends Binder { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - // This data is accessible to any app -- no permission check needed. + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); // Run a quick-and-dirty performance test: write 512 bytes byte[] junk = new byte[512]; diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index d37c9ab..932cba1 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -28,6 +28,7 @@ import android.os.Debug; import android.os.DropBoxManager; import android.os.FileUtils; import android.os.Handler; +import android.os.Message; import android.os.StatFs; import android.os.SystemClock; import android.provider.Settings; @@ -64,6 +65,9 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { private static final int DEFAULT_RESERVE_PERCENT = 10; private static final int QUOTA_RESCAN_MILLIS = 5000; + // mHandler 'what' value. + private static final int MSG_SEND_BROADCAST = 1; + private static final boolean PROFILE_DUMP = false; // TODO: This implementation currently uses one file per entry, which is @@ -88,11 +92,11 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { private int mCachedQuotaBlocks = 0; // Space we can use: computed from free space, etc. private long mCachedQuotaUptimeMillis = 0; - // Ensure that all log entries have a unique timestamp - private long mLastTimestamp = 0; - private volatile boolean mBooted = false; + // Provide a way to perform sendBroadcast asynchronously to avoid deadlocks. + private final Handler mHandler; + /** Receives events that might indicate a need to clean up files. */ private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -143,11 +147,21 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { mContentResolver.registerContentObserver( Settings.Secure.CONTENT_URI, true, new ContentObserver(new Handler()) { + @Override public void onChange(boolean selfChange) { mReceiver.onReceive(context, (Intent) null); } }); + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_SEND_BROADCAST) { + mContext.sendBroadcast((Intent)msg.obj, android.Manifest.permission.READ_LOGS); + } + } + }; + // The real work gets done lazily in init() -- that way service creation always // succeeds, and things like disk problems cause individual method failures. } @@ -157,6 +171,7 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { mContext.unregisterReceiver(mReceiver); } + @Override public void add(DropBoxManager.Entry entry) { File temp = null; OutputStream output = null; @@ -227,14 +242,17 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { long time = createEntry(temp, tag, flags); temp = null; - Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); + final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag); dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time); if (!mBooted) { dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } - mContext.sendBroadcast(dropboxIntent, android.Manifest.permission.READ_LOGS); - + // Call sendBroadcast after returning from this call to avoid deadlock. In particular + // the caller may be holding the WindowManagerService lock but sendBroadcast requires a + // lock in ActivityManagerService. ActivityManagerService has been caught holding that + // very lock while waiting for the WindowManagerService lock. + mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent)); } catch (IOException e) { Slog.e(TAG, "Can't write: " + tag, e); } finally { diff --git a/services/java/com/android/server/EntropyService.java b/services/java/com/android/server/EntropyMixer.java index 788a2f5..b63a70e 100644 --- a/services/java/com/android/server/EntropyService.java +++ b/services/java/com/android/server/EntropyMixer.java @@ -47,8 +47,8 @@ import android.util.Slog; * <p>TODO: Investigate attempting to write entropy data at shutdown time * instead of periodically. */ -public class EntropyService extends Binder { - private static final String TAG = "EntropyService"; +public class EntropyMixer extends Binder { + private static final String TAG = "EntropyMixer"; private static final int ENTROPY_WHAT = 1; private static final int ENTROPY_WRITE_PERIOD = 3 * 60 * 60 * 1000; // 3 hrs private static final long START_TIME = System.currentTimeMillis(); @@ -72,12 +72,12 @@ public class EntropyService extends Binder { } }; - public EntropyService() { + public EntropyMixer() { this(getSystemDir() + "/entropy.dat", "/dev/urandom"); } /** Test only interface, not for public use */ - public EntropyService(String entropyFile, String randomDevice) { + public EntropyMixer(String entropyFile, String randomDevice) { if (randomDevice == null) { throw new NullPointerException("randomDevice"); } if (entropyFile == null) { throw new NullPointerException("entropyFile"); } diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags index 4dad209..41f7335 100644 --- a/services/java/com/android/server/EventLogTags.logtags +++ b/services/java/com/android/server/EventLogTags.logtags @@ -36,7 +36,7 @@ option java_package com.android.server # --------------------------- -# DeviceStorageMonitoryService.java +# DeviceStorageMonitorService.java # --------------------------- # The disk space free on the /data partition, in bytes 2744 free_storage_changed (data|2|2) @@ -44,6 +44,8 @@ option java_package com.android.server 2745 low_storage (data|2|2) # disk space free on the /data, /system, and /cache partitions in bytes 2746 free_storage_left (data|2|2),(system|2|2),(cache|2|2) +# file on cache partition was deleted +2748 cache_file_deleted (path|3) # --------------------------- @@ -132,15 +134,15 @@ option java_package com.android.server # ConnectivityService.java # --------------------------- # Connectivity state changed: -# [31-13] Reserved for future use -# [12- 9] Network subtype (for mobile network, as defined by TelephonyManager) -# [ 8- 3] Detailed state ordinal (as defined by NetworkInfo.DetailedState) -# [ 2- 0] Network type (as defined by ConnectivityManager) +# [31-14] Reserved for future use +# [13-10] Network subtype (for mobile network, as defined by TelephonyManager) +# [ 9- 4] Detailed state ordinal (as defined by NetworkInfo.DetailedState) +# [ 3- 0] Network type (as defined by ConnectivityManager) 50020 connectivity_state_changed (custom|1|5) # --------------------------- # NetworkStatsService.java # --------------------------- -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),(dev_history_start|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),(dev_history_start|2|3) +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) diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 7d4faea..43c2292 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -27,6 +27,7 @@ import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; import com.android.internal.view.InputBindResult; import com.android.server.EventLogTags; +import com.android.server.wm.WindowManagerService; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -91,7 +92,10 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import android.widget.ArrayAdapter; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.RadioButton; +import android.widget.Switch; import android.widget.TextView; import java.io.File; @@ -101,6 +105,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -113,7 +118,7 @@ import java.util.TreeMap; public class InputMethodManagerService extends IInputMethodManager.Stub implements ServiceConnection, Handler.Callback { static final boolean DEBUG = false; - static final String TAG = "InputManagerService"; + static final String TAG = "InputMethodManagerService"; static final int MSG_SHOW_IM_PICKER = 1; static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; @@ -133,6 +138,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_UNBIND_METHOD = 3000; static final int MSG_BIND_METHOD = 3010; + static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; + static final long TIME_TO_RECONNECT = 10*1000; static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20; @@ -142,6 +149,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; private static final String SUBTYPE_MODE_VOICE = "voice"; private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; + private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = + "EnabledWhenDefaultIsNotAsciiCapable"; + private static final String TAG_ASCII_CAPABLE = "AsciiCapable"; final Context mContext; final Resources mRes; @@ -151,6 +161,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final IWindowManager mIWindowManager; final HandlerCaller mCaller; private final InputMethodFileManager mFileManager; + private final InputMethodAndSubtypeListManager mImListManager; + private final HardKeyboardListener mHardKeyboardListener; + private final WindowManagerService mWindowManagerService; final InputBindResult mNoBinding = new InputBindResult(null, null, -1); @@ -355,6 +368,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private AlertDialog.Builder mDialogBuilder; private AlertDialog mSwitchingDialog; + private View mSwitchingDialogTitleView; private InputMethodInfo[] mIms; private int[] mSubtypeIds; @@ -523,7 +537,31 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - public InputMethodManagerService(Context context) { + private class HardKeyboardListener + implements WindowManagerService.OnHardKeyboardStatusChangeListener { + @Override + public void onHardKeyboardStatusChange(boolean available, boolean enabled) { + mHandler.sendMessage(mHandler.obtainMessage( + MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0, enabled ? 1 : 0)); + } + + public void handleHardKeyboardStatusChange(boolean available, boolean enabled) { + if (DEBUG) { + Slog.w(TAG, "HardKeyboardStatusChanged: available = " + available + ", enabled = " + + enabled); + } + synchronized(mMethodMap) { + if (mSwitchingDialog != null && mSwitchingDialogTitleView != null + && mSwitchingDialog.isShowing()) { + mSwitchingDialogTitleView.findViewById( + com.android.internal.R.id.hard_keyboard_section).setVisibility( + available ? View.VISIBLE : View.GONE); + } + } + } + } + + public InputMethodManagerService(Context context, WindowManagerService windowManager) { mContext = context; mRes = context.getResources(); mHandler = new Handler(this); @@ -535,6 +573,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub handleMessage(msg); } }); + mWindowManagerService = windowManager; + mHardKeyboardListener = new HardKeyboardListener(); mImeSwitcherNotification = new Notification(); mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default; @@ -552,8 +592,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub synchronized (mMethodMap) { mFileManager = new InputMethodFileManager(mMethodMap); } + mImListManager = new InputMethodAndSubtypeListManager(context, this); - (new MyPackageMonitor()).register(mContext, true); + (new MyPackageMonitor()).register(mContext, null, true); IntentFilter screenOnOffFilt = new IntentFilter(); screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON); @@ -627,6 +668,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub updateImeWindowStatusLocked(); mShowOngoingImeSwitcherForPhones = mRes.getBoolean( com.android.internal.R.bool.show_ongoing_ime_switcher); + if (mShowOngoingImeSwitcherForPhones) { + mWindowManagerService.setOnHardKeyboardStatusChangeListener( + mHardKeyboardListener); + } try { startInputInnerLocked(); } catch (RuntimeException e) { @@ -1126,6 +1171,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private boolean needsToShowImeSwitchOngoingNotification() { if (!mShowOngoingImeSwitcherForPhones) return false; + if (isScreenLocked()) return false; synchronized (mMethodMap) { List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); final int N = imis.size(); @@ -1735,6 +1781,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override + public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { + synchronized (mMethodMap) { + final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod( + onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype); + if (nextSubtype == null) { + return false; + } + setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId); + return true; + } + } + + @Override public InputMethodSubtype getLastInputMethodSubtype() { synchronized (mMethodMap) { final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); @@ -1974,6 +2033,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.w(TAG, "Client died receiving input method " + args.arg2); } return true; + + // -------------------------------------------------------------- + case MSG_HARD_KEYBOARD_SWITCH_CHANGED: + mHardKeyboardListener.handleHardKeyboardStatusChange( + msg.arg1 == 1, msg.arg2 == 1); + return true; } return false; } @@ -2129,15 +2194,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mContext.startActivity(intent); } + private boolean isScreenLocked() { + return mKeyguardManager != null + && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); + } private void showInputMethodMenuInternal(boolean showSubtypes) { if (DEBUG) Slog.v(TAG, "Show switching menu"); final Context context = mContext; final PackageManager pm = context.getPackageManager(); - final boolean isScreenLocked = mKeyguardManager != null - && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); + final boolean isScreenLocked = isScreenLocked(); - String lastInputMethodId = Settings.Secure.getString(context + final String lastInputMethodId = Settings.Secure.getString(context .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); @@ -2151,60 +2219,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub hideInputMethodMenuLocked(); - final TreeMap<InputMethodInfo, List<InputMethodSubtype>> sortedImmis = - new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( - new Comparator<InputMethodInfo>() { - @Override - public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { - if (imi2 == null) return 0; - if (imi1 == null) return 1; - if (pm == null) { - return imi1.getId().compareTo(imi2.getId()); - } - CharSequence imiId1 = imi1.loadLabel(pm) + "/" + imi1.getId(); - CharSequence imiId2 = imi2.loadLabel(pm) + "/" + imi2.getId(); - return imiId1.toString().compareTo(imiId2.toString()); - } - }); - - sortedImmis.putAll(immis); - - final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>(); + final List<ImeSubtypeListItem> imList = + mImListManager.getSortedInputMethodAndSubtypeList( + showSubtypes, mInputShown, isScreenLocked); - for (InputMethodInfo imi : sortedImmis.keySet()) { - if (imi == null) continue; - List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); - HashSet<String> enabledSubtypeSet = new HashSet<String>(); - for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) { - enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); - } - ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi); - final CharSequence imeLabel = imi.loadLabel(pm); - if (showSubtypes && enabledSubtypeSet.size() > 0) { - final int subtypeCount = imi.getSubtypeCount(); - if (DEBUG) { - Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); - } - for (int j = 0; j < subtypeCount; ++j) { - final InputMethodSubtype subtype = imi.getSubtypeAt(j); - final String subtypeHashCode = String.valueOf(subtype.hashCode()); - // We show all enabled IMEs and subtypes when an IME is shown. - if (enabledSubtypeSet.contains(subtypeHashCode) - && ((mInputShown && !isScreenLocked) || !subtype.isAuxiliary())) { - final CharSequence subtypeLabel = - subtype.overridesImplicitlyEnabledSubtype() ? null - : subtype.getDisplayName(context, imi.getPackageName(), - imi.getServiceInfo().applicationInfo); - imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j)); - - // Removing this subtype from enabledSubtypeSet because we no longer - // need to add an entry of this subtype to imList to avoid duplicated - // entries. - enabledSubtypeSet.remove(subtypeHashCode); - } - } - } else { - imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID)); + if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { + final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtype(); + if (currentSubtype != null) { + final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); + lastInputMethodSubtypeId = + getSubtypeIdFromHashCode(currentImi, currentSubtype.hashCode()); } } @@ -2225,12 +2249,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } } - final TypedArray a = context.obtainStyledAttributes(null, com.android.internal.R.styleable.DialogPreference, com.android.internal.R.attr.alertDialogStyle, 0); mDialogBuilder = new AlertDialog.Builder(context) - .setTitle(com.android.internal.R.string.select_input_method) .setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { @@ -2240,6 +2262,29 @@ public class InputMethodManagerService extends IInputMethodManager.Stub .setIcon(a.getDrawable( com.android.internal.R.styleable.DialogPreference_dialogTitle)); a.recycle(); + final LayoutInflater inflater = + (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final View tv = inflater.inflate( + com.android.internal.R.layout.input_method_switch_dialog_title, null); + mDialogBuilder.setCustomTitle(tv); + + // Setup layout for a toggle switch of the hardware keyboard + mSwitchingDialogTitleView = tv; + mSwitchingDialogTitleView.findViewById( + com.android.internal.R.id.hard_keyboard_section).setVisibility( + mWindowManagerService.isHardKeyboardAvailable() ? + View.VISIBLE : View.GONE); + final Switch hardKeySwitch = ((Switch)mSwitchingDialogTitleView.findViewById( + com.android.internal.R.id.hard_keyboard_switch)); + hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled()); + hardKeySwitch.setOnCheckedChangeListener( + new OnCheckedChangeListener() { + @Override + public void onCheckedChanged( + CompoundButton buttonView, boolean isChecked) { + mWindowManagerService.setHardKeyboardEnabled(isChecked); + } + }); final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context, com.android.internal.R.layout.simple_list_item_2_single_choice, imList, @@ -2517,7 +2562,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new HashMap<String, InputMethodSubtype>(); final int N = subtypes.size(); - boolean containsKeyboardSubtype = false; for (int i = 0; i < N; ++i) { // scan overriding implicitly enabled subtypes. InputMethodSubtype subtype = subtypes.get(i); @@ -2551,15 +2595,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!systemLocale.equals(locale)) continue; } applicableModeAndSubtypesMap.put(mode, subtype); - if (!containsKeyboardSubtype - && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) { - containsKeyboardSubtype = true; - } } } + final InputMethodSubtype keyboardSubtype + = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD); final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( applicableModeAndSubtypesMap.values()); - if (!containsKeyboardSubtype) { + if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) { + for (int i = 0; i < N; ++i) { + final InputMethodSubtype subtype = subtypes.get(i); + final String mode = subtype.getMode(); + if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey( + TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) { + applicableSubtypes.add(subtype); + } + } + } + if (keyboardSubtype == null) { InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); if (lastResortKeyboardSubtype != null) { @@ -2812,6 +2864,117 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private static class InputMethodAndSubtypeListManager { + private final Context mContext; + private final PackageManager mPm; + private final InputMethodManagerService mImms; + public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) { + mContext = context; + mPm = context.getPackageManager(); + mImms = imms; + } + + private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis = + new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( + new Comparator<InputMethodInfo>() { + @Override + public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { + if (imi2 == null) return 0; + if (imi1 == null) return 1; + if (mPm == null) { + return imi1.getId().compareTo(imi2.getId()); + } + CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId(); + CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId(); + return imiId1.toString().compareTo(imiId2.toString()); + } + }); + + public ImeSubtypeListItem getNextInputMethod( + boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList(); + if (imList.size() <= 1) { + return null; + } + final int N = imList.size(); + final int currentSubtypeId = subtype != null + ? mImms.getSubtypeIdFromHashCode(imi, subtype.hashCode()) + : NOT_A_SUBTYPE_ID; + for (int i = 0; i < N; ++i) { + final ImeSubtypeListItem isli = imList.get(i); + if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) { + if (!onlyCurrentIme) { + return imList.get((i + 1) % N); + } + for (int j = 0; j < N - 1; ++j) { + final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); + if (candidate.mImi.equals(imi)) { + return candidate; + } + } + return null; + } + } + return null; + } + + public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() { + return getSortedInputMethodAndSubtypeList(true, false, false); + } + + public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes, + boolean inputShown, boolean isScreenLocked) { + final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>(); + final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = + mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); + if (immis == null || immis.size() == 0) { + return Collections.emptyList(); + } + mSortedImmis.clear(); + mSortedImmis.putAll(immis); + for (InputMethodInfo imi : mSortedImmis.keySet()) { + if (imi == null) continue; + List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); + HashSet<String> enabledSubtypeSet = new HashSet<String>(); + for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) { + enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); + } + ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi); + final CharSequence imeLabel = imi.loadLabel(mPm); + if (showSubtypes && enabledSubtypeSet.size() > 0) { + final int subtypeCount = imi.getSubtypeCount(); + if (DEBUG) { + Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); + } + for (int j = 0; j < subtypeCount; ++j) { + final InputMethodSubtype subtype = imi.getSubtypeAt(j); + final String subtypeHashCode = String.valueOf(subtype.hashCode()); + // We show all enabled IMEs and subtypes when an IME is shown. + if (enabledSubtypeSet.contains(subtypeHashCode) + && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { + final CharSequence subtypeLabel = + subtype.overridesImplicitlyEnabledSubtype() ? null + : subtype.getDisplayName(mContext, imi.getPackageName(), + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j)); + + // Removing this subtype from enabledSubtypeSet because we no longer + // need to add an entry of this subtype to imList to avoid duplicated + // entries. + enabledSubtypeSet.remove(subtypeHashCode); + } + } + } else { + imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID)); + } + } + return imList; + } + } + /** * Utility class for putting and getting settings for InputMethod * TODO: Move all putters and getters of settings to this class. diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index b3d7220..f7e841e 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -201,7 +201,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) { + boolean defaultOnly, ArrayList<ArrayList<F>> listCut, int userId) { ArrayList<R> resultList = new ArrayList<R>(); final boolean debug = localLOGV || @@ -212,13 +212,14 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { int N = listCut.size(); for (int i = 0; i < N; ++i) { buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, listCut.get(i), resultList); + resolvedType, scheme, listCut.get(i), resultList, userId); } sortResults(resultList); return resultList; } - public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly) { + public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly, + int userId) { String scheme = intent.getScheme(); ArrayList<R> finalList = new ArrayList<R>(); @@ -290,19 +291,19 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { FastImmutableArraySet<String> categories = getFastIntentCategories(intent); if (firstTypeCut != null) { buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, firstTypeCut, finalList); + resolvedType, scheme, firstTypeCut, finalList, userId); } if (secondTypeCut != null) { buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, secondTypeCut, finalList); + resolvedType, scheme, secondTypeCut, finalList, userId); } if (thirdTypeCut != null) { buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, thirdTypeCut, finalList); + resolvedType, scheme, thirdTypeCut, finalList, userId); } if (schemeCut != null) { buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, schemeCut, finalList); + resolvedType, scheme, schemeCut, finalList, userId); } sortResults(finalList); @@ -329,7 +330,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { * "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) { + protected boolean isFilterStopped(F filter, int userId) { return false; } @@ -341,7 +342,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { protected abstract String packageForFilter(F filter); @SuppressWarnings("unchecked") - protected R newResult(F filter, int match) { + protected R newResult(F filter, int match, int userId) { return (R)filter; } @@ -504,7 +505,7 @@ 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) { + 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(); @@ -519,7 +520,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { int match; if (debug) Slog.v(TAG, "Matching against filter " + filter); - if (excludingStopped && isFilterStopped(filter)) { + if (excludingStopped && isFilterStopped(filter, userId)) { if (debug) { Slog.v(TAG, " Filter's target is stopped; skipping"); } @@ -547,7 +548,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { 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); + final R oneResult = newResult(filter, match, userId); if (oneResult != null) { dest.add(oneResult); } diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 56afe7f..d651111 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -511,7 +511,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run com.android.internal.R.string.config_networkLocationProvider); mGeocodeProviderPackageName = resources.getString( com.android.internal.R.string.config_geocodeProvider); - mPackageMonitor.register(context, true); + mPackageMonitor.register(context, null, true); if (LOCAL_LOGV) { Slog.v(TAG, "Constructed LocationManager Service"); @@ -1962,8 +1962,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } else { mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; } - NetworkInfo info = - (NetworkInfo)intent.getExtra(ConnectivityManager.EXTRA_NETWORK_INFO); + + final ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo info = connManager.getActiveNetworkInfo(); // Notify location providers of current network state synchronized (mLock) { diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 5425813..d6606f6 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -20,6 +20,7 @@ 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 android.Manifest; import android.content.BroadcastReceiver; @@ -47,6 +48,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserId; import android.os.storage.IMountService; import android.os.storage.IMountServiceListener; import android.os.storage.IMountShutdownObserver; @@ -564,8 +566,7 @@ class MountService extends IMountService.Stub } try { - mConnector.doCommand(String.format( - "volume %sshare %s %s", (enable ? "" : "un"), path, method)); + mConnector.execute("volume", enable ? "share" : "unshare", path, method); } catch (NativeDaemonConnectorException e) { Slog.e(TAG, "Failed to share/unshare", e); } @@ -633,8 +634,9 @@ class MountService extends IMountService.Stub * Determine media state and UMS detection status */ try { - String[] vols = mConnector.doListCommand( - "volume list", VoldResponseCode.VolumeListResult); + final String[] vols = NativeDaemonEvent.filterMessageList( + mConnector.executeForList("volume", "list"), + VoldResponseCode.VolumeListResult); for (String volstr : vols) { String[] tok = volstr.split(" "); // FMT: <label> <mountpoint> <state> @@ -666,6 +668,9 @@ class MountService extends IMountService.Stub updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED); } + // Let package manager load internal ASECs. + mPms.updateExternalMediaStatus(true, false); + /* * Now that we've done our initialization, release * the hounds! @@ -839,7 +844,7 @@ class MountService extends IMountService.Stub if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); try { - mConnector.doCommand(String.format("volume mount %s", path)); + mConnector.execute("volume", "mount", path); } catch (NativeDaemonConnectorException e) { /* * Mount failed for some reason @@ -909,10 +914,13 @@ class MountService extends IMountService.Stub // Redundant probably. But no harm in updating state again. mPms.updateExternalMediaStatus(false, false); try { - String arg = removeEncryption - ? " force_and_revert" - : (force ? " force" : ""); - mConnector.doCommand(String.format("volume unmount %s%s", path, arg)); + final Command cmd = new Command("volume", "unmount", path); + if (removeEncryption) { + cmd.appendArg("force_and_revert"); + } else if (force) { + cmd.appendArg("force"); + } + mConnector.execute(cmd); // We unmounted the volume. None of the asec containers are available now. synchronized (mAsecMountSet) { mAsecMountSet.clear(); @@ -934,8 +942,7 @@ class MountService extends IMountService.Stub private int doFormatVolume(String path) { try { - String cmd = String.format("volume format %s", path); - mConnector.doCommand(cmd); + mConnector.execute("volume", "format", path); return StorageResultCode.OperationSucceeded; } catch (NativeDaemonConnectorException e) { int code = e.getCode(); @@ -950,39 +957,19 @@ class MountService extends IMountService.Stub } private boolean doGetVolumeShared(String path, String method) { - String cmd = String.format("volume shared %s %s", path, method); - ArrayList<String> rsp; - + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand(cmd); + event = mConnector.execute("volume", "shared", path, method); } catch (NativeDaemonConnectorException ex) { Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method); return false; } - for (String line : rsp) { - String[] tok = line.split(" "); - if (tok.length < 3) { - Slog.e(TAG, "Malformed response to volume shared " + path + " " + method + " command"); - return false; - } - - int code; - try { - code = Integer.parseInt(tok[0]); - } catch (NumberFormatException nfe) { - Slog.e(TAG, String.format("Error parsing code %s", tok[0])); - return false; - } - if (code == VoldResponseCode.ShareEnabledResult) { - return "enabled".equals(tok[2]); - } else { - Slog.e(TAG, String.format("Unexpected response code %d", code)); - return false; - } + if (event.getCode() == VoldResponseCode.ShareEnabledResult) { + return event.getMessage().endsWith("enabled"); + } else { + return false; } - Slog.e(TAG, "Got an empty response"); - return false; } private void notifyShareAvailabilityChange(final boolean avail) { @@ -1186,7 +1173,7 @@ class MountService extends IMountService.Stub * amount of containers we'd ever expect to have. This keeps an * "asec list" from blocking a thread repeatedly. */ - mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG); + mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25); mReady = false; Thread thread = new Thread(mConnector, VOLD_TAG); thread.start(); @@ -1410,13 +1397,14 @@ class MountService extends IMountService.Stub validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); try { - String[] r = mConnector.doListCommand( - String.format("storage users %s", path), - VoldResponseCode.StorageUsersListResult); + final String[] r = NativeDaemonEvent.filterMessageList( + mConnector.executeForList("storage", "users", path), + VoldResponseCode.StorageUsersListResult); + // FMT: <pid> <process name> int[] data = new int[r.length]; for (int i = 0; i < r.length; i++) { - String []tok = r[i].split(" "); + String[] tok = r[i].split(" "); try { data[i] = Integer.parseInt(tok[0]); } catch (NumberFormatException nfe) { @@ -1443,22 +1431,23 @@ class MountService extends IMountService.Stub warnOnNotMounted(); try { - return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult); } catch (NativeDaemonConnectorException e) { return new String[0]; } } - public int createSecureContainer(String id, int sizeMb, String fstype, - String key, int ownerUid) { + public int createSecureContainer(String id, int sizeMb, String fstype, String key, + int ownerUid, boolean external) { validatePermission(android.Manifest.permission.ASEC_CREATE); waitForReady(); warnOnNotMounted(); int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid); try { - mConnector.doCommand(cmd); + mConnector.execute("asec", "create", id, sizeMb, fstype, key, ownerUid, + external ? "1" : "0"); } catch (NativeDaemonConnectorException e) { rc = StorageResultCode.OperationFailedInternalError; } @@ -1477,7 +1466,7 @@ class MountService extends IMountService.Stub int rc = StorageResultCode.OperationSucceeded; try { - mConnector.doCommand(String.format("asec finalize %s", id)); + mConnector.execute("asec", "finalize", id); /* * Finalization does a remount, so no need * to update mAsecMountSet @@ -1488,6 +1477,23 @@ class MountService extends IMountService.Stub return rc; } + public int fixPermissionsSecureContainer(String id, int gid, String filename) { + validatePermission(android.Manifest.permission.ASEC_CREATE); + warnOnNotMounted(); + + int rc = StorageResultCode.OperationSucceeded; + try { + mConnector.execute("asec", "fixperms", id, gid, filename); + /* + * Fix permissions does a remount, so no need to update + * mAsecMountSet + */ + } catch (NativeDaemonConnectorException e) { + rc = StorageResultCode.OperationFailedInternalError; + } + return rc; + } + public int destroySecureContainer(String id, boolean force) { validatePermission(android.Manifest.permission.ASEC_DESTROY); waitForReady(); @@ -1503,7 +1509,11 @@ class MountService extends IMountService.Stub int rc = StorageResultCode.OperationSucceeded; try { - mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : ""))); + final Command cmd = new Command("asec", "destroy", id); + if (force) { + cmd.appendArg("force"); + } + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageBusy) { @@ -1536,9 +1546,8 @@ class MountService extends IMountService.Stub } int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("asec mount %s %s %d", id, key, ownerUid); try { - mConnector.doCommand(cmd); + mConnector.execute("asec", "mount", id, key, ownerUid); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code != VoldResponseCode.OpFailedStorageBusy) { @@ -1574,9 +1583,12 @@ class MountService extends IMountService.Stub Runtime.getRuntime().gc(); int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("asec unmount %s%s", id, (force ? " force" : "")); try { - mConnector.doCommand(cmd); + final Command cmd = new Command("asec", "unmount", id); + if (force) { + cmd.appendArg("force"); + } + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageBusy) { @@ -1620,9 +1632,8 @@ class MountService extends IMountService.Stub } int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("asec rename %s %s", oldId, newId); try { - mConnector.doCommand(cmd); + mConnector.execute("asec", "rename", oldId, newId); } catch (NativeDaemonConnectorException e) { rc = StorageResultCode.OperationFailedInternalError; } @@ -1635,14 +1646,11 @@ class MountService extends IMountService.Stub waitForReady(); warnOnNotMounted(); + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand(String.format("asec path %s", id)); - String []tok = rsp.get(0).split(" "); - int code = Integer.parseInt(tok[0]); - if (code != VoldResponseCode.AsecPathResult) { - throw new IllegalStateException(String.format("Unexpected response code %d", code)); - } - return tok[1]; + event = mConnector.execute("asec", "path", id); + event.checkCode(VoldResponseCode.AsecPathResult); + return event.getMessage(); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageNotFound) { @@ -1659,14 +1667,11 @@ class MountService extends IMountService.Stub waitForReady(); warnOnNotMounted(); + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand(String.format("asec fspath %s", id)); - String []tok = rsp.get(0).split(" "); - int code = Integer.parseInt(tok[0]); - if (code != VoldResponseCode.AsecPathResult) { - throw new IllegalStateException(String.format("Unexpected response code %d", code)); - } - return tok[1]; + event = mConnector.execute("asec", "fspath", id); + event.checkCode(VoldResponseCode.AsecPathResult); + return event.getMessage(); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageNotFound) { @@ -1691,7 +1696,7 @@ class MountService extends IMountService.Stub return false; } - final int packageUid = mPms.getPackageUid(packageName); + final int packageUid = mPms.getPackageUid(packageName, UserId.getUserId(callerUid)); if (DEBUG_OBB) { Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + @@ -1709,14 +1714,11 @@ class MountService extends IMountService.Stub waitForReady(); warnOnNotMounted(); + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand(String.format("obb path %s", filename)); - String []tok = rsp.get(0).split(" "); - int code = Integer.parseInt(tok[0]); - if (code != VoldResponseCode.AsecPathResult) { - throw new IllegalStateException(String.format("Unexpected response code %d", code)); - } - return tok[1]; + event = mConnector.execute("obb", "path", filename); + event.checkCode(VoldResponseCode.AsecPathResult); + return event.getMessage(); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageNotFound) { @@ -1778,18 +1780,10 @@ class MountService extends IMountService.Stub waitForReady(); + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand("cryptfs cryptocomplete"); - String[] tokens = rsp.get(0).split(" "); - - if (tokens == null || tokens.length != 2) { - // Unexpected. - Slog.w(TAG, "Unexpected result from cryptfs cryptocomplete"); - return ENCRYPTION_STATE_ERROR_UNKNOWN; - } - - return Integer.parseInt(tokens[1]); - + event = mConnector.execute("cryptfs", "cryptocomplete"); + return Integer.parseInt(event.getMessage()); } catch (NumberFormatException e) { // Bad result - unexpected. Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete"); @@ -1816,22 +1810,21 @@ class MountService extends IMountService.Stub Slog.i(TAG, "decrypting storage..."); } + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand("cryptfs checkpw " + password); - String[] tokens = rsp.get(0).split(" "); - - if (tokens == null || tokens.length != 2) { - return -1; - } - - int code = Integer.parseInt(tokens[1]); + event = mConnector.execute("cryptfs", "checkpw", password); + final int code = Integer.parseInt(event.getMessage()); if (code == 0) { // Decrypt was successful. Post a delayed message before restarting in order // to let the UI to clear itself mHandler.postDelayed(new Runnable() { public void run() { - mConnector.doCommand(String.format("cryptfs restart")); + try { + mConnector.execute("cryptfs", "restart"); + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "problem executing in background", e); + } } }, 1000); // 1 second } @@ -1858,7 +1851,7 @@ class MountService extends IMountService.Stub } try { - mConnector.doCommand(String.format("cryptfs enablecrypto inplace %s", password)); + mConnector.execute("cryptfs", "enablecrypto", "inplace", password); } catch (NativeDaemonConnectorException e) { // Encryption failed return e.getCode(); @@ -1881,16 +1874,10 @@ class MountService extends IMountService.Stub Slog.i(TAG, "changing encryption password..."); } + final NativeDaemonEvent event; try { - ArrayList<String> response = mConnector.doCommand("cryptfs changepw " + password); - - String[] tokens = response.get(0).split(" "); - - if (tokens == null || tokens.length != 2) { - return -1; - } - - return Integer.parseInt(tokens[1]); + event = mConnector.execute("cryptfs", "changepw", password); + return Integer.parseInt(event.getMessage()); } catch (NativeDaemonConnectorException e) { // Encryption failed return e.getCode(); @@ -1920,24 +1907,11 @@ class MountService extends IMountService.Stub Slog.i(TAG, "validating encryption password..."); } + final NativeDaemonEvent event; try { - ArrayList<String> response = mConnector.doCommand("cryptfs verifypw " + password); - String[] tokens = response.get(0).split(" "); - - if (tokens == null || tokens.length != 2) { - String msg = "Unexpected result from cryptfs verifypw: {"; - if (tokens == null) msg += "null"; - else for (int i = 0; i < tokens.length; i++) { - if (i != 0) msg += ','; - msg += tokens[i]; - } - msg += '}'; - Slog.e(TAG, msg); - return -1; - } - - Slog.i(TAG, "cryptfs verifypw => " + tokens[1]); - return Integer.parseInt(tokens[1]); + event = mConnector.execute("cryptfs", "verifypw", password); + Slog.i(TAG, "cryptfs verifypw => " + event.getMessage()); + return Integer.parseInt(event.getMessage()); } catch (NativeDaemonConnectorException e) { // Encryption failed return e.getCode(); @@ -2296,10 +2270,9 @@ class MountService extends IMountService.Stub } int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("obb mount %s %s %d", mObbState.filename, hashedKey, - mObbState.callerUid); try { - mConnector.doCommand(cmd); + mConnector.execute( + "obb", "mount", mObbState.filename, hashedKey, mObbState.callerUid); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code != VoldResponseCode.OpFailedStorageBusy) { @@ -2380,10 +2353,12 @@ class MountService extends IMountService.Stub mObbState.filename = obbInfo.filename; int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("obb unmount %s%s", mObbState.filename, - (mForceUnmount ? " force" : "")); try { - mConnector.doCommand(cmd); + final Command cmd = new Command("obb", "unmount", mObbState.filename); + if (mForceUnmount) { + cmd.appendArg("force"); + } + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageBusy) { @@ -2476,6 +2451,10 @@ class MountService extends IMountService.Stub pw.println(v.toString()); } } + + pw.println(); + pw.println(" mConnection:"); + mConnector.dump(fd, pw, args); } /** {@inheritDoc} */ diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java index 28013bd..6a6c585 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -22,59 +22,56 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.SystemClock; +import android.util.LocalLog; import android.util.Slog; +import com.google.android.collect.Lists; + +import java.nio.charset.Charsets; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.util.ArrayList; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.LinkedList; /** - * Generic connector class for interfacing with a native - * daemon which uses the libsysutils FrameworkListener - * protocol. + * Generic connector class for interfacing with a native daemon which uses the + * {@code libsysutils} FrameworkListener protocol. */ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor { - private static final boolean LOCAL_LOGD = false; + private static final boolean LOGD = false; - private BlockingQueue<String> mResponseQueue; - private OutputStream mOutputStream; - private String TAG = "NativeDaemonConnector"; - private String mSocket; - private INativeDaemonConnectorCallbacks mCallbacks; - private Handler mCallbackHandler; + private final String TAG; - /** Lock held whenever communicating with native daemon. */ - private Object mDaemonLock = new Object(); + private String mSocket; + private OutputStream mOutputStream; + private LocalLog mLocalLog; - private final int BUFFER_SIZE = 4096; + private final ResponseQueue mResponseQueue; - class ResponseCode { - public static final int ActionInitiated = 100; + private INativeDaemonConnectorCallbacks mCallbacks; + private Handler mCallbackHandler; - public static final int CommandOkay = 200; + private AtomicInteger mSequenceNumber; - // The range of 400 -> 599 is reserved for cmd failures - public static final int OperationFailed = 400; - public static final int CommandSyntaxError = 500; - public static final int CommandParameterError = 501; + private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */ - public static final int UnsolicitedInformational = 600; + /** Lock held whenever communicating with native daemon. */ + private final Object mDaemonLock = new Object(); - // - public static final int FailedRangeStart = 400; - public static final int FailedRangeEnd = 599; - } + private final int BUFFER_SIZE = 4096; - NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, - String socket, int responseQueueSize, String logTag) { + NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, + int responseQueueSize, String logTag, int maxLogSize) { mCallbacks = callbacks; - if (logTag != null) - TAG = logTag; mSocket = socket; - mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize); + mResponseQueue = new ResponseQueue(responseQueueSize); + mSequenceNumber = new AtomicInteger(0); + TAG = logTag != null ? logTag : "NativeDaemonConnector"; + mLocalLog = new LocalLog(maxLogSize); } @Override @@ -87,7 +84,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo try { listenToSocket(); } catch (Exception e) { - Slog.e(TAG, "Error in NativeDaemonConnector", e); + loge("Error in NativeDaemonConnector: " + e); SystemClock.sleep(5000); } } @@ -97,13 +94,11 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo public boolean handleMessage(Message msg) { String event = (String) msg.obj; try { - if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) { - Slog.w(TAG, String.format( - "Unhandled event '%s'", event)); + if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) { + log(String.format("Unhandled event '%s'", event)); } } catch (Exception e) { - Slog.e(TAG, String.format( - "Error handling '%s'", event), e); + loge("Error handling '" + event + "': " + e); } return true; } @@ -119,7 +114,9 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo socket.connect(address); InputStream inputStream = socket.getInputStream(); - mOutputStream = socket.getOutputStream(); + synchronized (mDaemonLock) { + mOutputStream = socket.getOutputStream(); + } mCallbacks.onDaemonConnected(); @@ -128,7 +125,10 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo while (true) { int count = inputStream.read(buffer, start, BUFFER_SIZE - start); - if (count < 0) break; + if (count < 0) { + loge("got " + count + " reading with start = " + start); + break; + } // Add our starting point to the count and reset the start. count += start; @@ -136,29 +136,31 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo for (int i = 0; i < count; i++) { if (buffer[i] == 0) { - String event = new String(buffer, start, i - start); - if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event)); + final String rawEvent = new String( + buffer, start, i - start, Charsets.UTF_8); + log("RCV <- {" + rawEvent + "}"); - String[] tokens = event.split(" ", 2); try { - int code = Integer.parseInt(tokens[0]); - - if (code >= ResponseCode.UnsolicitedInformational) { - mCallbackHandler.sendMessage( - mCallbackHandler.obtainMessage(code, event)); + final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent( + rawEvent); + if (event.isClassUnsolicited()) { + // TODO: migrate to sending NativeDaemonEvent instances + mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage( + event.getCode(), event.getRawEvent())); } else { - try { - mResponseQueue.put(event); - } catch (InterruptedException ex) { - Slog.e(TAG, "Failed to put response onto queue", ex); - } + mResponseQueue.add(event.getCmdNumber(), event); } - } catch (NumberFormatException nfe) { - Slog.w(TAG, String.format("Bad msg (%s)", event)); + } catch (IllegalArgumentException e) { + log("Problem parsing message: " + rawEvent + " - " + e); } + start = i + 1; } } + if (start == 0) { + final String rawEvent = new String(buffer, start, count, Charsets.UTF_8); + log("RCV incomplete <- {" + rawEvent + "}"); + } // We should end at the amount we read. If not, compact then // buffer and read again. @@ -171,15 +173,16 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo } } } catch (IOException ex) { - Slog.e(TAG, "Communications error", ex); + loge("Communications error: " + ex); throw ex; } finally { synchronized (mDaemonLock) { if (mOutputStream != null) { try { + loge("closing stream for " + mSocket); mOutputStream.close(); } catch (IOException e) { - Slog.w(TAG, "Failed closing output stream", e); + loge("Failed closing output stream: " + e); } mOutputStream = null; } @@ -190,142 +193,385 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo socket.close(); } } catch (IOException ex) { - Slog.w(TAG, "Failed closing socket", ex); + loge("Failed closing socket: " + ex); + } + } + } + + /** + * Make command for daemon, escaping arguments as needed. + */ + private void makeCommand(StringBuilder builder, String cmd, Object... args) + throws NativeDaemonConnectorException { + // TODO: eventually enforce that cmd doesn't contain arguments + if (cmd.indexOf('\0') >= 0) { + throw new IllegalArgumentException("unexpected command: " + cmd); + } + + builder.append(cmd); + for (Object arg : args) { + final String argString = String.valueOf(arg); + if (argString.indexOf('\0') >= 0) { + throw new IllegalArgumentException("unexpected argument: " + arg); } + + builder.append(' '); + appendEscaped(builder, argString); } } - private void sendCommandLocked(String command) throws NativeDaemonConnectorException { - sendCommandLocked(command, null); + /** + * Issue the given command to the native daemon and return a single expected + * response. + * + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. + */ + public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException { + return execute(cmd.mCmd, cmd.mArguments.toArray()); } /** - * Sends a command to the daemon with a single argument + * Issue the given command to the native daemon and return a single expected + * response. * - * @param command The command to send to the daemon - * @param argument The argument to send with the command (or null) + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. */ - private void sendCommandLocked(String command, String argument) + public NativeDaemonEvent execute(String cmd, Object... args) throws NativeDaemonConnectorException { - if (command != null && command.indexOf('\0') >= 0) { - throw new IllegalArgumentException("unexpected command: " + command); - } - if (argument != null && argument.indexOf('\0') >= 0) { - throw new IllegalArgumentException("unexpected argument: " + argument); + final NativeDaemonEvent[] events = executeForList(cmd, args); + if (events.length != 1) { + throw new NativeDaemonConnectorException( + "Expected exactly one response, but received " + events.length); } + return events[0]; + } - if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument)); - if (mOutputStream == null) { - Slog.e(TAG, "No connection to daemon", new IllegalStateException()); - throw new NativeDaemonConnectorException("No output stream!"); - } else { - StringBuilder builder = new StringBuilder(command); - if (argument != null) { - builder.append(argument); + /** + * Issue the given command to the native daemon and return any + * {@link NativeDaemonEvent#isClassContinue()} responses, including the + * final terminal response. + * + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. + */ + public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException { + return executeForList(cmd.mCmd, cmd.mArguments.toArray()); + } + + /** + * Issue the given command to the native daemon and return any + * {@link NativeDaemonEvent#isClassContinue()} responses, including the + * final terminal response. + * + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. + */ + public NativeDaemonEvent[] executeForList(String cmd, Object... args) + throws NativeDaemonConnectorException { + return execute(DEFAULT_TIMEOUT, cmd, args); + } + + /** + * Issue the given command to the native daemon and return any + * {@linke NativeDaemonEvent@isClassContinue()} responses, including the + * final terminal response. Note that the timeout does not count time in + * deep sleep. + * + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. + */ + public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args) + throws NativeDaemonConnectorException { + final ArrayList<NativeDaemonEvent> events = Lists.newArrayList(); + + final int sequenceNumber = mSequenceNumber.incrementAndGet(); + final StringBuilder cmdBuilder = + new StringBuilder(Integer.toString(sequenceNumber)).append(' '); + + makeCommand(cmdBuilder, cmd, args); + + final String logCmd = cmdBuilder.toString(); /* includes cmdNum, cmd, args */ + log("SND -> {" + logCmd + "}"); + + cmdBuilder.append('\0'); + final String sentCmd = cmdBuilder.toString(); /* logCmd + \0 */ + + synchronized (mDaemonLock) { + if (mOutputStream == null) { + throw new NativeDaemonConnectorException("missing output stream"); + } else { + try { + mOutputStream.write(sentCmd.getBytes(Charsets.UTF_8)); + } catch (IOException e) { + throw new NativeDaemonConnectorException("problem sending command", e); + } } - builder.append('\0'); + } - try { - mOutputStream.write(builder.toString().getBytes()); - } catch (IOException ex) { - Slog.e(TAG, "IOException in sendCommand", ex); + NativeDaemonEvent event = null; + do { + event = mResponseQueue.remove(sequenceNumber, timeout, sentCmd); + if (event == null) { + loge("timed-out waiting for response to " + logCmd); + throw new NativeDaemonFailureException(logCmd, event); } + events.add(event); + } while (event.isClassContinue()); + + if (event.isClassClientError()) { + throw new NativeDaemonArgumentException(logCmd, event); } + if (event.isClassServerError()) { + throw new NativeDaemonFailureException(logCmd, event); + } + + return events.toArray(new NativeDaemonEvent[events.size()]); } /** - * Issue a command to the native daemon and return the responses + * Issue a command to the native daemon and return the raw responses. + * + * @deprecated callers should move to {@link #execute(String, Object...)} + * which returns parsed {@link NativeDaemonEvent}. */ + @Deprecated public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException { - synchronized (mDaemonLock) { - return doCommandLocked(cmd); + final ArrayList<String> rawEvents = Lists.newArrayList(); + final NativeDaemonEvent[] events = executeForList(cmd); + for (NativeDaemonEvent event : events) { + rawEvents.add(event.getRawEvent()); } + return rawEvents; } - private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException { - mResponseQueue.clear(); - sendCommandLocked(cmd); + /** + * Issues a list command and returns the cooked list of all + * {@link NativeDaemonEvent#getMessage()} which match requested code. + */ + @Deprecated + public String[] doListCommand(String cmd, int expectedCode) + throws NativeDaemonConnectorException { + final ArrayList<String> list = Lists.newArrayList(); + + final NativeDaemonEvent[] events = executeForList(cmd); + for (int i = 0; i < events.length - 1; i++) { + final NativeDaemonEvent event = events[i]; + final int code = event.getCode(); + if (code == expectedCode) { + list.add(event.getMessage()); + } else { + throw new NativeDaemonConnectorException( + "unexpected list response " + code + " instead of " + expectedCode); + } + } + + final NativeDaemonEvent finalEvent = events[events.length - 1]; + if (!finalEvent.isClassOk()) { + throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent); + } - ArrayList<String> response = new ArrayList<String>(); - boolean complete = false; - int code = -1; + return list.toArray(new String[list.size()]); + } - while (!complete) { - try { - // TODO - this should not block forever - String line = mResponseQueue.take(); - if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line)); - String[] tokens = line.split(" "); - try { - code = Integer.parseInt(tokens[0]); - } catch (NumberFormatException nfe) { - throw new NativeDaemonConnectorException( - String.format("Invalid response from daemon (%s)", line)); - } + /** + * Append the given argument to {@link StringBuilder}, escaping as needed, + * and surrounding with quotes when it contains spaces. + */ + // @VisibleForTesting + static void appendEscaped(StringBuilder builder, String arg) { + final boolean hasSpaces = arg.indexOf(' ') >= 0; + if (hasSpaces) { + builder.append('"'); + } - if ((code >= 200) && (code < 600)) { - complete = true; - } - response.add(line); - } catch (InterruptedException ex) { - Slog.e(TAG, "Failed to process response", ex); + final int length = arg.length(); + for (int i = 0; i < length; i++) { + final char c = arg.charAt(i); + + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else { + builder.append(c); } } - if (code >= ResponseCode.FailedRangeStart && - code <= ResponseCode.FailedRangeEnd) { - /* - * Note: The format of the last response in this case is - * "NNN <errmsg>" - */ - throw new NativeDaemonConnectorException( - code, cmd, response.get(response.size()-1).substring(4)); + if (hasSpaces) { + builder.append('"'); + } + } + + private static class NativeDaemonArgumentException extends NativeDaemonConnectorException { + public NativeDaemonArgumentException(String command, NativeDaemonEvent event) { + super(command, event); + } + + @Override + public IllegalArgumentException rethrowAsParcelableException() { + throw new IllegalArgumentException(getMessage(), this); + } + } + + private static class NativeDaemonFailureException extends NativeDaemonConnectorException { + public NativeDaemonFailureException(String command, NativeDaemonEvent event) { + super(command, event); } - return response; } /** - * Issues a list command and returns the cooked list + * Command builder that handles argument list building. */ - public String[] doListCommand(String cmd, int expectedResponseCode) - throws NativeDaemonConnectorException { + public static class Command { + private String mCmd; + private ArrayList<Object> mArguments = Lists.newArrayList(); + + public Command(String cmd, Object... args) { + mCmd = cmd; + for (Object arg : args) { + appendArg(arg); + } + } - ArrayList<String> rsp = doCommand(cmd); - String[] rdata = new String[rsp.size()-1]; - int idx = 0; + public Command appendArg(Object arg) { + mArguments.add(arg); + return this; + } + } - for (int i = 0; i < rsp.size(); i++) { - String line = rsp.get(i); - try { - String[] tok = line.split(" "); - int code = Integer.parseInt(tok[0]); - if (code == expectedResponseCode) { - rdata[idx++] = line.substring(tok[0].length() + 1); - } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) { - if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line)); - int last = rsp.size() -1; - if (i != last) { - Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd)); - for (int j = i; j <= last ; j++) { - Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i))); + /** {@inheritDoc} */ + public void monitor() { + synchronized (mDaemonLock) { } + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mLocalLog.dump(fd, pw, args); + pw.println(); + mResponseQueue.dump(fd, pw, args); + } + + private void log(String logstring) { + if (LOGD) Slog.d(TAG, logstring); + mLocalLog.log(logstring); + } + + private void loge(String logstring) { + Slog.e(TAG, logstring); + mLocalLog.log(logstring); + } + + private static class ResponseQueue { + + private static class Response { + public int cmdNum; + public LinkedList<NativeDaemonEvent> responses = new LinkedList<NativeDaemonEvent>(); + public String request; + public Response(int c, String r) {cmdNum = c; request = r;} + } + + private final LinkedList<Response> mResponses; + private int mMaxCount; + + ResponseQueue(int maxCount) { + mResponses = new LinkedList<Response>(); + mMaxCount = maxCount; + } + + public void add(int cmdNum, NativeDaemonEvent response) { + Response found = null; + synchronized (mResponses) { + for (Response r : mResponses) { + if (r.cmdNum == cmdNum) { + found = r; + break; + } + } + if (found == null) { + // didn't find it - make sure our queue isn't too big before adding + // another.. + while (mResponses.size() >= mMaxCount) { + Slog.e("NativeDaemonConnector.ResponseQueue", + "more buffered than allowed: " + mResponses.size() + + " >= " + mMaxCount); + // let any waiter timeout waiting for this + Response r = mResponses.remove(); + Slog.e("NativeDaemonConnector.ResponseQueue", + "Removing request: " + r.request + " (" + r.cmdNum + ")"); + } + found = new Response(cmdNum, null); + mResponses.add(found); + } + found.responses.add(response); + } + synchronized (found) { + found.notify(); + } + } + + // 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(); + } } } - return rdata; - } else { - throw new NativeDaemonConnectorException( - String.format("Expected list response %d, but got %d", - expectedResponseCode, code)); + 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); + } + } + 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 } - } catch (NumberFormatException nfe) { - throw new NativeDaemonConnectorException( - String.format("Error reading code '%s'", line)); } } - throw new NativeDaemonConnectorException("Got an empty response"); - } - /** {@inheritDoc} */ - public void monitor() { - synchronized (mDaemonLock) { } + 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); + } + } + } } } diff --git a/services/java/com/android/server/NativeDaemonConnectorException.java b/services/java/com/android/server/NativeDaemonConnectorException.java index 426742b..590bbcc 100644 --- a/services/java/com/android/server/NativeDaemonConnectorException.java +++ b/services/java/com/android/server/NativeDaemonConnectorException.java @@ -16,33 +16,43 @@ package com.android.server; +import android.os.Parcel; + /** - * An exception that indicates there was an error with a NativeDaemonConnector operation + * An exception that indicates there was an error with a + * {@link NativeDaemonConnector} operation. */ -public class NativeDaemonConnectorException extends RuntimeException -{ - private int mCode = -1; +public class NativeDaemonConnectorException extends Exception { private String mCmd; + private NativeDaemonEvent mEvent; - public NativeDaemonConnectorException() {} + public NativeDaemonConnectorException(String detailMessage) { + super(detailMessage); + } - public NativeDaemonConnectorException(String error) - { - super(error); + public NativeDaemonConnectorException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); } - public NativeDaemonConnectorException(int code, String cmd, String error) - { - super(String.format("Cmd {%s} failed with code %d : {%s}", cmd, code, error)); - mCode = code; + public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) { + super("command '" + cmd + "' failed with '" + event + "'"); mCmd = cmd; + mEvent = event; } public int getCode() { - return mCode; + return mEvent.getCode(); } public String getCmd() { return mCmd; } + + /** + * Rethrow as a {@link RuntimeException} subclass that is handled by + * {@link Parcel#writeException(Exception)}. + */ + public IllegalArgumentException rethrowAsParcelableException() { + throw new IllegalStateException(getMessage(), this); + } } diff --git a/services/java/com/android/server/NativeDaemonEvent.java b/services/java/com/android/server/NativeDaemonEvent.java new file mode 100644 index 0000000..f11ae1d --- /dev/null +++ b/services/java/com/android/server/NativeDaemonEvent.java @@ -0,0 +1,254 @@ +/* + * 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; + +import android.util.Slog; +import com.google.android.collect.Lists; + +import java.util.ArrayList; + +/** + * Parsed event from native side of {@link NativeDaemonConnector}. + */ +public class NativeDaemonEvent { + + // TODO: keep class ranges in sync with ResponseCode.h + // TODO: swap client and server error ranges to roughly mirror HTTP spec + + private final int mCmdNumber; + private final int mCode; + private final String mMessage; + private final String mRawEvent; + private String[] mParsed; + + private NativeDaemonEvent(int cmdNumber, int code, String message, String rawEvent) { + mCmdNumber = cmdNumber; + mCode = code; + mMessage = message; + mRawEvent = rawEvent; + mParsed = null; + } + + public int getCmdNumber() { + return mCmdNumber; + } + + public int getCode() { + return mCode; + } + + public String getMessage() { + return mMessage; + } + + @Deprecated + public String getRawEvent() { + return mRawEvent; + } + + @Override + public String toString() { + return mRawEvent; + } + + /** + * Test if event represents a partial response which is continued in + * additional subsequent events. + */ + public boolean isClassContinue() { + return mCode >= 100 && mCode < 200; + } + + /** + * Test if event represents a command success. + */ + public boolean isClassOk() { + return mCode >= 200 && mCode < 300; + } + + /** + * Test if event represents a remote native daemon error. + */ + public boolean isClassServerError() { + return mCode >= 400 && mCode < 500; + } + + /** + * Test if event represents a command syntax or argument error. + */ + public boolean isClassClientError() { + return mCode >= 500 && mCode < 600; + } + + /** + * Test if event represents an unsolicited event from native daemon. + */ + public boolean isClassUnsolicited() { + return isClassUnsolicited(mCode); + } + + private static boolean isClassUnsolicited(int code) { + return code >= 600 && code < 700; + } + + /** + * Verify this event matches the given code. + * + * @throws IllegalStateException if {@link #getCode()} doesn't match. + */ + public void checkCode(int code) { + if (mCode != code) { + throw new IllegalStateException("Expected " + code + " but was: " + this); + } + } + + /** + * Parse the given raw event into {@link NativeDaemonEvent} instance. + * + * @throws IllegalArgumentException when line doesn't match format expected + * from native side. + */ + public static NativeDaemonEvent parseRawEvent(String rawEvent) { + final String[] parsed = rawEvent.split(" "); + if (parsed.length < 2) { + throw new IllegalArgumentException("Insufficient arguments"); + } + + int skiplength = 0; + + final int code; + try { + code = Integer.parseInt(parsed[0]); + skiplength = parsed[0].length() + 1; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("problem parsing code", e); + } + + int cmdNumber = -1; + if (isClassUnsolicited(code) == false) { + if (parsed.length < 3) { + throw new IllegalArgumentException("Insufficient arguemnts"); + } + try { + cmdNumber = Integer.parseInt(parsed[1]); + skiplength += parsed[1].length() + 1; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("problem parsing cmdNumber", e); + } + } + + final String message = rawEvent.substring(skiplength); + + return new NativeDaemonEvent(cmdNumber, code, message, rawEvent); + } + + /** + * Filter the given {@link NativeDaemonEvent} list, returning + * {@link #getMessage()} for any events matching the requested code. + */ + public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) { + final ArrayList<String> result = Lists.newArrayList(); + for (NativeDaemonEvent event : events) { + if (event.getCode() == matchCode) { + result.add(event.getMessage()); + } + } + return result.toArray(new String[result.size()]); + } + + /** + * Find the Nth field of the event. + * + * This ignores and code or cmdNum, the first return value is given for N=0. + * Also understands "\"quoted\" multiword responses" and tries them as a single field + */ + public String getField(int n) { + if (mParsed == null) { + mParsed = unescapeArgs(mRawEvent); + } + n += 2; // skip code and command# + if (n > mParsed.length) return null; + return mParsed[n]; + } + + public static String[] unescapeArgs(String rawEvent) { + final boolean DEBUG_ROUTINE = false; + final String LOGTAG = "unescapeArgs"; + final ArrayList<String> parsed = new ArrayList<String>(); + final int length = rawEvent.length(); + int current = 0; + int wordEnd = -1; + boolean quoted = false; + + if (DEBUG_ROUTINE) Slog.e(LOGTAG, "parsing '" + rawEvent + "'"); + if (rawEvent.charAt(current) == '\"') { + quoted = true; + current++; + } + while (current < length) { + // find the end of the word + if (quoted) { + wordEnd = current; + while ((wordEnd = rawEvent.indexOf('\"', wordEnd)) != -1) { + if (rawEvent.charAt(wordEnd - 1) != '\\') { + break; + } else { + wordEnd++; // skip this escaped quote and keep looking + } + } + } else { + wordEnd = rawEvent.indexOf(' ', current); + } + // if we didn't find the end-o-word token, take the rest of the string + if (wordEnd == -1) wordEnd = length; + String word = rawEvent.substring(current, wordEnd); + current += word.length(); + if (!quoted) { + word = word.trim(); + } else { + current++; // skip the trailing quote + } + // unescape stuff within the word + word.replace("\\\\", "\\"); + word.replace("\\\"", "\""); + + if (DEBUG_ROUTINE) Slog.e(LOGTAG, "found '" + word + "'"); + parsed.add(word); + + // find the beginning of the next word - either of these options + int nextSpace = rawEvent.indexOf(' ', current); + int nextQuote = rawEvent.indexOf(" \"", current); + if (DEBUG_ROUTINE) { + Slog.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote); + } + if (nextQuote > -1 && nextQuote <= nextSpace) { + quoted = true; + current = nextQuote + 2; + } else { + quoted = false; + if (nextSpace > -1) { + current = nextSpace + 1; + } + } // else we just start the next word after the current and read til the end + if (DEBUG_ROUTINE) { + Slog.e(LOGTAG, "next loop - current=" + current + + ", length=" + length + ", quoted=" + quoted); + } + } + return parsed.toArray(new String[parsed.size()]); + } +} diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 75e5366..09d0698 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -16,19 +16,27 @@ package com.android.server; -import static android.Manifest.permission.ACCESS_NETWORK_STATE; -import static android.Manifest.permission.CHANGE_NETWORK_STATE; +import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.DUMP; -import static android.Manifest.permission.MANAGE_NETWORK_POLICY; +import static android.Manifest.permission.SHUTDOWN; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_TETHERING; import static android.provider.Settings.Secure.NETSTATS_ENABLED; +import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceRxThrottleResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceTxThrottleResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetherDnsFwdTgtListResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetherInterfaceListResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetherStatusResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetheringStatsResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult; import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; import android.content.Context; -import android.content.pm.PackageManager; import android.net.INetworkManagementEventObserver; import android.net.InterfaceConfiguration; import android.net.LinkAddress; @@ -37,8 +45,9 @@ 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.INetworkManagementService; +import android.os.RemoteCallbackList; +import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; @@ -47,6 +56,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.net.NetworkStatsFactory; +import com.android.server.NativeDaemonConnector.Command; import com.google.android.collect.Sets; import java.io.BufferedReader; @@ -78,8 +88,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final boolean DBG = false; private static final String NETD_TAG = "NetdConnector"; - private static final int ADD = 1; - private static final int REMOVE = 2; + private static final String ADD = "add"; + private static final String REMOVE = "remove"; private static final String DEFAULT = "default"; private static final String SECONDARY = "secondary"; @@ -107,6 +117,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub public static final int InterfaceTxThrottleResult = 219; public static final int QuotaCounterResult = 220; public static final int TetheringStatsResult = 221; + public static final int DnsProxyQueryResult = 222; public static final int InterfaceChange = 600; public static final int BandwidthControl = 601; @@ -125,8 +136,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private Thread mThread; private final CountDownLatch mConnectedSignal = new CountDownLatch(1); - // TODO: replace with RemoteCallbackList - private ArrayList<INetworkManagementEventObserver> mObservers; + private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = + new RemoteCallbackList<INetworkManagementEventObserver>(); private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); @@ -147,14 +158,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub */ private NetworkManagementService(Context context) { mContext = context; - mObservers = new ArrayList<INetworkManagementEventObserver>(); if ("simulator".equals(SystemProperties.get("ro.product.device"))) { return; } mConnector = new NativeDaemonConnector( - new NetdCallbackReceiver(), "netd", 10, NETD_TAG); + new NetdCallbackReceiver(), "netd", 10, NETD_TAG, 50); mThread = new Thread(mConnector, NETD_TAG); // Add ourself to the Watchdog monitors. @@ -181,7 +191,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub if (hasKernelSupport && shouldEnable) { Slog.d(TAG, "enabling bandwidth control"); try { - mConnector.doCommand("bandwidth enable"); + mConnector.execute("bandwidth", "enable"); mBandwidthControlEnabled = true; } catch (NativeDaemonConnectorException e) { Log.wtf(TAG, "problem enabling bandwidth controls", e); @@ -193,27 +203,30 @@ public class NetworkManagementService extends INetworkManagementService.Stub SystemProperties.set(PROP_QTAGUID_ENABLED, mBandwidthControlEnabled ? "1" : "0"); } - public void registerObserver(INetworkManagementEventObserver obs) { - Slog.d(TAG, "Registering observer"); - mObservers.add(obs); + @Override + public void registerObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + mObservers.register(observer); } - public void unregisterObserver(INetworkManagementEventObserver obs) { - Slog.d(TAG, "Unregistering observer"); - mObservers.remove(mObservers.indexOf(obs)); + @Override + public void unregisterObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + mObservers.unregister(observer); } /** * Notify our observers of an interface status change */ private void notifyInterfaceStatusChanged(String iface, boolean up) { - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.interfaceStatusChanged(iface, up); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** @@ -221,26 +234,28 @@ public class NetworkManagementService extends INetworkManagementService.Stub * (typically, an Ethernet cable has been plugged-in or unplugged). */ private void notifyInterfaceLinkStateChanged(String iface, boolean up) { - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.interfaceLinkStateChanged(iface, up); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** * Notify our observers of an interface addition. */ private void notifyInterfaceAdded(String iface) { - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.interfaceAdded(iface); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).interfaceAdded(iface); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** @@ -252,33 +267,34 @@ public class NetworkManagementService extends INetworkManagementService.Stub mActiveAlertIfaces.remove(iface); mActiveQuotaIfaces.remove(iface); - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.interfaceRemoved(iface); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).interfaceRemoved(iface); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** * Notify our observers of a limit reached. */ private void notifyLimitReached(String limitName, String iface) { - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.limitReached(limitName, iface); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).limitReached(limitName, iface); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** * Let us know the daemon is connected */ protected void onDaemonConnected() { - if (DBG) Slog.d(TAG, "onConnected"); mConnectedSignal.countDown(); } @@ -351,233 +367,188 @@ public class NetworkManagementService extends INetworkManagementService.Stub // INetworkManagementService members // - public String[] listInterfaces() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - + @Override + public String[] listInterfaces() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("interface", "list"), InterfaceListResult); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Cannot communicate with native daemon to list interfaces"); + throw e.rethrowAsParcelableException(); } } - public InterfaceConfiguration getInterfaceConfig(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG); - String rsp; + @Override + public InterfaceConfiguration getInterfaceConfig(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand("interface getcfg " + iface).get(0); + event = mConnector.execute("interface", "getcfg", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Cannot communicate with native daemon to get interface config"); + throw e.rethrowAsParcelableException(); } - Slog.d(TAG, String.format("rsp <%s>", rsp)); - // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz [flag1 flag2 flag3] - StringTokenizer st = new StringTokenizer(rsp); + event.checkCode(InterfaceGetCfgResult); + + // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz flag1 flag2 flag3 + final StringTokenizer st = new StringTokenizer(event.getMessage()); InterfaceConfiguration cfg; try { - try { - int code = Integer.parseInt(st.nextToken(" ")); - if (code != NetdResponseCode.InterfaceGetCfgResult) { - throw new IllegalStateException( - String.format("Expected code %d, but got %d", - NetdResponseCode.InterfaceGetCfgResult, code)); - } - } catch (NumberFormatException nfe) { - throw new IllegalStateException( - String.format("Invalid response from daemon (%s)", rsp)); - } - cfg = new InterfaceConfiguration(); - cfg.hwAddr = st.nextToken(" "); + cfg.setHardwareAddress(st.nextToken(" ")); InetAddress addr = null; int prefixLength = 0; try { - addr = NetworkUtils.numericToInetAddress(st.nextToken(" ")); + addr = NetworkUtils.numericToInetAddress(st.nextToken()); } catch (IllegalArgumentException iae) { Slog.e(TAG, "Failed to parse ipaddr", iae); } try { - prefixLength = Integer.parseInt(st.nextToken(" ")); + prefixLength = Integer.parseInt(st.nextToken()); } catch (NumberFormatException nfe) { Slog.e(TAG, "Failed to parse prefixLength", nfe); } - cfg.addr = new LinkAddress(addr, prefixLength); - cfg.interfaceFlags = st.nextToken("]").trim() +"]"; + cfg.setLinkAddress(new LinkAddress(addr, prefixLength)); + while (st.hasMoreTokens()) { + cfg.setFlag(st.nextToken()); + } } catch (NoSuchElementException nsee) { - throw new IllegalStateException( - String.format("Invalid response from daemon (%s)", rsp)); + throw new IllegalStateException("Invalid response from daemon: " + event); } - Slog.d(TAG, String.format("flags <%s>", cfg.interfaceFlags)); return cfg; } - public void setInterfaceConfig( - String iface, InterfaceConfiguration cfg) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); - LinkAddress linkAddr = cfg.addr; + @Override + public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + LinkAddress linkAddr = cfg.getLinkAddress(); if (linkAddr == null || linkAddr.getAddress() == null) { throw new IllegalStateException("Null LinkAddress given"); } - String cmd = String.format("interface setcfg %s %s %d %s", iface, + + final Command cmd = new Command("interface", "setcfg", iface, linkAddr.getAddress().getHostAddress(), - linkAddr.getNetworkPrefixLength(), - cfg.interfaceFlags); - try { - mConnector.doCommand(cmd); - } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon to interface setcfg - " + e); + linkAddr.getNetworkPrefixLength()); + for (String flag : cfg.getFlags()) { + cmd.appendArg(flag); } - } - public void setInterfaceDown(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); try { - InterfaceConfiguration ifcg = getInterfaceConfig(iface); - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down"); - setInterfaceConfig(iface, ifcg); + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon for interface down - " + e); + throw e.rethrowAsParcelableException(); } } - public void setInterfaceUp(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); - try { - InterfaceConfiguration ifcg = getInterfaceConfig(iface); - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); - setInterfaceConfig(iface, ifcg); - } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon for interface up - " + e); - } + @Override + public void setInterfaceDown(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + final InterfaceConfiguration ifcg = getInterfaceConfig(iface); + ifcg.setInterfaceDown(); + setInterfaceConfig(iface, ifcg); } - public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); - String cmd = String.format("interface ipv6privacyextensions %s %s", iface, - enable ? "enable" : "disable"); + @Override + public void setInterfaceUp(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + final InterfaceConfiguration ifcg = getInterfaceConfig(iface); + ifcg.setInterfaceUp(); + setInterfaceConfig(iface, ifcg); + } + + @Override + public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(cmd); + mConnector.execute( + "interface", "ipv6privacyextensions", iface, enable ? "enable" : "disable"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon to set ipv6privacyextensions - " + e); + throw e.rethrowAsParcelableException(); } } - - /* TODO: This is right now a IPv4 only function. Works for wifi which loses its IPv6 addresses on interface down, but we need to do full clean up here */ - public void clearInterfaceAddresses(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); - String cmd = String.format("interface clearaddrs %s", iface); + @Override + public void clearInterfaceAddresses(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(cmd); + mConnector.execute("interface", "clearaddrs", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon to interface clearallips - " + e); + throw e.rethrowAsParcelableException(); } } - public void enableIpv6(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void enableIpv6(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format("interface ipv6 %s enable", iface)); + mConnector.execute("interface", "ipv6", iface, "enable"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for enabling ipv6"); + throw e.rethrowAsParcelableException(); } } - public void disableIpv6(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void disableIpv6(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format("interface ipv6 %s disable", iface)); + mConnector.execute("interface", "ipv6", iface, "disable"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for disabling ipv6"); + throw e.rethrowAsParcelableException(); } } + @Override public void addRoute(String interfaceName, RouteInfo route) { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); modifyRoute(interfaceName, ADD, route, DEFAULT); } + @Override public void removeRoute(String interfaceName, RouteInfo route) { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); modifyRoute(interfaceName, REMOVE, route, DEFAULT); } + @Override public void addSecondaryRoute(String interfaceName, RouteInfo route) { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); modifyRoute(interfaceName, ADD, route, SECONDARY); } + @Override public void removeSecondaryRoute(String interfaceName, RouteInfo route) { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); modifyRoute(interfaceName, REMOVE, route, SECONDARY); } - private void modifyRoute(String interfaceName, int action, RouteInfo route, String type) { - ArrayList<String> rsp; - - StringBuilder cmd; - - switch (action) { - case ADD: - { - cmd = new StringBuilder("interface route add " + interfaceName + " " + type); - break; - } - case REMOVE: - { - cmd = new StringBuilder("interface route remove " + interfaceName + " " + type); - break; - } - default: - throw new IllegalStateException("Unknown action type " + action); - } + private void modifyRoute(String interfaceName, String action, RouteInfo route, String type) { + final Command cmd = new Command("interface", "route", action, interfaceName, type); // create triplet: dest-ip-addr prefixlength gateway-ip-addr - LinkAddress la = route.getDestination(); - cmd.append(' '); - cmd.append(la.getAddress().getHostAddress()); - cmd.append(' '); - cmd.append(la.getNetworkPrefixLength()); - cmd.append(' '); + final LinkAddress la = route.getDestination(); + cmd.appendArg(la.getAddress().getHostAddress()); + cmd.appendArg(la.getNetworkPrefixLength()); + if (route.getGateway() == null) { if (la.getAddress() instanceof Inet4Address) { - cmd.append("0.0.0.0"); + cmd.appendArg("0.0.0.0"); } else { - cmd.append ("::0"); + cmd.appendArg("::0"); } } else { - cmd.append(route.getGateway().getHostAddress()); + cmd.appendArg(route.getGateway().getHostAddress()); } + try { - rsp = mConnector.doCommand(cmd.toString()); + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native dameon to add routes - " - + e); - } - - if (DBG) { - for (String line : rsp) { - Log.v(TAG, "add route response is " + line); - } + throw e.rethrowAsParcelableException(); } } @@ -609,8 +580,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub return list; } + @Override public RouteInfo[] getRoutes(String interfaceName) { - mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>(); // v4 routes listed as: @@ -680,308 +652,247 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } } - return (RouteInfo[]) routes.toArray(new RouteInfo[0]); + return routes.toArray(new RouteInfo[routes.size()]); } + @Override public void shutdown() { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.SHUTDOWN) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires SHUTDOWN permission"); - } + // TODO: remove from aidl if nobody calls externally + mContext.enforceCallingOrSelfPermission(SHUTDOWN, TAG); Slog.d(TAG, "Shutting down"); } + @Override public boolean getIpForwardingEnabled() throws IllegalStateException{ - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - ArrayList<String> rsp; + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand("ipfwd status"); + event = mConnector.execute("ipfwd", "status"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon to ipfwd status"); + throw e.rethrowAsParcelableException(); } - for (String line : rsp) { - String[] tok = line.split(" "); - if (tok.length < 3) { - Slog.e(TAG, "Malformed response from native daemon: " + line); - return false; - } - - int code = Integer.parseInt(tok[0]); - if (code == NetdResponseCode.IpFwdStatusResult) { - // 211 Forwarding <enabled/disabled> - return "enabled".equals(tok[2]); - } else { - throw new IllegalStateException(String.format("Unexpected response code %d", code)); - } - } - throw new IllegalStateException("Got an empty response"); + // 211 Forwarding enabled + event.checkCode(IpFwdStatusResult); + return event.getMessage().endsWith("enabled"); } - public void setIpForwardingEnabled(boolean enable) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("ipfwd %sable", (enable ? "en" : "dis"))); + @Override + public void setIpForwardingEnabled(boolean enable) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("ipfwd", enable ? "enable" : "disable"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } } - public void startTethering(String[] dhcpRange) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void startTethering(String[] dhcpRange) { + 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"; + + final Command cmd = new Command("tether", "start"); for (String d : dhcpRange) { - cmd += " " + d; + cmd.appendArg(d); } try { - mConnector.doCommand(cmd); + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Unable to communicate to native daemon"); + throw e.rethrowAsParcelableException(); } } - public void stopTethering() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void stopTethering() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand("tether stop"); + mConnector.execute("tether", "stop"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Unable to communicate to native daemon to stop tether"); + throw e.rethrowAsParcelableException(); } } - public boolean isTetheringStarted() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + @Override + public boolean isTetheringStarted() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - ArrayList<String> rsp; + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand("tether status"); + event = mConnector.execute("tether", "status"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon to get tether status"); + throw e.rethrowAsParcelableException(); } - for (String line : rsp) { - String[] tok = line.split(" "); - if (tok.length < 3) { - throw new IllegalStateException("Malformed response for tether status: " + line); - } - int code = Integer.parseInt(tok[0]); - if (code == NetdResponseCode.TetherStatusResult) { - // XXX: Tethering services <started/stopped> <TBD>... - return "started".equals(tok[2]); - } else { - throw new IllegalStateException(String.format("Unexpected response code %d", code)); - } - } - throw new IllegalStateException("Got an empty response"); + // 210 Tethering services started + event.checkCode(TetherStatusResult); + return event.getMessage().endsWith("started"); } - public void tetherInterface(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void tetherInterface(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand("tether interface add " + iface); + mConnector.execute("tether", "interface", "add", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for adding tether interface"); + throw e.rethrowAsParcelableException(); } } + @Override public void untetherInterface(String iface) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand("tether interface remove " + iface); + mConnector.execute("tether", "interface", "remove", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for removing tether interface"); + throw e.rethrowAsParcelableException(); } } - public String[] listTetheredInterfaces() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + @Override + public String[] listTetheredInterfaces() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return mConnector.doListCommand( - "tether interface list", NetdResponseCode.TetherInterfaceListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("tether", "interface", "list"), + TetherInterfaceListResult); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for listing tether interfaces"); + throw e.rethrowAsParcelableException(); } } - public void setDnsForwarders(String[] dns) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void setDnsForwarders(String[] dns) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + final Command cmd = new Command("tether", "dns", "set"); + for (String s : dns) { + cmd.appendArg(NetworkUtils.numericToInetAddress(s).getHostAddress()); + } + try { - String cmd = "tether dns set"; - for (String s : dns) { - cmd += " " + NetworkUtils.numericToInetAddress(s).getHostAddress(); - } - try { - mConnector.doCommand(cmd); - } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for setting tether dns"); - } - } catch (IllegalArgumentException e) { - throw new IllegalStateException("Error resolving dns name", e); + mConnector.execute(cmd); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); } } - public String[] getDnsForwarders() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + @Override + public String[] getDnsForwarders() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return mConnector.doListCommand( - "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("tether", "dns", "list"), TetherDnsFwdTgtListResult); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for listing tether dns"); + throw e.rethrowAsParcelableException(); } } - private void modifyNat(String cmd, String internalInterface, String externalInterface) + private void modifyNat(String action, String internalInterface, String externalInterface) throws SocketException { - cmd = String.format("nat %s %s %s", cmd, internalInterface, externalInterface); + final Command cmd = new Command("nat", action, internalInterface, externalInterface); - NetworkInterface internalNetworkInterface = - NetworkInterface.getByName(internalInterface); + final NetworkInterface internalNetworkInterface = NetworkInterface.getByName( + internalInterface); if (internalNetworkInterface == null) { - cmd += " 0"; + cmd.appendArg("0"); } else { - Collection<InterfaceAddress>interfaceAddresses = - internalNetworkInterface.getInterfaceAddresses(); - cmd += " " + interfaceAddresses.size(); + Collection<InterfaceAddress> interfaceAddresses = internalNetworkInterface + .getInterfaceAddresses(); + cmd.appendArg(interfaceAddresses.size()); for (InterfaceAddress ia : interfaceAddresses) { - InetAddress addr = NetworkUtils.getNetworkPart(ia.getAddress(), - ia.getNetworkPrefixLength()); - cmd = cmd + " " + addr.getHostAddress() + "/" + ia.getNetworkPrefixLength(); + InetAddress addr = NetworkUtils.getNetworkPart( + ia.getAddress(), ia.getNetworkPrefixLength()); + cmd.appendArg(addr.getHostAddress() + "/" + ia.getNetworkPrefixLength()); } } - mConnector.doCommand(cmd); + try { + mConnector.execute(cmd); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } } - public void enableNat(String internalInterface, String externalInterface) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - if (DBG) Log.d(TAG, "enableNat(" + internalInterface + ", " + externalInterface + ")"); + @Override + public void enableNat(String internalInterface, String externalInterface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { modifyNat("enable", internalInterface, externalInterface); - } catch (Exception e) { - Log.e(TAG, "enableNat got Exception " + e.toString()); - throw new IllegalStateException( - "Unable to communicate to native daemon for enabling NAT interface"); + } catch (SocketException e) { + throw new IllegalStateException(e); } } - public void disableNat(String internalInterface, String externalInterface) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - if (DBG) Log.d(TAG, "disableNat(" + internalInterface + ", " + externalInterface + ")"); + @Override + public void disableNat(String internalInterface, String externalInterface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { modifyNat("disable", internalInterface, externalInterface); - } catch (Exception e) { - Log.e(TAG, "disableNat got Exception " + e.toString()); - throw new IllegalStateException( - "Unable to communicate to native daemon for disabling NAT interface"); + } catch (SocketException e) { + throw new IllegalStateException(e); } } - public String[] listTtys() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + @Override + public String[] listTtys() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("list_ttys"), TtyListResult); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for listing TTYs"); + throw e.rethrowAsParcelableException(); } } - public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr, - String dns2Addr) throws IllegalStateException { + @Override + public void attachPppd( + String tty, String localAddr, String remoteAddr, String dns1Addr, String dns2Addr) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty, + mConnector.execute("pppd", "attach", tty, NetworkUtils.numericToInetAddress(localAddr).getHostAddress(), NetworkUtils.numericToInetAddress(remoteAddr).getHostAddress(), NetworkUtils.numericToInetAddress(dns1Addr).getHostAddress(), - NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress())); - } catch (IllegalArgumentException e) { - throw new IllegalStateException("Error resolving addr", e); + NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress()); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to attach pppd", e); + throw e.rethrowAsParcelableException(); } } - public void detachPppd(String tty) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void detachPppd(String tty) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format("pppd detach %s", tty)); + mConnector.execute("pppd", "detach", tty); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to detach pppd", e); + throw e.rethrowAsParcelableException(); } } - public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); + @Override + public void startAccessPoint( + WifiConfiguration wifiConfig, String wlanIface, String softapIface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { wifiFirmwareReload(wlanIface, "AP"); - mConnector.doCommand(String.format("softap start " + wlanIface)); if (wifiConfig == null) { - mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); + mConnector.execute("softap", "set", wlanIface, softapIface); } else { - /** - * softap set arg1 arg2 arg3 [arg4 arg5 arg6 arg7 arg8] - * argv1 - wlan interface - * argv2 - softap interface - * argv3 - SSID - * argv4 - Security - * argv5 - Key - * argv6 - Channel - * argv7 - Preamble - * argv8 - Max SCB - */ - String str = String.format("softap set " + wlanIface + " " + softapIface + - " %s %s %s", convertQuotedString(wifiConfig.SSID), - getSecurityType(wifiConfig), - convertQuotedString(wifiConfig.preSharedKey)); - mConnector.doCommand(str); + mConnector.execute("softap", "set", wlanIface, softapIface, wifiConfig.SSID, + getSecurityType(wifiConfig), wifiConfig.preSharedKey); } - mConnector.doCommand(String.format("softap startap")); + mConnector.execute("softap", "startap"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to start softap", e); + throw e.rethrowAsParcelableException(); } } - private String convertQuotedString(String s) { - if (s == null) { - return s; - } - /* Replace \ with \\, then " with \" and add quotes at end */ - return '"' + s.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\\\\"") + '"'; - } - - private String getSecurityType(WifiConfiguration wifiConfig) { + private static String getSecurityType(WifiConfiguration wifiConfig) { switch (wifiConfig.getAuthType()) { case KeyMgmt.WPA_PSK: return "wpa-psk"; @@ -993,113 +904,58 @@ public class NetworkManagementService extends INetworkManagementService.Stub } /* @param mode can be "AP", "STA" or "P2P" */ - public void wifiFirmwareReload(String wlanIface, String mode) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); - + @Override + public void wifiFirmwareReload(String wlanIface, String mode) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format("softap fwreload " + wlanIface + " " + mode)); + mConnector.execute("softap", "fwreload", wlanIface, mode); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon ", e); + throw e.rethrowAsParcelableException(); } } - public void stopAccessPoint(String wlanIface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); + @Override + public void stopAccessPoint(String wlanIface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand("softap stopap"); - mConnector.doCommand("softap stop " + wlanIface); + mConnector.execute("softap", "stopap"); + mConnector.execute("softap", "stop", wlanIface); wifiFirmwareReload(wlanIface, "STA"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to stop soft AP", - e); + throw e.rethrowAsParcelableException(); } } - public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); + @Override + public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { if (wifiConfig == null) { - mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); + mConnector.execute("softap", "set", wlanIface, softapIface); } else { - String str = String.format("softap set " + wlanIface + " " + softapIface - + " %s %s %s", convertQuotedString(wifiConfig.SSID), - getSecurityType(wifiConfig), - convertQuotedString(wifiConfig.preSharedKey)); - mConnector.doCommand(str); + mConnector.execute("softap", "set", wlanIface, softapIface, wifiConfig.SSID, + getSecurityType(wifiConfig), wifiConfig.preSharedKey); } } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to set soft AP", - e); - } - } - - private long getInterfaceCounter(String iface, boolean rx) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - try { - String rsp; - try { - rsp = mConnector.doCommand( - String.format("interface read%scounter %s", (rx ? "rx" : "tx"), iface)).get(0); - } catch (NativeDaemonConnectorException e1) { - Slog.e(TAG, "Error communicating with native daemon", e1); - return -1; - } - - String[] tok = rsp.split(" "); - if (tok.length < 2) { - Slog.e(TAG, String.format("Malformed response for reading %s interface", - (rx ? "rx" : "tx"))); - return -1; - } - - int code; - try { - code = Integer.parseInt(tok[0]); - } catch (NumberFormatException nfe) { - Slog.e(TAG, String.format("Error parsing code %s", tok[0])); - return -1; - } - if ((rx && code != NetdResponseCode.InterfaceRxCounterResult) || ( - !rx && code != NetdResponseCode.InterfaceTxCounterResult)) { - Slog.e(TAG, String.format("Unexpected response code %d", code)); - return -1; - } - return Long.parseLong(tok[1]); - } catch (Exception e) { - Slog.e(TAG, String.format( - "Failed to read interface %s counters", (rx ? "rx" : "tx")), e); + throw e.rethrowAsParcelableException(); } - return -1; } @Override public NetworkStats getNetworkStatsSummary() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mStatsFactory.readNetworkStatsSummary(); } @Override public NetworkStats getNetworkStatsDetail() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mStatsFactory.readNetworkStatsDetail(UID_ALL); } @Override public void setInterfaceQuota(String iface, long quotaBytes) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1110,22 +966,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException("iface " + iface + " already has quota"); } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth setiquota ").append(iface).append(" ").append(quotaBytes); - try { // TODO: support quota shared across interfaces - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "setiquota", iface, quotaBytes); mActiveQuotaIfaces.add(iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public void removeInterfaceQuota(String iface) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1137,25 +990,21 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth removeiquota ").append(iface); - mActiveQuotaIfaces.remove(iface); mActiveAlertIfaces.remove(iface); try { // TODO: support quota shared across interfaces - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "removeiquota", iface); } catch (NativeDaemonConnectorException e) { - // TODO: include current iptables state - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public void setInterfaceAlert(String iface, long alertBytes) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1171,23 +1020,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException("iface " + iface + " already has alert"); } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth setinterfacealert ").append(iface).append(" ").append( - alertBytes); - try { // TODO: support alert shared across interfaces - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "setinterfacealert", iface, alertBytes); mActiveAlertIfaces.add(iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public void removeInterfaceAlert(String iface) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1199,40 +1044,34 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth removeinterfacealert ").append(iface); - try { // TODO: support alert shared across interfaces - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "removeinterfacealert", iface); mActiveAlertIfaces.remove(iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public void setGlobalAlert(long alertBytes) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled if (!mBandwidthControlEnabled) return; - final StringBuilder command = new StringBuilder(); - command.append("bandwidth setglobalalert ").append(alertBytes); - try { - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "setglobalalert", alertBytes); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } @Override public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1245,47 +1084,35 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth"); - if (rejectOnQuotaInterfaces) { - command.append(" addnaughtyapps"); - } else { - command.append(" removenaughtyapps"); - } - command.append(" ").append(uid); - try { - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", + rejectOnQuotaInterfaces ? "addnaughtyapps" : "removenaughtyapps", uid); if (rejectOnQuotaInterfaces) { mUidRejectOnQuota.put(uid, true); } else { mUidRejectOnQuota.delete(uid); } } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public boolean isBandwidthControlEnabled() { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mBandwidthControlEnabled; } @Override public NetworkStats getNetworkStatsUidDetail(int uid) { - if (Binder.getCallingUid() != uid) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - } + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mStatsFactory.readNetworkStatsDetail(uid); } @Override public NetworkStats getNetworkStatsTethering(String[] ifacePairs) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); if (ifacePairs.length % 2 != 0) { throw new IllegalArgumentException( @@ -1304,33 +1131,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub } private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) { - final StringBuilder command = new StringBuilder(); - command.append("bandwidth gettetherstats ").append(ifaceIn).append(" ").append(ifaceOut); - - final String rsp; + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand(command.toString()).get(0); + event = mConnector.execute("bandwidth", "gettetherstats", ifaceIn, ifaceOut); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } - final String[] tok = rsp.split(" "); - /* Expecting: "code ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets" */ - if (tok.length != 7) { - throw new IllegalStateException("Native daemon returned unexpected result: " + rsp); - } + event.checkCode(TetheringStatsResult); - final int code; - try { - code = Integer.parseInt(tok[0]); - } catch (NumberFormatException e) { - throw new IllegalStateException( - "Failed to parse native daemon return code for " + ifaceIn + " " + ifaceOut); - } - if (code != NetdResponseCode.TetheringStatsResult) { - throw new IllegalStateException( - "Unexpected return code from native daemon for " + ifaceIn + " " + ifaceOut); - } + // 221 ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets + final StringTokenizer tok = new StringTokenizer(event.getMessage()); + tok.nextToken(); + tok.nextToken(); try { final NetworkStats.Entry entry = new NetworkStats.Entry(); @@ -1338,10 +1151,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub entry.uid = UID_TETHERING; entry.set = SET_DEFAULT; entry.tag = TAG_NONE; - entry.rxBytes = Long.parseLong(tok[3]); - entry.rxPackets = Long.parseLong(tok[4]); - entry.txBytes = Long.parseLong(tok[5]); - entry.txPackets = Long.parseLong(tok[6]); + entry.rxBytes = Long.parseLong(tok.nextToken()); + entry.rxPackets = Long.parseLong(tok.nextToken()); + entry.txBytes = Long.parseLong(tok.nextToken()); + entry.txPackets = Long.parseLong(tok.nextToken()); return entry; } catch (NumberFormatException e) { throw new IllegalStateException( @@ -1349,122 +1162,95 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } + @Override public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format( - "interface setthrottle %s %d %d", iface, rxKbps, txKbps)); + mConnector.execute("interface", "setthrottle", iface, rxKbps, txKbps); } catch (NativeDaemonConnectorException e) { - Slog.e(TAG, "Error communicating with native daemon to set throttle", e); + throw e.rethrowAsParcelableException(); } } private int getInterfaceThrottle(String iface, boolean rx) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + final NativeDaemonEvent event; try { - String rsp; - try { - rsp = mConnector.doCommand( - String.format("interface getthrottle %s %s", iface, - (rx ? "rx" : "tx"))).get(0); - } catch (NativeDaemonConnectorException e) { - Slog.e(TAG, "Error communicating with native daemon to getthrottle", e); - return -1; - } + event = mConnector.execute("interface", "getthrottle", iface, rx ? "rx" : "tx"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } - String[] tok = rsp.split(" "); - if (tok.length < 2) { - Slog.e(TAG, "Malformed response to getthrottle command"); - return -1; - } + if (rx) { + event.checkCode(InterfaceRxThrottleResult); + } else { + event.checkCode(InterfaceTxThrottleResult); + } - int code; - try { - code = Integer.parseInt(tok[0]); - } catch (NumberFormatException nfe) { - Slog.e(TAG, String.format("Error parsing code %s", tok[0])); - return -1; - } - if ((rx && code != NetdResponseCode.InterfaceRxThrottleResult) || ( - !rx && code != NetdResponseCode.InterfaceTxThrottleResult)) { - Slog.e(TAG, String.format("Unexpected response code %d", code)); - return -1; - } - return Integer.parseInt(tok[1]); - } catch (Exception e) { - Slog.e(TAG, String.format( - "Failed to read interface %s throttle value", (rx ? "rx" : "tx")), e); + try { + return Integer.parseInt(event.getMessage()); + } catch (NumberFormatException e) { + throw new IllegalStateException("unexpected response:" + event); } - return -1; } + @Override public int getInterfaceRxThrottle(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return getInterfaceThrottle(iface, true); } + @Override public int getInterfaceTxThrottle(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return getInterfaceThrottle(iface, false); } - public void setDefaultInterfaceForDns(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void setDefaultInterfaceForDns(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - String cmd = "resolver setdefaultif " + iface; - - mConnector.doCommand(cmd); + mConnector.execute("resolver", "setdefaultif", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Error communicating with native daemon to set default interface", e); + throw e.rethrowAsParcelableException(); } } - public void setDnsServersForInterface(String iface, String[] servers) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE, - "NetworkManagementService"); - try { - String cmd = "resolver setifdns " + iface; - for (String s : servers) { - InetAddress a = NetworkUtils.numericToInetAddress(s); - if (a.isAnyLocalAddress() == false) { - cmd += " " + a.getHostAddress(); - } + @Override + public void setDnsServersForInterface(String iface, String[] servers) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + final Command cmd = new Command("resolver", "setifdns", iface); + for (String s : servers) { + InetAddress a = NetworkUtils.numericToInetAddress(s); + if (a.isAnyLocalAddress() == false) { + cmd.appendArg(a.getHostAddress()); } - mConnector.doCommand(cmd); - } catch (IllegalArgumentException e) { - throw new IllegalStateException("Error setting dnsn for interface", e); + } + + try { + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Error communicating with native daemon to set dns for interface", e); + throw e.rethrowAsParcelableException(); } } - public void flushDefaultDnsCache() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void flushDefaultDnsCache() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - String cmd = "resolver flushdefaultif"; - - mConnector.doCommand(cmd); + mConnector.execute("resolver", "flushdefaultif"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Error communicating with native deamon to flush default interface", e); + throw e.rethrowAsParcelableException(); } } - public void flushInterfaceDnsCache(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void flushInterfaceDnsCache(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - String cmd = "resolver flushif " + iface; - - mConnector.doCommand(cmd); + mConnector.execute("resolver", "flushif", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Error communicating with native daemon to flush interface " + iface, e); + throw e.rethrowAsParcelableException(); } } @@ -1479,6 +1265,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); + pw.println("NetworkManagementService NativeDaemonConnector Log:"); + mConnector.dump(fd, pw, args); + pw.println(); + pw.print("Bandwidth control enabled: "); pw.println(mBandwidthControlEnabled); synchronized (mQuotaLock) { diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java index f7fe39e..1ff914f 100644 --- a/services/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -55,7 +55,7 @@ public class NetworkTimeUpdateService { private static final int EVENT_AUTO_TIME_CHANGED = 1; private static final int EVENT_POLL_NETWORK_TIME = 2; - private static final int EVENT_WIFI_CONNECTED = 3; + private static final int EVENT_NETWORK_CONNECTED = 3; /** Normal polling frequency */ private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs @@ -234,13 +234,15 @@ public class NetworkTimeUpdateService { String action = intent.getAction(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) { // There is connectivity - NetworkInfo netInfo = (NetworkInfo)intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); + final ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo netInfo = connManager.getActiveNetworkInfo(); if (netInfo != null) { // Verify that it's a WIFI connection if (netInfo.getState() == NetworkInfo.State.CONNECTED && - netInfo.getType() == ConnectivityManager.TYPE_WIFI ) { - mHandler.obtainMessage(EVENT_WIFI_CONNECTED).sendToTarget(); + (netInfo.getType() == ConnectivityManager.TYPE_WIFI || + netInfo.getType() == ConnectivityManager.TYPE_ETHERNET) ) { + mHandler.obtainMessage(EVENT_NETWORK_CONNECTED).sendToTarget(); } } } @@ -259,7 +261,7 @@ public class NetworkTimeUpdateService { switch (msg.what) { case EVENT_AUTO_TIME_CHANGED: case EVENT_POLL_NETWORK_TIME: - case EVENT_WIFI_CONNECTED: + case EVENT_NETWORK_CONNECTED: onPollNetworkTime(msg.what); break; } diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 3cf447c..663a031 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -16,14 +16,15 @@ package com.android.server; -import com.android.internal.statusbar.StatusBarNotification; +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.ActivityManagerNative; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.ITransientNotification; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; @@ -37,14 +38,17 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioManager; +import android.media.IAudioService; +import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserId; import android.os.Vibrator; import android.provider.Settings; import android.telephony.TelephonyManager; @@ -52,14 +56,32 @@ import android.text.TextUtils; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.Xml; 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; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; + +import libcore.io.IoUtils; + /** {@hide} */ public class NotificationManagerService extends INotificationManager.Stub @@ -78,6 +100,14 @@ public class NotificationManagerService extends INotificationManager.Stub private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION; + private static final boolean SCORE_ONGOING_HIGHER = false; + + private static final int JUNK_SCORE = -1000; + private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; + private static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER; + + private static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true; + private static final boolean ENABLE_BLOCKED_TOASTS = true; final Context mContext; final IActivityManager mAm; @@ -92,13 +122,14 @@ public class NotificationManagerService extends INotificationManager.Stub private int mDefaultNotificationLedOn; private int mDefaultNotificationLedOff; - private NotificationRecord mSoundNotification; - private NotificationPlayer mSound; private boolean mSystemReady; private int mDisabledNotifications; + private NotificationRecord mSoundNotification; private NotificationRecord mVibrateNotification; - private Vibrator mVibrator = new Vibrator(); + + private IAudioService mAudioService; + private Vibrator mVibrator; // for enabling and disabling notification pulse behavior private boolean mScreenOn = true; @@ -113,6 +144,144 @@ public class NotificationManagerService extends INotificationManager.Stub private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>(); private NotificationRecord mLedNotification; + // Notification control database. For now just contains disabled packages. + private AtomicFile mPolicyFile; + private HashSet<String> mBlockedPackages = new HashSet<String>(); + + private static final int DB_VERSION = 1; + + private static final String TAG_BODY = "notification-policy"; + private static final String ATTR_VERSION = "version"; + + private static final String TAG_BLOCKED_PKGS = "blocked-packages"; + private static final String TAG_PACKAGE = "package"; + private static final String ATTR_NAME = "name"; + + private void loadBlockDb() { + synchronized(mBlockedPackages) { + if (mPolicyFile == null) { + File dir = new File("/data/system"); + mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml")); + + mBlockedPackages.clear(); + + FileInputStream infile = null; + try { + infile = mPolicyFile.openRead(); + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(infile, null); + + int type; + String tag; + int version = DB_VERSION; + while ((type = parser.next()) != END_DOCUMENT) { + tag = parser.getName(); + if (type == START_TAG) { + if (TAG_BODY.equals(tag)) { + version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION)); + } else if (TAG_BLOCKED_PKGS.equals(tag)) { + while ((type = parser.next()) != END_DOCUMENT) { + tag = parser.getName(); + if (TAG_PACKAGE.equals(tag)) { + mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME)); + } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) { + break; + } + } + } + } + } + } catch (FileNotFoundException e) { + // No data yet + } catch (IOException e) { + Log.wtf(TAG, "Unable to read blocked notifications database", e); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Unable to parse blocked notifications database", e); + } catch (XmlPullParserException e) { + Log.wtf(TAG, "Unable to parse blocked notifications database", e); + } finally { + IoUtils.closeQuietly(infile); + } + } + } + } + + private void writeBlockDb() { + synchronized(mBlockedPackages) { + FileOutputStream outfile = null; + try { + outfile = mPolicyFile.startWrite(); + + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(outfile, "utf-8"); + + out.startDocument(null, true); + + out.startTag(null, TAG_BODY); { + out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION)); + out.startTag(null, TAG_BLOCKED_PKGS); { + // write all known network policies + for (String pkg : mBlockedPackages) { + out.startTag(null, TAG_PACKAGE); { + out.attribute(null, ATTR_NAME, pkg); + } out.endTag(null, TAG_PACKAGE); + } + } out.endTag(null, TAG_BLOCKED_PKGS); + } out.endTag(null, TAG_BODY); + + out.endDocument(); + + mPolicyFile.finishWrite(outfile); + } catch (IOException e) { + if (outfile != null) { + mPolicyFile.failWrite(outfile); + } + } + } + } + + public boolean areNotificationsEnabledForPackage(String pkg) { + checkCallerIsSystem(); + return areNotificationsEnabledForPackageInt(pkg); + } + + // Unchecked. Not exposed via Binder, but can be called in the course of enqueue*(). + private boolean areNotificationsEnabledForPackageInt(String pkg) { + final boolean enabled = !mBlockedPackages.contains(pkg); + if (DBG) { + Slog.v(TAG, "notifications are " + (enabled?"en":"dis") + "abled for " + pkg); + } + return enabled; + } + + public void setNotificationsEnabledForPackage(String pkg, boolean enabled) { + checkCallerIsSystem(); + if (DBG) { + Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg); + } + if (enabled) { + mBlockedPackages.remove(pkg); + } else { + mBlockedPackages.add(pkg); + + // Now, cancel any outstanding notifications that are part of a just-disabled app + if (ENABLE_BLOCKED_NOTIFICATIONS) { + synchronized (mNotificationList) { + final int N = mNotificationList.size(); + for (int i=0; i<N; i++) { + final NotificationRecord r = mNotificationList.get(i); + if (r.pkg.equals(pkg)) { + cancelNotificationLocked(r, false); + } + } + } + } + // Don't bother canceling toasts, they'll go away soon enough. + } + writeBlockDb(); + } + + private static String idDebugString(Context baseContext, String packageName, int id) { Context c = null; @@ -145,19 +314,18 @@ public class NotificationManagerService extends INotificationManager.Stub final int id; final int uid; final int initialPid; - final int priority; final Notification notification; + final int score; IBinder statusBarKey; - NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, int priority, - Notification notification) + NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, int score, Notification notification) { this.pkg = pkg; this.tag = tag; this.id = id; this.uid = uid; this.initialPid = initialPid; - this.priority = priority; + this.score = score; this.notification = notification; } @@ -165,10 +333,13 @@ public class NotificationManagerService extends INotificationManager.Stub pw.println(prefix + this); pw.println(prefix + " icon=0x" + Integer.toHexString(notification.icon) + " / " + idDebugString(baseContext, this.pkg, notification.icon)); + pw.println(prefix + " pri=" + notification.priority); + pw.println(prefix + " score=" + this.score); pw.println(prefix + " contentIntent=" + notification.contentIntent); 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 + " defaults=0x" + Integer.toHexString(notification.defaults)); pw.println(prefix + " flags=0x" + Integer.toHexString(notification.flags)); pw.println(prefix + " sound=" + notification.sound); @@ -186,7 +357,7 @@ public class NotificationManagerService extends INotificationManager.Stub + " pkg=" + pkg + " id=" + Integer.toHexString(id) + " tag=" + tag - + " pri=" + priority + + " score=" + score + "}"; } } @@ -235,17 +406,19 @@ public class NotificationManagerService extends INotificationManager.Stub // cancel whatever's going on long identity = Binder.clearCallingIdentity(); try { - mSound.stop(); - } - finally { + final IRingtonePlayer player = mAudioService.getRingtonePlayer(); + if (player != null) { + player.stopAsync(); + } + } catch (RemoteException e) { + } finally { Binder.restoreCallingIdentity(identity); } identity = Binder.clearCallingIdentity(); try { mVibrator.cancel(); - } - finally { + } finally { Binder.restoreCallingIdentity(identity); } } @@ -271,11 +444,15 @@ public class NotificationManagerService extends INotificationManager.Stub synchronized (mNotificationList) { // sound mSoundNotification = null; + long identity = Binder.clearCallingIdentity(); try { - mSound.stop(); - } - finally { + final IRingtonePlayer player = mAudioService.getRingtonePlayer(); + if (player != null) { + player.stopAsync(); + } + } catch (RemoteException e) { + } finally { Binder.restoreCallingIdentity(identity); } @@ -284,8 +461,7 @@ public class NotificationManagerService extends INotificationManager.Stub identity = Binder.clearCallingIdentity(); try { mVibrator.cancel(); - } - finally { + } finally { Binder.restoreCallingIdentity(identity); } @@ -394,12 +570,13 @@ public class NotificationManagerService extends INotificationManager.Stub { super(); mContext = context; + mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); mAm = ActivityManagerNative.getDefault(); - mSound = new NotificationPlayer(TAG); - mSound.setUsesWakeLock(context); mToastQueue = new ArrayList<ToastRecord>(); mHandler = new WorkerHandler(); + loadBlockDb(); + mStatusBar = statusBar; statusBar.setNotificationCallbacks(mNotificationCallbacks); @@ -445,6 +622,9 @@ public class NotificationManagerService extends INotificationManager.Stub } void systemReady() { + mAudioService = IAudioService.Stub.asInterface( + ServiceManager.getService(Context.AUDIO_SERVICE)); + // no beeping until we're basically done booting mSystemReady = true; } @@ -460,6 +640,13 @@ public class NotificationManagerService extends INotificationManager.Stub return ; } + final boolean isSystemToast = ("android".equals(pkg)); + + if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) { + Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request."); + return; + } + synchronized (mToastQueue) { int callingPid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); @@ -474,7 +661,7 @@ public class NotificationManagerService extends INotificationManager.Stub } else { // Limit the number of toasts that any given package except the android // package can enqueue. Prevents DOS attacks and deals with leaks. - if (!"android".equals(pkg)) { + if (!isSystemToast) { int count = 0; final int N = mToastQueue.size(); for (int i=0; i<N; i++) { @@ -659,33 +846,26 @@ public class NotificationManagerService extends INotificationManager.Stub enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(), tag, id, notification, idOut); } - - public void enqueueNotificationWithTagPriority(String pkg, String tag, int id, int priority, - Notification notification, int[] idOut) - { - enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(), - tag, id, priority, notification, idOut); + + private final static int clamp(int x, int low, int high) { + return (x < low) ? low : ((x > high) ? high : x); } + // 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) { - enqueueNotificationInternal(pkg, callingUid, callingPid, tag, id, - ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) - ? StatusBarNotification.PRIORITY_ONGOING - : StatusBarNotification.PRIORITY_NORMAL, - notification, idOut); - } - public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid, - String tag, int id, int priority, Notification notification, int[] idOut) - { - checkIncomingCall(pkg); + if (DBG) { + Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); + } + checkCallerIsSystemOrSameApp(pkg); + final boolean isSystemNotification = ("android".equals(pkg)); // Limit the number of notifications that any given package except the android // package can enqueue. Prevents DOS attacks and deals with leaks. - if (!"android".equals(pkg)) { + if (!isSystemNotification) { synchronized (mNotificationList) { int count = 0; final int N = mNotificationList.size(); @@ -722,10 +902,43 @@ public class NotificationManagerService extends INotificationManager.Stub } } + // === Scoring === + + // 0. Sanitize inputs + notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX); + // Migrate notification flags to scores + if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) { + if (notification.priority < Notification.PRIORITY_MAX) notification.priority = Notification.PRIORITY_MAX; + } else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) { + if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH; + } + + // 1. initial score: buckets of 10, around the app + int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20] + + // 2. Consult external heuristics (TBD) + + // 3. Apply local rules + + // blocked apps + if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) { + score = JUNK_SCORE; + Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request."); + } + + if (DBG) { + Slog.v(TAG, "Assigned score=" + score + " to " + notification); + } + + if (score < SCORE_DISPLAY_THRESHOLD) { + // Notification will be blocked because the score is too low. + return; + } + synchronized (mNotificationList) { NotificationRecord r = new NotificationRecord(pkg, tag, id, callingUid, callingPid, - priority, + score, notification); NotificationRecord old = null; @@ -751,9 +964,7 @@ public class NotificationManagerService extends INotificationManager.Stub if (notification.icon != 0) { StatusBarNotification n = new StatusBarNotification(pkg, id, tag, - r.uid, r.initialPid, notification); - n.priority = r.priority; - + r.uid, r.initialPid, score, notification); if (old != null && old.statusBarKey != null) { r.statusBarKey = old.statusBarKey; long identity = Binder.clearCallingIdentity(); @@ -818,11 +1029,14 @@ public class NotificationManagerService extends INotificationManager.Stub // do not play notifications if stream volume is 0 // (typically because ringer mode is silent). if (audioManager.getStreamVolume(audioStreamType) != 0) { - long identity = Binder.clearCallingIdentity(); + final long identity = Binder.clearCallingIdentity(); try { - mSound.play(mContext, uri, looping, audioStreamType); - } - finally { + final IRingtonePlayer player = mAudioService.getRingtonePlayer(); + if (player != null) { + player.playAsync(uri, looping, audioStreamType); + } + } catch (RemoteException e) { + } finally { Binder.restoreCallingIdentity(identity); } } @@ -913,11 +1127,14 @@ public class NotificationManagerService extends INotificationManager.Stub // sound if (mSoundNotification == r) { mSoundNotification = null; - long identity = Binder.clearCallingIdentity(); + final long identity = Binder.clearCallingIdentity(); try { - mSound.stop(); - } - finally { + final IRingtonePlayer player = mAudioService.getRingtonePlayer(); + if (player != null) { + player.stopAsync(); + } + } catch (RemoteException e) { + } finally { Binder.restoreCallingIdentity(identity); } } @@ -1013,7 +1230,7 @@ public class NotificationManagerService extends INotificationManager.Stub } public void cancelNotificationWithTag(String pkg, String tag, int id) { - checkIncomingCall(pkg); + checkCallerIsSystemOrSameApp(pkg); // Don't allow client applications to cancel foreground service notis. cancelNotification(pkg, tag, id, 0, Binder.getCallingUid() == Process.SYSTEM_UID @@ -1021,14 +1238,22 @@ public class NotificationManagerService extends INotificationManager.Stub } public void cancelAllNotifications(String pkg) { - checkIncomingCall(pkg); + checkCallerIsSystemOrSameApp(pkg); // Calling from user space, don't allow the canceling of actively // running foreground services. cancelAllNotificationsInt(pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true); } - void checkIncomingCall(String pkg) { + void checkCallerIsSystem() { + int uid = Binder.getCallingUid(); + if (uid == Process.SYSTEM_UID || uid == 0) { + return; + } + throw new SecurityException("Disallowed call for uid " + uid); + } + + void checkCallerIsSystemOrSameApp(String pkg) { int uid = Binder.getCallingUid(); if (uid == Process.SYSTEM_UID || uid == 0) { return; @@ -1036,7 +1261,7 @@ public class NotificationManagerService extends INotificationManager.Stub try { ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo( pkg, 0); - if (ai.uid != uid) { + if (!UserId.isSameApp(ai.uid, uid)) { throw new SecurityException("Calling uid " + uid + " gave package" + pkg + " which is owned by uid " + ai.uid); } @@ -1170,7 +1395,6 @@ public class NotificationManagerService extends INotificationManager.Stub } pw.println(" mSoundNotification=" + mSoundNotification); - pw.println(" mSound=" + mSound); pw.println(" mVibrateNotification=" + mVibrateNotification); pw.println(" mDisabledNotifications=0x" + Integer.toHexString(mDisabledNotifications)); pw.println(" mSystemReady=" + mSystemReady); diff --git a/services/java/com/android/server/NotificationPlayer.java b/services/java/com/android/server/NotificationPlayer.java deleted file mode 100644 index 52d2381..0000000 --- a/services/java/com/android/server/NotificationPlayer.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.content.Context; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.PowerManager; -import android.os.SystemClock; -import android.util.Log; - -import java.io.IOException; -import java.lang.IllegalStateException; -import java.lang.Thread; -import java.util.LinkedList; - -/** - * @hide - * This class is provides the same interface and functionality as android.media.AsyncPlayer - * with the following differences: - * - whenever audio is played, audio focus is requested, - * - whenever audio playback is stopped or the playback completed, audio focus is abandoned. - */ -public class NotificationPlayer implements OnCompletionListener { - private static final int PLAY = 1; - private static final int STOP = 2; - private static final boolean mDebug = false; - - private static final class Command { - int code; - Context context; - Uri uri; - boolean looping; - int stream; - long requestTime; - - public String toString() { - return "{ code=" + code + " looping=" + looping + " stream=" + stream - + " uri=" + uri + " }"; - } - } - - private LinkedList<Command> mCmdQueue = new LinkedList(); - - private Looper mLooper; - - /* - * Besides the use of audio focus, the only implementation difference between AsyncPlayer and - * NotificationPlayer resides in the creation of the MediaPlayer. For the completion callback, - * OnCompletionListener, to be called at the end of the playback, the MediaPlayer needs to - * be created with a looper running so its event handler is not null. - */ - private final class CreationAndCompletionThread extends Thread { - public Command mCmd; - public CreationAndCompletionThread(Command cmd) { - super(); - mCmd = cmd; - } - - public void run() { - Looper.prepare(); - mLooper = Looper.myLooper(); - synchronized(this) { - AudioManager audioManager = - (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE); - try { - MediaPlayer player = new MediaPlayer(); - player.setAudioStreamType(mCmd.stream); - player.setDataSource(mCmd.context, mCmd.uri); - player.setLooping(mCmd.looping); - player.prepare(); - if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null) - && (mCmd.uri.getEncodedPath().length() > 0)) { - if (mCmd.looping) { - audioManager.requestAudioFocus(null, mCmd.stream, - AudioManager.AUDIOFOCUS_GAIN); - } else { - audioManager.requestAudioFocus(null, mCmd.stream, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); - } - } - player.setOnCompletionListener(NotificationPlayer.this); - player.start(); - if (mPlayer != null) { - mPlayer.release(); - } - mPlayer = player; - } - catch (Exception e) { - Log.w(mTag, "error loading sound for " + mCmd.uri, e); - } - mAudioManager = audioManager; - this.notify(); - } - Looper.loop(); - } - }; - - private void startSound(Command cmd) { - // Preparing can be slow, so if there is something else - // is playing, let it continue until we're done, so there - // is less of a glitch. - try { - if (mDebug) Log.d(mTag, "Starting playback"); - //----------------------------------- - // This is were we deviate from the AsyncPlayer implementation and create the - // MediaPlayer in a new thread with which we're synchronized - synchronized(mCompletionHandlingLock) { - // if another sound was already playing, it doesn't matter we won't get notified - // of the completion, since only the completion notification of the last sound - // matters - if((mLooper != null) - && (mLooper.getThread().getState() != Thread.State.TERMINATED)) { - mLooper.quit(); - } - mCompletionThread = new CreationAndCompletionThread(cmd); - synchronized(mCompletionThread) { - mCompletionThread.start(); - mCompletionThread.wait(); - } - } - //----------------------------------- - - long delay = SystemClock.uptimeMillis() - cmd.requestTime; - if (delay > 1000) { - Log.w(mTag, "Notification sound delayed by " + delay + "msecs"); - } - } - catch (Exception e) { - Log.w(mTag, "error loading sound for " + cmd.uri, e); - } - } - - private final class CmdThread extends java.lang.Thread { - CmdThread() { - super("NotificationPlayer-" + mTag); - } - - public void run() { - while (true) { - Command cmd = null; - - synchronized (mCmdQueue) { - if (mDebug) Log.d(mTag, "RemoveFirst"); - cmd = mCmdQueue.removeFirst(); - } - - switch (cmd.code) { - case PLAY: - if (mDebug) Log.d(mTag, "PLAY"); - startSound(cmd); - break; - case STOP: - if (mDebug) Log.d(mTag, "STOP"); - if (mPlayer != null) { - long delay = SystemClock.uptimeMillis() - cmd.requestTime; - if (delay > 1000) { - Log.w(mTag, "Notification stop delayed by " + delay + "msecs"); - } - mPlayer.stop(); - mPlayer.release(); - mPlayer = null; - mAudioManager.abandonAudioFocus(null); - mAudioManager = null; - if((mLooper != null) - && (mLooper.getThread().getState() != Thread.State.TERMINATED)) { - mLooper.quit(); - } - } else { - Log.w(mTag, "STOP command without a player"); - } - break; - } - - synchronized (mCmdQueue) { - if (mCmdQueue.size() == 0) { - // nothing left to do, quit - // doing this check after we're done prevents the case where they - // added it during the operation from spawning two threads and - // trying to do them in parallel. - mThread = null; - releaseWakeLock(); - return; - } - } - } - } - } - - public void onCompletion(MediaPlayer mp) { - if (mAudioManager != null) { - mAudioManager.abandonAudioFocus(null); - } - // if there are no more sounds to play, end the Looper to listen for media completion - synchronized (mCmdQueue) { - if (mCmdQueue.size() == 0) { - synchronized(mCompletionHandlingLock) { - if(mLooper != null) { - mLooper.quit(); - } - mCompletionThread = null; - } - } - } - } - - private String mTag; - private CmdThread mThread; - private CreationAndCompletionThread mCompletionThread; - private final Object mCompletionHandlingLock = new Object(); - private MediaPlayer mPlayer; - private PowerManager.WakeLock mWakeLock; - private AudioManager mAudioManager; - - // The current state according to the caller. Reality lags behind - // because of the asynchronous nature of this class. - private int mState = STOP; - - /** - * Construct a NotificationPlayer object. - * - * @param tag a string to use for debugging - */ - public NotificationPlayer(String tag) { - if (tag != null) { - mTag = tag; - } else { - mTag = "NotificationPlayer"; - } - } - - /** - * Start playing the sound. It will actually start playing at some - * point in the future. There are no guarantees about latency here. - * Calling this before another audio file is done playing will stop - * that one and start the new one. - * - * @param context Your application's context. - * @param uri The URI to play. (see {@link MediaPlayer#setDataSource(Context, Uri)}) - * @param looping Whether the audio should loop forever. - * (see {@link MediaPlayer#setLooping(boolean)}) - * @param stream the AudioStream to use. - * (see {@link MediaPlayer#setAudioStreamType(int)}) - */ - public void play(Context context, Uri uri, boolean looping, int stream) { - Command cmd = new Command(); - cmd.requestTime = SystemClock.uptimeMillis(); - cmd.code = PLAY; - cmd.context = context; - cmd.uri = uri; - cmd.looping = looping; - cmd.stream = stream; - synchronized (mCmdQueue) { - enqueueLocked(cmd); - mState = PLAY; - } - } - - /** - * Stop a previously played sound. It can't be played again or unpaused - * at this point. Calling this multiple times has no ill effects. - */ - public void stop() { - synchronized (mCmdQueue) { - // This check allows stop to be called multiple times without starting - // a thread that ends up doing nothing. - if (mState != STOP) { - Command cmd = new Command(); - cmd.requestTime = SystemClock.uptimeMillis(); - cmd.code = STOP; - enqueueLocked(cmd); - mState = STOP; - } - } - } - - private void enqueueLocked(Command cmd) { - mCmdQueue.add(cmd); - if (mThread == null) { - acquireWakeLock(); - mThread = new CmdThread(); - mThread.start(); - } - } - - /** - * We want to hold a wake lock while we do the prepare and play. The stop probably is - * optional, but it won't hurt to have it too. The problem is that if you start a sound - * while you're holding a wake lock (e.g. an alarm starting a notification), you want the - * sound to play, but if the CPU turns off before mThread gets to work, it won't. The - * simplest way to deal with this is to make it so there is a wake lock held while the - * thread is starting or running. You're going to need the WAKE_LOCK permission if you're - * going to call this. - * - * This must be called before the first time play is called. - * - * @hide - */ - public void setUsesWakeLock(Context context) { - if (mWakeLock != null || mThread != null) { - // if either of these has happened, we've already played something. - // and our releases will be out of sync. - throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock - + " mThread=" + mThread); - } - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag); - } - - private void acquireWakeLock() { - if (mWakeLock != null) { - mWakeLock.acquire(); - } - } - - private void releaseWakeLock() { - if (mWakeLock != null) { - mWakeLock.release(); - } - } -} - diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java new file mode 100644 index 0000000..f33bf8b --- /dev/null +++ b/services/java/com/android/server/NsdService.java @@ -0,0 +1,763 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.Context; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.nsd.DnsSdServiceInfo; +import android.net.nsd.DnsSdTxtRecord; +import android.net.nsd.INsdManager; +import android.net.nsd.NsdManager; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.Messenger; +import android.os.IBinder; +import android.provider.Settings; +import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.server.am.BatteryStatsService; +import com.android.server.NativeDaemonConnector.Command; +import com.android.internal.R; + +/** + * Network Service Discovery Service handles remote service discovery operation requests by + * implementing the INsdManager interface. + * + * @hide + */ +public class NsdService extends INsdManager.Stub { + private static final String TAG = "NsdService"; + private static final String MDNS_TAG = "mDnsConnector"; + + private static final boolean DBG = true; + + private Context mContext; + private ContentResolver mContentResolver; + private NsdStateMachine mNsdStateMachine; + + /** + * Clients receiving asynchronous messages + */ + private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>(); + + private AsyncChannel mReplyChannel = new AsyncChannel(); + + private int INVALID_ID = 0; + private int mUniqueId = 1; + + private static final int BASE = Protocol.BASE_NSD_MANAGER; + private static final int CMD_TO_STRING_COUNT = NsdManager.STOP_RESOLVE - BASE + 1; + private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT]; + + static { + sCmdToString[NsdManager.DISCOVER_SERVICES - BASE] = "DISCOVER"; + sCmdToString[NsdManager.STOP_DISCOVERY - BASE] = "STOP-DISCOVER"; + sCmdToString[NsdManager.REGISTER_SERVICE - BASE] = "REGISTER"; + sCmdToString[NsdManager.UNREGISTER_SERVICE - BASE] = "UNREGISTER"; + sCmdToString[NsdManager.RESOLVE_SERVICE - BASE] = "RESOLVE"; + sCmdToString[NsdManager.STOP_RESOLVE - BASE] = "STOP-RESOLVE"; + } + + private static String cmdToString(int cmd) { + cmd -= BASE; + if ((cmd >= 0) && (cmd < sCmdToString.length)) { + return sCmdToString[cmd]; + } else { + return null; + } + } + + private class NsdStateMachine extends StateMachine { + + private DefaultState mDefaultState = new DefaultState(); + private DisabledState mDisabledState = new DisabledState(); + private EnabledState mEnabledState = new EnabledState(); + + @Override + protected String getMessageInfo(Message msg) { + return cmdToString(msg.what); + } + + NsdStateMachine(String name) { + super(name); + addState(mDefaultState); + addState(mDisabledState, mDefaultState); + addState(mEnabledState, mDefaultState); + if (isNsdEnabled()) { + setInitialState(mEnabledState); + } else { + setInitialState(mDisabledState); + } + setProcessedMessagesSize(25); + } + + class DefaultState extends State { + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + AsyncChannel c = (AsyncChannel) msg.obj; + if (DBG) Slog.d(TAG, "New client listening to asynchronous messages"); + c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED); + ClientInfo cInfo = new ClientInfo(c, msg.replyTo); + mClients.put(msg.replyTo, cInfo); + } else { + Slog.e(TAG, "Client connection failure, error=" + msg.arg1); + } + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { + Slog.e(TAG, "Send failed, client connection lost"); + } else { + if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); + } + mClients.remove(msg.replyTo); + break; + case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: + AsyncChannel ac = new AsyncChannel(); + ac.connect(mContext, getHandler(), msg.replyTo); + break; + case NsdManager.DISCOVER_SERVICES: + mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, + NsdManager.BUSY); + break; + case NsdManager.STOP_DISCOVERY: + mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, + NsdManager.ERROR); + break; + case NsdManager.REGISTER_SERVICE: + mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, + NsdManager.ERROR); + break; + case NsdManager.UNREGISTER_SERVICE: + mReplyChannel.replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, + NsdManager.ERROR); + break; + case NsdManager.RESOLVE_SERVICE: + mReplyChannel.replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.ERROR); + break; + case NsdManager.STOP_RESOLVE: + mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED, + NsdManager.ERROR); + break; + default: + Slog.e(TAG, "Unhandled " + msg); + return NOT_HANDLED; + } + return HANDLED; + } + } + + class DisabledState extends State { + @Override + public void enter() { + sendNsdStateChangeBroadcast(false); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case NsdManager.ENABLE: + transitionTo(mEnabledState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class EnabledState extends State { + @Override + public void enter() { + sendNsdStateChangeBroadcast(true); + if (mClients.size() > 0) { + startMDnsDaemon(); + } + } + + @Override + public void exit() { + if (mClients.size() > 0) { + stopMDnsDaemon(); + } + } + + @Override + public boolean processMessage(Message msg) { + ClientInfo clientInfo; + DnsSdServiceInfo servInfo; + boolean result = HANDLED; + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + //First client + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL && + mClients.size() == 0) { + startMDnsDaemon(); + } + result = NOT_HANDLED; + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + //Last client + if (mClients.size() == 1) { + stopMDnsDaemon(); + } + result = NOT_HANDLED; + break; + case NsdManager.DISABLE: + //TODO: cleanup clients + transitionTo(mDisabledState); + break; + case NsdManager.DISCOVER_SERVICES: + if (DBG) Slog.d(TAG, "Discover services"); + servInfo = (DnsSdServiceInfo) msg.obj; + clientInfo = mClients.get(msg.replyTo); + if (clientInfo.mDiscoveryId != INVALID_ID) { + //discovery already in progress + if (DBG) Slog.d(TAG, "discovery in progress"); + mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, + NsdManager.ALREADY_ACTIVE); + break; + } + clientInfo.mDiscoveryId = getUniqueId(); + if (discoverServices(clientInfo.mDiscoveryId, servInfo.getServiceType())) { + mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED); + } else { + mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED, + NsdManager.ERROR); + clientInfo.mDiscoveryId = INVALID_ID; + } + break; + case NsdManager.STOP_DISCOVERY: + if (DBG) Slog.d(TAG, "Stop service discovery"); + clientInfo = mClients.get(msg.replyTo); + if (clientInfo.mDiscoveryId == INVALID_ID) { + //already stopped + if (DBG) Slog.d(TAG, "discovery already stopped"); + mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, + NsdManager.ALREADY_ACTIVE); + break; + } + if (stopServiceDiscovery(clientInfo.mDiscoveryId)) { + clientInfo.mDiscoveryId = INVALID_ID; + mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED); + } else { + mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED, + NsdManager.ERROR); + } + break; + case NsdManager.REGISTER_SERVICE: + if (DBG) Slog.d(TAG, "Register service"); + clientInfo = mClients.get(msg.replyTo); + if (clientInfo.mRegisteredIds.size() >= ClientInfo.MAX_REG) { + if (DBG) Slog.d(TAG, "register service exceeds limit"); + mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, + NsdManager.MAX_REGS_REACHED); + } + + int id = getUniqueId(); + if (registerService(id, (DnsSdServiceInfo) msg.obj)) { + clientInfo.mRegisteredIds.add(id); + } else { + mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED, + NsdManager.ERROR); + } + break; + case NsdManager.UNREGISTER_SERVICE: + if (DBG) Slog.d(TAG, "unregister service"); + clientInfo = mClients.get(msg.replyTo); + int regId = msg.arg1; + if (clientInfo.mRegisteredIds.remove(new Integer(regId)) && + unregisterService(regId)) { + mReplyChannel.replyToMessage(msg, + NsdManager.UNREGISTER_SERVICE_SUCCEEDED); + } else { + mReplyChannel.replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED, + NsdManager.ERROR); + } + break; + case NsdManager.UPDATE_SERVICE: + if (DBG) Slog.d(TAG, "Update service"); + //TODO: implement + mReplyChannel.replyToMessage(msg, NsdManager.UPDATE_SERVICE_FAILED); + break; + case NsdManager.RESOLVE_SERVICE: + if (DBG) Slog.d(TAG, "Resolve service"); + servInfo = (DnsSdServiceInfo) msg.obj; + clientInfo = mClients.get(msg.replyTo); + if (clientInfo.mResolveId != INVALID_ID) { + //first cancel existing resolve + stopResolveService(clientInfo.mResolveId); + } + + clientInfo.mResolveId = getUniqueId(); + if (!resolveService(clientInfo.mResolveId, servInfo)) { + mReplyChannel.replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.ERROR); + clientInfo.mResolveId = INVALID_ID; + } + break; + case NsdManager.STOP_RESOLVE: + if (DBG) Slog.d(TAG, "Stop resolve"); + clientInfo = mClients.get(msg.replyTo); + if (clientInfo.mResolveId == INVALID_ID) { + //already stopped + if (DBG) Slog.d(TAG, "resolve already stopped"); + mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED, + NsdManager.ALREADY_ACTIVE); + break; + } + if (stopResolveService(clientInfo.mResolveId)) { + clientInfo.mResolveId = INVALID_ID; + mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_SUCCEEDED); + } else { + mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED, + NsdManager.ERROR); + } + break; + default: + result = NOT_HANDLED; + break; + } + return result; + } + } + } + + private NativeDaemonConnector mNativeConnector; + private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1); + + private NsdService(Context context) { + mContext = context; + mContentResolver = context.getContentResolver(); + + mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10, + MDNS_TAG, 25); + + mNsdStateMachine = new NsdStateMachine(TAG); + mNsdStateMachine.start(); + + Thread th = new Thread(mNativeConnector, MDNS_TAG); + th.start(); + } + + public static NsdService create(Context context) throws InterruptedException { + NsdService service = new NsdService(context); + service.mNativeDaemonConnected.await(); + return service; + } + + public Messenger getMessenger() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, + "NsdService"); + return new Messenger(mNsdStateMachine.getHandler()); + } + + public void setEnabled(boolean enable) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL, + "NsdService"); + Settings.Secure.putInt(mContentResolver, Settings.Secure.NSD_ON, enable ? 1 : 0); + if (enable) { + mNsdStateMachine.sendMessage(NsdManager.ENABLE); + } else { + mNsdStateMachine.sendMessage(NsdManager.DISABLE); + } + } + + private void sendNsdStateChangeBroadcast(boolean enabled) { + final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + if (enabled) { + intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_ENABLED); + } else { + intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED); + } + mContext.sendStickyBroadcast(intent); + } + + private boolean isNsdEnabled() { + boolean ret = Settings.Secure.getInt(mContentResolver, Settings.Secure.NSD_ON, 1) == 1; + if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret); + return ret; + } + + private int getUniqueId() { + if (++mUniqueId == INVALID_ID) return ++mUniqueId; + return mUniqueId; + } + + /* These should be in sync with system/netd/mDnsResponseCode.h */ + class NativeResponseCode { + public static final int SERVICE_DISCOVERY_FAILED = 602; + public static final int SERVICE_FOUND = 603; + public static final int SERVICE_LOST = 604; + + public static final int SERVICE_REGISTRATION_FAILED = 605; + public static final int SERVICE_REGISTERED = 606; + + public static final int SERVICE_RESOLUTION_FAILED = 607; + public static final int SERVICE_RESOLVED = 608; + + public static final int SERVICE_UPDATED = 609; + public static final int SERVICE_UPDATE_FAILED = 610; + + public static final int SERVICE_GET_ADDR_FAILED = 611; + public static final int SERVICE_GET_ADDR_SUCCESS = 612; + } + + class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks { + public void onDaemonConnected() { + mNativeDaemonConnected.countDown(); + } + + public boolean onEvent(int code, String raw, String[] cooked) { + ClientInfo clientInfo; + DnsSdServiceInfo servInfo; + int id = Integer.parseInt(cooked[1]); + switch (code) { + case NativeResponseCode.SERVICE_FOUND: + /* NNN uniqueId serviceName regType domain */ + if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw); + clientInfo = getClientByDiscovery(id); + if (clientInfo == null) break; + + servInfo = new DnsSdServiceInfo(cooked[2], cooked[3], null); + clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, servInfo); + break; + case NativeResponseCode.SERVICE_LOST: + /* NNN uniqueId serviceName regType domain */ + if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw); + clientInfo = getClientByDiscovery(id); + if (clientInfo == null) break; + + servInfo = new DnsSdServiceInfo(cooked[2], cooked[3], null); + clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, servInfo); + break; + case NativeResponseCode.SERVICE_DISCOVERY_FAILED: + /* NNN uniqueId errorCode */ + if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw); + clientInfo = getClientByDiscovery(id); + if (clientInfo == null) break; + + clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED, + NsdManager.ERROR); + break; + case NativeResponseCode.SERVICE_REGISTERED: + /* NNN regId serviceName regType */ + if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw); + clientInfo = getClientByRegistration(id); + if (clientInfo == null) break; + + servInfo = new DnsSdServiceInfo(cooked[2], null, null); + clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED, + id, 0, servInfo); + break; + case NativeResponseCode.SERVICE_REGISTRATION_FAILED: + /* NNN regId errorCode */ + if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw); + clientInfo = getClientByRegistration(id); + if (clientInfo == null) break; + + clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED, + NsdManager.ERROR); + break; + case NativeResponseCode.SERVICE_UPDATED: + /* NNN regId */ + break; + case NativeResponseCode.SERVICE_UPDATE_FAILED: + /* NNN regId errorCode */ + break; + case NativeResponseCode.SERVICE_RESOLVED: + /* NNN resolveId fullName hostName port txtlen txtdata */ + if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw); + clientInfo = getClientByResolve(id); + if (clientInfo == null) break; + + int index = cooked[2].indexOf("."); + if (index == -1) { + Slog.e(TAG, "Invalid service found " + raw); + break; + } + String name = cooked[2].substring(0, index); + String rest = cooked[2].substring(index); + String type = rest.replace(".local.", ""); + + clientInfo.mResolvedService = new DnsSdServiceInfo(name, type, null); + clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4])); + + stopResolveService(id); + getAddrInfo(id, cooked[3]); + break; + case NativeResponseCode.SERVICE_RESOLUTION_FAILED: + case NativeResponseCode.SERVICE_GET_ADDR_FAILED: + /* NNN resolveId errorCode */ + if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw); + clientInfo = getClientByResolve(id); + if (clientInfo == null) break; + + clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.ERROR); + break; + case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS: + /* NNN resolveId hostname ttl addr */ + if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw); + clientInfo = getClientByResolve(id); + if (clientInfo == null || clientInfo.mResolvedService == null) break; + + try { + clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4])); + clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED, + clientInfo.mResolvedService); + clientInfo.mResolvedService = null; + clientInfo.mResolveId = INVALID_ID; + } catch (java.net.UnknownHostException e) { + clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, + NsdManager.ERROR); + } + stopGetAddrInfo(id); + break; + default: + break; + } + return false; + } + } + + private boolean startMDnsDaemon() { + if (DBG) Slog.d(TAG, "startMDnsDaemon"); + try { + mNativeConnector.execute("mdnssd", "start-service"); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to start daemon" + e); + return false; + } + return true; + } + + private boolean stopMDnsDaemon() { + if (DBG) Slog.d(TAG, "stopMDnsDaemon"); + try { + mNativeConnector.execute("mdnssd", "stop-service"); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to start daemon" + e); + return false; + } + return true; + } + + private boolean registerService(int regId, DnsSdServiceInfo service) { + if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service); + try { + //Add txtlen and txtdata + mNativeConnector.execute("mdnssd", "register", regId, service.getServiceName(), + service.getServiceType(), service.getPort()); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to execute registerService " + e); + return false; + } + return true; + } + + private boolean unregisterService(int regId) { + if (DBG) Slog.d(TAG, "unregisterService: " + regId); + try { + mNativeConnector.execute("mdnssd", "stop-register", regId); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to execute unregisterService " + e); + return false; + } + return true; + } + + private boolean updateService(int regId, DnsSdTxtRecord t) { + if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t); + try { + if (t == null) return false; + mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData()); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to updateServices " + e); + return false; + } + return true; + } + + private boolean discoverServices(int discoveryId, String serviceType) { + if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType); + try { + mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to discoverServices " + e); + return false; + } + return true; + } + + private boolean stopServiceDiscovery(int discoveryId) { + if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId); + try { + mNativeConnector.execute("mdnssd", "stop-discover", discoveryId); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to stopServiceDiscovery " + e); + return false; + } + return true; + } + + private boolean resolveService(int resolveId, DnsSdServiceInfo service) { + if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service); + try { + mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(), + service.getServiceType(), "local."); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to resolveService " + e); + return false; + } + return true; + } + + private boolean stopResolveService(int resolveId) { + if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId); + try { + mNativeConnector.execute("mdnssd", "stop-resolve", resolveId); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to stop resolve " + e); + return false; + } + return true; + } + + private boolean getAddrInfo(int resolveId, String hostname) { + if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId); + try { + mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to getAddrInfo " + e); + return false; + } + return true; + } + + private boolean stopGetAddrInfo(int resolveId) { + if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId); + try { + mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to stopGetAddrInfo " + e); + return false; + } + return true; + } + + @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 ServiceDiscoverService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + for (ClientInfo client : mClients.values()) { + pw.println("Client Info"); + pw.println(client); + } + + mNsdStateMachine.dump(fd, pw, args); + } + + private ClientInfo getClientByDiscovery(int discoveryId) { + for (ClientInfo c: mClients.values()) { + if (c.mDiscoveryId == discoveryId) { + return c; + } + } + return null; + } + + private ClientInfo getClientByResolve(int resolveId) { + for (ClientInfo c: mClients.values()) { + if (c.mResolveId == resolveId) { + return c; + } + } + return null; + } + + private ClientInfo getClientByRegistration(int regId) { + for (ClientInfo c: mClients.values()) { + if (c.mRegisteredIds.contains(regId)) { + return c; + } + } + return null; + } + + /* Information tracked per client */ + private class ClientInfo { + + private static final int MAX_REG = 5; + private AsyncChannel mChannel; + private Messenger mMessenger; + private int mDiscoveryId; + private int mResolveId; + /* Remembers a resolved service until getaddrinfo completes */ + private DnsSdServiceInfo mResolvedService; + private ArrayList<Integer> mRegisteredIds = new ArrayList<Integer>(); + + private ClientInfo(AsyncChannel c, Messenger m) { + mChannel = c; + mMessenger = m; + mDiscoveryId = mResolveId = INVALID_ID; + if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("mChannel ").append(mChannel).append("\n"); + sb.append("mMessenger ").append(mMessenger).append("\n"); + sb.append("mDiscoveryId ").append(mDiscoveryId).append("\n"); + sb.append("mResolveId ").append(mResolveId).append("\n"); + sb.append("mResolvedService ").append(mResolvedService).append("\n"); + for(int regId : mRegisteredIds) { + sb.append("regId ").append(regId).append("\n"); + } + return sb.toString(); + } + } +} diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 2a0d2a0..bd50e22 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -37,6 +37,7 @@ 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; @@ -45,21 +46,24 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.IPowerManager; import android.os.LocalPowerManager; +import android.os.Message; import android.os.Power; 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.SettingNotFoundException; 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; @@ -75,6 +79,7 @@ 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"; @@ -106,6 +111,14 @@ public class PowerManagerService extends IPowerManager.Stub // 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; @@ -118,7 +131,11 @@ public class PowerManagerService extends IPowerManager.Stub // 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; + // 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; @@ -145,11 +162,14 @@ public class PowerManagerService extends IPowerManager.Stub // 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/4; + static final int ANIM_STEPS = 60; // nominal # of frames at 60Hz // Slower animation for autobrightness changes - static final int AUTOBRIGHTNESS_ANIM_STEPS = 60; + static final int AUTOBRIGHTNESS_ANIM_STEPS = 2 * ANIM_STEPS; + // 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 @@ -163,6 +183,7 @@ public class PowerManagerService extends IPowerManager.Stub 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]; @@ -206,12 +227,11 @@ public class PowerManagerService extends IPowerManager.Stub private UnsynchronizedWakeLock mPreventScreenOnPartialLock; private UnsynchronizedWakeLock mProximityPartialLock; private HandlerThread mHandlerThread; - private HandlerThread mScreenOffThread; private Handler mScreenOffHandler; + private Handler mScreenBrightnessHandler; private Handler mHandler; private final TimeoutTask mTimeoutTask = new TimeoutTask(); - private final BrightnessState mScreenBrightness - = new BrightnessState(SCREEN_BRIGHT_BIT); + private ScreenBrightnessAnimator mScreenBrightnessAnimator; private boolean mStillNeedSleepNotification; private boolean mIsPowered = false; private IActivityManager mActivityService; @@ -227,6 +247,7 @@ public class PowerManagerService extends IPowerManager.Stub 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; @@ -240,6 +261,7 @@ public class PowerManagerService extends IPowerManager.Stub // 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; @@ -254,6 +276,7 @@ public class PowerManagerService extends IPowerManager.Stub 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; @@ -268,7 +291,8 @@ public class PowerManagerService extends IPowerManager.Stub 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); @@ -460,6 +484,9 @@ public class PowerManagerService extends IPowerManager.Stub // DIM_SCREEN //mDimScreen = getInt(DIM_SCREEN) != 0; + mScreenBrightnessSetting = getInt(SCREEN_BRIGHTNESS, DEFAULT_SCREEN_BRIGHTNESS); + mLightSensorAdjustSetting = getFloat(SCREEN_AUTO_BRIGHTNESS_ADJ, 0); + // SCREEN_BRIGHTNESS_MODE, default to manual setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)); @@ -467,10 +494,10 @@ public class PowerManagerService extends IPowerManager.Stub // recalculate everything setScreenOffTimeoutsLocked(); - final float windowScale = getFloat(WINDOW_ANIMATION_SCALE, 1.0f); + mWindowScaleAnimation = getFloat(WINDOW_ANIMATION_SCALE, 1.0f); final float transitionScale = getFloat(TRANSITION_ANIMATION_SCALE, 1.0f); mAnimationSetting = 0; - if (windowScale > 0.5f) { + if (mWindowScaleAnimation > 0.5f) { mAnimationSetting |= ANIM_SETTING_OFF; } if (transitionScale > 0.5f) { @@ -512,6 +539,7 @@ public class PowerManagerService extends IPowerManager.Stub 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")); nativeInit(); synchronized (mLocks) { @@ -519,28 +547,20 @@ public class PowerManagerService extends IPowerManager.Stub } mInitComplete = false; - mScreenOffThread = new HandlerThread("PowerManagerService.mScreenOffThread") { - @Override - protected void onLooperPrepared() { - mScreenOffHandler = new Handler(); - synchronized (mScreenOffThread) { - mInitComplete = true; - mScreenOffThread.notifyAll(); - } - } - }; - mScreenOffThread.start(); + mScreenBrightnessAnimator = new ScreenBrightnessAnimator("mScreenBrightnessUpdaterThread", + Process.THREAD_PRIORITY_DISPLAY); + mScreenBrightnessAnimator.start(); - synchronized (mScreenOffThread) { + synchronized (mScreenBrightnessAnimator) { while (!mInitComplete) { try { - mScreenOffThread.wait(); + mScreenBrightnessAnimator.wait(); } catch (InterruptedException e) { // Ignore } } } - + mInitComplete = false; mHandlerThread = new HandlerThread("PowerManagerService") { @Override @@ -560,8 +580,9 @@ public class PowerManagerService extends IPowerManager.Stub } } } - + nativeInit(); + Power.powerInitNative(); synchronized (mLocks) { updateNativePowerStateLocked(); // We make sure to start out with the screen on due to user activity. @@ -624,9 +645,12 @@ public class PowerManagerService extends IPowerManager.Stub + 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_MODE, WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE}, + 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(); @@ -1054,7 +1078,6 @@ public class PowerManagerService extends IPowerManager.Stub int oldPokey = mPokey; int cumulative = 0; - boolean oldAwakeOnSet = mPokeAwakeOnSet; boolean awakeOnSet = false; for (PokeLock p: mPokeLocks.values()) { cumulative |= p.pokey; @@ -1163,7 +1186,8 @@ public class PowerManagerService extends IPowerManager.Stub pw.println(" mProximitySensorActive=" + mProximitySensorActive); pw.println(" mProximityPendingValue=" + mProximityPendingValue); pw.println(" mLastProximityEventTime=" + mLastProximityEventTime); - pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); + pw.println(" mLightSensorEnabled=" + mLightSensorEnabled + + " mLightSensorAdjustSetting=" + mLightSensorAdjustSetting); pw.println(" mLightSensorValue=" + mLightSensorValue + " mLightSensorPendingValue=" + mLightSensorPendingValue); pw.println(" mLightSensorPendingDecrease=" + mLightSensorPendingDecrease @@ -1173,7 +1197,7 @@ public class PowerManagerService extends IPowerManager.Stub + " mLightSensorKeyboardBrightness=" + mLightSensorKeyboardBrightness); pw.println(" mUseSoftwareAutoBrightness=" + mUseSoftwareAutoBrightness); pw.println(" mAutoBrightessEnabled=" + mAutoBrightessEnabled); - mScreenBrightness.dump(pw, " mScreenBrightness: "); + mScreenBrightnessAnimator.dump(pw, " mScreenBrightnessAnimator: "); int N = mLocks.size(); pw.println(); @@ -1405,7 +1429,7 @@ public class PowerManagerService extends IPowerManager.Stub private WindowManagerPolicy.ScreenOnListener mScreenOnListener = new WindowManagerPolicy.ScreenOnListener() { - @Override public void onScreenOn() { + public void onScreenOn() { synchronized (mLocks) { if (mPreparingForScreenOn) { mPreparingForScreenOn = false; @@ -1694,7 +1718,8 @@ public class PowerManagerService extends IPowerManager.Stub + Integer.toHexString(mPowerState) + " mSkippedScreenOn=" + mSkippedScreenOn); } - mScreenBrightness.forceValueLocked(Power.BRIGHTNESS_OFF); + mScreenBrightnessHandler.removeMessages(ScreenBrightnessAnimator.ANIMATE_LIGHTS); + mScreenBrightnessAnimator.animateTo(Power.BRIGHTNESS_OFF, SCREEN_BRIGHT_BIT, 0); } } int err = Power.setScreenState(on); @@ -1706,11 +1731,6 @@ public class PowerManagerService extends IPowerManager.Stub // make sure button and key backlights are off too mButtonLight.turnOff(); mKeyboardLight.turnOff(); - // clear current value so we will update based on the new conditions - // when the sensor is reenabled. - mLightSensorValue = -1; - // reset our highest light sensor value when the screen turns off - mHighestLightSensorValue = -1; } } } @@ -1733,7 +1753,7 @@ public class PowerManagerService extends IPowerManager.Stub + " noChangeLights=" + noChangeLights + " reason=" + reason); } - + if (noChangeLights) { newState = (newState & ~LIGHTS_MASK) | (mPowerState & LIGHTS_MASK); } @@ -1775,6 +1795,19 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ -1839,6 +1872,7 @@ public class PowerManagerService extends IPowerManager.Stub } 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); } @@ -1857,8 +1891,7 @@ public class PowerManagerService extends IPowerManager.Stub Binder.restoreCallingIdentity(identity); } mPowerState &= ~SCREEN_ON_BIT; - mScreenOffReason = reason; - if (!mScreenBrightness.animating) { + if (!mScreenBrightnessAnimator.isAnimating()) { err = screenOffFinishedAnimatingLocked(reason); } else { err = 0; @@ -1877,9 +1910,11 @@ public class PowerManagerService extends IPowerManager.Stub } private void updateNativePowerStateLocked() { - nativeSetPowerState( - (mPowerState & SCREEN_ON_BIT) != 0, - (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT); + if (!mHeadless) { + nativeSetPowerState( + (mPowerState & SCREEN_ON_BIT) != 0, + (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT); + } } private int screenOffFinishedAnimatingLocked(int reason) { @@ -1930,11 +1965,11 @@ public class PowerManagerService extends IPowerManager.Stub // 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) { + 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())) { + if ((mSkippedScreenOn = shouldDeferScreenOnLocked())) { newState &= ~(SCREEN_ON_BIT|SCREEN_BRIGHT_BIT); } } @@ -1994,7 +2029,7 @@ public class PowerManagerService extends IPowerManager.Stub case SCREEN_BRIGHT_BIT: default: // not possible - nominalCurrentValue = (int)mScreenBrightness.curValue; + nominalCurrentValue = (int)mScreenBrightnessAnimator.getCurrentBrightness(); break; } } @@ -2044,8 +2079,8 @@ public class PowerManagerService extends IPowerManager.Stub Binder.restoreCallingIdentity(identity); } if (!mSkippedScreenOn) { - mScreenBrightness.setTargetLocked(brightness, steps, - INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue); + 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(); @@ -2088,167 +2123,178 @@ public class PowerManagerService extends IPowerManager.Stub } } - private void setLightBrightness(int mask, int value) { - int brightnessMode = (mAutoBrightessEnabled - ? LightsService.BRIGHTNESS_MODE_SENSOR - : LightsService.BRIGHTNESS_MODE_USER); - if ((mask & SCREEN_BRIGHT_BIT) != 0) { - if (DEBUG_SCREEN_ON) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Slog.i(TAG, "Set LCD brightness: " + value, e); - } - mLcdLight.setBrightness(value, brightnessMode); - } - if ((mask & BUTTON_BRIGHT_BIT) != 0) { - mButtonLight.setBrightness(value); - } - if ((mask & KEYBOARD_BRIGHT_BIT) != 0) { - mKeyboardLight.setBrightness(value); + /** + * 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 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; } - } - class BrightnessState implements Runnable { - final int mask; + @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) Log.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); + } - boolean initialized; - int targetValue; - float curValue; - float delta; - boolean animating; + if (elapsed > 100) { + Log.e(TAG, "Excessive delay setting brightness: " + elapsed + + "ms, mask=" + mask); + } - BrightnessState(int m) { - mask = m; + // Throttle brightness updates to frame refresh rate + int delay = elapsed < NOMINAL_FRAME_TIME_MS ? NOMINAL_FRAME_TIME_MS : 0; + 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(); + } } - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "animating=" + animating - + " targetValue=" + targetValue - + " curValue=" + curValue - + " delta=" + delta); - } + 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(Power.BRIGHTNESS_OFF, newValue); + newValue = Math.min(Power.BRIGHTNESS_ON, newValue); + } else { + newValue = endValue; + mInitialAnimation = false; + } - void forceValueLocked(int value) { - targetValue = -1; - curValue = value; - setLightBrightness(mask, value); - if (animating) { - finishAnimationLocked(false, value); - } - } + if (mDebugLightAnimation) { + Log.v(TAG, "Animating light: " + "start:" + startValue + + ", end:" + endValue + ", elapsed:" + elapsed + + ", duration:" + duration + ", current:" + currentValue + + ", delay:" + delay); + } - void setTargetLocked(int target, int stepsToTarget, int initialValue, - int nominalCurrentValue) { - if (!initialized) { - initialized = true; - curValue = (float)initialValue; - } else if (targetValue == target) { - return; - } - targetValue = target; - delta = (targetValue - - (nominalCurrentValue >= 0 ? nominalCurrentValue : curValue)) - / stepsToTarget; - if (mSpew || DEBUG_SCREEN_ON) { - String noticeMe = nominalCurrentValue == curValue ? "" : " ******************"; - Slog.i(TAG, "setTargetLocked mask=" + mask + " curValue=" + curValue - + " target=" + target + " targetValue=" + targetValue + " delta=" + delta - + " nominalCurrentValue=" + nominalCurrentValue - + noticeMe); + if (turningOff && !mHeadless && !mAnimateScreenLights) { + int mode = mScreenOffReason == OFF_BECAUSE_OF_PROX_SENSOR + ? 0 : mAnimationSetting; + if (mDebugLightAnimation) Log.v(TAG, "Doing power-off anim, mode=" + mode); + mScreenBrightnessHandler.obtainMessage(ANIMATE_POWER_OFF, mode, 0) + .sendToTarget(); + } + Message msg = mScreenBrightnessHandler + .obtainMessage(ANIMATE_LIGHTS, mask, newValue); + mScreenBrightnessHandler.sendMessageDelayed(msg, delay); + } } - animating = true; + } - if (mSpew) { - Slog.i(TAG, "scheduling light animator"); - } - mScreenOffHandler.removeCallbacks(this); - mScreenOffHandler.post(this); + public void dump(PrintWriter pw, String string) { + pw.println(prefix + "animating: " + "start:" + startValue + ", end:" + endValue + + ", duration:" + duration + ", current:" + currentValue); } - boolean stepLocked() { - if (!animating) return false; - if (false && mSpew) { - Slog.i(TAG, "Step target " + mask + ": cur=" + curValue - + " target=" + targetValue + " delta=" + delta); - } - curValue += delta; - int curIntValue = (int)curValue; - boolean more = true; - if (delta == 0) { - curValue = curIntValue = targetValue; - more = false; - } else if (delta > 0) { - if (curIntValue >= targetValue) { - curValue = curIntValue = targetValue; - more = false; + public void animateTo(int target, int mask, int animationDuration) { + synchronized(this) { + startValue = currentValue; + endValue = target; + currentMask = mask; + duration = (int) (mWindowScaleAnimation * animationDuration); + startTimeMillis = SystemClock.elapsedRealtime(); + mInitialAnimation = currentValue == 0 && target > 0; + + if (mDebugLightAnimation) { + Log.v(TAG, "animateTo(target=" + target + ", mask=" + mask + + ", duration=" + animationDuration +")" + + ", currentValue=" + currentValue + + ", startTime=" + startTimeMillis); } - } else { - if (curIntValue <= targetValue) { - curValue = curIntValue = targetValue; - more = false; + + if (target != currentValue) { + final boolean doScreenAnim = (mask & (SCREEN_BRIGHT_BIT | SCREEN_ON_BIT)) != 0; + final boolean turningOff = endValue == Power.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 } } - if (mSpew) Slog.d(TAG, "Animating curIntValue=" + curIntValue + ": " + mask); - setLightBrightness(mask, curIntValue); - finishAnimationLocked(more, curIntValue); - return more; } - void jumpToTargetLocked() { - if (mSpew) Slog.d(TAG, "jumpToTargetLocked targetValue=" + targetValue + ": " + mask); - setLightBrightness(mask, targetValue); - final int tv = targetValue; - curValue = tv; - targetValue = -1; - finishAnimationLocked(false, tv); + public int getCurrentBrightness() { + synchronized (this) { + return currentValue; + } } - private void finishAnimationLocked(boolean more, int curIntValue) { - animating = more; - if (!more) { - if (mask == SCREEN_BRIGHT_BIT && curIntValue == Power.BRIGHTNESS_OFF) { - screenOffFinishedAnimatingLocked(mScreenOffReason); - } + public boolean isAnimating() { + synchronized (this) { + return currentValue != endValue; } } - public void run() { - synchronized (mLocks) { - // we're turning off - final boolean turningOff = animating && targetValue == Power.BRIGHTNESS_OFF; - if (mAnimateScreenLights || !turningOff) { - long now = SystemClock.uptimeMillis(); - boolean more = mScreenBrightness.stepLocked(); - if (more) { - mScreenOffHandler.postAtTime(this, now+(1000/60)); - } - } else { - // It's pretty scary to hold mLocks for this long, and we should - // redesign this, but it works for now. - nativeStartSurfaceFlingerAnimation( - mScreenOffReason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR - ? 0 : mAnimationSetting); - mScreenBrightness.jumpToTargetLocked(); - } - } + public void cancelAnimation() { + animateTo(endValue, currentMask, 0); } } + private void setLightBrightness(int mask, int value) { + mScreenBrightnessAnimator.animateTo(value, mask, 0); + } + private int getPreferredBrightness() { - try { - if (mScreenBrightnessOverride >= 0) { - return mScreenBrightnessOverride; - } else if (mLightSensorScreenBrightness >= 0 && mUseSoftwareAutoBrightness - && mAutoBrightessEnabled) { - return mLightSensorScreenBrightness; - } - final int brightness = Settings.System.getInt(mContext.getContentResolver(), - SCREEN_BRIGHTNESS); - // Don't let applications turn the screen all the way off - return Math.max(brightness, mScreenBrightnessDim); - } catch (SettingNotFoundException snfe) { - return Power.BRIGHTNESS_ON; + 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) { @@ -2306,7 +2352,8 @@ public class PowerManagerService extends IPowerManager.Stub } private boolean isScreenTurningOffLocked() { - return (mScreenBrightness.animating && mScreenBrightness.targetValue == 0); + return (mScreenBrightnessAnimator.isAnimating() + && mScreenBrightnessAnimator.endValue == Power.BRIGHTNESS_OFF); } private boolean shouldLog(long time) { @@ -2327,7 +2374,7 @@ public class PowerManagerService extends IPowerManager.Stub private void forceUserActivityLocked() { if (isScreenTurningOffLocked()) { // cancel animation so userActivity will succeed - mScreenBrightness.animating = false; + mScreenBrightnessAnimator.cancelAnimation(); } boolean savedActivityAllowed = mUserActivityAllowed; mUserActivityAllowed = true; @@ -2444,10 +2491,38 @@ public class PowerManagerService extends IPowerManager.Stub break; } } - return values[i]; + // 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, "getAutoBrightnessValue", e); + Slog.e(TAG, "Values array must be non-empty and must be the same length " + + "as the auto-brightness levels array. Check config.xml.", e); return 255; } } @@ -2473,28 +2548,31 @@ public class PowerManagerService extends IPowerManager.Stub int value = (int)mLightSensorPendingValue; mLightSensorPendingDecrease = false; mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value); + lightSensorChangedLocked(value, false); } } } }; + private boolean mInitialAnimation; // used to prevent lightsensor changes while turning on + 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); + lightSensorChangedLocked(value, false); } } } - private void lightSensorChangedLocked(int value) { + private void lightSensorChangedLocked(int value, boolean immediate) { if (mDebugLightSensor) { Slog.d(TAG, "lightSensorChangedLocked " + value); } @@ -2539,9 +2617,11 @@ public class PowerManagerService extends IPowerManager.Stub } if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) { - if (!mSkippedScreenOn) { - mScreenBrightness.setTargetLocked(lcdValue, AUTOBRIGHTNESS_ANIM_STEPS, - INITIAL_SCREEN_BRIGHTNESS, (int)mScreenBrightness.curValue); + if (!mSkippedScreenOn && !mInitialAnimation) { + int steps = immediate ? IMMEDIATE_ANIM_STEPS : AUTOBRIGHTNESS_ANIM_STEPS; + mScreenBrightnessAnimator.cancelAnimation(); + mScreenBrightnessAnimator.animateTo(lcdValue, + SCREEN_BRIGHT_BIT, steps * NOMINAL_FRAME_TIME_MS); } } if (mButtonBrightnessOverride < 0) { @@ -2593,7 +2673,7 @@ public class PowerManagerService extends IPowerManager.Stub synchronized (this) { ShutdownThread.reboot(mContext, finalReason, false); } - + } }; // ShutdownThread must run on a looper capable of displaying the UI. @@ -2688,7 +2768,7 @@ public class PowerManagerService extends IPowerManager.Stub if (mLightSensorValue >= 0) { int value = (int)mLightSensorValue; mLightSensorValue = -1; - lightSensorChangedLocked(value); + lightSensorChangedLocked(value, false); } } userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true); @@ -2868,7 +2948,7 @@ public class PowerManagerService extends IPowerManager.Stub } void systemReady() { - mSensorManager = new SensorManager(mHandlerThread.getLooper()); + 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) { @@ -2947,11 +3027,27 @@ public class PowerManagerService extends IPowerManager.Stub } finally { Binder.restoreCallingIdentity(identity); } + mScreenBrightnessAnimator.animateTo(brightness, SCREEN_BRIGHT_BIT, 0); + } + } - // update our animation state - synchronized (mLocks) { - mScreenBrightness.targetValue = brightness; - mScreenBrightness.jumpToTargetLocked(); + 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); + } } } } @@ -3056,20 +3152,25 @@ public class PowerManagerService extends IPowerManager.Stub } if (mSensorManager != null && mLightSensorEnabled != enable) { mLightSensorEnabled = enable; - // clear previous values so we will adjust to current brightness when - // auto-brightness is reenabled - mHighestLightSensorValue = -1; - mLightSensorValue = -1; - // 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 + if (mLightSensorValue >= 0) { + int value = (int)mLightSensorValue; + 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); @@ -3121,43 +3222,45 @@ public class PowerManagerService extends IPowerManager.Stub } }; + private void handleLightSensorValue(int value, boolean immediate) { + long milliseconds = SystemClock.elapsedRealtime(); + if (mLightSensorValue == -1 || + milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) { + // 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() { 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; } - - int value = (int)event.values[0]; - long milliseconds = SystemClock.elapsedRealtime(); - if (mDebugLightSensor) { - Slog.d(TAG, "onSensorChanged: light value: " + value); - } - if (mLightSensorValue == -1 || - milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) { - // process the value immediately if screen has just turned on - mHandler.removeCallbacks(mAutoBrightnessTask); - mLightSensorPendingDecrease = false; - mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value); - } 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; - } - } + handleLightSensorValue((int)event.values[0], false); } } diff --git a/services/java/com/android/server/RecognitionManagerService.java b/services/java/com/android/server/RecognitionManagerService.java index 8e55512..3567cfc 100644 --- a/services/java/com/android/server/RecognitionManagerService.java +++ b/services/java/com/android/server/RecognitionManagerService.java @@ -65,7 +65,7 @@ public class RecognitionManagerService extends Binder { RecognitionManagerService(Context context) { mContext = context; mMonitor = new MyPackageMonitor(); - mMonitor.register(context, true); + mMonitor.register(context, null, true); } public void systemReady() { @@ -75,7 +75,10 @@ public class RecognitionManagerService extends Binder { try { mContext.getPackageManager().getServiceInfo(comp, 0); } catch (NameNotFoundException e) { - setCurRecognizer(null); + comp = findAvailRecognizer(null); + if (comp != null) { + setCurRecognizer(comp); + } } } else { comp = findAvailRecognizer(null); diff --git a/services/java/com/android/server/SamplingProfilerService.java b/services/java/com/android/server/SamplingProfilerService.java index 61267d0..0034d2c 100644 --- a/services/java/com/android/server/SamplingProfilerService.java +++ b/services/java/com/android/server/SamplingProfilerService.java @@ -39,9 +39,11 @@ public class SamplingProfilerService extends Binder { private static final boolean LOCAL_LOGV = false; public static final String SNAPSHOT_DIR = SamplingProfilerIntegration.SNAPSHOT_DIR; + private final Context mContext; private FileObserver snapshotObserver; public SamplingProfilerService(Context context) { + mContext = context; registerSettingObserver(context); startWorking(context); } @@ -94,6 +96,8 @@ public class SamplingProfilerService extends Binder { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + pw.println("SamplingProfilerService:"); pw.println("Watching directory: " + SNAPSHOT_DIR); } diff --git a/services/java/com/android/server/SerialService.java b/services/java/com/android/server/SerialService.java new file mode 100644 index 0000000..5d2b2a0 --- /dev/null +++ b/services/java/com/android/server/SerialService.java @@ -0,0 +1,58 @@ +/* + * 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 an + * limitations under the License. + */ + +package com.android.server; + +import android.content.Context; +import android.hardware.ISerialManager; +import android.os.ParcelFileDescriptor; + +import java.io.File; +import java.util.ArrayList; + +public class SerialService extends ISerialManager.Stub { + + private final Context mContext; + private final String[] mSerialPorts; + + public SerialService(Context context) { + mContext = context; + mSerialPorts = context.getResources().getStringArray( + com.android.internal.R.array.config_serialPorts); + } + + public String[] getSerialPorts() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null); + + ArrayList<String> ports = new ArrayList<String>(); + for (int i = 0; i < mSerialPorts.length; i++) { + String path = mSerialPorts[i]; + if (new File(path).exists()) { + ports.add(path); + } + } + String[] result = new String[ports.size()]; + ports.toArray(result); + return result; + } + + public ParcelFileDescriptor openSerialPort(String path) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null); + return native_open(path); + } + + private native ParcelFileDescriptor native_open(String path); +} diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index a9ff6c5..8429086 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -292,27 +292,27 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } - public void setSystemUiVisibility(int vis) { + public void setSystemUiVisibility(int vis, int mask) { // also allows calls from window manager which is in this process. enforceStatusBarService(); if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")"); synchronized (mLock) { - updateUiVisibilityLocked(vis); + updateUiVisibilityLocked(vis, mask); disableLocked(vis & StatusBarManager.DISABLE_MASK, mSysUiVisToken, "WindowManager.LayoutParams"); } } - private void updateUiVisibilityLocked(final int vis) { + private void updateUiVisibilityLocked(final int vis, final int mask) { if (mSystemUiVisibility != vis) { mSystemUiVisibility = vis; mHandler.post(new Runnable() { public void run() { if (mBar != null) { try { - mBar.setSystemUiVisibility(vis); + mBar.setSystemUiVisibility(vis, mask); } catch (RemoteException ex) { } } @@ -352,6 +352,24 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } + @Override + public void preloadRecentApps() { + if (mBar != null) { + try { + mBar.preloadRecentApps(); + } catch (RemoteException ex) {} + } + } + + @Override + public void cancelPreloadRecentApps() { + if (mBar != null) { + try { + mBar.cancelPreloadRecentApps(); + } catch (RemoteException ex) {} + } + } + private void enforceStatusBar() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, "StatusBarManagerService"); diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java index da97089..a7a583c 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -44,12 +44,16 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String WALLPAPER_IMAGE_FILENAME = "wallpaper"; private static final String WALLPAPER_INFO_FILENAME = "wallpaper_info.xml"; - private static final String WALLPAPER_IMAGE_DIR = "/data/data/com.android.settings/files"; - private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/" + WALLPAPER_IMAGE_FILENAME; - - private static final String WALLPAPER_INFO_DIR = "/data/system"; - private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/" + WALLPAPER_INFO_FILENAME; + // 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 = 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 = 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; + private static final String WALLPAPER_INFO_KEY = WallpaperBackupHelper.WALLPAPER_INFO_KEY; @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, @@ -58,13 +62,15 @@ public class SystemBackupAgent extends BackupAgentHelper { WallpaperManagerService wallpaper = (WallpaperManagerService)ServiceManager.getService( Context.WALLPAPER_SERVICE); String[] files = new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO }; - if (wallpaper != null && wallpaper.mName != null && wallpaper.mName.length() > 0) { + String[] keys = new String[] { WALLPAPER_IMAGE_KEY, WALLPAPER_INFO_KEY }; + if (wallpaper != null && wallpaper.getName() != null && wallpaper.getName().length() > 0) { // When the wallpaper has a name, back up the info by itself. // TODO: Don't rely on the innards of the service object like this! // TODO: Send a delete for any stored wallpaper image in this case? files = new String[] { WALLPAPER_INFO }; + keys = new String[] { WALLPAPER_INFO_KEY }; } - addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files)); + addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys)); super.onBackup(oldState, data, newState); } @@ -90,9 +96,11 @@ public class SystemBackupAgent extends BackupAgentHelper { throws IOException { // On restore, we also support a previous data schema "system_files" addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, - new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO })); + new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO }, + new String[] { WALLPAPER_IMAGE_KEY, WALLPAPER_INFO_KEY} )); addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this, - new String[] { WALLPAPER_IMAGE })); + new String[] { WALLPAPER_IMAGE }, + new String[] { WALLPAPER_IMAGE_KEY} )); try { super.onRestore(data, appVersionCode, newState); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3ae62ad..849281d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -30,6 +30,7 @@ import android.media.AudioService; import android.net.wifi.p2p.WifiP2pService; import android.os.Looper; import android.os.RemoteException; +import android.os.SchedulingPolicyService; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; @@ -38,6 +39,7 @@ 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; import android.util.EventLog; import android.util.Log; @@ -47,8 +49,10 @@ import android.view.WindowManager; import com.android.internal.app.ShutdownThread; import com.android.internal.os.BinderInternal; 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.input.InputManagerService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.NetworkStatsService; import com.android.server.pm.PackageManagerService; @@ -108,10 +112,12 @@ class ServerThread extends Thread { String factoryTestStr = SystemProperties.get("ro.factorytest"); int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF : Integer.parseInt(factoryTestStr); + final boolean headless = "1".equals(SystemProperties.get("ro.config.headless", "0")); LightsService lights = null; PowerManagerService power = null; BatteryService battery = null; + VibratorService vibrator = null; AlarmManagerService alarm = null; NetworkManagementService networkManagement = null; NetworkStatsService networkStats = null; @@ -119,6 +125,7 @@ class ServerThread extends Thread { ConnectivityService connectivity = null; WifiP2pService wifiP2p = null; WifiService wifi = null; + NsdService serviceDiscovery= null; IPackageManager pm = null; Context context = null; WindowManagerService wm = null; @@ -126,15 +133,18 @@ class ServerThread extends Thread { BluetoothA2dpService bluetoothA2dp = null; DockObserver dock = null; UsbService usb = null; + SerialService serial = null; UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; NetworkTimeUpdateService networkTimeUpdater = null; + CommonTimeManagementService commonTimeMgmtService = null; + InputManagerService inputManager = null; // Critical services... try { - Slog.i(TAG, "Entropy Service"); - ServiceManager.addService("entropy", new EntropyService()); + Slog.i(TAG, "Entropy Mixer"); + ServiceManager.addService("entropy", new EntropyMixer()); Slog.i(TAG, "Power Manager"); power = new PowerManagerService(); @@ -146,6 +156,10 @@ class ServerThread extends Thread { Slog.i(TAG, "Telephony Registry"); ServiceManager.addService("telephony.registry", new TelephonyRegistry(context)); + Slog.i(TAG, "Scheduling Policy"); + ServiceManager.addService(Context.SCHEDULING_POLICY_SERVICE, + new SchedulingPolicyService()); + AttributeCache.init(context); Slog.i(TAG, "Package Manager"); @@ -197,7 +211,8 @@ class ServerThread extends Thread { ServiceManager.addService("battery", battery); Slog.i(TAG, "Vibrator Service"); - ServiceManager.addService("vibrator", new VibratorService(context)); + vibrator = new VibratorService(context); + ServiceManager.addService("vibrator", vibrator); // only initialize the power service after we have started the // lights service, content providers and the battery service. @@ -216,6 +231,8 @@ class ServerThread extends Thread { factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL, !firstBoot); ServiceManager.addService(Context.WINDOW_SERVICE, wm); + inputManager = wm.getInputManagerService(); + ServiceManager.addService(Context.INPUT_SERVICE, inputManager); ActivityManagerService.self().setWindowManager(wm); @@ -231,16 +248,17 @@ class ServerThread extends Thread { bluetooth = new BluetoothService(context); ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth); bluetooth.initAfterRegistration(); - bluetoothA2dp = new BluetoothA2dpService(context, bluetooth); - ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE, - bluetoothA2dp); - bluetooth.initAfterA2dpRegistration(); - int airplaneModeOn = Settings.System.getInt(mContentResolver, - Settings.System.AIRPLANE_MODE_ON, 0); + 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 (airplaneModeOn == 0 && bluetoothOn != 0) { + if (bluetoothOn != 0) { bluetooth.enable(); } } @@ -259,12 +277,14 @@ class ServerThread extends Thread { LocationManagerService location = null; CountryDetectorService countryDetector = null; TextServicesManagerService tsms = null; + LockSettingsService lockSettings = null; + DreamManagerService dreamy = null; // Bring up services needed for UI. if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { Slog.i(TAG, "Input Method Service"); - imm = new InputMethodManagerService(context); + imm = new InputMethodManagerService(context, wm); ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm); } catch (Throwable e) { reportWtf("starting Input Manager Service", e); @@ -301,6 +321,14 @@ class ServerThread extends Thread { if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { + Slog.i(TAG, "LockSettingsService"); + lockSettings = new LockSettingsService(context); + ServiceManager.addService("lock_settings", lockSettings); + } catch (Throwable e) { + reportWtf("starting LockSettingsService service", e); + } + + try { Slog.i(TAG, "Device Policy"); devicePolicy = new DevicePolicyManagerService(context); ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy); @@ -388,6 +416,15 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Network Service Discovery Service"); + serviceDiscovery = NsdService.create(context); + ServiceManager.addService( + Context.NSD_SERVICE, serviceDiscovery); + } catch (Throwable e) { + reportWtf("starting Service Discovery Service", e); + } + + try { Slog.i(TAG, "Throttle Service"); throttle = new ThrottleService(context); ServiceManager.addService( @@ -397,14 +434,24 @@ class ServerThread extends Thread { } try { - /* - * NotificationManagerService is dependant on MountService, - * (for media / usb notifications) so we must start MountService first. - */ - Slog.i(TAG, "Mount Service"); - ServiceManager.addService("mount", new MountService(context)); + Slog.i(TAG, "UpdateLock Service"); + ServiceManager.addService(Context.UPDATE_LOCK_SERVICE, + new UpdateLockService(context)); } catch (Throwable e) { - reportWtf("starting Mount Service", e); + reportWtf("starting UpdateLockService", e); + } + + if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) { + try { + /* + * NotificationManagerService is dependant on MountService, + * (for media / usb notifications) so we must start MountService first. + */ + Slog.i(TAG, "Mount Service"); + ServiceManager.addService("mount", new MountService(context)); + } catch (Throwable e) { + reportWtf("starting Mount Service", e); + } } try { @@ -456,19 +503,26 @@ class ServerThread extends Thread { reportWtf("starting DropBoxManagerService", e); } - try { - Slog.i(TAG, "Wallpaper Service"); - wallpaper = new WallpaperManagerService(context); - ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper); - } catch (Throwable e) { - reportWtf("starting Wallpaper Service", e); + if (context.getResources().getBoolean( + com.android.internal.R.bool.config_enableWallpaperService)) { + try { + Slog.i(TAG, "Wallpaper Service"); + if (!headless) { + wallpaper = new WallpaperManagerService(context); + ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper); + } + } catch (Throwable e) { + reportWtf("starting Wallpaper Service", e); + } } - try { - Slog.i(TAG, "Audio Service"); - ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context)); - } catch (Throwable e) { - reportWtf("starting Audio Service", e); + if (!"0".equals(SystemProperties.get("system_init.startaudioservice"))) { + try { + Slog.i(TAG, "Audio Service"); + ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context)); + } catch (Throwable e) { + reportWtf("starting Audio Service", e); + } } try { @@ -497,6 +551,15 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Serial Service"); + // Serial port support + serial = new SerialService(context); + ServiceManager.addService(Context.SERIAL_SERVICE, serial); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting SerialService", e); + } + + try { Slog.i(TAG, "UI Mode Manager Service"); // Listen for UI mode changes uiMode = new UiModeManagerService(context); @@ -552,6 +615,26 @@ class ServerThread extends Thread { } catch (Throwable e) { reportWtf("starting NetworkTimeUpdate service", e); } + + try { + Slog.i(TAG, "CommonTimeManagementService"); + commonTimeMgmtService = new CommonTimeManagementService(context); + ServiceManager.addService("commontime_management", commonTimeMgmtService); + } catch (Throwable e) { + reportWtf("starting CommonTimeManagementService service", e); + } + + if (context.getResources().getBoolean( + com.android.internal.R.bool.config_enableDreams)) { + try { + Slog.i(TAG, "Dreams Service"); + // Dreams (interactive idle-time views, a/k/a screen savers) + dreamy = new DreamManagerService(context); + ServiceManager.addService("dreams", dreamy); + } catch (Throwable e) { + reportWtf("starting DreamManagerService", e); + } + } } // Before things start rolling, be sure we have decided whether @@ -570,6 +653,12 @@ class ServerThread extends Thread { // It is now time to start up the app processes... + try { + vibrator.systemReady(); + } catch (Throwable e) { + reportWtf("making Vibrator Service ready", e); + } + if (devicePolicy != null) { try { devicePolicy.systemReady(); @@ -611,6 +700,11 @@ class ServerThread extends Thread { } catch (Throwable e) { reportWtf("making Package Manager Service ready", e); } + try { + lockSettings.systemReady(); + } catch (Throwable e) { + reportWtf("making Lock Settings Service ready", e); + } // These are needed to propagate to the runnable below. final Context contextF = context; @@ -630,8 +724,12 @@ class ServerThread extends Thread { final LocationManagerService locationF = location; final CountryDetectorService countryDetectorF = countryDetector; final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater; + final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService; final TextServicesManagerService textServiceManagerServiceF = tsms; 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 @@ -642,7 +740,7 @@ class ServerThread extends Thread { public void run() { Slog.i(TAG, "Making services ready"); - startSystemUi(contextF); + if (!headless) startSystemUi(contextF); try { if (batteryF != null) batteryF.systemReady(); } catch (Throwable e) { @@ -729,10 +827,25 @@ class ServerThread extends Thread { reportWtf("making Network Time Service ready", e); } try { + if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady(); + } catch (Throwable e) { + reportWtf("making Common time management service ready", e); + } + try { if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady(); } catch (Throwable e) { reportWtf("making Text Services Manager Service ready", e); } + try { + if (dreamyF != null) dreamyF.systemReady(); + } catch (Throwable e) { + reportWtf("making DreamManagerService ready", e); + } + try { + if (inputManagerF != null) inputManagerF.systemReady(bluetoothF); + } 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 8c8e725..1b1638a 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -29,6 +29,7 @@ import android.telephony.CellLocation; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; +import android.telephony.CellInfo; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Slog; @@ -107,6 +108,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN; + private CellInfo mCellInfo = null; + static final int PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | PhoneStateListener.LISTEN_CALL_STATE | @@ -236,6 +239,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if ((events & PhoneStateListener.LISTEN_CELL_INFO) != 0) { + try { + r.callback.onCellInfoChanged(new CellInfo(mCellInfo)); + } catch (RemoteException ex) { + remove(r.binder); + } + } } } } else { @@ -325,6 +335,26 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { broadcastSignalStrengthChanged(signalStrength); } + public void notifyCellInfo(CellInfo cellInfo) { + if (!checkNotifyPermission("notifyCellInfo()")) { + return; + } + + synchronized (mRecords) { + mCellInfo = cellInfo; + for (Record r : mRecords) { + if ((r.events & PhoneStateListener.LISTEN_CELL_INFO) != 0) { + try { + r.callback.onCellInfoChanged(new CellInfo(cellInfo)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); + } + } + public void notifyMessageWaitingChanged(boolean mwi) { if (!checkNotifyPermission("notifyMessageWaitingChanged()")) { return; @@ -530,6 +560,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties); pw.println(" mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities); pw.println(" mCellLocation=" + mCellLocation); + pw.println(" mCellInfo=" + mCellInfo); pw.println("registrations: count=" + recordCount); for (Record r : mRecords) { pw.println(" " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events)); @@ -655,6 +686,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } + if ((events & PhoneStateListener.LISTEN_CELL_INFO) != 0) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_COARSE_LOCATION, null); + + } + if ((events & PHONE_STATE_PERMISSION_MASK) != 0) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.READ_PHONE_STATE, null); diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java index 8384ebc..499ff7a 100644 --- a/services/java/com/android/server/TextServicesManagerService.java +++ b/services/java/com/android/server/TextServicesManagerService.java @@ -52,6 +52,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; public class TextServicesManagerService extends ITextServicesManager.Stub { private static final String TAG = TextServicesManagerService.class.getSimpleName(); @@ -76,7 +77,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { mSystemReady = false; mContext = context; mMonitor = new TextServicesMonitor(); - mMonitor.register(context, true); + mMonitor.register(context, null, true); synchronized (mSpellCheckerMap) { buildSpellCheckerMapLocked(context, mSpellCheckerList, mSpellCheckerMap); } @@ -582,8 +583,8 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { private class SpellCheckerBindGroup { private final String TAG = SpellCheckerBindGroup.class.getSimpleName(); private final InternalServiceConnection mInternalConnection; - private final ArrayList<InternalDeathRecipient> mListeners = - new ArrayList<InternalDeathRecipient>(); + private final CopyOnWriteArrayList<InternalDeathRecipient> mListeners = + new CopyOnWriteArrayList<InternalDeathRecipient>(); public boolean mBound; public ISpellCheckerService mSpellChecker; public boolean mConnected; @@ -601,19 +602,24 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { if (DBG) { Slog.d(TAG, "onServiceConnected"); } - synchronized(mSpellCheckerMap) { - for (InternalDeathRecipient listener : mListeners) { - try { - final ISpellCheckerSession session = spellChecker.getISpellCheckerSession( - listener.mScLocale, listener.mScListener, listener.mBundle); - listener.mTsListener.onServiceConnected(session); - } catch (RemoteException e) { - Slog.e(TAG, "Exception in getting the spell checker session." - + "Reconnect to the spellchecker. ", e); - removeAll(); - return; + + for (InternalDeathRecipient listener : mListeners) { + try { + final ISpellCheckerSession session = spellChecker.getISpellCheckerSession( + listener.mScLocale, listener.mScListener, listener.mBundle); + synchronized(mSpellCheckerMap) { + if (mListeners.contains(listener)) { + listener.mTsListener.onServiceConnected(session); + } } + } catch (RemoteException e) { + Slog.e(TAG, "Exception in getting the spell checker session." + + "Reconnect to the spellchecker. ", e); + removeAll(); + return; } + } + synchronized(mSpellCheckerMap) { mSpellChecker = spellChecker; mConnected = true; } diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index c7fbc00..84daead 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -65,7 +65,7 @@ class UiModeManagerService extends IUiModeManager.Stub { // 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 boolean ENABLE_LAUNCH_DESK_DOCK_APP = false; private static final int MSG_UPDATE_TWILIGHT = 0; private static final int MSG_ENABLE_LOCATION_UPDATES = 1; @@ -90,6 +90,7 @@ class UiModeManagerService extends IUiModeManager.Stub { private int mNightMode = UiModeManager.MODE_NIGHT_NO; private boolean mCarModeEnabled = false; private boolean mCharging = false; + private final int mDefaultUiModeType; private final boolean mCarModeKeepsScreenOn; private final boolean mDeskModeKeepsScreenOn; @@ -188,8 +189,8 @@ class UiModeManagerService extends IUiModeManager.Stub { } try { ActivityManagerNative.getDefault().startActivityWithConfig( - null, homeIntent, null, null, 0, null, null, 0, false, false, - newConfig); + null, homeIntent, null, null, null, 0, 0, + newConfig, null); mHoldingConfiguration = false; } catch (RemoteException e) { Slog.w(TAG, e.getCause()); @@ -347,6 +348,8 @@ class UiModeManagerService extends IUiModeManager.Stub { mConfiguration.setToDefaults(); + mDefaultUiModeType = context.getResources().getInteger( + com.android.internal.R.integer.config_defaultUiModeType); mCarModeKeepsScreenOn = (context.getResources().getInteger( com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1); mDeskModeKeepsScreenOn = (context.getResources().getInteger( @@ -452,7 +455,7 @@ class UiModeManagerService extends IUiModeManager.Stub { } final void updateConfigurationLocked(boolean sendIt) { - int uiMode = Configuration.UI_MODE_TYPE_NORMAL; + int uiMode = mDefaultUiModeType; if (mCarModeEnabled) { uiMode = Configuration.UI_MODE_TYPE_CAR; } else if (isDeskDockState(mDockState)) { diff --git a/services/java/com/android/server/UpdateLockService.java b/services/java/com/android/server/UpdateLockService.java new file mode 100644 index 0000000..1ffd196 --- /dev/null +++ b/services/java/com/android/server/UpdateLockService.java @@ -0,0 +1,125 @@ +/* + * 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.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.IUpdateLock; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.TokenWatcher; +import android.os.UpdateLock; +import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +public class UpdateLockService extends IUpdateLock.Stub { + static final boolean DEBUG = false; + static final String TAG = "UpdateLockService"; + + // signatureOrSystem required to use update locks + static final String PERMISSION = "android.permission.UPDATE_LOCK"; + + Context mContext; + LockWatcher mLocks; + + class LockWatcher extends TokenWatcher { + LockWatcher(Handler h, String tag) { + super(h, tag); + } + + public void acquired() { + if (DEBUG) { + Slog.d(TAG, "first acquire; broadcasting convenient=false"); + } + sendLockChangedBroadcast(false); + } + public void released() { + if (DEBUG) { + Slog.d(TAG, "last release; broadcasting convenient=true"); + } + sendLockChangedBroadcast(true); + } + } + + UpdateLockService(Context context) { + mContext = context; + mLocks = new LockWatcher(new Handler(), "UpdateLocks"); + + // Consider just-booting to be a reasonable time to allow + // interruptions for update installation etc. + sendLockChangedBroadcast(true); + } + + void sendLockChangedBroadcast(boolean state) { + // Safe early during boot because this broadcast only goes to registered receivers. + long oldIdent = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent(UpdateLock.UPDATE_LOCK_CHANGED) + .putExtra(UpdateLock.NOW_IS_CONVENIENT, state) + .putExtra(UpdateLock.TIMESTAMP, System.currentTimeMillis()) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendStickyBroadcast(intent); + } finally { + Binder.restoreCallingIdentity(oldIdent); + } + } + + @Override + public void acquireUpdateLock(IBinder token, String tag) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "acquire(" + token + ") by " + makeTag(tag)); + } + + mContext.enforceCallingOrSelfPermission(PERMISSION, "acquireUpdateLock"); + mLocks.acquire(token, makeTag(tag)); + } + + @Override + public void releaseUpdateLock(IBinder token) throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "release(" + token + ')'); + } + + mContext.enforceCallingOrSelfPermission(PERMISSION, "releaseUpdateLock"); + mLocks.release(token); + }; + + private String makeTag(String tag) { + return "{tag=" + tag + + " uid=" + Binder.getCallingUid() + + " pid=" + Binder.getCallingPid() + '}'; + } + + @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 update lock service from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + mLocks.dump(pw); + } +} diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index de25747..6282c31 100755 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -21,6 +21,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.hardware.input.InputManager; import android.os.Handler; import android.os.IVibratorService; import android.os.PowerManager; @@ -29,18 +31,41 @@ import android.os.RemoteException; import android.os.IBinder; import android.os.Binder; import android.os.SystemClock; +import android.os.Vibrator; import android.os.WorkSource; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; import android.util.Slog; +import android.view.InputDevice; +import java.util.ArrayList; import java.util.LinkedList; import java.util.ListIterator; -public class VibratorService extends IVibratorService.Stub { +public class VibratorService extends IVibratorService.Stub + implements InputManager.InputDeviceListener { private static final String TAG = "VibratorService"; private final LinkedList<Vibration> mVibrations; private Vibration mCurrentVibration; private final WorkSource mTmpWorkSource = new WorkSource(); + private final Handler mH = new Handler(); + + private final Context mContext; + private final PowerManager.WakeLock mWakeLock; + private InputManager mIm; + + volatile VibrateThread mThread; + + // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are + // to be acquired + private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>(); + private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators + private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators + + native static boolean vibratorExists(); + native static void vibratorOn(long milliseconds); + native static void vibratorOff(); private class Vibration implements IBinder.DeathRecipient { private final IBinder mToken; @@ -112,10 +137,23 @@ public class VibratorService extends IVibratorService.Stub { context.registerReceiver(mIntentReceiver, filter); } + public void systemReady() { + mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE); + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES), true, + new ContentObserver(mH) { + @Override + public void onChange(boolean selfChange) { + updateInputDeviceVibrators(); + } + }); + updateInputDeviceVibrators(); + } + public boolean hasVibrator() { - return vibratorExists(); + return doVibratorExists(); } - + public void vibrate(long milliseconds, IBinder token) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { @@ -131,6 +169,7 @@ public class VibratorService extends IVibratorService.Stub { // longer than milliseconds. return; } + Vibration vib = new Vibration(token, milliseconds, uid); synchronized (mVibrations) { removeVibrationLocked(token); @@ -240,7 +279,7 @@ public class VibratorService extends IVibratorService.Stub { } mThread = null; } - vibratorOff(); + doVibratorOff(); mH.removeCallbacks(mVibrationRunnable); } @@ -257,7 +296,7 @@ public class VibratorService extends IVibratorService.Stub { // Lock held on mVibrations private void startVibrationLocked(final Vibration vib) { if (vib.mTimeout != 0) { - vibratorOn(vib.mTimeout); + doVibratorOn(vib.mTimeout); mH.postDelayed(mVibrationRunnable, vib.mTimeout); } else { // mThread better be null here. doCancelVibrate should always be @@ -295,6 +334,94 @@ public class VibratorService extends IVibratorService.Stub { } } + private void updateInputDeviceVibrators() { + synchronized (mVibrations) { + doCancelVibrateLocked(); + + synchronized (mInputDeviceVibrators) { + mVibrateInputDevicesSetting = false; + try { + mVibrateInputDevicesSetting = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.VIBRATE_INPUT_DEVICES) > 0; + } catch (SettingNotFoundException snfe) { + } + + if (mVibrateInputDevicesSetting) { + if (!mInputDeviceListenerRegistered) { + mInputDeviceListenerRegistered = true; + mIm.registerInputDeviceListener(this, mH); + } + } else { + if (mInputDeviceListenerRegistered) { + mInputDeviceListenerRegistered = false; + mIm.unregisterInputDeviceListener(this); + } + } + + mInputDeviceVibrators.clear(); + if (mVibrateInputDevicesSetting) { + int[] ids = mIm.getInputDeviceIds(); + for (int i = 0; i < ids.length; i++) { + InputDevice device = mIm.getInputDevice(ids[i]); + Vibrator vibrator = device.getVibrator(); + if (vibrator.hasVibrator()) { + mInputDeviceVibrators.add(vibrator); + } + } + } + } + + startNextVibrationLocked(); + } + } + + @Override + public void onInputDeviceAdded(int deviceId) { + updateInputDeviceVibrators(); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + updateInputDeviceVibrators(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + updateInputDeviceVibrators(); + } + + private boolean doVibratorExists() { + synchronized (mInputDeviceVibrators) { + return !mInputDeviceVibrators.isEmpty() || vibratorExists(); + } + } + + private void doVibratorOn(long millis) { + synchronized (mInputDeviceVibrators) { + final int vibratorCount = mInputDeviceVibrators.size(); + if (vibratorCount != 0) { + for (int i = 0; i < vibratorCount; i++) { + mInputDeviceVibrators.get(i).vibrate(millis); + } + } else { + vibratorOn(millis); + } + } + } + + private void doVibratorOff() { + synchronized (mInputDeviceVibrators) { + final int vibratorCount = mInputDeviceVibrators.size(); + if (vibratorCount != 0) { + for (int i = 0; i < vibratorCount; i++) { + mInputDeviceVibrators.get(i).cancel(); + } + } else { + vibratorOff(); + } + } + } + private class VibrateThread extends Thread { final Vibration mVibration; boolean mDone; @@ -350,7 +477,7 @@ public class VibratorService extends IVibratorService.Stub { // duration is saved for delay() at top of loop duration = pattern[index++]; if (duration > 0) { - VibratorService.this.vibratorOn(duration); + VibratorService.this.doVibratorOn(duration); } } else { if (repeat < 0) { @@ -394,15 +521,4 @@ public class VibratorService extends IVibratorService.Stub { } } }; - - private Handler mH = new Handler(); - - private final Context mContext; - private final PowerManager.WakeLock mWakeLock; - - volatile VibrateThread mThread; - - native static boolean vibratorExists(); - native static void vibratorOn(long milliseconds); - native static void vibratorOff(); } diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 4925a4e..d97d335 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -24,9 +24,12 @@ import android.app.IWallpaperManagerCallback; import android.app.PendingIntent; import android.app.WallpaperInfo; import android.app.backup.BackupManager; +import android.app.backup.WallpaperBackupHelper; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -35,6 +38,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.os.Binder; import android.os.Bundle; +import android.os.Environment; import android.os.FileUtils; import android.os.IBinder; import android.os.RemoteException; @@ -43,11 +47,13 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteCallbackList; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserId; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; import android.util.Slog; +import android.util.SparseArray; import android.util.Xml; import android.view.Display; import android.view.IWindowManager; @@ -70,6 +76,7 @@ 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"; @@ -83,17 +90,9 @@ class WallpaperManagerService extends IWallpaperManager.Stub { */ static final long MIN_WALLPAPER_CRASH_TIME = 10000; - static final File WALLPAPER_DIR = new File( - "/data/data/com.android.settings/files"); + static final File WALLPAPER_BASE_DIR = new File("/data/system/users"); static final String WALLPAPER = "wallpaper"; - static final File WALLPAPER_FILE = new File(WALLPAPER_DIR, WALLPAPER); - - /** - * List of callbacks registered they should each be notified - * when the wallpaper is changed. - */ - private final RemoteCallbackList<IWallpaperManagerCallback> mCallbacks - = new RemoteCallbackList<IWallpaperManagerCallback>(); + static final String WALLPAPER_INFO = "wallpaper_info.xml"; /** * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks @@ -101,97 +100,135 @@ class WallpaperManagerService extends IWallpaperManager.Stub { * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered * everytime the wallpaper is changed. */ - private final FileObserver mWallpaperObserver = new FileObserver( - WALLPAPER_DIR.getAbsolutePath(), CLOSE_WRITE | DELETE | DELETE_SELF) { - @Override - public void onEvent(int event, String path) { - if (path == null) { - return; - } - synchronized (mLock) { - // changing the wallpaper means we'll need to back up the new one - long origId = Binder.clearCallingIdentity(); - BackupManager bm = new BackupManager(mContext); - bm.dataChanged(); - Binder.restoreCallingIdentity(origId); - - File changedFile = new File(WALLPAPER_DIR, path); - if (WALLPAPER_FILE.equals(changedFile)) { - notifyCallbacksLocked(); - if (mWallpaperComponent == null || event != CLOSE_WRITE - || mImageWallpaperPending) { - if (event == CLOSE_WRITE) { - mImageWallpaperPending = false; - } - bindWallpaperComponentLocked(mImageWallpaperComponent, - true, false); - saveSettingsLocked(); - } + private class WallpaperObserver extends FileObserver { + + final WallpaperData mWallpaper; + final File mWallpaperDir; + final File mWallpaperFile; + + public WallpaperObserver(WallpaperData wallpaper) { + super(getWallpaperDir(wallpaper.userId).getAbsolutePath(), + CLOSE_WRITE | DELETE | DELETE_SELF); + mWallpaperDir = getWallpaperDir(wallpaper.userId); + mWallpaper = wallpaper; + mWallpaperFile = new File(mWallpaperDir, WALLPAPER); + } + + @Override + public void onEvent(int event, String path) { + if (path == null) { + return; + } + synchronized (mLock) { + // changing the wallpaper means we'll need to back up the new one + long origId = Binder.clearCallingIdentity(); + BackupManager bm = new BackupManager(mContext); + bm.dataChanged(); + Binder.restoreCallingIdentity(origId); + + File changedFile = new File(mWallpaperDir, path); + if (mWallpaperFile.equals(changedFile)) { + notifyCallbacksLocked(mWallpaper); + if (mWallpaper.wallpaperComponent == null || event != CLOSE_WRITE + || mWallpaper.imageWallpaperPending) { + if (event == CLOSE_WRITE) { + mWallpaper.imageWallpaperPending = false; } + bindWallpaperComponentLocked(mWallpaper.imageWallpaperComponent, true, + false, mWallpaper); + saveSettingsLocked(mWallpaper); } } - }; - + } + } + } + final Context mContext; final IWindowManager mIWindowManager; final MyPackageMonitor mMonitor; + WallpaperData mLastWallpaper; - int mWidth = -1; - int mHeight = -1; + SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>(); - /** - * Client is currently writing a new image wallpaper. - */ - boolean mImageWallpaperPending; + int mCurrentUserId; + + static class WallpaperData { + + int userId; + + File wallpaperFile; + + /** + * Client is currently writing a new image wallpaper. + */ + boolean imageWallpaperPending; + + /** + * Resource name if using a picture from the wallpaper gallery + */ + String name = ""; + + /** + * The component name of the currently set live wallpaper. + */ + ComponentName wallpaperComponent; + + /** + * The component name of the wallpaper that should be set next. + */ + ComponentName nextWallpaperComponent; + + /** + * Name of the component used to display bitmap wallpapers from either the gallery or + * built-in wallpapers. + */ + ComponentName imageWallpaperComponent = new ComponentName("com.android.systemui", + "com.android.systemui.ImageWallpaper"); + + WallpaperConnection connection; + long lastDiedTime; + boolean wallpaperUpdating; + WallpaperObserver wallpaperObserver; + + /** + * List of callbacks registered they should each be notified when the wallpaper is changed. + */ + private RemoteCallbackList<IWallpaperManagerCallback> callbacks + = new RemoteCallbackList<IWallpaperManagerCallback>(); + + int width = -1; + int height = -1; + + WallpaperData(int userId) { + this.userId = userId; + wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER); + } + } - /** - * Resource name if using a picture from the wallpaper gallery - */ - String mName = ""; - - /** - * The component name of the currently set live wallpaper. - */ - ComponentName mWallpaperComponent; - - /** - * The component name of the wallpaper that should be set next. - */ - ComponentName mNextWallpaperComponent; - - /** - * Name of the component used to display bitmap wallpapers from either the gallery or - * built-in wallpapers. - */ - ComponentName mImageWallpaperComponent = new ComponentName("com.android.systemui", - "com.android.systemui.ImageWallpaper"); - - WallpaperConnection mWallpaperConnection; - long mLastDiedTime; - boolean mWallpaperUpdating; - class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection { final WallpaperInfo mInfo; final Binder mToken = new Binder(); IWallpaperService mService; IWallpaperEngine mEngine; + WallpaperData mWallpaper; - public WallpaperConnection(WallpaperInfo info) { + public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) { mInfo = info; + mWallpaper = wallpaper; } public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { - if (mWallpaperConnection == this) { - mLastDiedTime = SystemClock.uptimeMillis(); + if (mWallpaper.connection == this) { + mWallpaper.lastDiedTime = SystemClock.uptimeMillis(); mService = IWallpaperService.Stub.asInterface(service); - attachServiceLocked(this); + attachServiceLocked(this, mWallpaper); // XXX should probably do saveSettingsLocked() later // when we have an engine, but I'm not sure about // locking there and anyway we always need to be able to // recover if there is something wrong. - saveSettingsLocked(); + saveSettingsLocked(mWallpaper); } } } @@ -200,43 +237,50 @@ class WallpaperManagerService extends IWallpaperManager.Stub { synchronized (mLock) { mService = null; mEngine = null; - if (mWallpaperConnection == this) { - Slog.w(TAG, "Wallpaper service gone: " + mWallpaperComponent); - if (!mWallpaperUpdating && (mLastDiedTime+MIN_WALLPAPER_CRASH_TIME) - > SystemClock.uptimeMillis()) { + if (mWallpaper.connection == this) { + Slog.w(TAG, "Wallpaper service gone: " + mWallpaper.wallpaperComponent); + if (!mWallpaper.wallpaperUpdating + && (mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME) + > SystemClock.uptimeMillis() + && mWallpaper.userId == mCurrentUserId) { Slog.w(TAG, "Reverting to built-in wallpaper!"); - clearWallpaperLocked(true); + clearWallpaperLocked(true, mWallpaper.userId); } } } } - + public void attachEngine(IWallpaperEngine engine) { mEngine = engine; } - + public ParcelFileDescriptor setWallpaper(String name) { synchronized (mLock) { - if (mWallpaperConnection == this) { - return updateWallpaperBitmapLocked(name); + if (mWallpaper.connection == this) { + return updateWallpaperBitmapLocked(name, mWallpaper); } return null; } } } - + class MyPackageMonitor extends PackageMonitor { @Override public void onPackageUpdateFinished(String packageName, int uid) { synchronized (mLock) { - if (mWallpaperComponent != null && - mWallpaperComponent.getPackageName().equals(packageName)) { - mWallpaperUpdating = false; - ComponentName comp = mWallpaperComponent; - clearWallpaperComponentLocked(); - if (!bindWallpaperComponentLocked(comp, false, false)) { - Slog.w(TAG, "Wallpaper no longer available; reverting to default"); - clearWallpaperLocked(false); + for (int i = 0; i < mWallpaperMap.size(); i++) { + WallpaperData wallpaper = mWallpaperMap.valueAt(i); + if (wallpaper.wallpaperComponent != null + && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + wallpaper.wallpaperUpdating = false; + ComponentName comp = wallpaper.wallpaperComponent; + clearWallpaperComponentLocked(wallpaper); + // Do this only for the current user's wallpaper + if (wallpaper.userId == mCurrentUserId + && !bindWallpaperComponentLocked(comp, false, false, wallpaper)) { + Slog.w(TAG, "Wallpaper no longer available; reverting to default"); + clearWallpaperLocked(false, wallpaper.userId); + } } } } @@ -245,73 +289,94 @@ class WallpaperManagerService extends IWallpaperManager.Stub { @Override public void onPackageModified(String packageName) { synchronized (mLock) { - if (mWallpaperComponent == null || - !mWallpaperComponent.getPackageName().equals(packageName)) { - return; + for (int i = 0; i < mWallpaperMap.size(); i++) { + WallpaperData wallpaper = mWallpaperMap.valueAt(i); + if (wallpaper.wallpaperComponent == null + || !wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + continue; + } + doPackagesChangedLocked(true, wallpaper); } } - doPackagesChanged(true); } @Override public void onPackageUpdateStarted(String packageName, int uid) { synchronized (mLock) { - if (mWallpaperComponent != null && - mWallpaperComponent.getPackageName().equals(packageName)) { - mWallpaperUpdating = true; + for (int i = 0; i < mWallpaperMap.size(); i++) { + WallpaperData wallpaper = mWallpaperMap.valueAt(i); + if (wallpaper.wallpaperComponent != null + && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + wallpaper.wallpaperUpdating = true; + } } } } @Override public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { - return doPackagesChanged(doit); + synchronized (mLock) { + boolean changed = false; + for (int i = 0; i < mWallpaperMap.size(); i++) { + WallpaperData wallpaper = mWallpaperMap.valueAt(i); + boolean res = doPackagesChangedLocked(doit, wallpaper); + changed |= res; + } + return changed; + } } @Override public void onSomePackagesChanged() { - doPackagesChanged(true); + synchronized (mLock) { + for (int i = 0; i < mWallpaperMap.size(); i++) { + WallpaperData wallpaper = mWallpaperMap.valueAt(i); + doPackagesChangedLocked(true, wallpaper); + } + } } - - boolean doPackagesChanged(boolean doit) { + + boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) { boolean changed = false; - synchronized (mLock) { - if (mWallpaperComponent != null) { - int change = isPackageDisappearing(mWallpaperComponent.getPackageName()); - if (change == PACKAGE_PERMANENT_CHANGE - || change == PACKAGE_TEMPORARY_CHANGE) { - changed = true; - if (doit) { - Slog.w(TAG, "Wallpaper uninstalled, removing: " + mWallpaperComponent); - clearWallpaperLocked(false); - } + if (wallpaper.wallpaperComponent != null) { + int change = isPackageDisappearing(wallpaper.wallpaperComponent + .getPackageName()); + if (change == PACKAGE_PERMANENT_CHANGE + || change == PACKAGE_TEMPORARY_CHANGE) { + changed = true; + if (doit) { + Slog.w(TAG, "Wallpaper uninstalled, removing: " + + wallpaper.wallpaperComponent); + clearWallpaperLocked(false, wallpaper.userId); } } - if (mNextWallpaperComponent != null) { - int change = isPackageDisappearing(mNextWallpaperComponent.getPackageName()); - if (change == PACKAGE_PERMANENT_CHANGE - || change == PACKAGE_TEMPORARY_CHANGE) { - mNextWallpaperComponent = null; - } + } + if (wallpaper.nextWallpaperComponent != null) { + int change = isPackageDisappearing(wallpaper.nextWallpaperComponent + .getPackageName()); + if (change == PACKAGE_PERMANENT_CHANGE + || change == PACKAGE_TEMPORARY_CHANGE) { + wallpaper.nextWallpaperComponent = null; } - if (mWallpaperComponent != null - && isPackageModified(mWallpaperComponent.getPackageName())) { - try { - mContext.getPackageManager().getServiceInfo( - mWallpaperComponent, 0); - } catch (NameNotFoundException e) { - Slog.w(TAG, "Wallpaper component gone, removing: " + mWallpaperComponent); - clearWallpaperLocked(false); - } + } + if (wallpaper.wallpaperComponent != null + && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) { + try { + mContext.getPackageManager().getServiceInfo( + wallpaper.wallpaperComponent, 0); + } catch (NameNotFoundException e) { + Slog.w(TAG, "Wallpaper component gone, removing: " + + wallpaper.wallpaperComponent); + clearWallpaperLocked(false, wallpaper.userId); } - if (mNextWallpaperComponent != null - && isPackageModified(mNextWallpaperComponent.getPackageName())) { - try { - mContext.getPackageManager().getServiceInfo( - mNextWallpaperComponent, 0); - } catch (NameNotFoundException e) { - mNextWallpaperComponent = null; - } + } + if (wallpaper.nextWallpaperComponent != null + && isPackageModified(wallpaper.nextWallpaperComponent.getPackageName())) { + try { + mContext.getPackageManager().getServiceInfo( + wallpaper.nextWallpaperComponent, 0); + } catch (NameNotFoundException e) { + wallpaper.nextWallpaperComponent = null; } } return changed; @@ -324,52 +389,118 @@ class WallpaperManagerService extends IWallpaperManager.Stub { mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); mMonitor = new MyPackageMonitor(); - mMonitor.register(context, true); - WALLPAPER_DIR.mkdirs(); - loadSettingsLocked(); - mWallpaperObserver.startWatching(); + mMonitor.register(context, null, true); + WALLPAPER_BASE_DIR.mkdirs(); + loadSettingsLocked(0); } + private static File getWallpaperDir(int userId) { + return new File(WALLPAPER_BASE_DIR + "/" + userId); + } + @Override protected void finalize() throws Throwable { super.finalize(); - mWallpaperObserver.stopWatching(); + for (int i = 0; i < mWallpaperMap.size(); i++) { + WallpaperData wallpaper = mWallpaperMap.valueAt(i); + wallpaper.wallpaperObserver.stopWatching(); + } } - + public void systemReady() { if (DEBUG) Slog.v(TAG, "systemReady"); + WallpaperData wallpaper = mWallpaperMap.get(0); + switchWallpaper(wallpaper); + 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)); + } + } + }, userFilter); + } + + String getName() { + return mWallpaperMap.get(0).name; + } + + void removeUser(int userId) { + synchronized (mLock) { + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper != null) { + wallpaper.wallpaperObserver.stopWatching(); + mWallpaperMap.remove(userId); + } + File wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER); + wallpaperFile.delete(); + File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO); + wallpaperInfoFile.delete(); + } + } + + void switchUser(int userId) { + synchronized (mLock) { + mCurrentUserId = userId; + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper == null) { + wallpaper = new WallpaperData(userId); + mWallpaperMap.put(userId, wallpaper); + loadSettingsLocked(userId); + wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper); + wallpaper.wallpaperObserver.startWatching(); + } + switchWallpaper(wallpaper); + } + } + + void switchWallpaper(WallpaperData wallpaper) { synchronized (mLock) { RuntimeException e = null; try { - if (bindWallpaperComponentLocked(mNextWallpaperComponent, false, false)) { + ComponentName cname = wallpaper.wallpaperComponent != null ? + wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; + if (bindWallpaperComponentLocked(cname, true, false, wallpaper)) { return; } } catch (RuntimeException e1) { e = e1; } Slog.w(TAG, "Failure starting previous wallpaper", e); - clearWallpaperLocked(false); + clearWallpaperLocked(false, wallpaper.userId); } } - + public void clearWallpaper() { if (DEBUG) Slog.v(TAG, "clearWallpaper"); synchronized (mLock) { - clearWallpaperLocked(false); + clearWallpaperLocked(false, UserId.getCallingUserId()); } } - public void clearWallpaperLocked(boolean defaultFailed) { - File f = WALLPAPER_FILE; + void clearWallpaperLocked(boolean defaultFailed, int userId) { + WallpaperData wallpaper = mWallpaperMap.get(userId); + File f = new File(getWallpaperDir(userId), WALLPAPER); if (f.exists()) { f.delete(); } final long ident = Binder.clearCallingIdentity(); RuntimeException e = null; try { - mImageWallpaperPending = false; + wallpaper.imageWallpaperPending = false; + if (userId != mCurrentUserId) return; if (bindWallpaperComponentLocked(defaultFailed - ? mImageWallpaperComponent : null, true, false)) { + ? wallpaper.imageWallpaperComponent + : null, true, false, wallpaper)) { return; } } catch (IllegalArgumentException e1) { @@ -383,29 +514,35 @@ class WallpaperManagerService extends IWallpaperManager.Stub { // let's not let it crash the system and just live with no // wallpaper. Slog.e(TAG, "Default wallpaper component not found!", e); - clearWallpaperComponentLocked(); + clearWallpaperComponentLocked(wallpaper); } 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"); } synchronized (mLock) { - if (width != mWidth || height != mHeight) { - mWidth = width; - mHeight = height; - saveSettingsLocked(); - if (mWallpaperConnection != null) { - if (mWallpaperConnection.mEngine != null) { + if (width != wallpaper.width || height != wallpaper.height) { + wallpaper.width = width; + wallpaper.height = height; + saveSettingsLocked(wallpaper); + if (mCurrentUserId != userId) return; // Don't change the properties now + if (wallpaper.connection != null) { + if (wallpaper.connection.mEngine != null) { try { - mWallpaperConnection.mEngine.setDesiredSize( + wallpaper.connection.mEngine.setDesiredSize( width, height); } catch (RemoteException e) { } - notifyCallbacksLocked(); + notifyCallbacksLocked(wallpaper); } } } @@ -414,26 +551,38 @@ class WallpaperManagerService extends IWallpaperManager.Stub { public int getWidthHint() throws RemoteException { synchronized (mLock) { - return mWidth; + WallpaperData wallpaper = mWallpaperMap.get(UserId.getCallingUserId()); + return wallpaper.width; } } public int getHeightHint() throws RemoteException { synchronized (mLock) { - return mHeight; + WallpaperData wallpaper = mWallpaperMap.get(UserId.getCallingUserId()); + return wallpaper.height; } } public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb, Bundle outParams) { synchronized (mLock) { + // This returns the current user's wallpaper, if called by a system service. Else it + // returns the wallpaper for the calling user. + int callingUid = Binder.getCallingUid(); + int wallpaperUserId = 0; + if (callingUid == android.os.Process.SYSTEM_UID) { + wallpaperUserId = mCurrentUserId; + } else { + wallpaperUserId = UserId.getUserId(callingUid); + } + WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId); try { if (outParams != null) { - outParams.putInt("width", mWidth); - outParams.putInt("height", mHeight); + outParams.putInt("width", wallpaper.width); + outParams.putInt("height", wallpaper.height); } - mCallbacks.register(cb); - File f = WALLPAPER_FILE; + wallpaper.callbacks.register(cb); + File f = new File(getWallpaperDir(wallpaperUserId), WALLPAPER); if (!f.exists()) { return null; } @@ -447,24 +596,30 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } public WallpaperInfo getWallpaperInfo() { + int userId = UserId.getCallingUserId(); synchronized (mLock) { - if (mWallpaperConnection != null) { - return mWallpaperConnection.mInfo; + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper.connection != null) { + return wallpaper.connection.mInfo; } return null; } } - + 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) { final long ident = Binder.clearCallingIdentity(); try { - ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name); + ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper); if (pfd != null) { - mImageWallpaperPending = true; + wallpaper.imageWallpaperPending = true; } return pfd; } finally { @@ -473,19 +628,20 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } - ParcelFileDescriptor updateWallpaperBitmapLocked(String name) { + ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper) { if (name == null) name = ""; try { - if (!WALLPAPER_DIR.exists()) { - WALLPAPER_DIR.mkdir(); + File dir = getWallpaperDir(wallpaper.userId); + if (!dir.exists()) { + dir.mkdir(); FileUtils.setPermissions( - WALLPAPER_DIR.getPath(), + dir.getPath(), FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, -1, -1); } - ParcelFileDescriptor fd = ParcelFileDescriptor.open(WALLPAPER_FILE, + ParcelFileDescriptor fd = ParcelFileDescriptor.open(new File(dir, WALLPAPER), MODE_CREATE|MODE_READ_WRITE); - mName = name; + wallpaper.name = name; return fd; } catch (FileNotFoundException e) { Slog.w(TAG, "Error setting wallpaper", e); @@ -495,31 +651,36 @@ 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) { final long ident = Binder.clearCallingIdentity(); try { - mImageWallpaperPending = false; - bindWallpaperComponentLocked(name, false, true); + wallpaper.imageWallpaperPending = false; + bindWallpaperComponentLocked(name, false, true, wallpaper); } finally { Binder.restoreCallingIdentity(ident); } } } - boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, boolean fromUser) { + boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, + boolean fromUser, WallpaperData wallpaper) { if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName); - // Has the component changed? if (!force) { - if (mWallpaperConnection != null) { - if (mWallpaperComponent == null) { + if (wallpaper.connection != null) { + if (wallpaper.wallpaperComponent == null) { if (componentName == null) { if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default"); // Still using default wallpaper. return true; } - } else if (mWallpaperComponent.equals(componentName)) { + } else if (wallpaper.wallpaperComponent.equals(componentName)) { // Changing to same wallpaper. if (DEBUG) Slog.v(TAG, "same wallpaper"); return true; @@ -538,7 +699,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } if (componentName == null) { // Fall back to static image wallpaper - componentName = mImageWallpaperComponent; + componentName = wallpaper.imageWallpaperComponent; //clearWallpaperComponentLocked(); //return; if (DEBUG) Slog.v(TAG, "Using image wallpaper"); @@ -560,7 +721,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { WallpaperInfo wi = null; Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); - if (componentName != null && !componentName.equals(mImageWallpaperComponent)) { + 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); @@ -599,8 +760,13 @@ class WallpaperManagerService extends IWallpaperManager.Stub { // Bind the service! if (DEBUG) Slog.v(TAG, "Binding to:" + componentName); - WallpaperConnection newConn = new WallpaperConnection(wi); + 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( @@ -608,8 +774,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER), mContext.getText(com.android.internal.R.string.chooser_wallpaper)), 0)); - if (!mContext.bindService(intent, newConn, - Context.BIND_AUTO_CREATE)) { + if (!mContext.bindService(intent, newConn, Context.BIND_AUTO_CREATE, serviceUserId)) { String msg = "Unable to bind service: " + componentName; if (fromUser) { @@ -618,18 +783,22 @@ class WallpaperManagerService extends IWallpaperManager.Stub { Slog.w(TAG, msg); return false; } - - clearWallpaperComponentLocked(); - mWallpaperComponent = componentName; - mWallpaperConnection = newConn; - mLastDiedTime = SystemClock.uptimeMillis(); + if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) { + detachWallpaperLocked(mLastWallpaper); + } + wallpaper.wallpaperComponent = componentName; + wallpaper.connection = newConn; + wallpaper.lastDiedTime = SystemClock.uptimeMillis(); try { - if (DEBUG) Slog.v(TAG, "Adding window token: " + newConn.mToken); - mIWindowManager.addWindowToken(newConn.mToken, - WindowManager.LayoutParams.TYPE_WALLPAPER); + if (wallpaper.userId == mCurrentUserId) { + if (DEBUG) + Slog.v(TAG, "Adding window token: " + newConn.mToken); + mIWindowManager.addWindowToken(newConn.mToken, + WindowManager.LayoutParams.TYPE_WALLPAPER); + mLastWallpaper = wallpaper; + } } catch (RemoteException e) { } - } catch (PackageManager.NameNotFoundException e) { String msg = "Unknown component " + componentName; if (fromUser) { @@ -640,54 +809,58 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } return true; } - - void clearWallpaperComponentLocked() { - mWallpaperComponent = null; - if (mWallpaperConnection != null) { - if (mWallpaperConnection.mEngine != null) { + + void detachWallpaperLocked(WallpaperData wallpaper) { + if (wallpaper.connection != null) { + if (wallpaper.connection.mEngine != null) { try { - mWallpaperConnection.mEngine.destroy(); + wallpaper.connection.mEngine.destroy(); } catch (RemoteException e) { } } - mContext.unbindService(mWallpaperConnection); + mContext.unbindService(wallpaper.connection); try { - if (DEBUG) Slog.v(TAG, "Removing window token: " - + mWallpaperConnection.mToken); - mIWindowManager.removeWindowToken(mWallpaperConnection.mToken); + if (DEBUG) + Slog.v(TAG, "Removing window token: " + wallpaper.connection.mToken); + mIWindowManager.removeWindowToken(wallpaper.connection.mToken); } catch (RemoteException e) { } - mWallpaperConnection.mService = null; - mWallpaperConnection.mEngine = null; - mWallpaperConnection = null; + wallpaper.connection.mService = null; + wallpaper.connection.mEngine = null; + wallpaper.connection = null; } } - - void attachServiceLocked(WallpaperConnection conn) { + + void clearWallpaperComponentLocked(WallpaperData wallpaper) { + wallpaper.wallpaperComponent = null; + detachWallpaperLocked(wallpaper); + } + + void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) { try { conn.mService.attach(conn, conn.mToken, WindowManager.LayoutParams.TYPE_WALLPAPER, false, - mWidth, mHeight); + wallpaper.width, wallpaper.height); } catch (RemoteException e) { Slog.w(TAG, "Failed attaching wallpaper; clearing", e); - if (!mWallpaperUpdating) { - bindWallpaperComponentLocked(null, false, false); + if (!wallpaper.wallpaperUpdating) { + bindWallpaperComponentLocked(null, false, false, wallpaper); } } } - - private void notifyCallbacksLocked() { - final int n = mCallbacks.beginBroadcast(); + + private void notifyCallbacksLocked(WallpaperData wallpaper) { + final int n = wallpaper.callbacks.beginBroadcast(); for (int i = 0; i < n; i++) { try { - mCallbacks.getBroadcastItem(i).onWallpaperChanged(); + wallpaper.callbacks.getBroadcastItem(i).onWallpaperChanged(); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing // the dead object for us. } } - mCallbacks.finishBroadcast(); + wallpaper.callbacks.finishBroadcast(); final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED); mContext.sendBroadcast(intent); } @@ -699,13 +872,13 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } - private static JournaledFile makeJournaledFile() { - final String base = "/data/system/wallpaper_info.xml"; + private static JournaledFile makeJournaledFile(int userId) { + final String base = getWallpaperDir(userId) + "/" + WALLPAPER_INFO; return new JournaledFile(new File(base), new File(base + ".tmp")); } - private void saveSettingsLocked() { - JournaledFile journal = makeJournaledFile(); + private void saveSettingsLocked(WallpaperData wallpaper) { + JournaledFile journal = makeJournaledFile(wallpaper.userId); FileOutputStream stream = null; try { stream = new FileOutputStream(journal.chooseForWrite(), false); @@ -714,13 +887,13 @@ class WallpaperManagerService extends IWallpaperManager.Stub { out.startDocument(null, true); out.startTag(null, "wp"); - out.attribute(null, "width", Integer.toString(mWidth)); - out.attribute(null, "height", Integer.toString(mHeight)); - out.attribute(null, "name", mName); - if (mWallpaperComponent != null && - !mWallpaperComponent.equals(mImageWallpaperComponent)) { + out.attribute(null, "width", Integer.toString(wallpaper.width)); + out.attribute(null, "height", Integer.toString(wallpaper.height)); + out.attribute(null, "name", wallpaper.name); + if (wallpaper.wallpaperComponent != null + && !wallpaper.wallpaperComponent.equals(wallpaper.imageWallpaperComponent)) { out.attribute(null, "component", - mWallpaperComponent.flattenToShortString()); + wallpaper.wallpaperComponent.flattenToShortString()); } out.endTag(null, "wp"); @@ -739,12 +912,34 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } - private void loadSettingsLocked() { + private void migrateFromOld() { + File oldWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY); + File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY); + if (oldWallpaper.exists()) { + File newWallpaper = new File(getWallpaperDir(0), WALLPAPER); + oldWallpaper.renameTo(newWallpaper); + } + if (oldInfo.exists()) { + File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO); + oldInfo.renameTo(newInfo); + } + } + + private void loadSettingsLocked(int userId) { if (DEBUG) Slog.v(TAG, "loadSettingsLocked"); - JournaledFile journal = makeJournaledFile(); + JournaledFile journal = makeJournaledFile(userId); FileInputStream stream = null; File file = journal.chooseForRead(); + if (!file.exists()) { + // This should only happen one time, when upgrading from a legacy system + migrateFromOld(); + } + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper == null) { + wallpaper = new WallpaperData(userId); + mWallpaperMap.put(userId, wallpaper); + } boolean success = false; try { stream = new FileInputStream(file); @@ -757,23 +952,26 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (type == XmlPullParser.START_TAG) { String tag = parser.getName(); if ("wp".equals(tag)) { - mWidth = Integer.parseInt(parser.getAttributeValue(null, "width")); - mHeight = Integer.parseInt(parser.getAttributeValue(null, "height")); - mName = parser.getAttributeValue(null, "name"); + wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width")); + wallpaper.height = Integer.parseInt(parser + .getAttributeValue(null, "height")); + wallpaper.name = parser.getAttributeValue(null, "name"); String comp = parser.getAttributeValue(null, "component"); - mNextWallpaperComponent = comp != null + wallpaper.nextWallpaperComponent = comp != null ? ComponentName.unflattenFromString(comp) : null; - if (mNextWallpaperComponent == null || - "android".equals(mNextWallpaperComponent.getPackageName())) { - mNextWallpaperComponent = mImageWallpaperComponent; + if (wallpaper.nextWallpaperComponent == null + || "android".equals(wallpaper.nextWallpaperComponent + .getPackageName())) { + wallpaper.nextWallpaperComponent = wallpaper.imageWallpaperComponent; } if (DEBUG) { - Slog.v(TAG, "mWidth:" + mWidth); - Slog.v(TAG, "mHeight:" + mHeight); - Slog.v(TAG, "mName:" + mName); - Slog.v(TAG, "mNextWallpaperComponent:" + mNextWallpaperComponent); + Slog.v(TAG, "mWidth:" + wallpaper.width); + Slog.v(TAG, "mHeight:" + wallpaper.height); + Slog.v(TAG, "mName:" + wallpaper.name); + Slog.v(TAG, "mNextWallpaperComponent:" + + wallpaper.nextWallpaperComponent); } } } @@ -799,70 +997,75 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } if (!success) { - mWidth = -1; - mHeight = -1; - mName = ""; + wallpaper.width = -1; + wallpaper.height = -1; + wallpaper.name = ""; } // We always want to have some reasonable width hint. WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); Display d = wm.getDefaultDisplay(); int baseSize = d.getMaximumSizeDimension(); - if (mWidth < baseSize) { - mWidth = baseSize; + if (wallpaper.width < baseSize) { + wallpaper.width = baseSize; } - if (mHeight < baseSize) { - mHeight = baseSize; + if (wallpaper.height < baseSize) { + wallpaper.height = baseSize; } } // Called by SystemBackupAgent after files are restored to disk. void settingsRestored() { + // TODO: If necessary, make it work for secondary users as well. This currently assumes + // restores only to the primary user if (DEBUG) Slog.v(TAG, "settingsRestored"); - + WallpaperData wallpaper = null; boolean success = false; synchronized (mLock) { - loadSettingsLocked(); - if (mNextWallpaperComponent != null && - !mNextWallpaperComponent.equals(mImageWallpaperComponent)) { - if (!bindWallpaperComponentLocked(mNextWallpaperComponent, false, false)) { + loadSettingsLocked(0); + wallpaper = mWallpaperMap.get(0); + if (wallpaper.nextWallpaperComponent != null + && !wallpaper.nextWallpaperComponent.equals(wallpaper.imageWallpaperComponent)) { + if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false, + wallpaper)) { // 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); + bindWallpaperComponentLocked(null, false, false, wallpaper); } success = true; } else { // If there's a wallpaper name, we use that. If that can't be loaded, then we // use the default. - if ("".equals(mName)) { + if ("".equals(wallpaper.name)) { if (DEBUG) Slog.v(TAG, "settingsRestored: name is empty"); success = true; } else { if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource"); - success = restoreNamedResourceLocked(); + success = restoreNamedResourceLocked(wallpaper); } if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success); if (success) { - bindWallpaperComponentLocked(mNextWallpaperComponent, false, false); + bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false, + wallpaper); } } } if (!success) { - Slog.e(TAG, "Failed to restore wallpaper: '" + mName + "'"); - mName = ""; - WALLPAPER_FILE.delete(); + Slog.e(TAG, "Failed to restore wallpaper: '" + wallpaper.name + "'"); + wallpaper.name = ""; + getWallpaperDir(0).delete(); } synchronized (mLock) { - saveSettingsLocked(); + saveSettingsLocked(wallpaper); } } - boolean restoreNamedResourceLocked() { - if (mName.length() > 4 && "res:".equals(mName.substring(0, 4))) { - String resName = mName.substring(4); + boolean restoreNamedResourceLocked(WallpaperData wallpaper) { + if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) { + String resName = wallpaper.name.substring(4); String pkg = null; int colon = resName.indexOf(':'); @@ -896,10 +1099,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } res = r.openRawResource(resId); - if (WALLPAPER_FILE.exists()) { - WALLPAPER_FILE.delete(); + if (wallpaper.wallpaperFile.exists()) { + wallpaper.wallpaperFile.delete(); } - fos = new FileOutputStream(WALLPAPER_FILE); + fos = new FileOutputStream(wallpaper.wallpaperFile); byte[] buffer = new byte[32768]; int amt; @@ -933,7 +1136,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } return false; } - + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -947,20 +1150,35 @@ class WallpaperManagerService extends IWallpaperManager.Stub { synchronized (mLock) { pw.println("Current Wallpaper Service state:"); - pw.print(" mWidth="); pw.print(mWidth); - pw.print(" mHeight="); pw.println(mHeight); - pw.print(" mName="); pw.println(mName); - pw.print(" mWallpaperComponent="); pw.println(mWallpaperComponent); - if (mWallpaperConnection != null) { - WallpaperConnection conn = mWallpaperConnection; - pw.print(" Wallpaper connection "); - pw.print(conn); pw.println(":"); - pw.print(" mInfo.component="); pw.println(conn.mInfo.getComponent()); - pw.print(" mToken="); pw.println(conn.mToken); - pw.print(" mService="); pw.println(conn.mService); - pw.print(" mEngine="); pw.println(conn.mEngine); - pw.print(" mLastDiedTime="); - pw.println(mLastDiedTime - SystemClock.uptimeMillis()); + for (int i = 0; i < mWallpaperMap.size(); i++) { + WallpaperData wallpaper = mWallpaperMap.valueAt(i); + pw.println(" User " + wallpaper.userId + ":"); + pw.print(" mWidth="); + pw.print(wallpaper.width); + pw.print(" mHeight="); + pw.println(wallpaper.height); + pw.print(" mName="); + pw.println(wallpaper.name); + pw.print(" mWallpaperComponent="); + pw.println(wallpaper.wallpaperComponent); + if (wallpaper.connection != null) { + WallpaperConnection conn = wallpaper.connection; + pw.print(" Wallpaper connection "); + pw.print(conn); + pw.println(":"); + if (conn.mInfo != null) { + pw.print(" mInfo.component="); + pw.println(conn.mInfo.getComponent()); + } + pw.print(" mToken="); + pw.println(conn.mToken); + pw.print(" mService="); + pw.println(conn.mService); + pw.print(" mEngine="); + pw.println(conn.mEngine); + pw.print(" mLastDiedTime="); + pw.println(wallpaper.lastDiedTime - SystemClock.uptimeMillis()); + } } } } diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index aef3426..bb38cd9 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -61,6 +61,7 @@ import android.text.TextUtils; import android.util.Slog; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -100,9 +101,6 @@ public class WifiService extends IWifiManager.Stub { private boolean mEmergencyCallbackMode = false; private int mPluggedType; - /* Chipset supports background scan */ - private final boolean mBackgroundScanSupported; - private final LockList mLocks = new LockList(); // some wifi lock statistics private int mFullHighPerfLocksAcquired; @@ -112,6 +110,10 @@ 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; @@ -258,47 +260,46 @@ public class WifiService extends IWifiManager.Stub { ac.connect(mContext, this, msg.replyTo); break; } - case WifiManager.CMD_ENABLE_TRAFFIC_STATS_POLL: { + case WifiManager.ENABLE_TRAFFIC_STATS_POLL: { mEnableTrafficStatsPoll = (msg.arg1 == 1); mTrafficStatsPollToken++; if (mEnableTrafficStatsPoll) { notifyOnDataActivity(); - sendMessageDelayed(Message.obtain(this, WifiManager.CMD_TRAFFIC_STATS_POLL, + sendMessageDelayed(Message.obtain(this, WifiManager.TRAFFIC_STATS_POLL, mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS); } break; } - case WifiManager.CMD_TRAFFIC_STATS_POLL: { + case WifiManager.TRAFFIC_STATS_POLL: { if (msg.arg1 == mTrafficStatsPollToken) { notifyOnDataActivity(); - sendMessageDelayed(Message.obtain(this, WifiManager.CMD_TRAFFIC_STATS_POLL, + sendMessageDelayed(Message.obtain(this, WifiManager.TRAFFIC_STATS_POLL, mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS); } break; } - case WifiManager.CMD_CONNECT_NETWORK: { - if (msg.obj != null) { - mWifiStateMachine.connectNetwork((WifiConfiguration)msg.obj); - } else { - mWifiStateMachine.connectNetwork(msg.arg1); - } + case WifiManager.CONNECT_NETWORK: { + mWifiStateMachine.sendMessage(Message.obtain(msg)); break; } - case WifiManager.CMD_SAVE_NETWORK: { - mWifiStateMachine.saveNetwork((WifiConfiguration)msg.obj); + case WifiManager.SAVE_NETWORK: { + mWifiStateMachine.sendMessage(Message.obtain(msg)); break; } - case WifiManager.CMD_FORGET_NETWORK: { - mWifiStateMachine.forgetNetwork(msg.arg1); + case WifiManager.FORGET_NETWORK: { + mWifiStateMachine.sendMessage(Message.obtain(msg)); break; } - case WifiManager.CMD_START_WPS: { - //replyTo has the original source - mWifiStateMachine.startWps(msg.replyTo, (WpsInfo)msg.obj); + case WifiManager.START_WPS: { + mWifiStateMachine.sendMessage(Message.obtain(msg)); break; } - case WifiManager.CMD_DISABLE_NETWORK: { - mWifiStateMachine.disableNetwork(msg.replyTo, msg.arg1, msg.arg2); + case WifiManager.CANCEL_WPS: { + mWifiStateMachine.sendMessage(Message.obtain(msg)); + break; + } + case WifiManager.DISABLE_NETWORK: { + mWifiStateMachine.sendMessage(Message.obtain(msg)); break; } default: { @@ -431,9 +432,6 @@ public class WifiService extends IWifiManager.Stub { Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l; mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler()); mNotificationEnabledSettingObserver.register(); - - mBackgroundScanSupported = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_wifi_background_scan_support); } /** @@ -527,6 +525,15 @@ 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); } @@ -676,7 +683,12 @@ public class WifiService extends IWifiManager.Stub { */ public List<WifiConfiguration> getConfiguredNetworks() { enforceAccessPermission(); - return mWifiStateMachine.syncGetConfiguredNetworks(); + if (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncGetConfiguredNetworks(mWifiStateMachineChannel); + } else { + Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + return null; + } } /** @@ -895,7 +907,7 @@ public class WifiService extends IWifiManager.Stub { * Get a reference to handler. This is used by a client to establish * an AsyncChannel communication with WifiService */ - public Messenger getMessenger() { + 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 @@ -905,6 +917,13 @@ public class WifiService extends IWifiManager.Stub { return new Messenger(mAsyncServiceHandler); } + /** Get a reference to WifiStateMachine handler for AsyncChannel communication */ + public Messenger getWifiStateMachineMessenger() { + enforceAccessPermission(); + enforceChangePermission(); + return mWifiStateMachine.getMessenger(); + } + /** * Get the IP and proxy configuration file */ @@ -931,11 +950,6 @@ public class WifiService extends IWifiManager.Stub { mAlarmManager.cancel(mIdleIntent); mScreenOff = false; evaluateTrafficStatsPolling(); - mWifiStateMachine.enableRssiPolling(true); - if (mBackgroundScanSupported) { - mWifiStateMachine.enableBackgroundScanCommand(false); - } - mWifiStateMachine.enableAllNetworks(); setDeviceIdleAndUpdateWifi(false); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { if (DBG) { @@ -943,10 +957,6 @@ public class WifiService extends IWifiManager.Stub { } mScreenOff = true; evaluateTrafficStatsPolling(); - mWifiStateMachine.enableRssiPolling(false); - if (mBackgroundScanSupported) { - mWifiStateMachine.enableBackgroundScanCommand(true); - } /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off * AND the "stay on while plugged in" setting doesn't match the @@ -985,6 +995,13 @@ 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, @@ -1175,9 +1192,18 @@ 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); + pw.println("WifiStateMachine dump"); + mWifiStateMachine.dump(fd, pw, args); } private class WifiLock extends DeathRecipient { @@ -1554,10 +1580,10 @@ public class WifiService extends IWifiManager.Stub { Message msg; if (mNetworkInfo.getDetailedState() == DetailedState.CONNECTED && !mScreenOff) { msg = Message.obtain(mAsyncServiceHandler, - WifiManager.CMD_ENABLE_TRAFFIC_STATS_POLL, 1, 0); + WifiManager.ENABLE_TRAFFIC_STATS_POLL, 1, 0); } else { msg = Message.obtain(mAsyncServiceHandler, - WifiManager.CMD_ENABLE_TRAFFIC_STATS_POLL, 0, 0); + WifiManager.ENABLE_TRAFFIC_STATS_POLL, 0, 0); } msg.sendToTarget(); } diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java index 6a63eac..53d1f0e 100644 --- a/services/java/com/android/server/WiredAccessoryObserver.java +++ b/services/java/com/android/server/WiredAccessoryObserver.java @@ -30,8 +30,11 @@ import android.util.Slog; import android.media.AudioManager; import android.util.Log; +import java.io.File; import java.io.FileReader; import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; /** * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock. @@ -39,17 +42,6 @@ import java.io.FileNotFoundException; class WiredAccessoryObserver extends UEventObserver { private static final String TAG = WiredAccessoryObserver.class.getSimpleName(); private static final boolean LOG = true; - private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */ - private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w", - "/sys/class/switch/h2w/state", - "/sys/class/switch/h2w/name"}, - {"DEVPATH=/devices/virtual/switch/usb_audio", - "/sys/class/switch/usb_audio/state", - "/sys/class/switch/usb_audio/name"}, - {"DEVPATH=/devices/virtual/switch/hdmi", - "/sys/class/switch/hdmi/state", - "/sys/class/switch/hdmi/name"} }; - private static final int BIT_HEADSET = (1 << 0); private static final int BIT_HEADSET_NO_MIC = (1 << 1); private static final int BIT_USB_HEADSET_ANLG = (1 << 2); @@ -60,10 +52,89 @@ class WiredAccessoryObserver extends UEventObserver { 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"); + } + + // 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 int mHeadsetState; private int mPrevHeadsetState; private String mHeadsetName; - private int switchState; private final Context mContext; private final WakeLock mWakeLock; // held while there is a pending route change @@ -85,71 +156,60 @@ class WiredAccessoryObserver extends UEventObserver { // one on the board, one on the dock and one on HDMI: // observe three UEVENTs init(); // set initial status - for (int i = 0; i < MAX_AUDIO_PORTS; i++) { - startObserving(uEventInfo[i][0]); + 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()); try { + String devPath = event.get("DEVPATH"); String name = event.get("SWITCH_NAME"); int state = Integer.parseInt(event.get("SWITCH_STATE")); - updateState(name, state); + updateState(devPath, name, state); } catch (NumberFormatException e) { Slog.e(TAG, "Could not parse switch state from event " + event); } } - private synchronized final void updateState(String name, int state) + private synchronized final void updateState(String devPath, String name, int state) { - if (name.equals("usb_audio")) { - switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)) | - ((state == 1) ? BIT_USB_HEADSET_ANLG : - ((state == 2) ? BIT_USB_HEADSET_DGTL : 0))); - } else if (name.equals("hdmi")) { - switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC| - BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) | - ((state == 1) ? BIT_HDMI_AUDIO : 0)); - } else { - switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG| - BIT_USB_HEADSET_DGTL)) | - ((state == 1) ? BIT_HEADSET : - ((state == 2) ? BIT_HEADSET_NO_MIC : 0))); + 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; + } } - update(name, switchState); } private synchronized final void init() { char[] buffer = new char[1024]; - - String newName = mHeadsetName; - int newState = mHeadsetState; mPrevHeadsetState = mHeadsetState; if (LOG) Slog.v(TAG, "init()"); - for (int i = 0; i < MAX_AUDIO_PORTS; i++) { + for (int i = 0; i < uEventInfo.size(); ++i) { + UEventInfo uei = uEventInfo.get(i); try { - FileReader file = new FileReader(uEventInfo[i][1]); + int curState; + FileReader file = new FileReader(uei.getSwitchStatePath()); int len = file.read(buffer, 0, 1024); file.close(); - newState = Integer.valueOf((new String(buffer, 0, len)).trim()); + curState = Integer.valueOf((new String(buffer, 0, len)).trim()); - file = new FileReader(uEventInfo[i][2]); - len = file.read(buffer, 0, 1024); - file.close(); - newName = new String(buffer, 0, len).trim(); - - if (newState > 0) { - updateState(newName, newState); + if (curState > 0) { + updateState(uei.getDevPath(), uei.getDevName(), curState); } } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have wired headset support"); + Slog.w(TAG, uei.getSwitchStatePath() + + " not found while attempting to determine initial switch state"); } catch (Exception e) { Slog.e(TAG, "" , e); } @@ -191,8 +251,12 @@ class WiredAccessoryObserver extends UEventObserver { mHeadsetState = headsetState; if (headsetState == 0) { - Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - mContext.sendBroadcast(intent); + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_sendAudioBecomingNoisy)) { + Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + mContext.sendBroadcast(intent); + } + // It can take hundreds of ms flush the audio pipeline after // apps pause audio playback, but audio route changes are // immediate, so delay the route change by 1000ms. @@ -237,13 +301,13 @@ class WiredAccessoryObserver extends UEventObserver { // Pack up the values and broadcast them to everyone if (headset == BIT_USB_HEADSET_ANLG) { - intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG); + intent = new Intent(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); intent.putExtra("state", state); intent.putExtra("name", headsetName); ActivityManagerNative.broadcastStickyIntent(intent, null); } else if (headset == BIT_USB_HEADSET_DGTL) { - intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG); + intent = new Intent(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); intent.putExtra("state", state); intent.putExtra("name", headsetName); diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java index 769cb6a..889fbe4 100644 --- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -16,7 +16,8 @@ package com.android.server.accessibility; -import com.android.server.wm.InputFilter; +import com.android.server.accessibility.TouchExplorer.GestureListener; +import com.android.server.input.InputFilter; import android.content.Context; import android.util.Slog; @@ -36,6 +37,8 @@ public class AccessibilityInputFilter extends InputFilter { private final Context mContext; + private final GestureListener mGestureListener; + /** * This is an interface for explorers that take a {@link MotionEvent} * stream and perform touch exploration of the screen content. @@ -64,11 +67,13 @@ public class AccessibilityInputFilter extends InputFilter { } private TouchExplorer mTouchExplorer; + private int mTouchscreenSourceDeviceId; - public AccessibilityInputFilter(Context context) { + public AccessibilityInputFilter(Context context, GestureListener gestureListener) { super(context.getMainLooper()); mContext = context; + mGestureListener = gestureListener; } @Override @@ -76,7 +81,7 @@ public class AccessibilityInputFilter extends InputFilter { if (DEBUG) { Slog.d(TAG, "Accessibility input filter installed."); } - mTouchExplorer = new TouchExplorer(this, mContext); + mTouchExplorer = new TouchExplorer(this, mContext, mGestureListener); super.onInstalled(); } diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index b70ed96..885389f 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -16,11 +16,15 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT; +import static android.accessibilityservice.AccessibilityServiceInfo.INCLUDE_NOT_IMPORTANT_VIEWS; + import android.Manifest; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.IAccessibilityServiceClient; +import android.accessibilityservice.IAccessibilityServiceClientCallback; import android.accessibilityservice.IAccessibilityServiceConnection; -import android.accessibilityservice.IEventListener; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -33,21 +37,30 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.graphics.Rect; +import android.hardware.input.InputManager; import android.net.Uri; import android.os.Binder; +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; +import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; import android.util.Slog; import android.util.SparseArray; import android.view.IWindow; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityInteractionClient; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityInteractionConnection; @@ -57,7 +70,8 @@ import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.content.PackageMonitor; import com.android.internal.os.HandlerCaller; -import com.android.internal.os.HandlerCaller.SomeArgs; +import com.android.internal.os.HandlerCaller.Callback; +import com.android.server.accessibility.TouchExplorer.GestureListener; import com.android.server.wm.WindowManagerService; import org.xmlpull.v1.XmlPullParserException; @@ -71,6 +85,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * This class is instantiated by the system as a system level service and can be @@ -81,24 +96,22 @@ import java.util.Set; * @hide */ public class AccessibilityManagerService extends IAccessibilityManager.Stub - implements HandlerCaller.Callback { + implements GestureListener { private static final boolean DEBUG = false; private static final String LOG_TAG = "AccessibilityManagerService"; - private static final String FUNCTION_REGISTER_EVENT_LISTENER = - "registerEventListener"; - - private static int sIdCounter = 0; + private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE = + "registerUiTestAutomationService"; private static final int OWN_PROCESS_ID = android.os.Process.myPid(); - private static final int DO_SET_SERVICE_INFO = 10; + private static final int UNDEFINED = -1; - private static int sNextWindowId; + private static int sIdCounter = 0; - final HandlerCaller mCaller; + private static int sNextWindowId; final Context mContext; @@ -140,6 +153,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final SecurityPolicy mSecurityPolicy; + private Service mUiAutomationService; + + private GestureHandler mGestureHandler; + + private int mDefaultGestureHandlingHelperServiceId = UNDEFINED; + /** * Handler for delayed event dispatch. */ @@ -151,7 +170,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub int eventType = message.arg1; synchronized (mLock) { - notifyEventListenerLocked(service, eventType); + notifyAccessibilityEventLocked(service, eventType); } } }; @@ -164,7 +183,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public AccessibilityManagerService(Context context) { mContext = context; mPackageManager = mContext.getPackageManager(); - mCaller = new HandlerCaller(context, this); mWindowManagerService = (WindowManagerService) ServiceManager.getService( Context.WINDOW_SERVICE); mSecurityPolicy = new SecurityPolicy(); @@ -236,19 +254,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) { synchronized (mLock) { populateAccessibilityServiceListLocked(); - // get accessibility enabled setting on boot - mIsAccessibilityEnabled = Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; - - manageServicesLocked(); - - // get touch exploration enabled setting on boot - mIsTouchExplorationEnabled = Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1; + handleAccessibilityEnabledSettingChangedLocked(); + handleTouchExplorationEnabledSettingChangedLocked(); updateInputFilterLocked(); - sendStateToClientsLocked(); } @@ -275,11 +283,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }; // package changes - monitor.register(context, true); + monitor.register(context, null, true); // boot completed IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); - mContext.registerReceiver(monitor, bootFiler); + mContext.registerReceiver(monitor, bootFiler, null, monitor.getRegisteredHandler()); } /** @@ -296,9 +304,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onChange(boolean selfChange) { super.onChange(selfChange); - synchronized (mLock) { handleAccessibilityEnabledSettingChangedLocked(); + updateInputFilterLocked(); + sendStateToClientsLocked(); } } }); @@ -310,11 +319,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onChange(boolean selfChange) { super.onChange(selfChange); - synchronized (mLock) { - mIsTouchExplorationEnabled = Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1; + handleTouchExplorationEnabledSettingChangedLocked(); updateInputFilterLocked(); sendStateToClientsLocked(); } @@ -328,7 +334,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onChange(boolean selfChange) { super.onChange(selfChange); - synchronized (mLock) { manageServicesLocked(); } @@ -407,32 +412,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public void executeMessage(Message message) { - switch (message.what) { - case DO_SET_SERVICE_INFO: { - SomeArgs arguments = ((SomeArgs) message.obj); - - AccessibilityServiceInfo info = (AccessibilityServiceInfo) arguments.arg1; - Service service = (Service) arguments.arg2; - - synchronized (mLock) { - // If the XML manifest had data to configure the service its info - // should be already set. In such a case update only the dynamically - // configurable properties. - AccessibilityServiceInfo oldInfo = service.mAccessibilityServiceInfo; - if (oldInfo != null) { - oldInfo.updateDynamicallyConfigurableProperties(info); - service.setDynamicallyConfigurableProperties(oldInfo); - } else { - service.setDynamicallyConfigurableProperties(info); - } - } - } return; - default: - Slog.w(LOG_TAG, "Unknown message type: " + message.what); - } - } - public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) throws RemoteException { synchronized (mLock) { @@ -467,9 +446,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public void registerEventListener(IEventListener listener) { + public void registerUiTestAutomationService(IAccessibilityServiceClient serviceClient, + AccessibilityServiceInfo accessibilityServiceInfo) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, - FUNCTION_REGISTER_EVENT_LISTENER); + FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE); ComponentName componentName = new ComponentName("foo.bar", "AutomationAccessibilityService"); synchronized (mLock) { @@ -490,11 +470,78 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } // Hook the automation service up. - AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo(); - accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; - accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; - Service service = new Service(componentName, accessibilityServiceInfo, true); - service.onServiceConnected(componentName, listener.asBinder()); + mUiAutomationService = new Service(componentName, accessibilityServiceInfo, true); + mUiAutomationService.onServiceConnected(componentName, serviceClient.asBinder()); + } + + public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) { + synchronized (mLock) { + // Automation service is not bound, so pretend it died to perform clean up. + if (mUiAutomationService != null + && mUiAutomationService.mServiceInterface == serviceClient) { + mUiAutomationService.binderDied(); + } + } + } + + @Override + public boolean onGesture(int gestureId) { + // Lazily instantiate the gesture handler. + if (mGestureHandler == null) { + mGestureHandler = new GestureHandler(); + } + synchronized (mLock) { + boolean handled = notifyGestureLocked(gestureId, false); + if (!handled) { + handled = notifyGestureLocked(gestureId, true); + } + if (!handled) { + mGestureHandler.scheduleHandleGestureDefault(gestureId); + } + return handled; + } + } + + private Service getDefaultGestureHandlingHelperService() { + // Since querying of screen content is done through the + // AccessibilityInteractionClient which talks to an + // IAccessibilityServiceConnection implementation we create a proxy + // Service when necessary to enable interaction with the remote + // view tree. Note that this service is just a stateless proxy + // that does not get any events or interrupts. + if (mDefaultGestureHandlingHelperServiceId == UNDEFINED) { + ComponentName name = new ComponentName("android", + "DefaultGestureHandlingHelperService"); + AccessibilityServiceInfo info = new AccessibilityServiceInfo(); + Service service = new Service(name, info, true); + mDefaultGestureHandlingHelperServiceId = service.mId; + AccessibilityInteractionClient.getInstance().addConnection( + mDefaultGestureHandlingHelperServiceId, service); + return service; + } else { + return (Service) AccessibilityInteractionClient.getInstance() + .getConnection(mDefaultGestureHandlingHelperServiceId); + } + } + + 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 + // in our list since we write the last enabled as the + // last record in the enabled services setting. Ideally, + // the user should make the call which service handles + // gestures. However, only one service should handle + // gestrues to avoid user frustration when different + // bahiour is observed from different combinations of + // enabled accessibility services. + for (int i = mServices.size() - 1; i >= 0; i--) { + Service service = mServices.get(i); + if (service.mCanHandleGestures && service.mIsDefault == isDefault) { + mGestureHandler.scheduleHandleGesture(gestureId, service.mServiceInterface); + return true; + } + } + return false; } /** @@ -593,13 +640,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** - * Notifies a service for a scheduled event given the event type. + * Notifies an accessibility service client for a scheduled event given the event type. * - * @param service The service. + * @param service The service client. * @param eventType The type of the event to dispatch. */ - private void notifyEventListenerLocked(Service service, int eventType) { - IEventListener listener = service.mServiceInterface; + private void notifyAccessibilityEventLocked(Service service, int eventType) { + IAccessibilityServiceClient listener = service.mServiceInterface; // If the service died/was disabled while the message for dispatching // the accessibility event was propagating the listener may be null. @@ -704,6 +751,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } + if (!event.isImportantForAccessibility() + && !service.mIncludeNotImportantViews) { + return false; + } + int eventType = event.getEventType(); if ((service.mEventTypes & eventType) != eventType) { return false; @@ -727,7 +779,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * Manages services by starting enabled ones and stopping disabled ones. */ private void manageServicesLocked() { - unbindAutomationService(); + // While the UI automation service is running it takes over. + if (mUiAutomationService != null) { + return; + } populateEnabledServicesLocked(mEnabledServices); final int enabledInstalledServicesCount = updateServicesStateLocked(mInstalledServices, mEnabledServices); @@ -755,21 +810,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** - * Unbinds the automation service if such is running. - */ - private void unbindAutomationService() { - List<Service> runningServices = mServices; - int runningServiceCount = mServices.size(); - for (int i = 0; i < runningServiceCount; i++) { - Service service = runningServices.get(i); - if (service.mIsAutomation) { - service.unbind(); - return; - } - } - } - - /** * Populates a list with the {@link ComponentName}s of all enabled * {@link AccessibilityService}s. * @@ -881,7 +921,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!mHasInputFilter) { mHasInputFilter = true; if (mInputFilter == null) { - mInputFilter = new AccessibilityInputFilter(mContext); + mInputFilter = new AccessibilityInputFilter(mContext, this); } mWindowManagerService.setInputFilter(mInputFilter); } @@ -905,8 +945,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } else { unbindAllServicesLocked(); } - updateInputFilterLocked(); - sendStateToClientsLocked(); + } + + /** + * Updates the state based on the touch exploration enabled setting. + */ + private void handleTouchExplorationEnabledSettingChangedLocked() { + mIsTouchExplorationEnabled = Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1; } private class AccessibilityConnectionWrapper implements DeathRecipient { @@ -936,6 +983,212 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + class GestureHandler extends IAccessibilityServiceClientCallback.Stub + implements Runnable, Callback { + + private static final String THREAD_NAME = "AccessibilityGestureHandler"; + + private static final long TIMEOUT_INTERACTION_MILLIS = 5000; + + private static final int MSG_HANDLE_GESTURE = 1; + + private static final int MSG_HANDLE_GESTURE_DEFAULT = 2; + + private final AtomicInteger mInteractionCounter = new AtomicInteger(); + + private final Object mGestureLock = new Object(); + + private HandlerCaller mHandlerCaller; + + private volatile int mInteractionId = -1; + + private volatile boolean mGestureResult; + + public GestureHandler() { + synchronized (mGestureLock) { + Thread worker = new Thread(this, THREAD_NAME); + worker.start(); + while (mHandlerCaller == null) { + try { + mGestureLock.wait(); + } catch (InterruptedException ie) { + /* ignore */ + } + } + } + } + + @Override + public void run() { + Looper.prepare(); + synchronized (mGestureLock) { + mHandlerCaller = new HandlerCaller(mContext, Looper.myLooper(), this); + mGestureLock.notifyAll(); + } + Looper.loop(); + } + + @Override + public void setGestureResult(int gestureId, boolean handled, int interactionId) { + synchronized (mGestureLock) { + if (interactionId > mInteractionId) { + mGestureResult = handled; + mInteractionId = interactionId; + } + mGestureLock.notifyAll(); + } + } + + @Override + public void executeMessage(Message message) { + final int type = message.what; + switch (type) { + case MSG_HANDLE_GESTURE: { + IAccessibilityServiceClient service = + (IAccessibilityServiceClient) message.obj; + final int gestureId = message.arg1; + final int interactionId = message.arg2; + + try { + service.onGesture(gestureId, this, interactionId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error dispatching a gesture to a client.", re); + return; + } + + long waitTimeMillis = 0; + final long startTimeMillis = SystemClock.uptimeMillis(); + synchronized (mGestureLock) { + while (true) { + try { + // Did we get the expected callback? + if (mInteractionId == interactionId) { + break; + } + // Did we get an obsolete callback? + if (mInteractionId > interactionId) { + break; + } + // Did we time out? + final long elapsedTimeMillis = + SystemClock.uptimeMillis() - startTimeMillis; + waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis; + if (waitTimeMillis <= 0) { + break; + } + mGestureLock.wait(waitTimeMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + handleGestureIfNeededAndResetLocked(gestureId); + } + } break; + case MSG_HANDLE_GESTURE_DEFAULT: { + final int gestureId = message.arg1; + handleGestureDefault(gestureId); + } break; + default: { + throw new IllegalArgumentException("Unknown message type: " + type); + } + } + } + + private void handleGestureIfNeededAndResetLocked(int gestureId) { + if (!mGestureResult) { + handleGestureDefault(gestureId); + } + mGestureResult = false; + mInteractionId = -1; + } + + public void scheduleHandleGesture(int gestureId, IAccessibilityServiceClient service) { + final int interactionId = mInteractionCounter.incrementAndGet(); + mHandlerCaller.obtainMessageIIO(MSG_HANDLE_GESTURE, gestureId, interactionId, + service).sendToTarget(); + } + + public void scheduleHandleGestureDefault(int gestureId) { + final int interactionId = mInteractionCounter.incrementAndGet(); + mHandlerCaller.obtainMessageI(MSG_HANDLE_GESTURE_DEFAULT, gestureId).sendToTarget(); + } + + private void handleGestureDefault(int gestureId) { + Service service = getDefaultGestureHandlingHelperService(); + + // Global actions. + switch (gestureId) { + case AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT: { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); + } return; + case AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT: { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME); + } return; + case AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT: { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS); + } return; + case AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT: { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS); + } return; + } + + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + + AccessibilityNodeInfo root = client.getRootInActiveWindow(service.mId); + if (root == null) { + return; + } + + AccessibilityNodeInfo current = root.findFocus( + AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); + if (current == null) { + current = root; + } + + // Local actions. + AccessibilityNodeInfo next = null; + switch (gestureId) { + case AccessibilityService.GESTURE_SWIPE_UP: { + // TODO: + } break; + case AccessibilityService.GESTURE_SWIPE_DOWN: { + // TODO: + } break; + case AccessibilityService.GESTURE_SWIPE_LEFT: { + // TODO: Implement the RTL support. +// if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD); +// } else { // LAYOUT_DIRECTION_RTL +// next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD); +// } + } break; + case AccessibilityService.GESTURE_SWIPE_RIGHT: { + // TODO: Implement the RTL support. +// if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD); +// } else { // LAYOUT_DIRECTION_RTL +// next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD); +// } + } break; + case AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_UP); + } break; + case AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_DOWN); + } break; + case AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_LEFT); + } break; + case AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT: { + next = current.focusSearch(View.ACCESSIBILITY_FOCUS_RIGHT); + } break; + } + if (next != null && !next.equals(current)) { + next.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); + } + } + } + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the @@ -952,7 +1205,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub IBinder mService; - IEventListener mServiceInterface; + IAccessibilityServiceClient mServiceInterface; int mEventTypes; @@ -962,6 +1215,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub boolean mIsDefault; + boolean mIncludeNotImportantViews; + long mNotificationTimeout; ComponentName mComponentName; @@ -970,6 +1225,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub boolean mCanRetrieveScreenContent; + boolean mCanHandleGestures; + boolean mIsAutomation; final Rect mTempBounds = new Rect(); @@ -986,6 +1243,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mIsAutomation = isAutomation; if (!isAutomation) { mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent(); + mCanHandleGestures = accessibilityServiceInfo.getCanHandleGestures(); mIntent = new Intent().setComponent(mComponentName); mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.accessibility_binding_label); @@ -993,6 +1251,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0)); } else { mCanRetrieveScreenContent = true; + mIncludeNotImportantViews = true; + mCanHandleGestures = true; } setDynamicallyConfigurableProperties(accessibilityServiceInfo); } @@ -1005,7 +1265,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mPackageNames.addAll(Arrays.asList(packageNames)); } mNotificationTimeout = info.notificationTimeout; - mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0; + mIsDefault = (info.flags & DEFAULT) != 0; + + if (!mIsAutomation) { + final int targetSdkVersion = + info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion; + // TODO: Uncomment this line and remove the line below when JellyBean + // SDK version is finalized. + // if (targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { + if (targetSdkVersion > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + mIncludeNotImportantViews = + (info.flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0; + } + } synchronized (mLock) { tryAddServiceLocked(this); @@ -1053,13 +1325,33 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return (mEventTypes != 0 && mFeedbackType != 0 && mService != null); } + @Override + public AccessibilityServiceInfo getServiceInfo() { + synchronized (mLock) { + return mAccessibilityServiceInfo; + } + } + + @Override public void setServiceInfo(AccessibilityServiceInfo info) { - mCaller.obtainMessageOO(DO_SET_SERVICE_INFO, info, this).sendToTarget(); + synchronized (mLock) { + // If the XML manifest had data to configure the service its info + // should be already set. In such a case update only the dynamically + // configurable properties. + AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo; + if (oldInfo != null) { + oldInfo.updateDynamicallyConfigurableProperties(info); + setDynamicallyConfigurableProperties(oldInfo); + } else { + setDynamicallyConfigurableProperties(info); + } + } } + @Override public void onServiceConnected(ComponentName componentName, IBinder service) { mService = service; - mServiceInterface = IEventListener.Stub.asInterface(service); + mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); try { mServiceInterface.setConnection(this, mId); synchronized (mLock) { @@ -1070,10 +1362,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId, - int interactionId, IAccessibilityInteractionConnectionCallback callback, - long interrogatingTid) + @Override + public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, + long accessibilityNodeId, int viewId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); @@ -1081,147 +1375,205 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!permissionGranted) { return 0; } else { - connection = getConnectionToRetrievalAllowingWindowLocked(); + connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to a retrieve " - + "allowing window."); - } return 0; } } } + final int flags = (mIncludeNotImportantViews) ? + AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, callback, - interrogatingPid, interrogatingTid); + connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId, + interactionId, callback, flags, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error finding node."); + Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(mSecurityPolicy.getRetrievalAllowingWindowLocked()); + return getCompatibilityScale(resolvedWindowId); } - public float findAccessibilityNodeInfosByViewTextInActiveWindow( - String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long threadId) + @Override + public float findAccessibilityNodeInfosByText(int accessibilityWindowId, + long accessibilityNodeId, String text, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { - return findAccessibilityNodeInfosByViewText(text, - mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID, interactionId, callback, - threadId); + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); + IAccessibilityInteractionConnection connection = null; + synchronized (mLock) { + mSecurityPolicy.enforceCanRetrieveWindowContent(this); + final boolean permissionGranted = + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); + if (!permissionGranted) { + return 0; + } else { + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { + return 0; + } + } + } + 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, + interactionId, callback, flags, interrogatingPid, interrogatingTid); + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); + } + } finally { + Binder.restoreCallingIdentity(identityToken); + } + return getCompatibilityScale(resolvedWindowId); } - public float findAccessibilityNodeInfosByViewText(String text, - int accessibilityWindowId, int accessibilityViewId, int interactionId, + @Override + public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, + long accessibilityNodeId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, + long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); + IAccessibilityInteractionConnection connection = null; + synchronized (mLock) { + mSecurityPolicy.enforceCanRetrieveWindowContent(this); + final boolean permissionGranted = + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); + if (!permissionGranted) { + return 0; + } else { + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { + return 0; + } + } + } + final int allFlags = flags | ((mIncludeNotImportantViews) ? + AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0); + final int interrogatingPid = Binder.getCallingPid(); + final long identityToken = Binder.clearCallingIdentity(); + try { + connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, + interactionId, callback, allFlags, interrogatingPid, interrogatingTid); + } catch (RemoteException re) { + if (DEBUG) { + Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); + } + } finally { + Binder.restoreCallingIdentity(identityToken); + } + return getCompatibilityScale(resolvedWindowId); + } + + @Override + public float findFocus(int accessibilityWindowId, long accessibilityNodeId, + int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId); + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { return 0; } else { - connection = getConnectionToRetrievalAllowingWindowLocked(); + connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to focused window."); - } return 0; } } } + final int flags = (mIncludeNotImportantViews) ? + AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByViewText(text, accessibilityViewId, - interactionId, callback, interrogatingPid, interrogatingTid); + connection.findFocus(accessibilityNodeId, interactionId, focusType, callback, + flags, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error finding node."); + Slog.e(LOG_TAG, "Error calling findAccessibilityFocus()"); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(accessibilityWindowId); + return getCompatibilityScale(resolvedWindowId); } - public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, - int accessibilityViewId, int interactionId, + @Override + public float focusSearch(int accessibilityWindowId, long accessibilityNodeId, + int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId); + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { return 0; } else { - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId); - if (wrapper == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to window: " - + accessibilityWindowId); - } + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { return 0; } - connection = wrapper.mConnection; } } + final int flags = (mIncludeNotImportantViews) ? + AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityViewId, - interactionId, callback, interrogatingPid, interrogatingTid); + connection.focusSearch(accessibilityNodeId, interactionId, direction, callback, + flags, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: " - + accessibilityViewId); + Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()"); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(accessibilityWindowId); + return getCompatibilityScale(resolvedWindowId); } + @Override public boolean performAccessibilityAction(int accessibilityWindowId, - int accessibilityViewId, int action, int interactionId, + long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this, - accessibilityWindowId, action); + resolvedWindowId, action, arguments); if (!permissionGranted) { return false; } else { - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId); - if (wrapper == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to window: " - + accessibilityWindowId); - } + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { return false; } - connection = wrapper.mConnection; } } - final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.performAccessibilityAction(accessibilityViewId, action, interactionId, - callback, interrogatingPid, interrogatingTid); + final int flags = (mIncludeNotImportantViews) ? + AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0; + final int interrogatingPid = Binder.getCallingPid(); + connection.performAccessibilityAction(accessibilityNodeId, action, arguments, + interactionId, callback, flags, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: " - + accessibilityViewId); + Slog.e(LOG_TAG, "Error calling performAccessibilityAction()"); } } finally { Binder.restoreCallingIdentity(identityToken); @@ -1229,6 +1581,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return true; } + public boolean performGlobalAction(int action) { + switch (action) { + case AccessibilityService.GLOBAL_ACTION_BACK: { + sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK); + } return true; + case AccessibilityService.GLOBAL_ACTION_HOME: { + sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME); + } return true; + case AccessibilityService.GLOBAL_ACTION_RECENTS: { + sendDownAndUpKeyEvents(KeyEvent.KEYCODE_APP_SWITCH); + } return true; + case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: { + // TODO: Implement when 6346026 is fixed. + } return true; + } + return false; + } + public void onServiceDisconnected(ComponentName componentName) { /* do nothing - #binderDied takes care */ } @@ -1255,24 +1625,64 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public void binderDied() { synchronized (mLock) { - unlinkToOwnDeath(); + // The death recipient is unregistered in tryRemoveServiceLocked tryRemoveServiceLocked(this); // We no longer have an automation service, so restore // the state based on values in the settings database. if (mIsAutomation) { + mUiAutomationService = null; handleAccessibilityEnabledSettingChangedLocked(); + handleTouchExplorationEnabledSettingChangedLocked(); + updateInputFilterLocked(); + sendStateToClientsLocked(); } } } - private IAccessibilityInteractionConnection getConnectionToRetrievalAllowingWindowLocked() { - final int windowId = mSecurityPolicy.getRetrievalAllowingWindowLocked(); + private void sendDownAndUpKeyEvents(int keyCode) { + final long token = Binder.clearCallingIdentity(); + + // Inject down. + final long downTime = SystemClock.uptimeMillis(); + KeyEvent down = KeyEvent.obtain(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0, 0, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, + InputDevice.SOURCE_KEYBOARD, null); + InputManager.getInstance().injectInputEvent(down, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + down.recycle(); + + // Inject up. + final long upTime = SystemClock.uptimeMillis(); + KeyEvent up = KeyEvent.obtain(downTime, upTime, KeyEvent.ACTION_UP, keyCode, 0, 0, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, + InputDevice.SOURCE_KEYBOARD, null); + InputManager.getInstance().injectInputEvent(up, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + up.recycle(); + + Binder.restoreCallingIdentity(token); + } + + private IAccessibilityInteractionConnection getConnectionLocked(int windowId) { if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); } - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(windowId); - return (wrapper != null) ? wrapper.mConnection : null; + AccessibilityConnectionWrapper wrapper = mWindowIdToInteractionConnectionWrapperMap.get( + windowId); + if (wrapper != null && wrapper.mConnection != null) { + return wrapper.mConnection; + } + if (DEBUG) { + Slog.e(LOG_TAG, "No interaction connection to window: " + windowId); + } + return null; + } + + private int resolveAccessibilityWindowId(int accessibilityWindowId) { + if (accessibilityWindowId == AccessibilityNodeInfo.ACTIVE_WINDOW_ID) { + return mSecurityPolicy.mRetrievalAlowingWindowId; + } + return accessibilityWindowId; } private float getCompatibilityScale(int windowId) { @@ -1282,22 +1692,47 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } final class SecurityPolicy { - private static final int VALID_ACTIONS = AccessibilityNodeInfo.ACTION_FOCUS - | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS | AccessibilityNodeInfo.ACTION_SELECT - | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION; + private static final int VALID_ACTIONS = + AccessibilityNodeInfo.ACTION_CLICK + | AccessibilityNodeInfo.ACTION_LONG_CLICK + | AccessibilityNodeInfo.ACTION_FOCUS + | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS + | AccessibilityNodeInfo.ACTION_SELECT + | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION + | AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS + | AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS + | AccessibilityNodeInfo.ACTION_NEXT_AT_GRANULARITY + | AccessibilityNodeInfo.ACTION_PREVIOUS_AT_GRANULARITY + | AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT + | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT; + + private static final int VALID_GRANULARITIES = + AccessibilityNodeInfo.GRANULARITY_CHARACTER + | AccessibilityNodeInfo.GRANULARITY_WORD + | AccessibilityNodeInfo.GRANULARITY_LINE + | AccessibilityNodeInfo.GRANULARITY_PARAGRAPH + | AccessibilityNodeInfo.GRANULARITY_PAGE; private static final int RETRIEVAL_ALLOWING_EVENT_TYPES = - AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED - | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT - | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED - | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_SELECTED + AccessibilityEvent.TYPE_VIEW_CLICKED + | AccessibilityEvent.TYPE_VIEW_FOCUSED + | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER + | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT + | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED + | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED + | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + | AccessibilityEvent.TYPE_VIEW_SELECTED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED - | AccessibilityEvent.TYPE_VIEW_SCROLLED; + | AccessibilityEvent.TYPE_VIEW_SCROLLED + | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED + | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED; private static final int RETRIEVAL_ALLOWING_WINDOW_CHANGE_EVENT_TYPES = - AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER - | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT; + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER + | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT + | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; private int mRetrievalAlowingWindowId; @@ -1326,7 +1761,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return canRetrieveWindowContent(service) && isRetrievalAllowingWindow(windowId); } - public boolean canPerformActionLocked(Service service, int windowId, int action) { + public boolean canPerformActionLocked(Service service, int windowId, int action, + Bundle arguments) { return canRetrieveWindowContent(service) && isRetrievalAllowingWindow(windowId) && isActionPermitted(action); diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index 41cf9a6..39012e6 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -20,17 +20,25 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATI import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START; import android.content.Context; +import android.gesture.Gesture; +import android.gesture.GestureLibraries; +import android.gesture.GestureLibrary; +import android.gesture.GesturePoint; +import android.gesture.GestureStroke; +import android.gesture.Prediction; import android.os.Handler; import android.util.Slog; import android.view.MotionEvent; +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.server.accessibility.AccessibilityInputFilter.Explorer; -import com.android.server.wm.InputFilter; +import com.android.server.input.InputFilter; +import com.android.internal.R; +import java.util.ArrayList; import java.util.Arrays; /** @@ -54,34 +62,24 @@ import java.util.Arrays; * * @hide */ -public class TouchExplorer implements Explorer { +public class TouchExplorer { + private static final boolean DEBUG = false; // Tag for logging received events. - private static final String LOG_TAG_RECEIVED = "TouchExplorer-RECEIVED"; - // Tag for logging injected events. - private static final String LOG_TAG_INJECTED = "TouchExplorer-INJECTED"; - // Tag for logging the current state. - private static final String LOG_TAG_STATE = "TouchExplorer-STATE"; + private static final String LOG_TAG = "TouchExplorer"; // States this explorer can be in. private static final int STATE_TOUCH_EXPLORING = 0x00000001; private static final int STATE_DRAGGING = 0x00000002; private static final int STATE_DELEGATING = 0x00000004; - - // Invalid pointer ID. - private static final int INVALID_POINTER_ID = -1; + private static final int STATE_GESTURE_DETECTING = 0x00000005; // The time slop in milliseconds for activating an item after it has // been touch explored. Tapping on an item within this slop will perform // a click and tapping and holding down a long press. private static final long ACTIVATION_TIME_SLOP = 2000; - // This constant captures the current implementation detail that - // pointer IDs are between 0 and 31 inclusive (subject to change). - // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) - private static final int MAX_POINTER_COUNT = 32; - // The minimum of the cosine between the vectors of two moving // pointers so they can be considered moving in the same direction. private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) @@ -92,6 +90,14 @@ public class TouchExplorer implements Explorer { // Constant referring to the ids bits of all pointers. private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; + // This constant captures the current implementation detail that + // pointer IDs are between 0 and 31 inclusive (subject to change). + // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) + public static final int MAX_POINTER_COUNT = 32; + + // Invalid pointer ID. + public static final int INVALID_POINTER_ID = -1; + // Temporary array for storing pointer IDs. private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT]; @@ -103,10 +109,6 @@ public class TouchExplorer implements Explorer { // which delegates event processing to this touch explorer. private final InputFilter mInputFilter; - // Helper class for tracking pointers on the screen, for example which - // pointers are down, which are active, etc. - private final PointerTracker mPointerTracker; - // Handle to the accessibility manager for firing accessibility events // announcing touch exploration gesture start and end. private final AccessibilityManager mAccessibilityManager; @@ -132,21 +134,48 @@ public class TouchExplorer implements Explorer { // Command for delayed sending of a long press. private final PerformLongPressDelayed mPerformLongPressDelayed; + private VelocityTracker mVelocityTracker; + + private final ReceivedPointerTracker mReceivedPointerTracker; + + private final InjectedPointerTracker mInjectedPointerTracker; + + private final GestureListener mGestureListener; + + /** + * Callback for gesture detection. + */ + public interface GestureListener { + + /** + * Called when a given gesture was performed. + * + * @param gestureId The gesture id. + */ + public boolean onGesture(int gestureId); + } + /** * 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) { + public TouchExplorer(InputFilter inputFilter, Context context, + GestureListener gestureListener) { + mGestureListener = gestureListener; + mReceivedPointerTracker = new ReceivedPointerTracker(context); + mInjectedPointerTracker = new InjectedPointerTracker(); mInputFilter = inputFilter; mTouchExplorationTapSlop = - ViewConfiguration.get(context).getScaledTouchExplorationTapSlop(); - mPointerTracker = new PointerTracker(context); + ViewConfiguration.get(context).getScaledTouchExploreTapSlop(); mHandler = new Handler(context.getMainLooper()); mSendHoverDelayed = new SendHoverDelayed(); mPerformLongPressDelayed = new PerformLongPressDelayed(); mAccessibilityManager = AccessibilityManager.getInstance(context); + mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures); + mGestureLibrary.setOrientationStyle(4); + mGestureLibrary.load(); } public void clear(MotionEvent event, int policyFlags) { @@ -154,18 +183,14 @@ public class TouchExplorer implements Explorer { clear(); } - /** - * {@inheritDoc} - */ public void onMotionEvent(MotionEvent event, int policyFlags) { if (DEBUG) { - Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x" + Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); - Slog.d(LOG_TAG_STATE, getStateSymbolicName(mCurrentState)); + Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState)); } - // Keep track of the pointers's state. - mPointerTracker.onReceivedMotionEvent(event); + mReceivedPointerTracker.onMotionEvent(event); switch(mCurrentState) { case STATE_TOUCH_EXPLORING: { @@ -177,9 +202,11 @@ public class TouchExplorer implements Explorer { case STATE_DELEGATING: { handleMotionEventStateDelegating(event, policyFlags); } break; - default: { + case STATE_GESTURE_DETECTING: { + handleMotionEventGestureDetecting(event, policyFlags); + } break; + default: throw new IllegalStateException("Illegal state: " + mCurrentState); - } } } @@ -190,8 +217,14 @@ public class TouchExplorer implements Explorer { * @param policyFlags The policy flags associated with the event. */ private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) { - PointerTracker pointerTracker = mPointerTracker; - final int activePointerCount = pointerTracker.getActivePointerCount(); + ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; + InjectedPointerTracker injectedTracker = mInjectedPointerTracker; + final int activePointerCount = receivedTracker.getActivePointerCount(); + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(event); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: @@ -205,9 +238,9 @@ public class TouchExplorer implements Explorer { mSendHoverDelayed.remove(); mPerformLongPressDelayed.remove(); // Send a hover for every finger down so the user gets feedback. - final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerId = receivedTracker.getPrimaryActivePointerId(); final int pointerIdBits = (1 << pointerId); - final int lastAction = pointerTracker.getLastInjectedHoverAction(); + final int lastAction = injectedTracker.getLastInjectedHoverAction(); // Deliver hover enter with a delay to have a change to detect // whether the user actually starts a scrolling gesture. @@ -232,7 +265,7 @@ public class TouchExplorer implements Explorer { // If the down is in the time slop => schedule a long press. final long pointerDownTime = - pointerTracker.getReceivedPointerDownTime(pointerId); + receivedTracker.getReceivedPointerDownTime(pointerId); final long lastExploreTime = mLastTouchExploreEvent.getEventTime(); final long deltaTimeExplore = pointerDownTime - lastExploreTime; if (deltaTimeExplore <= ACTIVATION_TIME_SLOP) { @@ -247,7 +280,7 @@ public class TouchExplorer implements Explorer { } } break; case MotionEvent.ACTION_MOVE: { - final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerId = receivedTracker.getPrimaryActivePointerId(); final int pointerIndex = event.findPointerIndex(pointerId); final int pointerIdBits = (1 << pointerId); switch (activePointerCount) { @@ -258,13 +291,27 @@ public class TouchExplorer implements Explorer { // Detect touch exploration gesture start by having one active pointer // that moved more than a given distance. if (!mTouchExploreGestureInProgress) { - final float deltaX = pointerTracker.getReceivedPointerDownX(pointerId) + final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) - event.getX(pointerIndex); - final float deltaY = pointerTracker.getReceivedPointerDownY(pointerId) + final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) - event.getY(pointerIndex); final double moveDelta = Math.hypot(deltaX, deltaY); if (moveDelta > mTouchExplorationTapSlop) { + + mVelocityTracker.computeCurrentVelocity(1000); + final float maxAbsVelocity = Math.max( + Math.abs(mVelocityTracker.getXVelocity(pointerId)), + Math.abs(mVelocityTracker.getYVelocity(pointerId))); + // TODO: Tune the velocity cut off and add a constant. + if (maxAbsVelocity > 1000) { + clear(event, policyFlags); + mCurrentState = STATE_GESTURE_DETECTING; + event.setAction(MotionEvent.ACTION_DOWN); + handleMotionEventGestureDetecting(event, policyFlags); + return; + } + mTouchExploreGestureInProgress = true; sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START); // Make sure the scheduled down/move event is sent. @@ -272,7 +319,7 @@ public class TouchExplorer implements Explorer { mPerformLongPressDelayed.remove(); // If we have transitioned to exploring state from another one // we need to send a hover enter event here. - final int lastAction = mPointerTracker.getLastInjectedHoverAction(); + final int lastAction = injectedTracker.getLastInjectedHoverAction(); if (lastAction == MotionEvent.ACTION_HOVER_EXIT) { sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags); @@ -355,12 +402,12 @@ public class TouchExplorer implements Explorer { } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: { - final int pointerId = pointerTracker.getLastReceivedUpPointerId(); + final int pointerId = receivedTracker.getLastReceivedUpPointerId(); final int pointerIdBits = (1 << pointerId); switch (activePointerCount) { case 0: { // If the pointer that went up was not active we have nothing to do. - if (!pointerTracker.wasLastReceivedUpPointerActive()) { + if (!receivedTracker.wasLastReceivedUpPointerActive()) { break; } @@ -381,7 +428,7 @@ public class TouchExplorer implements Explorer { if (mLastTouchExploreEvent != null) { // If the down was not in the time slop => nothing else to do. final long eventTime = - pointerTracker.getLastReceivedUpPointerDownTime(); + receivedTracker.getLastReceivedUpPointerDownTime(); final long exploreTime = mLastTouchExploreEvent.getEventTime(); final long deltaTime = eventTime - exploreTime; if (deltaTime > ACTIVATION_TIME_SLOP) { @@ -422,14 +469,22 @@ public class TouchExplorer implements Explorer { } } break; } + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + mVelocityTracker = null; + } } break; case MotionEvent.ACTION_CANCEL: { mSendHoverDelayed.remove(); mPerformLongPressDelayed.remove(); - final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerId = receivedTracker.getPrimaryActivePointerId(); final int pointerIdBits = (1 << pointerId); ensureHoverExitSent(event, pointerIdBits, policyFlags); clear(); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + mVelocityTracker = null; + } } break; } } @@ -455,7 +510,7 @@ public class TouchExplorer implements Explorer { sendDownForAllActiveNotInjectedPointers(event, policyFlags); } break; case MotionEvent.ACTION_MOVE: { - final int activePointerCount = mPointerTracker.getActivePointerCount(); + final int activePointerCount = mReceivedPointerTracker.getActivePointerCount(); switch (activePointerCount) { case 1: { // do nothing @@ -487,7 +542,7 @@ public class TouchExplorer implements Explorer { } } break; case MotionEvent.ACTION_POINTER_UP: { - final int activePointerCount = mPointerTracker.getActivePointerCount(); + final int activePointerCount = mReceivedPointerTracker.getActivePointerCount(); switch (activePointerCount) { case 1: { // Send an event to the end of the drag gesture. @@ -525,7 +580,8 @@ public class TouchExplorer implements Explorer { case MotionEvent.ACTION_MOVE: { // Check whether some other pointer became active because they have moved // a given distance and if such exist send them to the view hierarchy - final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount(); + final int notInjectedCount = getNotInjectedActivePointerCount( + mReceivedPointerTracker, mInjectedPointerTracker); if (notInjectedCount > 0) { MotionEvent prototype = MotionEvent.obtain(event); sendDownForAllActiveNotInjectedPointers(prototype, policyFlags); @@ -533,7 +589,7 @@ public class TouchExplorer implements Explorer { } break; case MotionEvent.ACTION_POINTER_UP: { // No active pointers => go to initial state. - if (mPointerTracker.getActivePointerCount() == 0) { + if (mReceivedPointerTracker.getActivePointerCount() == 0) { mCurrentState = STATE_TOUCH_EXPLORING; } } break; @@ -545,6 +601,72 @@ public class TouchExplorer implements Explorer { sendMotionEventStripInactivePointers(event, policyFlags); } + private float mPreviousX; + private float mPreviousY; + + private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100); + + private static final int TOUCH_TOLERANCE = 3; + private static final float MIN_PREDICTION_SCORE = 2.0f; + + private GestureLibrary mGestureLibrary; + + private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + final float x = event.getX(); + final float y = event.getY(); + mPreviousX = x; + mPreviousY = y; + mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); + } break; + case MotionEvent.ACTION_MOVE: { + final float x = event.getX(); + final float y = event.getY(); + final float dX = Math.abs(x - mPreviousX); + final float dY = Math.abs(y - mPreviousY); + if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { + mPreviousX = x; + mPreviousY = y; + mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); + } + } break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: { + float x = event.getX(); + float y = event.getY(); + mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); + + Gesture gesture = new Gesture(); + gesture.addStroke(new GestureStroke(mStrokeBuffer)); + + ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture); + if (!predictions.isEmpty()) { + Prediction bestPrediction = predictions.get(0); + if (bestPrediction.score >= MIN_PREDICTION_SCORE) { + if (DEBUG) { + Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: " + + bestPrediction.score); + } + try { + final int gestureId = Integer.parseInt(bestPrediction.name); + mGestureListener.onGesture(gestureId); + } catch (NumberFormatException nfe) { + Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name); + } + } + } + + mStrokeBuffer.clear(); + mCurrentState = STATE_TOUCH_EXPLORING; + } break; + case MotionEvent.ACTION_CANCEL: { + mStrokeBuffer.clear(); + mCurrentState = STATE_TOUCH_EXPLORING; + } break; + } + } + /** * 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. @@ -553,14 +675,15 @@ public class TouchExplorer implements Explorer { * @param policyFlags The policy flags associated with the event. */ private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) { - final PointerTracker pointerTracker = mPointerTracker; + ReceivedPointerTracker receivedPointers = mReceivedPointerTracker; + InjectedPointerTracker injectedPointers = mInjectedPointerTracker; int pointerIdBits = 0; final int pointerCount = prototype.getPointerCount(); // Find which pointers are already injected. for (int i = 0; i < pointerCount; i++) { final int pointerId = prototype.getPointerId(i); - if (pointerTracker.isInjectedPointerDown(pointerId)) { + if (injectedPointers.isInjectedPointerDown(pointerId)) { pointerIdBits |= (1 << pointerId); } } @@ -569,11 +692,11 @@ public class TouchExplorer implements Explorer { for (int i = 0; i < pointerCount; i++) { final int pointerId = prototype.getPointerId(i); // Skip inactive pointers. - if (!pointerTracker.isActivePointer(pointerId)) { + if (!receivedPointers.isActivePointer(pointerId)) { continue; } // Do not send event for already delivered pointers. - if (pointerTracker.isInjectedPointerDown(pointerId)) { + if (injectedPointers.isInjectedPointerDown(pointerId)) { continue; } pointerIdBits |= (1 << pointerId); @@ -590,7 +713,7 @@ public class TouchExplorer implements Explorer { * @param policyFlags The policy flags associated with the event. */ private void ensureHoverExitSent(MotionEvent prototype, int pointerIdBits, int policyFlags) { - final int lastAction = mPointerTracker.getLastInjectedHoverAction(); + final int lastAction = mInjectedPointerTracker.getLastInjectedHoverAction(); if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { sendMotionEvent(prototype, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags); @@ -605,13 +728,13 @@ public class TouchExplorer implements Explorer { * @param policyFlags The policy flags associated with the event. */ private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { - final PointerTracker pointerTracker = mPointerTracker; + final InjectedPointerTracker injectedTracked = mInjectedPointerTracker; int pointerIdBits = 0; final int pointerCount = prototype.getPointerCount(); for (int i = 0; i < pointerCount; i++) { final int pointerId = prototype.getPointerId(i); // Skip non injected down pointers. - if (!pointerTracker.isInjectedPointerDown(pointerId)) { + if (!injectedTracked.isInjectedPointerDown(pointerId)) { continue; } pointerIdBits |= (1 << pointerId); @@ -627,18 +750,18 @@ public class TouchExplorer implements Explorer { * @param policyFlags The policy flags associated with the event. */ private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) { - PointerTracker pointerTracker = mPointerTracker; + ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; // All pointers active therefore we just inject the event as is. - if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) { + if (prototype.getPointerCount() == receivedTracker.getActivePointerCount()) { sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags); return; } // No active pointers and the one that just went up was not // active, therefore we have nothing to do. - if (pointerTracker.getActivePointerCount() == 0 - && !pointerTracker.wasLastReceivedUpPointerActive()) { + if (receivedTracker.getActivePointerCount() == 0 + && !receivedTracker.wasLastReceivedUpPointerActive()) { return; } @@ -647,7 +770,7 @@ public class TouchExplorer implements Explorer { final int actionMasked = prototype.getActionMasked(); final int actionPointerId = prototype.getPointerId(prototype.getActionIndex()); if (actionMasked != MotionEvent.ACTION_MOVE) { - if (!pointerTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) { + if (!receivedTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) { return; } } @@ -658,7 +781,7 @@ public class TouchExplorer implements Explorer { final int pointerCount = prototype.getPointerCount(); for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) { final int pointerId = prototype.getPointerId(pointerIndex); - if (pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) { + if (receivedTracker.isActiveOrWasLastActiveUpPointer(pointerId)) { pointerIdBits |= (1 << pointerId); } } @@ -700,19 +823,20 @@ public class TouchExplorer implements Explorer { if (action == MotionEvent.ACTION_DOWN) { event.setDownTime(event.getEventTime()); } else { - event.setDownTime(mPointerTracker.getLastInjectedDownEventTime()); + event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime()); } if (DEBUG) { - Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x" + Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x" + Integer.toHexString(policyFlags)); } // Make sure that the user will see the event. policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; - mPointerTracker.onInjectedMotionEvent(event); mInputFilter.sendInputEvent(event, policyFlags); + mInjectedPointerTracker.onMotionEvent(event); + if (event != prototype) { event.recycle(); } @@ -730,9 +854,9 @@ public class TouchExplorer implements Explorer { switch (actionMasked) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: { - PointerTracker pointerTracker = mPointerTracker; + InjectedPointerTracker injectedTracker = mInjectedPointerTracker; // Compute the action based on how many down pointers are injected. - if (pointerTracker.getInjectedPointerDownCount() == 0) { + if (injectedTracker.getInjectedPointerDownCount() == 0) { return MotionEvent.ACTION_DOWN; } else { return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) @@ -740,9 +864,9 @@ public class TouchExplorer implements Explorer { } } case MotionEvent.ACTION_POINTER_UP: { - PointerTracker pointerTracker = mPointerTracker; + InjectedPointerTracker injectedTracker = mInjectedPointerTracker; // Compute the action based on how many down pointers are injected. - if (pointerTracker.getInjectedPointerDownCount() == 1) { + if (injectedTracker.getInjectedPointerDownCount() == 1) { return MotionEvent.ACTION_UP; } else { return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) @@ -761,9 +885,9 @@ public class TouchExplorer implements Explorer { * @return True if the gesture is a dragging one. */ private boolean isDraggingGesture(MotionEvent event) { - PointerTracker pointerTracker = mPointerTracker; + ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; int[] pointerIds = mTempPointerIds; - pointerTracker.populateActivePointerIds(pointerIds); + receivedTracker.populateActivePointerIds(pointerIds); final int firstPtrIndex = event.findPointerIndex(pointerIds[0]); final int secondPtrIndex = event.findPointerIndex(pointerIds[1]); @@ -775,9 +899,9 @@ public class TouchExplorer implements Explorer { // Check if the pointers are moving in the same direction. final float firstDeltaX = - firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex); + firstPtrX - receivedTracker.getReceivedPointerDownX(firstPtrIndex); final float firstDeltaY = - firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex); + firstPtrY - receivedTracker.getReceivedPointerDownY(firstPtrIndex); if (firstDeltaX == 0 && firstDeltaY == 0) { return true; @@ -791,9 +915,9 @@ public class TouchExplorer implements Explorer { (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY; final float secondDeltaX = - secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex); + secondPtrX - receivedTracker.getReceivedPointerDownX(secondPtrIndex); final float secondDeltaY = - secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex); + secondPtrY - receivedTracker.getReceivedPointerDownY(secondPtrIndex); if (secondDeltaX == 0 && secondDeltaY == 0) { return true; @@ -832,7 +956,8 @@ public class TouchExplorer implements Explorer { public void clear() { mSendHoverDelayed.remove(); mPerformLongPressDelayed.remove(); - mPointerTracker.clear(); + mReceivedPointerTracker.clear(); + mInjectedPointerTracker.clear(); mLastTouchExploreEvent = null; mCurrentState = STATE_TOUCH_EXPLORING; mTouchExploreGestureInProgress = false; @@ -853,27 +978,253 @@ public class TouchExplorer implements Explorer { return "STATE_DRAGGING"; case STATE_DELEGATING: return "STATE_DELEGATING"; + case STATE_GESTURE_DETECTING: + return "STATE_GESTURE_DETECTING"; default: throw new IllegalArgumentException("Unknown state: " + state); } } /** - * Helper class for tracking pointers and more specifically which of - * them are currently down, which are active, and which are delivered - * to the view hierarchy. The enclosing {@link TouchExplorer} uses the - * pointer state reported by this class to perform touch exploration. - * <p> - * The main purpose of this class is to allow the touch explorer to - * disregard pointers put down by accident by the user and not being - * involved in the interaction. For example, a blind user grabs the - * device with her left hand such that she touches the screen and she - * uses her right hand's index finger to explore the screen content. - * In this scenario the touches generated by the left hand are to be - * ignored. + * @return The number of non injected active pointers. */ - class PointerTracker { - private static final String LOG_TAG = "PointerTracker"; + private int getNotInjectedActivePointerCount(ReceivedPointerTracker receivedTracker, + InjectedPointerTracker injectedTracker) { + final int pointerState = receivedTracker.getActivePointers() + & ~injectedTracker.getInjectedPointersDown(); + return Integer.bitCount(pointerState); + } + + /** + * Class for delayed sending of long press. + */ + private final class PerformLongPressDelayed implements Runnable { + private MotionEvent mEvent; + private int mPolicyFlags; + + public void post(MotionEvent prototype, int policyFlags, long delay) { + mEvent = MotionEvent.obtain(prototype); + mPolicyFlags = policyFlags; + mHandler.postDelayed(this, delay); + } + + public void remove() { + if (isPenidng()) { + mHandler.removeCallbacks(this); + clear(); + } + } + + private boolean isPenidng() { + return (mEvent != null); + } + + @Override + public void run() { + mCurrentState = STATE_DELEGATING; + // Make sure the scheduled hover exit is delivered. + mSendHoverDelayed.remove(); + final int pointerId = mReceivedPointerTracker.getPrimaryActivePointerId(); + final int pointerIdBits = (1 << pointerId); + ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags); + + sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags); + mTouchExploreGestureInProgress = false; + mLastTouchExploreEvent = null; + clear(); + } + + private void clear() { + if (!isPenidng()) { + return; + } + mEvent.recycle(); + mEvent = null; + mPolicyFlags = 0; + } + } + + /** + * Class for delayed sending of hover events. + */ + private final class SendHoverDelayed implements Runnable { + private MotionEvent mEvent; + private int mAction; + private int mPointerIdBits; + private int mPolicyFlags; + + public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags, + long delay) { + remove(); + mEvent = MotionEvent.obtain(prototype); + mAction = action; + mPointerIdBits = pointerIdBits; + mPolicyFlags = policyFlags; + mHandler.postDelayed(this, delay); + } + + public void remove() { + mHandler.removeCallbacks(this); + clear(); + } + + private boolean isPenidng() { + return (mEvent != null); + } + + private void clear() { + if (!isPenidng()) { + return; + } + mEvent.recycle(); + mEvent = null; + mAction = 0; + mPointerIdBits = -1; + mPolicyFlags = 0; + } + + public void forceSendAndRemove() { + if (isPenidng()) { + run(); + remove(); + } + } + + public void run() { + if (DEBUG) { + if (mAction == MotionEvent.ACTION_HOVER_ENTER) { + Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER); + } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) { + Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE"); + } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) { + Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT"); + } + } + + sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags); + clear(); + } + } + + @Override + public String toString() { + return LOG_TAG; + } + + class InjectedPointerTracker { + private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker"; + + // Keep track of which pointers sent to the system are down. + private int mInjectedPointersDown; + + // The time of the last injected down. + private long mLastInjectedDownEventTime; + + // The action of the last injected hover event. + private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT; + + /** + * Processes an injected {@link MotionEvent} event. + * + * @param event The event to process. + */ + public void onMotionEvent(MotionEvent event) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: { + final int pointerId = event.getPointerId(event.getActionIndex()); + final int pointerFlag = (1 << pointerId); + mInjectedPointersDown |= pointerFlag; + mLastInjectedDownEventTime = event.getDownTime(); + } break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: { + final int pointerId = event.getPointerId(event.getActionIndex()); + final int pointerFlag = (1 << pointerId); + mInjectedPointersDown &= ~pointerFlag; + if (mInjectedPointersDown == 0) { + mLastInjectedDownEventTime = 0; + } + } break; + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_HOVER_EXIT: { + mLastInjectedHoverEventAction = event.getActionMasked(); + } break; + } + if (DEBUG) { + Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer: " + toString()); + } + } + + /** + * Clears the internals state. + */ + public void clear() { + mInjectedPointersDown = 0; + } + + /** + * @return The time of the last injected down event. + */ + public long getLastInjectedDownEventTime() { + return mLastInjectedDownEventTime; + } + + /** + * @return The number of down pointers injected to the view hierarchy. + */ + public int getInjectedPointerDownCount() { + return Integer.bitCount(mInjectedPointersDown); + } + + /** + * @return The bits of the injected pointers that are down. + */ + public int getInjectedPointersDown() { + return mInjectedPointersDown; + } + + /** + * Whether an injected pointer is down. + * + * @param pointerId The unique pointer id. + * @return True if the pointer is down. + */ + public boolean isInjectedPointerDown(int pointerId) { + final int pointerFlag = (1 << pointerId); + return (mInjectedPointersDown & pointerFlag) != 0; + } + + /** + * @return The action of the last injected hover event. + */ + public int getLastInjectedHoverAction() { + return mLastInjectedHoverEventAction; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("========================="); + builder.append("\nDown pointers #"); + builder.append(Integer.bitCount(mInjectedPointersDown)); + builder.append(" [ "); + for (int i = 0; i < MAX_POINTER_COUNT; i++) { + if ((mInjectedPointersDown & i) != 0) { + builder.append(i); + builder.append(" "); + } + } + builder.append("]"); + builder.append("\n========================="); + return builder.toString(); + } + } + + class ReceivedPointerTracker { + private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker"; // The coefficient by which to multiply // ViewConfiguration.#getScaledTouchSlop() @@ -902,26 +1253,19 @@ public class TouchExplorer implements Explorer { // Flag indicating that there is at least one active pointer moving. private boolean mHasMovingActivePointer; - // Keep track of which pointers sent to the system are down. - private int mInjectedPointersDown; - // Keep track of the last up pointer data. private long mLastReceivedUpPointerDownTime; private int mLastReceivedUpPointerId; private boolean mLastReceivedUpPointerActive; - - // The time of the last injected down. - private long mLastInjectedDownEventTime; - - // The action of the last injected hover event. - private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT; + private float mLastReceivedUpPointerDownX; + private float mLastReceivedUpPointerDownY; /** * Creates a new instance. * * @param context Context for looking up resources. */ - public PointerTracker(Context context) { + public ReceivedPointerTracker(Context context) { mThresholdActivePointer = ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER; } @@ -937,10 +1281,11 @@ public class TouchExplorer implements Explorer { mActivePointers = 0; mPrimaryActivePointerId = 0; mHasMovingActivePointer = false; - mInjectedPointersDown = 0; mLastReceivedUpPointerDownTime = 0; mLastReceivedUpPointerId = 0; mLastReceivedUpPointerActive = false; + mLastReceivedUpPointerDownX = 0; + mLastReceivedUpPointerDownY = 0; } /** @@ -948,12 +1293,10 @@ public class TouchExplorer implements Explorer { * * @param event The event to process. */ - public void onReceivedMotionEvent(MotionEvent event) { + public void onMotionEvent(MotionEvent event) { final int action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: { - // New gesture so restart tracking injected down pointers. - mInjectedPointersDown = 0; handleReceivedPointerDown(event.getActionIndex(), event); } break; case MotionEvent.ACTION_POINTER_DOWN: { @@ -970,47 +1313,22 @@ public class TouchExplorer implements Explorer { } break; } if (DEBUG) { - Slog.i(LOG_TAG, "Received pointer: " + toString()); + Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString()); } } /** - * Processes an injected {@link MotionEvent} event. - * - * @param event The event to process. + * @return The number of received pointers that are down. */ - public void onInjectedMotionEvent(MotionEvent event) { - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: { - handleInjectedPointerDown(event.getActionIndex(), event); - mLastInjectedDownEventTime = event.getDownTime(); - } break; - case MotionEvent.ACTION_POINTER_DOWN: { - handleInjectedPointerDown(event.getActionIndex(), event); - } break; - case MotionEvent.ACTION_UP: { - handleInjectedPointerUp(event.getActionIndex(), event); - } break; - case MotionEvent.ACTION_POINTER_UP: { - handleInjectedPointerUp(event.getActionIndex(), event); - } break; - case MotionEvent.ACTION_HOVER_ENTER: - case MotionEvent.ACTION_HOVER_MOVE: - case MotionEvent.ACTION_HOVER_EXIT: { - mLastInjectedHoverEventAction = event.getActionMasked(); - } break; - } - if (DEBUG) { - Slog.i(LOG_TAG, "Injected pointer: " + toString()); - } + public int getReceivedPointerDownCount() { + return Integer.bitCount(mReceivedPointersDown); } /** - * @return The number of received pointers that are down. + * @return The bits of the pointers that are active. */ - public int getReceivedPointerDownCount() { - return Integer.bitCount(mReceivedPointersDown); + public int getActivePointers() { + return mActivePointers; } /** @@ -1032,24 +1350,6 @@ public class TouchExplorer implements Explorer { } /** - * Whether an injected pointer is down. - * - * @param pointerId The unique pointer id. - * @return True if the pointer is down. - */ - public boolean isInjectedPointerDown(int pointerId) { - final int pointerFlag = (1 << pointerId); - return (mInjectedPointersDown & pointerFlag) != 0; - } - - /** - * @return The number of down pointers injected to the view hierarchy. - */ - public int getInjectedPointerDownCount() { - return Integer.bitCount(mInjectedPointersDown); - } - - /** * Whether an input pointer is active. * * @param pointerId The unique pointer id. @@ -1108,27 +1408,27 @@ public class TouchExplorer implements Explorer { return mLastReceivedUpPointerId; } + /** - * @return Whether the last received pointer that went up was active. + * @return The down X of the last received pointer that went up. */ - public boolean wasLastReceivedUpPointerActive() { - return mLastReceivedUpPointerActive; + public float getLastReceivedUpPointerDownX() { + return mLastReceivedUpPointerDownX; } /** - * @return The time of the last injected down event. + * @return The down Y of the last received pointer that went up. */ - public long getLastInjectedDownEventTime() { - return mLastInjectedDownEventTime; + public float getLastReceivedUpPointerDownY() { + return mLastReceivedUpPointerDownY; } /** - * @return The action of the last injected hover event. + * @return Whether the last received pointer that went up was active. */ - public int getLastInjectedHoverAction() { - return mLastInjectedHoverEventAction; + public boolean wasLastReceivedUpPointerActive() { + return mLastReceivedUpPointerActive; } - /** * Populates the active pointer IDs to the given array. * <p> @@ -1147,18 +1447,10 @@ public class TouchExplorer implements Explorer { } /** - * @return The number of non injected active pointers. - */ - public int getNotInjectedActivePointerCount() { - final int pointerState = mActivePointers & ~mInjectedPointersDown; - return Integer.bitCount(pointerState); - } - - /** * @param pointerId The unique pointer id. * @return Whether the pointer is active or was the last active than went up. */ - private boolean isActiveOrWasLastActiveUpPointer(int pointerId) { + public boolean isActiveOrWasLastActiveUpPointer(int pointerId) { return (isActivePointer(pointerId) || (mLastReceivedUpPointerId == pointerId && mLastReceivedUpPointerActive)); @@ -1177,6 +1469,8 @@ public class TouchExplorer implements Explorer { mLastReceivedUpPointerId = 0; mLastReceivedUpPointerDownTime = 0; mLastReceivedUpPointerActive = false; + mLastReceivedUpPointerDownX = 0; + mLastReceivedUpPointerDownX = 0; mReceivedPointersDown |= pointerFlag; mReceivedPointerDownX[pointerId] = event.getX(pointerIndex); @@ -1217,6 +1511,8 @@ public class TouchExplorer implements Explorer { mLastReceivedUpPointerId = pointerId; mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); mLastReceivedUpPointerActive = isActivePointer(pointerId); + mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId]; + mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId]; mReceivedPointersDown &= ~pointerFlag; mActivePointers &= ~pointerFlag; @@ -1233,33 +1529,6 @@ public class TouchExplorer implements Explorer { } /** - * Handles a injected pointer down event. - * - * @param pointerIndex The index of the pointer that has changed. - * @param event The event to be handled. - */ - private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) { - final int pointerId = event.getPointerId(pointerIndex); - final int pointerFlag = (1 << pointerId); - mInjectedPointersDown |= pointerFlag; - } - - /** - * Handles a injected pointer up event. - * - * @param pointerIndex The index of the pointer that has changed. - * @param event The event to be handled. - */ - private void handleInjectedPointerUp(int pointerIndex, MotionEvent event) { - final int pointerId = event.getPointerId(pointerIndex); - final int pointerFlag = (1 << pointerId); - mInjectedPointersDown &= ~pointerFlag; - if (mInjectedPointersDown == 0) { - mLastInjectedDownEventTime = 0; - } - } - - /** * Detects the active pointers in an event. * * @param event The event to examine. @@ -1348,117 +1617,4 @@ public class TouchExplorer implements Explorer { return builder.toString(); } } - - /** - * Class for delayed sending of long press. - */ - private final class PerformLongPressDelayed implements Runnable { - private MotionEvent mEvent; - private int mPolicyFlags; - - public void post(MotionEvent prototype, int policyFlags, long delay) { - mEvent = MotionEvent.obtain(prototype); - mPolicyFlags = policyFlags; - mHandler.postDelayed(this, delay); - } - - public void remove() { - if (isPenidng()) { - mHandler.removeCallbacks(this); - clear(); - } - } - - private boolean isPenidng() { - return (mEvent != null); - } - - @Override - public void run() { - mCurrentState = STATE_DELEGATING; - // Make sure the scheduled hover exit is delivered. - mSendHoverDelayed.remove(); - final int pointerId = mPointerTracker.getPrimaryActivePointerId(); - final int pointerIdBits = (1 << pointerId); - ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags); - - sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags); - mTouchExploreGestureInProgress = false; - mLastTouchExploreEvent = null; - clear(); - } - - private void clear() { - if (!isPenidng()) { - return; - } - mEvent.recycle(); - mEvent = null; - mPolicyFlags = 0; - } - } - - /** - * Class for delayed sending of hover events. - */ - private final class SendHoverDelayed implements Runnable { - private static final String LOG_TAG = "SendHoverEnterOrExitDelayed"; - - private MotionEvent mEvent; - private int mAction; - private int mPointerIdBits; - private int mPolicyFlags; - - public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags, - long delay) { - remove(); - mEvent = MotionEvent.obtain(prototype); - mAction = action; - mPointerIdBits = pointerIdBits; - mPolicyFlags = policyFlags; - mHandler.postDelayed(this, delay); - } - - public void remove() { - mHandler.removeCallbacks(this); - clear(); - } - - private boolean isPenidng() { - return (mEvent != null); - } - - private void clear() { - if (!isPenidng()) { - return; - } - mEvent.recycle(); - mEvent = null; - mAction = 0; - mPointerIdBits = -1; - mPolicyFlags = 0; - } - - public void forceSendAndRemove() { - if (isPenidng()) { - run(); - remove(); - } - } - - public void run() { - if (DEBUG) { - if (mAction == MotionEvent.ACTION_HOVER_ENTER) { - Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER); - } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) { - Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE"); - } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) { - Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT"); - } - } - - sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags); - clear(); - } - } } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index cffb391..4b40107 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import com.android.internal.R; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.ProcessStats; @@ -32,13 +34,13 @@ import dalvik.system.Zygote; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerNative; +import android.app.ActivityOptions; import android.app.ActivityThread; import android.app.AlertDialog; import android.app.AppGlobals; import android.app.ApplicationErrorReport; import android.app.Dialog; import android.app.IActivityController; -import android.app.IActivityWatcher; import android.app.IApplicationThread; import android.app.IInstrumentationWatcher; import android.app.INotificationManager; @@ -53,15 +55,17 @@ import android.app.Service; import android.app.backup.IBackupManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; +import android.content.ClipData; import android.content.ComponentCallbacks2; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; import android.content.IIntentReceiver; import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -71,11 +75,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.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.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -104,14 +109,16 @@ import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserId; import android.provider.Settings; import android.text.format.Time; import android.util.EventLog; -import android.util.Pair; -import android.util.Slog; import android.util.Log; +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; @@ -133,9 +140,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; -import java.lang.IllegalStateException; 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; @@ -144,13 +151,16 @@ 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; public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { + private static final String USER_DATA_DIR = "/data/user/"; static final String TAG = "ActivityManager"; + static final String TAG_MU = "ActivityManagerServiceMU"; static final boolean DEBUG = false; static final boolean localLOGV = DEBUG; static final boolean DEBUG_SWITCH = localLOGV || false; @@ -159,6 +169,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_OOM_ADJ = localLOGV || false; static final boolean DEBUG_TRANSITION = localLOGV || false; static final boolean DEBUG_BROADCAST = localLOGV || false; + static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; static final boolean DEBUG_SERVICE = localLOGV || false; static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false; @@ -172,6 +183,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_CONFIGURATION = localLOGV || false; static final boolean DEBUG_POWER = localLOGV || false; static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; + static final boolean DEBUG_MU = localLOGV || false; static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; @@ -224,7 +236,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int CPU_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000; // How long we allow a receiver to run before giving up on it. - static final int BROADCAST_TIMEOUT = 10*1000; + 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; @@ -262,7 +275,14 @@ public final class ActivityManagerService extends ActivityManagerNative static final String[] EMPTY_STRING_ARRAY = new String[0]; public ActivityStack mMainStack; - + + private final boolean mHeadless; + + // Whether we should show our dialogs (ANR, crash, etc) or just perform their + // default actuion automatically. Important for devices without direct input + // devices. + private boolean mShowDialogs = true; + /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. @@ -270,42 +290,38 @@ public final class ActivityManagerService extends ActivityManagerNative static class PendingActivityLaunch { ActivityRecord r; ActivityRecord sourceRecord; - Uri[] grantedUriPermissions; - int grantedMode; - boolean onlyIfNeeded; + int startFlags; } final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<PendingActivityLaunch>(); - /** - * List of all active broadcasts that are to be executed immediately - * (without waiting for another broadcast to finish). Currently this only - * contains broadcasts to registered receivers, to avoid spinning up - * a bunch of processes to execute IntentReceiver components. - */ - final ArrayList<BroadcastRecord> mParallelBroadcasts - = new ArrayList<BroadcastRecord>(); - /** - * List of all active broadcasts that are to be executed one at a time. - * The object at the top of the list is the currently activity broadcasts; - * those after it are waiting for the top to finish.. - */ - final ArrayList<BroadcastRecord> mOrderedBroadcasts - = new ArrayList<BroadcastRecord>(); + BroadcastQueue mFgBroadcastQueue; + BroadcastQueue mBgBroadcastQueue; + // Convenient for easy iteration over the queues. Foreground is first + // so that dispatch of foreground broadcasts gets precedence. + final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[2]; - /** - * Historical data of past broadcasts, for debugging. - */ - static final int MAX_BROADCAST_HISTORY = 25; - final BroadcastRecord[] mBroadcastHistory - = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + BroadcastQueue broadcastQueueForIntent(Intent intent) { + final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0; + if (DEBUG_BACKGROUND_BROADCAST) { + Slog.i(TAG, "Broadcast intent " + intent + " on " + + (isFg ? "foreground" : "background") + + " queue"); + } + return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue; + } - /** - * Set when we current have a BROADCAST_INTENT_MSG in flight. - */ - boolean mBroadcastsScheduled = false; + BroadcastRecord broadcastRecordForReceiverLocked(IBinder receiver) { + for (BroadcastQueue queue : mBroadcastQueues) { + BroadcastRecord r = queue.getMatchingOrderedReceiver(receiver); + if (r != null) { + return r; + } + } + return null; + } /** * Activity we have told the window manager to have key focus. @@ -330,6 +346,17 @@ public final class ActivityManagerService extends ActivityManagerNative final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>(); /** + * The currently running isolated processes. + */ + final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<ProcessRecord>(); + + /** + * Counter for assigning isolated process uids, to avoid frequently reusing the + * same ones. + */ + int mNextIsolatedProcessUid = 0; + + /** * The currently running heavy-weight process, if any. */ ProcessRecord mHeavyWeightProcess = null; @@ -459,25 +486,6 @@ public final class ActivityManagerService extends ActivityManagerNative private final StringBuilder mStrictModeBuffer = new StringBuilder(); /** - * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler. - */ - private boolean mPendingBroadcastTimeoutMessage; - - /** - * Intent broadcast that we have tried to start, but are - * waiting for its application's process to be created. We only - * need one (instead of a list) because we always process broadcasts - * one at a time, so no others can be started while waiting for this - * one. - */ - BroadcastRecord mPendingBroadcast = null; - - /** - * The receiver index that is pending, to restart the broadcast if needed. - */ - int mPendingBroadcastRecvIndex; - - /** * Keeps track of all IIntentReceivers that have been registered for * broadcasts. Hash keys are the receiver IBinder, hash value is * a ReceiverList. @@ -516,17 +524,7 @@ public final class ActivityManagerService extends ActivityManagerNative final HashMap<String, ArrayList<Intent>> mStickyBroadcasts = new HashMap<String, ArrayList<Intent>>(); - /** - * All currently running services. - */ - final HashMap<ComponentName, ServiceRecord> mServices = - new HashMap<ComponentName, ServiceRecord>(); - - /** - * All currently running services indexed by the Intent used to start them. - */ - final HashMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = - new HashMap<Intent.FilterComparison, ServiceRecord>(); + final ServiceMap mServiceMap = new ServiceMap(); /** * All currently bound service connections. Keys are the IBinder of @@ -574,23 +572,7 @@ public final class ActivityManagerService extends ActivityManagerNative */ final ArrayList mCancelledThumbnails = new ArrayList(); - /** - * All of the currently running global content providers. Keys are a - * string containing the provider name and values are a - * ContentProviderRecord object containing the data about it. Note - * that a single provider may be published under multiple names, so - * there may be multiple entries here for a single one in mProvidersByClass. - */ - final HashMap<String, ContentProviderRecord> mProvidersByName - = new HashMap<String, ContentProviderRecord>(); - - /** - * All of the currently running global content providers. Keys are a - * string containing the provider's implementation class and values are a - * ContentProviderRecord object containing the data about it. - */ - final HashMap<ComponentName, ContentProviderRecord> mProvidersByClass - = new HashMap<ComponentName, ContentProviderRecord>(); + final ProviderMap mProviderMap = new ProviderMap(); /** * List of content providers who have clients waiting for them. The @@ -622,6 +604,7 @@ public final class ActivityManagerService extends ActivityManagerNative uid = _uid; } } + private static ThreadLocal<Identity> sCallerIdentity = new ThreadLocal<Identity>(); /** @@ -714,6 +697,16 @@ public final class ActivityManagerService extends ActivityManagerNative boolean mSleeping = false; /** + * State of external calls telling us if the device is asleep. + */ + boolean mWentToSleep = false; + + /** + * State of external call telling us if the lock screen is shown. + */ + boolean mLockScreenShown = false; + + /** * Set if we are shutting down the system, similar to sleeping. */ boolean mShuttingDown = false; @@ -768,9 +761,7 @@ public final class ActivityManagerService extends ActivityManagerNative ParcelFileDescriptor mProfileFd; int mProfileType = 0; boolean mAutoStopProfiler = false; - - final RemoteCallbackList<IActivityWatcher> mWatchers - = new RemoteCallbackList<IActivityWatcher>(); + String mOpenGlTraceApp = null; final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<IProcessObserver>(); @@ -848,8 +839,6 @@ public final class ActivityManagerService extends ActivityManagerNative static final int UPDATE_CONFIGURATION_MSG = 4; static final int GC_BACKGROUND_PROCESSES_MSG = 5; static final int WAIT_FOR_DEBUGGER_MSG = 6; - static final int BROADCAST_INTENT_MSG = 7; - static final int BROADCAST_TIMEOUT_MSG = 8; static final int SERVICE_TIMEOUT_MSG = 12; static final int UPDATE_TIME_ZONE = 13; static final int SHOW_UID_ERROR_MSG = 14; @@ -869,6 +858,10 @@ public final class ActivityManagerService extends ActivityManagerNative static final int DISPATCH_PROCESS_DIED = 32; static final int REPORT_MEM_USAGE = 33; + static final int FIRST_ACTIVITY_STACK_MSG = 100; + static final int FIRST_BROADCAST_QUEUE_MSG = 200; + static final int FIRST_COMPAT_MODE_MSG = 300; + AlertDialog mUidAlert; CompatModeDialog mCompatModeDialog; long mLastMemUsageReportTime = 0; @@ -889,7 +882,7 @@ public final class ActivityManagerService extends ActivityManagerNative return; } AppErrorResult res = (AppErrorResult) data.get("result"); - if (!mSleeping && !mShuttingDown) { + if (mShowDialogs && !mSleeping && !mShuttingDown) { Dialog d = new AppErrorDialog(mContext, res, proc); d.show(); proc.crashDialog = d; @@ -913,16 +906,22 @@ public final class ActivityManagerService extends ActivityManagerNative Intent intent = new Intent("android.intent.action.ANR"); if (!mProcessesReady) { - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND); } broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, - false, false, MY_PID, Process.SYSTEM_UID); + false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); - Dialog d = new AppNotRespondingDialog(ActivityManagerService.this, - mContext, proc, (ActivityRecord)data.get("activity")); - d.show(); - proc.anrDialog = d; + if (mShowDialogs) { + Dialog d = new AppNotRespondingDialog(ActivityManagerService.this, + mContext, proc, (ActivityRecord)data.get("activity")); + d.show(); + proc.anrDialog = d; + } else { + // Just kill the app if there is no dialog to be shown. + killAppAtUsersRequest(proc, null); + } } ensureBootCompleted(); @@ -940,7 +939,7 @@ public final class ActivityManagerService extends ActivityManagerNative return; } AppErrorResult res = (AppErrorResult) data.get("result"); - if (!mSleeping && !mShuttingDown) { + if (mShowDialogs && !mSleeping && !mShuttingDown) { Dialog d = new StrictModeViolationDialog(mContext, res, proc); d.show(); proc.crashDialog = d; @@ -987,16 +986,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } } break; - case BROADCAST_INTENT_MSG: { - if (DEBUG_BROADCAST) Slog.v( - TAG, "Received BROADCAST_INTENT_MSG"); - processNextBroadcast(true); - } break; - case BROADCAST_TIMEOUT_MSG: { - synchronized (ActivityManagerService.this) { - broadcastTimeoutLocked(true); - } - } break; case SERVICE_TIMEOUT_MSG: { if (mDidDexOpt) { mDidDexOpt = false; @@ -1060,16 +1049,22 @@ public final class ActivityManagerService extends ActivityManagerNative } } break; case SHOW_UID_ERROR_MSG: { - // XXX This is a temporary dialog, no need to localize. - AlertDialog d = new BaseErrorDialog(mContext); - d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); - d.setCancelable(false); - d.setTitle("System UIDs Inconsistent"); - d.setMessage("UIDs on the system are inconsistent, you need to wipe your data partition or your device will be unstable."); - d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky", - mHandler.obtainMessage(IM_FEELING_LUCKY_MSG)); - mUidAlert = d; - d.show(); + String title = "System UIDs Inconsistent"; + String text = "UIDs on the system are inconsistent, you need to wipe your" + + " data partition or your device will be unstable."; + Log.e(TAG, title + ": " + text); + if (mShowDialogs) { + // XXX This is a temporary dialog, no need to localize. + AlertDialog d = new BaseErrorDialog(mContext); + d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); + d.setCancelable(false); + d.setTitle(title); + d.setMessage(text); + d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky", + mHandler.obtainMessage(IM_FEELING_LUCKY_MSG)); + mUidAlert = d; + d.show(); + } } break; case IM_FEELING_LUCKY_MSG: { if (mUidAlert != null) { @@ -1100,7 +1095,8 @@ public final class ActivityManagerService extends ActivityManagerNative int uid = msg.arg1; boolean restart = (msg.arg2 == 1); String pkg = (String) msg.obj; - forceStopPackageLocked(pkg, uid, restart, false, true, false); + forceStopPackageLocked(pkg, uid, restart, false, true, false, + UserId.getUserId(uid)); } } break; case FINALIZE_PENDING_INTENT_MSG: { @@ -1299,9 +1295,10 @@ public final class ActivityManagerService extends ActivityManagerNative try { ActivityManagerService m = mSelf; - ServiceManager.addService("activity", m); + ServiceManager.addService("activity", m, true); ServiceManager.addService("meminfo", new MemBinder(m)); ServiceManager.addService("gfxinfo", new GraphicsBinder(m)); + ServiceManager.addService("dbinfo", new DbBinder(m)); if (MONITOR_CPU_USAGE) { ServiceManager.addService("cpuinfo", new CpuBinder(m)); } @@ -1309,17 +1306,17 @@ public final class ActivityManagerService extends ActivityManagerNative ApplicationInfo info = mSelf.mContext.getPackageManager().getApplicationInfo( - "android", STOCK_PM_FLAGS); + "android", STOCK_PM_FLAGS); mSystemThread.installSystemApplicationInfo(info); synchronized (mSelf) { ProcessRecord app = mSelf.newProcessRecordLocked( mSystemThread.getApplicationThread(), info, - info.processName); + info.processName, false); app.persistent = true; app.pid = MY_PID; app.maxAdj = ProcessList.SYSTEM_ADJ; - mSelf.mProcessNames.put(app.processName, app.info.uid, app); + mSelf.mProcessNames.put(app.processName, app.uid, app); synchronized (mSelf.mPidsSelfLocked) { mSelf.mPidsSelfLocked.put(app.pid, app); } @@ -1456,6 +1453,26 @@ public final class ActivityManagerService extends ActivityManagerNative } } + static class DbBinder extends Binder { + ActivityManagerService mActivityManagerService; + DbBinder(ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mActivityManagerService.checkCallingPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump dbinfo from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + + mActivityManagerService.dumpDbInfo(fd, pw, args); + } + } + static class CpuBinder extends Binder { ActivityManagerService mActivityManagerService; CpuBinder(ActivityManagerService activityManagerService) { @@ -1483,6 +1500,11 @@ public final class ActivityManagerService extends ActivityManagerNative private ActivityManagerService() { Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass()); + mFgBroadcastQueue = new BroadcastQueue(this, "foreground", BROADCAST_FG_TIMEOUT); + mBgBroadcastQueue = new BroadcastQueue(this, "background", BROADCAST_BG_TIMEOUT); + mBroadcastQueues[0] = mFgBroadcastQueue; + mBroadcastQueues[1] = mBgBroadcastQueue; + File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); systemDir.mkdirs(); @@ -1496,6 +1518,7 @@ public final class ActivityManagerService extends ActivityManagerNative mUsageStatsService = new UsageStatsService(new File( systemDir, "usagestats").toString()); + mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0")); GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED); @@ -1791,7 +1814,11 @@ public final class ActivityManagerService extends ActivityManagerNative // should never happen). SparseArray<ProcessRecord> procs = mProcessNames.getMap().get( processName); - return procs != null ? procs.valueAt(0) : null; + 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); + } } ProcessRecord proc = mProcessNames.get(processName, uid); return proc; @@ -1816,8 +1843,15 @@ public final class ActivityManagerService extends ActivityManagerNative final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, - String hostingType, ComponentName hostingName, boolean allowWhileBooting) { - ProcessRecord app = getProcessRecordLocked(processName, info.uid); + String hostingType, ComponentName hostingName, boolean allowWhileBooting, + boolean isolated) { + ProcessRecord app; + if (!isolated) { + app = getProcessRecordLocked(processName, info.uid); + } else { + // If this is an isolated process, it can't re-use an existing process. + app = null; + } // We don't have to do anything more if: // (1) There is an existing application record; and // (2) The caller doesn't think it is dead, OR there is no thread @@ -1846,36 +1880,46 @@ public final class ActivityManagerService extends ActivityManagerNative String hostingNameStr = hostingName != null ? hostingName.flattenToShortString() : null; - - if ((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) { - // If we are in the background, then check to see if this process - // is bad. If so, we will just silently fail. - if (mBadProcesses.get(info.processName, info.uid) != null) { - if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid + + if (!isolated) { + if ((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) { + // If we are in the background, then check to see if this process + // is bad. If so, we will just silently fail. + if (mBadProcesses.get(info.processName, info.uid) != null) { + if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid + + "/" + info.processName); + return null; + } + } else { + // When the user is explicitly starting a process, then clear its + // crash count so that we won't make it bad until they see at + // least one crash dialog again, and make the process good again + // if it had been bad. + if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid + "/" + info.processName); - return null; - } - } else { - // When the user is explicitly starting a process, then clear its - // crash count so that we won't make it bad until they see at - // least one crash dialog again, and make the process good again - // if it had been bad. - if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid - + "/" + info.processName); - mProcessCrashTimes.remove(info.processName, info.uid); - if (mBadProcesses.get(info.processName, info.uid) != null) { - EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, info.uid, - info.processName); - mBadProcesses.remove(info.processName, info.uid); - if (app != null) { - app.bad = false; + mProcessCrashTimes.remove(info.processName, info.uid); + if (mBadProcesses.get(info.processName, info.uid) != null) { + EventLog.writeEvent(EventLogTags.AM_PROC_GOOD, info.uid, + info.processName); + mBadProcesses.remove(info.processName, info.uid); + if (app != null) { + app.bad = false; + } } } } - + if (app == null) { - app = newProcessRecordLocked(null, info, processName); - mProcessNames.put(processName, info.uid, app); + app = newProcessRecordLocked(null, info, processName, isolated); + if (app == null) { + Slog.w(TAG, "Failed making new process record for " + + processName + "/" + info.uid + " isolated=" + isolated); + return null; + } + mProcessNames.put(processName, app.uid, app); + if (isolated) { + mIsolatedProcesses.put(app.uid, app); + } } else { // If this is a new package in the process, add the package to the list app.addPackage(info.packageName); @@ -1921,13 +1965,16 @@ public final class ActivityManagerService extends ActivityManagerNative mProcDeaths[0] = 0; try { - int uid = app.info.uid; + int uid = app.uid; + int[] gids = null; - try { - gids = mContext.getPackageManager().getPackageGids( - app.info.packageName); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Unable to retrieve gids", e); + if (!app.isolated) { + try { + gids = mContext.getPackageManager().getPackageGids( + app.info.packageName); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Unable to retrieve gids", e); + } } if (mFactoryTest != SystemServer.FACTORY_TEST_OFF) { if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL @@ -2033,7 +2080,14 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean startHomeActivityLocked() { + boolean startHomeActivityLocked(int userId) { + if (mHeadless) { + // Added because none of the other calls to ensureBootCompleted seem to fire + // when running headless. + ensureBootCompleted(); + return false; + } + if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL && mTopAction == null) { // We are running in factory test mode, but unable to find @@ -2056,19 +2110,20 @@ public final class ActivityManagerService extends ActivityManagerNative aInfo.applicationInfo.packageName, aInfo.name)); // Don't do this if the home app is currently being // instrumented. + aInfo = new ActivityInfo(aInfo); + aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId); ProcessRecord app = getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid); if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo, - null, null, 0, 0, 0, false, false, null); + mMainStack.startActivityLocked(null, intent, null, aInfo, + null, null, 0, 0, 0, 0, null, false, null); } } - - + return true; } - + /** * Starts the "new version setup screen" if appropriate. */ @@ -2116,8 +2171,8 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); - mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo, - null, null, 0, 0, 0, false, false, null); + mMainStack.startActivityLocked(null, intent, null, ri.activityInfo, + null, null, 0, 0, 0, 0, null, false, null); } } } @@ -2127,37 +2182,52 @@ public final class ActivityManagerService extends ActivityManagerNative return mCompatModePackages.compatibilityInfoForPackageLocked(ai); } + void enforceNotIsolatedCaller(String caller) { + if (UserId.isIsolated(Binder.getCallingUid())) { + throw new SecurityException("Isolated process not allowed to call " + caller); + } + } + public int getFrontActivityScreenCompatMode() { + enforceNotIsolatedCaller("getFrontActivityScreenCompatMode"); synchronized (this) { return mCompatModePackages.getFrontActivityScreenCompatModeLocked(); } } public void setFrontActivityScreenCompatMode(int mode) { + enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY, + "setFrontActivityScreenCompatMode"); synchronized (this) { mCompatModePackages.setFrontActivityScreenCompatModeLocked(mode); } } public int getPackageScreenCompatMode(String packageName) { + enforceNotIsolatedCaller("getPackageScreenCompatMode"); synchronized (this) { return mCompatModePackages.getPackageScreenCompatModeLocked(packageName); } } public void setPackageScreenCompatMode(String packageName, int mode) { + enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY, + "setPackageScreenCompatMode"); synchronized (this) { mCompatModePackages.setPackageScreenCompatModeLocked(packageName, mode); } } public boolean getPackageAskScreenCompat(String packageName) { + enforceNotIsolatedCaller("getPackageAskScreenCompat"); synchronized (this) { return mCompatModePackages.getPackageAskCompatModeLocked(packageName); } } public void setPackageAskScreenCompat(String packageName, boolean ask) { + enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY, + "setPackageAskScreenCompat"); synchronized (this) { mCompatModePackages.setPackageAskCompatModeLocked(packageName, ask); } @@ -2168,19 +2238,6 @@ public final class ActivityManagerService extends ActivityManagerNative final int identHash = System.identityHashCode(r); updateUsageStats(r, true); - - int i = mWatchers.beginBroadcast(); - while (i > 0) { - i--; - IActivityWatcher w = mWatchers.getBroadcastItem(i); - if (w != null) { - try { - w.activityResuming(identHash); - } catch (RemoteException e) { - } - } - } - mWatchers.finishBroadcast(); } private void dispatchForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { @@ -2221,50 +2278,64 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<N; i++) { PendingActivityLaunch pal = mPendingActivityLaunches.get(i); mMainStack.startActivityUncheckedLocked(pal.r, pal.sourceRecord, - pal.grantedUriPermissions, pal.grantedMode, pal.onlyIfNeeded, - doResume && i == (N-1)); + pal.startFlags, doResume && i == (N-1), null); } mPendingActivityLaunches.clear(); } public final int startActivity(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, - String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, + String profileFile, ParcelFileDescriptor profileFd, Bundle options) { + 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(); + } + } return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler, - null, null); + resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, + null, null, options, userId); } public final WaitResult startActivityAndWait(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, - String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options) { + enforceNotIsolatedCaller("startActivityAndWait"); WaitResult res = new WaitResult(); + int userId = Binder.getOrigCallingUser(); mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler, - res, null); + resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, + res, null, options, userId); return res; } - + public final int startActivityWithConfig(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, Configuration config) { - return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, null, null, false, null, config); + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, Configuration config, + Bundle options) { + enforceNotIsolatedCaller("startActivityWithConfig"); + int ret = mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, + resultTo, resultWho, requestCode, startFlags, + null, null, null, config, options, Binder.getOrigCallingUser()); + return ret; } public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, - int flagsMask, int flagsValues) { + int flagsMask, int flagsValues, Bundle options) { + enforceNotIsolatedCaller("startActivityIntentSender"); // Refuse possible leaked file descriptors if (fillInIntent != null && fillInIntent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -2286,13 +2357,13 @@ public final class ActivityManagerService extends ActivityManagerNative mAppSwitchesAllowedTime = 0; } } - - return pir.sendInner(0, fillInIntent, resolvedType, null, - null, resultTo, resultWho, requestCode, flagsMask, flagsValues); + int ret = pir.sendInner(0, fillInIntent, resolvedType, null, null, + resultTo, resultWho, requestCode, flagsMask, flagsValues, options); + return ret; } public boolean startNextMatchingActivity(IBinder callingActivity, - Intent intent) { + Intent intent, Bundle options) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -2301,10 +2372,12 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { ActivityRecord r = mMainStack.isInStackLocked(callingActivity); if (r == null) { + ActivityOptions.abort(options); return false; } if (r.app == null || r.app.thread == null) { // The caller is not running... d'oh! + ActivityOptions.abort(options); return false; } intent = new Intent(intent); @@ -2318,7 +2391,8 @@ public final class ActivityManagerService extends ActivityManagerNative List<ResolveInfo> resolves = AppGlobals.getPackageManager().queryIntentActivities( intent, r.resolvedType, - PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); + PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS, + UserId.getCallingUserId()); // Look for the original activity in the list... final int N = resolves != null ? resolves.size() : 0; @@ -2340,6 +2414,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (aInfo == null) { // Nobody who is next! + ActivityOptions.abort(options); return false; } @@ -2369,16 +2444,14 @@ public final class ActivityManagerService extends ActivityManagerNative } final long origId = Binder.clearCallingIdentity(); - // XXX we are not dealing with propagating grantedUriPermissions... - // those are not yet exposed to user code, so there is no need. int res = mMainStack.startActivityLocked(r.app.thread, intent, - r.resolvedType, null, 0, aInfo, - resultTo != null ? resultTo.appToken : null, resultWho, - requestCode, -1, r.launchedFromUid, false, false, null); + r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null, + resultWho, requestCode, -1, r.launchedFromUid, 0, + options, false, null); Binder.restoreCallingIdentity(origId); r.finishing = wasFinishing; - if (res != START_SUCCESS) { + if (res != ActivityManager.START_SUCCESS) { return false; } return true; @@ -2387,28 +2460,34 @@ public final class ActivityManagerService extends ActivityManagerNative public final int startActivityInPackage(int uid, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded) { + 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"); } - return mMainStack.startActivityMayWait(null, uid, intent, resolvedType, - null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, - null, null, false, null, null); + int ret = mMainStack.startActivityMayWait(null, uid, intent, resolvedType, + resultTo, resultWho, requestCode, startFlags, + null, null, null, null, options, userId); + return ret; } public final int startActivities(IApplicationThread caller, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) { - return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo); + Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options) { + enforceNotIsolatedCaller("startActivities"); + int ret = mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo, + options, Binder.getOrigCallingUser()); + return ret; } public final int startActivitiesInPackage(int uid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options) { // This is so super not safe, that only the system (or okay root) // can do it. @@ -2417,8 +2496,9 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException( "startActivityInPackage only available to the system"); } - - return mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo); + int ret = mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo, + options, UserId.getUserId(uid)); + return ret; } final void addRecentTaskLocked(TaskRecord task) { @@ -2430,8 +2510,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Remove any existing entries that are the same kind of task. for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); - if ((task.affinity != null && task.affinity.equals(tr.affinity)) - || (task.intent != null && task.intent.filterEquals(tr.intent))) { + if (task.userId == tr.userId + && ((task.affinity != null && task.affinity.equals(tr.affinity)) + || (task.intent != null && task.intent.filterEquals(tr.intent)))) { mRecentTasks.remove(i); i--; N--; @@ -2576,7 +2657,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (mPidsSelfLocked) { for (int i=0; i<mPidsSelfLocked.size(); i++) { ProcessRecord p = mPidsSelfLocked.valueAt(i); - if (p.info.uid != uid) { + if (p.uid != uid) { continue; } if (p.pid == initialPid) { @@ -2616,26 +2697,18 @@ public final class ActivityManagerService extends ActivityManagerNative public final void finishSubActivity(IBinder token, String resultWho, int requestCode) { synchronized(this) { - ActivityRecord self = mMainStack.isInStackLocked(token); - if (self == null) { - return; - } - final long origId = Binder.clearCallingIdentity(); + mMainStack.finishSubActivityLocked(token, resultWho, requestCode); + Binder.restoreCallingIdentity(origId); + } + } - int i; - for (i=mMainStack.mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - if (r.resultTo == self && r.requestCode == requestCode) { - if ((r.resultWho == null && resultWho == null) || - (r.resultWho != null && r.resultWho.equals(resultWho))) { - mMainStack.finishActivityLocked(r, i, - Activity.RESULT_CANCELED, null, "request-sub"); - } - } - } - + public boolean finishActivityAffinity(IBinder token) { + synchronized(this) { + final long origId = Binder.clearCallingIdentity(); + boolean res = mMainStack.finishActivityAffinityLocked(token); Binder.restoreCallingIdentity(origId); + return res; } } @@ -2783,7 +2856,6 @@ public final class ActivityManagerService extends ActivityManagerNative private final int getLRURecordIndexForAppLocked(IApplicationThread thread) { IBinder threadBinder = thread.asBinder(); - // Find the application record. for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord rec = mLruProcesses.get(i); @@ -2975,23 +3047,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private final class AppNotResponding implements Runnable { - private final ProcessRecord mApp; - private final String mAnnotation; - - public AppNotResponding(ProcessRecord app, String annotation) { - mApp = app; - mAnnotation = annotation; - } - - @Override - public void run() { - appNotResponding(mApp, null, null, mAnnotation); - } - } - final void logAppTooSlow(ProcessRecord app, long startTime, String msg) { - if (IS_USER_BUILD) { + if (true || IS_USER_BUILD) { return; } String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null); @@ -3128,7 +3185,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // Log the ANR to the main log. - StringBuilder info = mStringBuilder; + StringBuilder info = new StringBuilder(); info.setLength(0); info.append("ANR in ").append(app.processName); if (activity != null && activity.shortComponentName != null) { @@ -3238,7 +3295,8 @@ public final class ActivityManagerService extends ActivityManagerNative } public boolean clearApplicationUserData(final String packageName, - final IPackageDataObserver observer) { + final IPackageDataObserver observer, final int userId) { + enforceNotIsolatedCaller("clearApplicationUserData"); int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); @@ -3247,7 +3305,7 @@ public final class ActivityManagerService extends ActivityManagerNative int pkgUid = -1; synchronized(this) { try { - pkgUid = pm.getPackageUid(packageName); + pkgUid = pm.getPackageUid(packageName, userId); } catch (RemoteException e) { } if (pkgUid == -1) { @@ -3268,12 +3326,12 @@ public final class ActivityManagerService extends ActivityManagerNative try { //clear application user data - pm.clearApplicationUserData(packageName, observer); + pm.clearApplicationUserData(packageName, observer, userId); Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED, Uri.fromParts("package", packageName, null)); intent.putExtra(Intent.EXTRA_UID, pkgUid); broadcastIntentInPackage("android", Process.SYSTEM_UID, intent, - null, null, 0, null, null, null, false, false); + null, null, 0, null, null, null, false, false, userId); } catch (RemoteException e) { } } finally { @@ -3295,13 +3353,14 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } + int userId = UserId.getCallingUserId(); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); int pkgUid = -1; synchronized(this) { try { - pkgUid = pm.getPackageUid(packageName); + pkgUid = pm.getPackageUid(packageName, userId); } catch (RemoteException e) { } if (pkgUid == -1) { @@ -3368,14 +3427,14 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - + final int userId = UserId.getCallingUserId(); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); int pkgUid = -1; synchronized(this) { try { - pkgUid = pm.getPackageUid(packageName); + pkgUid = pm.getPackageUid(packageName, userId); } catch (RemoteException e) { } if (pkgUid == -1) { @@ -3384,7 +3443,7 @@ public final class ActivityManagerService extends ActivityManagerNative } forceStopPackageLocked(packageName, pkgUid); try { - pm.setPackageStoppedState(packageName, true); + pm.setPackageStoppedState(packageName, true, userId); } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " @@ -3425,6 +3484,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public void closeSystemDialogs(String reason) { + enforceNotIsolatedCaller("closeSystemDialogs"); Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); if (reason != null) { @@ -3434,22 +3494,9 @@ public final class ActivityManagerService extends ActivityManagerNative final int uid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); synchronized (this) { - int i = mWatchers.beginBroadcast(); - while (i > 0) { - i--; - IActivityWatcher w = mWatchers.getBroadcastItem(i); - if (w != null) { - try { - w.closingSystemDialogs(reason); - } catch (RemoteException e) { - } - } - } - mWatchers.finishBroadcast(); - mWindowManager.closeSystemDialogs(reason); - for (i=mMainStack.mHistory.size()-1; i>=0; i--) { + 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, @@ -3458,13 +3505,14 @@ public final class ActivityManagerService extends ActivityManagerNative } broadcastIntentLocked(null, null, intent, null, - null, 0, null, null, null, false, false, -1, uid); + null, 0, null, null, null, false, false, -1, uid, 0 /* TODO: Verify */); } Binder.restoreCallingIdentity(origId); } public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) throws RemoteException { + enforceNotIsolatedCaller("getProcessMemoryInfo"); Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length]; for (int i=pids.length-1; i>=0; i--) { infos[i] = new Debug.MemoryInfo(); @@ -3474,6 +3522,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public long[] getProcessPss(int[] pids) throws RemoteException { + enforceNotIsolatedCaller("getProcessPss"); long[] pss = new long[pids.length]; for (int i=pids.length-1; i>=0; i--) { pss[i] = Debug.getPss(pids[i]); @@ -3509,7 +3558,7 @@ public final class ActivityManagerService extends ActivityManagerNative } private void forceStopPackageLocked(final String packageName, int uid) { - forceStopPackageLocked(packageName, uid, false, false, true, false); + forceStopPackageLocked(packageName, uid, false, false, true, false, UserId.getUserId(uid)); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, Uri.fromParts("package", packageName, null)); if (!mProcessesReady) { @@ -3518,7 +3567,8 @@ public final class ActivityManagerService extends ActivityManagerNative intent.putExtra(Intent.EXTRA_UID, uid); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, - false, false, MY_PID, Process.SYSTEM_UID); + false, false, + MY_PID, Process.SYSTEM_UID, UserId.getUserId(uid)); } private final boolean killPackageProcessesLocked(String packageName, int uid, @@ -3542,9 +3592,12 @@ public final class ActivityManagerService extends ActivityManagerNative if (doit) { procs.add(app); } - } else if ((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid) - || app.processName.equals(packageName) - || app.processName.startsWith(procNamePrefix)) { + // 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; @@ -3565,13 +3618,13 @@ public final class ActivityManagerService extends ActivityManagerNative private final boolean forceStopPackageLocked(String name, int uid, boolean callerWillRestart, boolean purgeCache, boolean doit, - boolean evenPersistent) { + boolean evenPersistent, int userId) { int i; int N; if (uid < 0) { try { - uid = AppGlobals.getPackageManager().getPackageUid(name); + uid = AppGlobals.getPackageManager().getPackageUid(name, userId); } catch (RemoteException e) { } } @@ -3595,7 +3648,8 @@ public final class ActivityManagerService extends ActivityManagerNative for (i=0; i<mMainStack.mHistory.size(); i++) { ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); final boolean samePackage = r.packageName.equals(name); - if ((samePackage || r.task == lastTask) + if (r.userId == userId + && (samePackage || r.task == lastTask) && (r.app == null || evenPersistent || !r.app.persistent)) { if (!doit) { if (r.finishing) { @@ -3615,14 +3669,14 @@ public final class ActivityManagerService extends ActivityManagerNative } lastTask = r.task; if (r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "force-stop")) { + null, "force-stop", true)) { i--; } } } ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord service : mServices.values()) { + for (ServiceRecord service : mServiceMap.getAllServices(userId)) { if (service.packageName.equals(name) && (service.app == null || evenPersistent || !service.app.persistent)) { if (!doit) { @@ -3634,6 +3688,7 @@ public final class ActivityManagerService extends ActivityManagerNative service.app.removed = true; } service.app = null; + service.isolatedProc = null; services.add(service); } } @@ -3644,7 +3699,7 @@ public final class ActivityManagerService extends ActivityManagerNative } ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); - for (ContentProviderRecord provider : mProvidersByClass.values()) { + for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(userId).values()) { if (provider.info.packageName.equals(name) && (provider.proc == null || evenPersistent || !provider.proc.persistent)) { if (!doit) { @@ -3679,12 +3734,13 @@ public final class ActivityManagerService extends ActivityManagerNative private final boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart, boolean allowRestart, String reason) { final String name = app.processName; - final int uid = app.info.uid; + final int uid = app.uid; if (DEBUG_PROCESSES) Slog.d( TAG, "Force removing proc " + app.toShortString() + " (" + name + "/" + uid + ")"); mProcessNames.remove(name, uid); + mIsolatedProcesses.remove(app.uid); if (mHeavyWeightProcess == app) { mHeavyWeightProcess = null; mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); @@ -3701,9 +3757,9 @@ public final class ActivityManagerService extends ActivityManagerNative mLruProcesses.remove(app); Process.killProcessQuiet(pid); - if (app.persistent) { + if (app.persistent && !app.isolated) { if (!callerWillRestart) { - addAppLocked(app.info); + addAppLocked(app.info, false); } else { needRestart = true; } @@ -3728,9 +3784,10 @@ public final class ActivityManagerService extends ActivityManagerNative if (gone) { Slog.w(TAG, "Process " + app + " failed to attach"); - EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, pid, app.info.uid, + EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, pid, app.uid, app.processName); - mProcessNames.remove(app.processName, app.info.uid); + mProcessNames.remove(app.processName, app.uid); + mIsolatedProcesses.remove(app.uid); if (mHeavyWeightProcess == app) { mHeavyWeightProcess = null; mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); @@ -3740,9 +3797,11 @@ public final class ActivityManagerService extends ActivityManagerNative // 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.info.uid == sr.appInfo.uid - && app.processName.equals(sr.processName)) { + 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); @@ -3761,12 +3820,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Can't happen; the backup manager is local } } - if (mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid) { + if (isPendingBroadcastProcessLocked(pid)) { Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping"); - mPendingBroadcast.state = BroadcastRecord.IDLE; - mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; - mPendingBroadcast = null; - scheduleBroadcastsLocked(); + skipPendingBroadcastLocked(pid); } } else { Slog.w(TAG, "Spurious process start timeout - pid not known for " + app); @@ -3871,6 +3927,11 @@ public final class ActivityManagerService extends ActivityManagerNative profileFd = mProfileFd; profileAutoStop = mAutoStopProfiler; } + boolean enableOpenGlTrace = false; + if (mOpenGlTraceApp != null && mOpenGlTraceApp.equals(processName)) { + enableOpenGlTrace = true; + mOpenGlTraceApp = null; + } // If the app is being launched for restore or full backup, set it up specially boolean isRestrictedBackupMode = false; @@ -3896,8 +3957,8 @@ public final class ActivityManagerService extends ActivityManagerNative } thread.bindApplication(processName, appInfo, providers, app.instrumentationClass, profileFile, profileFd, profileAutoStop, - app.instrumentationArguments, app.instrumentationWatcher, testMode, - isRestrictedBackupMode || !normalMode, app.persistent, + app.instrumentationArguments, app.instrumentationWatcher, testMode, + enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent, new Configuration(mConfiguration), app.compat, getCommonServicesLocked(), mCoreSettingsObserver.getCoreSettingsLocked()); updateLruProcessLocked(app, false, true); @@ -3926,10 +3987,12 @@ public final class ActivityManagerService extends ActivityManagerNative // See if the top visible activity is waiting to run in this process... ActivityRecord hr = mMainStack.topRunningActivityLocked(null); if (hr != null && normalMode) { - if (hr.app == null && app.info.uid == hr.info.applicationInfo.uid + if (hr.app == null && app.uid == hr.info.applicationInfo.uid && processName.equals(hr.processName)) { try { - if (mMainStack.realStartActivityLocked(hr, app, true, true)) { + if (mHeadless) { + Slog.e(TAG, "Starting activities not supported on headless device: " + hr); + } else if (mMainStack.realStartActivityLocked(hr, app, true, true)) { didSomething = true; } } catch (Exception e) { @@ -3948,8 +4011,8 @@ public final class ActivityManagerService extends ActivityManagerNative try { for (int i=0; i<mPendingServices.size(); i++) { sr = mPendingServices.get(i); - if (app.info.uid != sr.appInfo.uid - || !processName.equals(sr.processName)) { + if (app != sr.isolatedProc && (app.uid != sr.appInfo.uid + || !processName.equals(sr.processName))) { continue; } @@ -3965,28 +4028,18 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // Check if the next broadcast receiver is in this process... - BroadcastRecord br = mPendingBroadcast; - if (!badApp && br != null && br.curApp == app) { + // Check if a next-broadcast receiver is in this process... + if (!badApp && isPendingBroadcastProcessLocked(pid)) { try { - mPendingBroadcast = null; - processCurBroadcastLocked(br, app); - didSomething = true; + didSomething = sendPendingBroadcastsLocked(app); } catch (Exception e) { - Slog.w(TAG, "Exception in new application when starting receiver " - + br.curComponent.flattenToShortString(), e); + // If the app died trying to launch the receiver we declare it 'bad' badApp = true; - logBroadcastReceiverDiscardLocked(br); - finishReceiverLocked(br.receiver, br.resultCode, br.resultData, - br.resultExtras, br.resultAbort, true); - scheduleBroadcastsLocked(); - // We need to reset the state if we fails to start the receiver. - br.state = BroadcastRecord.IDLE; } } // Check whether the next backup agent is in this process... - if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.info.uid) { + if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.uid) { if (DEBUG_BACKUP) Slog.v(TAG, "New app is backup target, launching agent for " + app); ensurePackageDexOpt(mBackupTarget.appInfo.packageName); try { @@ -4048,12 +4101,23 @@ public final class ActivityManagerService extends ActivityManagerNative } public void showBootMessage(final CharSequence msg, final boolean always) { + enforceNotIsolatedCaller("showBootMessage"); mWindowManager.showBootMessage(msg, always); } public void dismissKeyguardOnNextActivity() { - synchronized (this) { - mMainStack.dismissKeyguardOnNextActivityLocked(); + enforceNotIsolatedCaller("dismissKeyguardOnNextActivity"); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (this) { + if (mLockScreenShown) { + mLockScreenShown = false; + comeOutOfSleepIfNeededLocked(); + } + mMainStack.dismissKeyguardOnNextActivityLocked(); + } + } finally { + Binder.restoreCallingIdentity(token); } } @@ -4068,16 +4132,25 @@ public final class ActivityManagerService extends ActivityManagerNative if (pkgs != null) { for (String pkg : pkgs) { synchronized (ActivityManagerService.this) { - if (forceStopPackageLocked(pkg, -1, false, false, false, false)) { - setResultCode(Activity.RESULT_OK); - return; - } - } + if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0)) { + setResultCode(Activity.RESULT_OK); + return; + } + } } } } }, 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. @@ -4098,11 +4171,13 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY); // 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); + false, false, MY_PID, Process.SYSTEM_UID, Binder.getOrigCallingUser()); } } } @@ -4211,7 +4286,9 @@ 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) { + int requestCode, Intent[] intents, String[] resolvedTypes, + int flags, Bundle options) { + enforceNotIsolatedCaller("getIntentSender"); // Refuse possible leaked file descriptors if (intents != null) { if (intents.length < 1) { @@ -4223,7 +4300,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } - if (type == INTENT_SENDER_BROADCAST && + if (type == ActivityManager.INTENT_SENDER_BROADCAST && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { throw new IllegalArgumentException( "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); @@ -4236,14 +4313,19 @@ public final class ActivityManagerService extends ActivityManagerNative "Intent array length does not match resolvedTypes length"); } } + if (options != null) { + if (options.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in options"); + } + } synchronized(this) { int callingUid = Binder.getCallingUid(); try { if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { int uid = AppGlobals.getPackageManager() - .getPackageUid(packageName); - if (uid != Binder.getCallingUid()) { + .getPackageUid(packageName, UserId.getUserId(callingUid)); + if (!UserId.isSameApp(callingUid, uid)) { String msg = "Permission Denial: getIntentSender() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() @@ -4254,8 +4336,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } - return getIntentSenderLocked(type, packageName, callingUid, - token, resultWho, requestCode, intents, resolvedTypes, flags); + if (DEBUG_MU) + Slog.i(TAG_MU, "Getting intent sender for origCallingUid=" + + Binder.getOrigCallingUid()); + return getIntentSenderLocked(type, packageName, Binder.getOrigCallingUid(), + token, resultWho, requestCode, intents, resolvedTypes, flags, options); } catch (RemoteException e) { throw new SecurityException(e); @@ -4265,9 +4350,12 @@ public final class ActivityManagerService extends ActivityManagerNative IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, IBinder token, String resultWho, - int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags, + Bundle options) { + if (DEBUG_MU) + Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid); ActivityRecord activity = null; - if (type == INTENT_SENDER_ACTIVITY_RESULT) { + if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { activity = mMainStack.isInStackLocked(token); if (activity == null) { return null; @@ -4285,7 +4373,7 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord.Key key = new PendingIntentRecord.Key( type, packageName, activity, resultWho, - requestCode, intents, resolvedTypes, flags); + requestCode, intents, resolvedTypes, flags, options); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; @@ -4293,7 +4381,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (!cancelCurrent) { if (updateCurrent) { if (rec.key.requestIntent != null) { - rec.key.requestIntent.replaceExtras(intents != null ? intents[0] : null); + rec.key.requestIntent.replaceExtras(intents != null ? + intents[intents.length - 1] : null); } if (intents != null) { intents[intents.length-1] = rec.key.requestIntent; @@ -4314,7 +4403,7 @@ public final class ActivityManagerService extends ActivityManagerNative } rec = new PendingIntentRecord(this, key, callingUid); mIntentSenderRecords.put(key, rec.ref); - if (type == INTENT_SENDER_ACTIVITY_RESULT) { + if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { if (activity.pendingResults == null) { activity.pendingResults = new HashSet<WeakReference<PendingIntentRecord>>(); @@ -4332,8 +4421,8 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord rec = (PendingIntentRecord)sender; try { int uid = AppGlobals.getPackageManager() - .getPackageUid(rec.key.packageName); - if (uid != Binder.getCallingUid()) { + .getPackageUid(rec.key.packageName, UserId.getCallingUserId()); + if (!UserId.isSameApp(uid, Binder.getCallingUid())) { String msg = "Permission Denial: cancelIntentSender() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() @@ -4369,6 +4458,17 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + public int getUidForIntentSender(IIntentSender sender) { + if (sender instanceof PendingIntentRecord) { + try { + PendingIntentRecord res = (PendingIntentRecord)sender; + return res.uid; + } catch (ClassCastException e) { + } + } + return -1; + } + public boolean isIntentSenderTargetedToPackage(IIntentSender pendingResult) { if (!(pendingResult instanceof PendingIntentRecord)) { return false; @@ -4434,7 +4534,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (mPidsSelfLocked) { ProcessRecord pr = mPidsSelfLocked.get(pid); - if (pr == null) { + if (pr == null && isForeground) { Slog.w(TAG, "setProcessForeground called on unknown pid: " + pid); return; } @@ -4442,7 +4542,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (oldToken != null) { oldToken.token.unlinkToDeath(oldToken, 0); mForegroundProcesses.remove(pid); - pr.forcingToForeground = null; + if (pr != null) { + pr.forcingToForeground = null; + } changed = true; } if (isForeground && token != null) { @@ -4507,9 +4609,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (uid == 0 || uid == Process.SYSTEM_UID || pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; } + // Isolated processes don't get any permissions. + if (UserId.isIsolated(uid)) { + return PackageManager.PERMISSION_DENIED; + } // If there is a uid that owns whatever is being accessed, it has // blanket access to it regardless of the permissions it requires. - if (owningUid >= 0 && uid == owningUid) { + if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) { return PackageManager.PERMISSION_GRANTED; } // If the target is not exported, then nobody else can get to it. @@ -4543,7 +4649,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (permission == null) { return PackageManager.PERMISSION_DENIED; } - return checkComponentPermission(permission, pid, uid, -1, true); + return checkComponentPermission(permission, pid, UserId.getAppId(uid), -1, true); } /** @@ -4553,7 +4659,7 @@ public final class ActivityManagerService extends ActivityManagerNative int checkCallingPermission(String permission) { return checkPermission(permission, Binder.getCallingPid(), - Binder.getCallingUid()); + UserId.getAppId(Binder.getCallingUid())); } /** @@ -4573,76 +4679,91 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - private final boolean checkHoldingPermissionsLocked(IPackageManager pm, - ProviderInfo pi, Uri uri, int uid, int modeFlags) { - boolean readPerm = (modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0; - boolean writePerm = (modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0; + /** + * Determine if UID is holding permissions required to access {@link Uri} in + * the given {@link ProviderInfo}. Final permission checking is always done + * in {@link ContentProvider}. + */ + private final boolean checkHoldingPermissionsLocked( + IPackageManager pm, ProviderInfo pi, Uri uri, int uid, int modeFlags) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, "checkHoldingPermissionsLocked: uri=" + uri + " uid=" + uid); - try { - // Is the component private from the target uid? - final boolean prv = !pi.exported && pi.applicationInfo.uid != uid; - // Acceptable if the there is no read permission needed from the - // target or the target is holding the read permission. - if (!readPerm) { - if ((!prv && pi.readPermission == null) || - (pm.checkUidPermission(pi.readPermission, uid) - == PackageManager.PERMISSION_GRANTED)) { - readPerm = true; - } - } + if (pi.applicationInfo.uid == uid) { + return true; + } else if (!pi.exported) { + return false; + } - // Acceptable if the there is no write permission needed from the - // target or the target is holding the read permission. - if (!writePerm) { - if (!prv && (pi.writePermission == null) || - (pm.checkUidPermission(pi.writePermission, uid) - == PackageManager.PERMISSION_GRANTED)) { - writePerm = true; - } + boolean readMet = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0; + boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0; + try { + // check if target holds top-level <provider> permissions + if (!readMet && pi.readPermission != null + && (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) { + readMet = true; } + if (!writeMet && pi.writePermission != null + && (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) { + writeMet = true; + } + + // track if unprotected read/write is allowed; any denied + // <path-permission> below removes this ability + boolean allowDefaultRead = pi.readPermission == null; + boolean allowDefaultWrite = pi.writePermission == null; - // Acceptable if there is a path permission matching the URI that - // the target holds the permission on. - PathPermission[] pps = pi.pathPermissions; - if (pps != null && (!readPerm || !writePerm)) { + // check if target holds any <path-permission> that match uri + final PathPermission[] pps = pi.pathPermissions; + if (pps != null) { final String path = uri.getPath(); int i = pps.length; - while (i > 0 && (!readPerm || !writePerm)) { + while (i > 0 && (!readMet || !writeMet)) { i--; PathPermission pp = pps[i]; - if (!readPerm) { - final String pprperm = pp.getReadPermission(); - if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for " - + pprperm + " for " + pp.getPath() - + ": match=" + pp.match(path) - + " check=" + pm.checkUidPermission(pprperm, uid)); - if (pprperm != null && pp.match(path) && - (pm.checkUidPermission(pprperm, uid) - == PackageManager.PERMISSION_GRANTED)) { - readPerm = true; + if (pp.match(path)) { + if (!readMet) { + final String pprperm = pp.getReadPermission(); + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for " + + pprperm + " for " + pp.getPath() + + ": match=" + pp.match(path) + + " check=" + pm.checkUidPermission(pprperm, uid)); + if (pprperm != null) { + if (pm.checkUidPermission(pprperm, uid) == PERMISSION_GRANTED) { + readMet = true; + } else { + allowDefaultRead = false; + } + } } - } - if (!writePerm) { - final String ppwperm = pp.getWritePermission(); - if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm " - + ppwperm + " for " + pp.getPath() - + ": match=" + pp.match(path) - + " check=" + pm.checkUidPermission(ppwperm, uid)); - if (ppwperm != null && pp.match(path) && - (pm.checkUidPermission(ppwperm, uid) - == PackageManager.PERMISSION_GRANTED)) { - writePerm = true; + if (!writeMet) { + final String ppwperm = pp.getWritePermission(); + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm " + + ppwperm + " for " + pp.getPath() + + ": match=" + pp.match(path) + + " check=" + pm.checkUidPermission(ppwperm, uid)); + if (ppwperm != null) { + if (pm.checkUidPermission(ppwperm, uid) == PERMISSION_GRANTED) { + writeMet = true; + } else { + allowDefaultWrite = false; + } + } } } } } + + // grant unprotected <provider> read/write, if not blocked by + // <path-permission> above + if (allowDefaultRead) readMet = true; + if (allowDefaultWrite) writeMet = true; + } catch (RemoteException e) { return false; } - return readPerm && writePerm; + return readMet && writeMet; } private final boolean checkUriPermissionLocked(Uri uri, int uid, @@ -4659,6 +4780,8 @@ public final class ActivityManagerService extends ActivityManagerNative } public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + enforceNotIsolatedCaller("checkUriPermission"); + // Another redirected-binder-call permissions check as in // {@link checkComponentPermission}. Identity tlsIdentity = sCallerIdentity.get(); @@ -4667,6 +4790,7 @@ 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; @@ -4684,9 +4808,11 @@ public final class ActivityManagerService extends ActivityManagerNative * if callingUid is not allowed to do this. Returns the uid of the target * if the URI permission grant should be performed; returns -1 if it is not * needed (for example targetPkg already has permission to access the URI). + * If you already know the uid of the target, you can supply it in + * lastTargetUid else set that to -1. */ int checkGrantUriPermissionLocked(int callingUid, String targetPkg, - Uri uri, int modeFlags) { + Uri uri, int modeFlags, int lastTargetUid) { modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); if (modeFlags == 0) { @@ -4709,13 +4835,14 @@ public final class ActivityManagerService extends ActivityManagerNative String name = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(name); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, + UserId.getUserId(callingUid)); if (cpr != null) { pi = cpr.info; } else { try { pi = pm.resolveContentProvider(name, - PackageManager.GET_URI_PERMISSION_PATTERNS); + PackageManager.GET_URI_PERMISSION_PATTERNS, UserId.getUserId(callingUid)); } catch (RemoteException ex) { } } @@ -4724,10 +4851,10 @@ public final class ActivityManagerService extends ActivityManagerNative return -1; } - int targetUid; - if (targetPkg != null) { + int targetUid = lastTargetUid; + if (targetUid < 0 && targetPkg != null) { try { - targetUid = pm.getPackageUid(targetPkg); + targetUid = pm.getPackageUid(targetPkg, UserId.getUserId(callingUid)); if (targetUid < 0) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Can't grant URI permission no uid for: " + targetPkg); @@ -4736,8 +4863,6 @@ public final class ActivityManagerService extends ActivityManagerNative } catch (RemoteException ex) { return -1; } - } else { - targetUid = -1; } if (targetUid >= 0) { @@ -4807,8 +4932,9 @@ public final class ActivityManagerService extends ActivityManagerNative public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri, int modeFlags) { + enforceNotIsolatedCaller("checkGrantUriPermission"); synchronized(this) { - return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags); + return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags, -1); } } @@ -4855,13 +4981,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } - void grantUriPermissionLocked(int callingUid, - String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) { + void grantUriPermissionLocked(int callingUid, String targetPkg, Uri uri, + int modeFlags, UriPermissionOwner owner) { if (targetPkg == null) { throw new NullPointerException("targetPkg"); } - int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags); + int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags, -1); if (targetUid < 0) { return; } @@ -4869,13 +4995,26 @@ public final class ActivityManagerService extends ActivityManagerNative grantUriPermissionUncheckedLocked(targetUid, targetPkg, uri, modeFlags, owner); } + static class NeededUriGrants extends ArrayList<Uri> { + final String targetPkg; + final int targetUid; + final int flags; + + NeededUriGrants(String _targetPkg, int _targetUid, int _flags) { + targetPkg = _targetPkg; + targetUid = _targetUid; + flags = _flags; + } + } + /** * Like checkGrantUriPermissionLocked, but takes an Intent. */ - int checkGrantUriPermissionFromIntentLocked(int callingUid, - String targetPkg, Intent intent) { + NeededUriGrants checkGrantUriPermissionFromIntentLocked(int callingUid, + String targetPkg, Intent intent, int mode, NeededUriGrants needed) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Checking URI perm to " + (intent != null ? intent.getData() : null) + "Checking URI perm to data=" + (intent != null ? intent.getData() : null) + + " clip=" + (intent != null ? intent.getClipData() : null) + " from " + intent + "; flags=0x" + Integer.toHexString(intent != null ? intent.getFlags() : 0)); @@ -4884,37 +5023,79 @@ public final class ActivityManagerService extends ActivityManagerNative } if (intent == null) { - return -1; + return null; } Uri data = intent.getData(); - if (data == null) { - return -1; + ClipData clip = intent.getClipData(); + if (data == null && clip == null) { + return null; } - return checkGrantUriPermissionLocked(callingUid, targetPkg, data, - intent.getFlags()); + if (data != null) { + int target = checkGrantUriPermissionLocked(callingUid, targetPkg, data, + mode, needed != null ? needed.targetUid : -1); + if (target > 0) { + if (needed == null) { + needed = new NeededUriGrants(targetPkg, target, mode); + } + needed.add(data); + } + } + if (clip != null) { + for (int i=0; i<clip.getItemCount(); i++) { + Uri uri = clip.getItemAt(i).getUri(); + if (uri != null) { + int target = -1; + target = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, + mode, needed != null ? needed.targetUid : -1); + if (target > 0) { + if (needed == null) { + needed = new NeededUriGrants(targetPkg, target, mode); + } + needed.add(uri); + } + } else { + Intent clipIntent = clip.getItemAt(i).getIntent(); + if (clipIntent != null) { + NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentLocked( + callingUid, targetPkg, clipIntent, mode, needed); + if (newNeeded != null) { + needed = newNeeded; + } + } + } + } + } + + return needed; } /** * Like grantUriPermissionUncheckedLocked, but takes an Intent. */ - void grantUriPermissionUncheckedFromIntentLocked(int targetUid, - String targetPkg, Intent intent, UriPermissionOwner owner) { - grantUriPermissionUncheckedLocked(targetUid, targetPkg, intent.getData(), - intent.getFlags(), owner); + void grantUriPermissionUncheckedFromIntentLocked(NeededUriGrants needed, + UriPermissionOwner owner) { + if (needed != null) { + for (int i=0; i<needed.size(); i++) { + grantUriPermissionUncheckedLocked(needed.targetUid, needed.targetPkg, + needed.get(i), needed.flags, owner); + } + } } void grantUriPermissionFromIntentLocked(int callingUid, String targetPkg, Intent intent, UriPermissionOwner owner) { - int targetUid = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg, intent); - if (targetUid < 0) { + NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg, + intent, intent != null ? intent.getFlags() : 0, null); + if (needed == null) { return; } - grantUriPermissionUncheckedFromIntentLocked(targetUid, targetPkg, intent, owner); + grantUriPermissionUncheckedFromIntentLocked(needed, owner); } public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int modeFlags) { + enforceNotIsolatedCaller("grantUriPermission"); synchronized(this) { final ProcessRecord r = getRecordForAppLocked(caller); if (r == null) { @@ -4929,7 +5110,7 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("null uri"); } - grantUriPermissionLocked(r.info.uid, targetPkg, uri, modeFlags, + grantUriPermissionLocked(r.uid, targetPkg, uri, modeFlags, null); } } @@ -4965,13 +5146,14 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(authority); + int userId = UserId.getUserId(callingUid); + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userId); if (cpr != null) { pi = cpr.info; } else { try { pi = pm.resolveContentProvider(authority, - PackageManager.GET_URI_PERMISSION_PATTERNS); + PackageManager.GET_URI_PERMISSION_PATTERNS, userId); } catch (RemoteException ex) { } } @@ -5037,6 +5219,7 @@ public final class ActivityManagerService extends ActivityManagerNative public void revokeUriPermission(IApplicationThread caller, Uri uri, int modeFlags) { + enforceNotIsolatedCaller("revokeUriPermission"); synchronized(this) { final ProcessRecord r = getRecordForAppLocked(caller); if (r == null) { @@ -5059,13 +5242,13 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(authority); + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, r.userId); if (cpr != null) { pi = cpr.info; } else { try { pi = pm.resolveContentProvider(authority, - PackageManager.GET_URI_PERMISSION_PATTERNS); + PackageManager.GET_URI_PERMISSION_PATTERNS, r.userId); } catch (RemoteException ex) { } } @@ -5075,12 +5258,13 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - revokeUriPermissionLocked(r.info.uid, uri, modeFlags); + revokeUriPermissionLocked(r.uid, uri, modeFlags); } } @Override public IBinder newUriPermissionOwner(String name) { + enforceNotIsolatedCaller("newUriPermissionOwner"); synchronized(this) { UriPermissionOwner owner = new UriPermissionOwner(this, name); return owner.getExternalTokenLocked(); @@ -5298,9 +5482,18 @@ public final class ActivityManagerService extends ActivityManagerNative public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags) { + 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); synchronized (this) { enforceCallingPermission(android.Manifest.permission.GET_TASKS, "getRecentTasks()"); + final boolean detailed = checkCallingPermission( + android.Manifest.permission.GET_DETAILED_TASKS) + == PackageManager.PERMISSION_GRANTED; IPackageManager pm = AppGlobals.getPackageManager(); @@ -5310,11 +5503,14 @@ public final class ActivityManagerService extends ActivityManagerNative maxNum < N ? maxNum : N); 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; // Return the entry if desired by the caller. We always return // the first entry, because callers always expect this to be the - // forground app. We may filter others if the caller has + // foreground app. We may filter others if the caller has // not supplied RECENT_WITH_EXCLUDED and there is some reason // we should exclude the entry. + if (i == 0 || ((flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0) || (tr.intent == null) @@ -5326,6 +5522,9 @@ public final class ActivityManagerService extends ActivityManagerNative rti.persistentId = tr.taskId; rti.baseIntent = new Intent( tr.intent != null ? tr.intent : tr.affinityIntent); + if (!detailed) { + rti.baseIntent.replaceExtras((Bundle)null); + } rti.origActivity = tr.origActivity; rti.description = tr.lastDescription; @@ -5333,12 +5532,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) == null) { + if (pm.getActivityInfo(rti.origActivity, 0, callingUserId) + == null) { continue; } } else if (rti.baseIntent != null) { if (pm.queryIntentActivities(rti.baseIntent, - null, 0) == null) { + null, 0, callingUserId) == null) { continue; } } @@ -5384,15 +5584,16 @@ public final class ActivityManagerService extends ActivityManagerNative "removeSubTask()"); long ident = Binder.clearCallingIdentity(); try { - return mMainStack.removeTaskActivitiesLocked(taskId, subTaskIndex) != null; + return mMainStack.removeTaskActivitiesLocked(taskId, subTaskIndex, + true) != null; } finally { Binder.restoreCallingIdentity(ident); } } } - private void cleanUpRemovedTaskLocked(ActivityRecord root, boolean killProcesses) { - TaskRecord tr = root.task; + private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) { + final boolean killProcesses = (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0; Intent baseIntent = new Intent( tr.intent != null ? tr.intent : tr.affinityIntent); ComponentName component = baseIntent.getComponent(); @@ -5403,7 +5604,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Find any running services associated with this app. ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord sr : mServices.values()) { + for (ServiceRecord sr : mServiceMap.getAllServices(tr.userId)) { if (sr.packageName.equals(component.getPackageName())) { services.add(sr); } @@ -5418,7 +5619,7 @@ public final class ActivityManagerService extends ActivityManagerNative stopServiceLocked(sr); } else { sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, - sr.makeNextStartId(), baseIntent, -1)); + sr.makeNextStartId(), baseIntent, null)); if (sr.app != null && sr.app.thread != null) { sendServiceArgsLocked(sr, false); } @@ -5458,11 +5659,11 @@ public final class ActivityManagerService extends ActivityManagerNative "removeTask()"); long ident = Binder.clearCallingIdentity(); try { - ActivityRecord r = mMainStack.removeTaskActivitiesLocked(taskId, -1); + ActivityRecord r = mMainStack.removeTaskActivitiesLocked(taskId, -1, + false); if (r != null) { mRecentTasks.remove(r.task); - cleanUpRemovedTaskLocked(r, - (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0); + cleanUpRemovedTaskLocked(r.task, flags); return true; } else { TaskRecord tr = null; @@ -5480,6 +5681,8 @@ public final class ActivityManagerService extends ActivityManagerNative // Caller is just removing a recent task that is // not actively running. That is easy! mRecentTasks.remove(i); + cleanUpRemovedTaskLocked(tr, flags); + return true; } else { Slog.w(TAG, "removeTask: task " + taskId + " does not have activities to remove, " @@ -5535,13 +5738,14 @@ public final class ActivityManagerService extends ActivityManagerNative /** * TODO: Add mController hook */ - public void moveTaskToFront(int task, int flags) { + public void moveTaskToFront(int task, int flags, Bundle options) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()"); synchronized(this) { if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), Binder.getCallingUid(), "Task to front")) { + ActivityOptions.abort(options); return; } final long origId = Binder.clearCallingIdentity(); @@ -5556,7 +5760,7 @@ public final class ActivityManagerService extends ActivityManagerNative // we'll just move the home task to the top first. mMainStack.moveHomeToFrontLocked(); } - mMainStack.moveTaskToFrontLocked(tr, null); + mMainStack.moveTaskToFrontLocked(tr, null, options); return; } for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { @@ -5570,13 +5774,14 @@ public final class ActivityManagerService extends ActivityManagerNative // we'll just move the home task to the top first. mMainStack.moveHomeToFrontLocked(); } - mMainStack.moveTaskToFrontLocked(hr.task, null); + mMainStack.moveTaskToFrontLocked(hr.task, null, options); return; } } } finally { Binder.restoreCallingIdentity(origId); } + ActivityOptions.abort(options); } } @@ -5608,6 +5813,7 @@ public final class ActivityManagerService extends ActivityManagerNative * @return Returns true if the move completed, false if not. */ public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) { + enforceNotIsolatedCaller("moveActivityTaskToBack"); synchronized(this) { final long origId = Binder.clearCallingIdentity(); int taskId = getTaskForActivityLocked(token, !nonRoot); @@ -5661,29 +5867,6 @@ public final class ActivityManagerService extends ActivityManagerNative return -1; } - public void finishOtherInstances(IBinder token, ComponentName className) { - synchronized(this) { - final long origId = Binder.clearCallingIdentity(); - - int N = mMainStack.mHistory.size(); - TaskRecord lastTask = null; - for (int i=0; i<N; i++) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - if (r.realActivity.equals(className) - && r.appToken != token && lastTask != r.task) { - if (r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "others")) { - i--; - N--; - } - } - lastTask = r.task; - } - - Binder.restoreCallingIdentity(origId); - } - } - // ========================================================= // THUMBNAILS // ========================================================= @@ -5770,21 +5953,27 @@ public final class ActivityManagerService extends ActivityManagerNative List<ProviderInfo> providers = null; try { providers = AppGlobals.getPackageManager(). - queryContentProviders(app.processName, app.info.uid, + queryContentProviders(app.processName, app.uid, STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { } + if (DEBUG_MU) + Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.uid); + int userId = app.userId; if (providers != null) { final int N = providers.size(); for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); + ComponentName comp = new ComponentName(cpi.packageName, cpi.name); - ContentProviderRecord cpr = mProvidersByClass.get(comp); + ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId); if (cpr == null) { - cpr = new ContentProviderRecord(cpi, app.info, comp); - mProvidersByClass.put(comp, cpr); + cpr = new ContentProviderRecord(this, cpi, app.info, comp); + mProviderMap.putProviderByClass(comp, cpr); } + if (DEBUG_MU) + Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid); app.pubProviders.put(cpi.name, cpr); app.addPackage(cpi.applicationInfo.packageName); ensurePackageDexOpt(cpi.applicationInfo.packageName); @@ -5793,10 +5982,15 @@ public final class ActivityManagerService extends ActivityManagerNative return providers; } + /** + * Check if {@link ProcessRecord} has a possible chance at accessing the + * given {@link ProviderInfo}. Final permission checking is always done + * in {@link ContentProvider}. + */ private final String checkContentProviderPermissionLocked( ProviderInfo cpi, ProcessRecord r) { final int callingPid = (r != null) ? r.pid : Binder.getCallingPid(); - final int callingUid = (r != null) ? r.info.uid : Binder.getCallingUid(); + final int callingUid = (r != null) ? r.uid : Binder.getCallingUid(); if (checkComponentPermission(cpi.readPermission, callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported) == PackageManager.PERMISSION_GRANTED) { @@ -5852,7 +6046,8 @@ public final class ActivityManagerService extends ActivityManagerNative return msg; } - boolean incProviderCount(ProcessRecord r, ContentProviderRecord cpr) { + boolean incProviderCount(ProcessRecord r, final ContentProviderRecord cpr, + IBinder externalProcessToken) { if (r != null) { Integer cnt = r.conProviders.get(cpr); if (DEBUG_PROVIDER) Slog.v(TAG, @@ -5868,12 +6063,13 @@ public final class ActivityManagerService extends ActivityManagerNative r.conProviders.put(cpr, new Integer(cnt.intValue()+1)); } } else { - cpr.externals++; + cpr.addExternalProcessHandleLocked(externalProcessToken); } return false; } - boolean decProviderCount(ProcessRecord r, ContentProviderRecord cpr) { + boolean decProviderCount(ProcessRecord r, final ContentProviderRecord cpr, + IBinder externalProcessToken) { if (r != null) { Integer cnt = r.conProviders.get(cpr); if (DEBUG_PROVIDER) Slog.v(TAG, @@ -5889,13 +6085,13 @@ public final class ActivityManagerService extends ActivityManagerNative r.conProviders.put(cpr, new Integer(cnt.intValue()-1)); } } else { - cpr.externals++; + cpr.removeExternalProcessHandleLocked(externalProcessToken); } return false; } - private final ContentProviderHolder getContentProviderImpl( - IApplicationThread caller, String name) { + private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, + String name, IBinder token) { ContentProviderRecord cpr; ProviderInfo cpi = null; @@ -5912,7 +6108,8 @@ public final class ActivityManagerService extends ActivityManagerNative } // First check if this content provider has been published... - cpr = mProvidersByName.get(name); + int userId = UserId.getUserId(r != null ? r.uid : Binder.getCallingUid()); + cpr = mProviderMap.getProviderByName(name, userId); boolean providerRunning = cpr != null; if (providerRunning) { cpi = cpr.info; @@ -5938,7 +6135,7 @@ public final class ActivityManagerService extends ActivityManagerNative // In this case the provider instance already exists, so we can // return it right away. - final boolean countChanged = incProviderCount(r, cpr); + final boolean countChanged = incProviderCount(r, cpr, token); if (countChanged) { if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) { // If this is a perceptible app accessing the provider, @@ -5972,7 +6169,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.i(TAG, "Existing provider " + cpr.name.flattenToShortString() + " is crashing; detaching " + r); - boolean lastRef = decProviderCount(r, cpr); + boolean lastRef = decProviderCount(r, cpr, token); appDiedLocked(cpr.proc, cpr.proc.pid, cpr.proc.thread); if (!lastRef) { // This wasn't the last ref our process had on @@ -5990,12 +6187,16 @@ public final class ActivityManagerService extends ActivityManagerNative try { cpi = AppGlobals.getPackageManager(). resolveContentProvider(name, - STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); + STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId); } catch (RemoteException ex) { } if (cpi == null) { return null; } + if (isSingleton(cpi.processName, cpi.applicationInfo)) { + userId = 0; + } + cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId); String msg; if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) { @@ -6012,7 +6213,7 @@ public final class ActivityManagerService extends ActivityManagerNative } ComponentName comp = new ComponentName(cpi.packageName, cpi.name); - cpr = mProvidersByClass.get(comp); + cpr = mProviderMap.getProviderByClass(comp, userId); final boolean firstClass = cpr == null; if (firstClass) { try { @@ -6020,13 +6221,14 @@ public final class ActivityManagerService extends ActivityManagerNative AppGlobals.getPackageManager(). getApplicationInfo( cpi.applicationInfo.packageName, - STOCK_PM_FLAGS); + STOCK_PM_FLAGS, userId); if (ai == null) { Slog.w(TAG, "No package info for content provider " + cpi.name); return null; } - cpr = new ContentProviderRecord(cpi, ai, comp); + ai = getAppInfoForUser(ai, userId); + cpr = new ContentProviderRecord(this, cpi, ai, comp); } catch (RemoteException ex) { // pm is in same process, this will never happen. } @@ -6042,7 +6244,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_PROVIDER) { RuntimeException e = new RuntimeException("here"); - Slog.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + r.info.uid + Slog.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + r.uid + " pruid " + cpr.appInfo.uid + "): " + cpr.info.name, e); } @@ -6066,7 +6268,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Content provider is now in use, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( - cpr.appInfo.packageName, false); + cpr.appInfo.packageName, false, userId); } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " @@ -6076,7 +6278,7 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord proc = startProcessLocked(cpi.processName, cpr.appInfo, false, 0, "content provider", new ComponentName(cpi.applicationInfo.packageName, - cpi.name), false); + cpi.name), false, false); if (proc == null) { Slog.w(TAG, "Unable to launch app " + cpi.applicationInfo.packageName + "/" @@ -6094,10 +6296,11 @@ public final class ActivityManagerService extends ActivityManagerNative // Make sure the provider is published (the same provider class // may be published under multiple names). if (firstClass) { - mProvidersByClass.put(comp, cpr); + mProviderMap.putProviderByClass(comp, cpr); } - mProvidersByName.put(name, cpr); - incProviderCount(r, cpr); + + mProviderMap.putProviderByName(name, cpr); + incProviderCount(r, cpr, token); } } @@ -6115,6 +6318,10 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } try { + if (DEBUG_MU) { + Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp=" + + cpr.launchingApp); + } cpr.wait(); } catch (InterruptedException ex) { } @@ -6125,6 +6332,7 @@ public final class ActivityManagerService extends ActivityManagerNative public final ContentProviderHolder getContentProvider( IApplicationThread caller, String name) { + enforceNotIsolatedCaller("getContentProvider"); if (caller == null) { String msg = "null IApplicationThread when getting content provider " + name; @@ -6132,11 +6340,17 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - return getContentProviderImpl(caller, name); + return getContentProviderImpl(caller, name, null); } - private ContentProviderHolder getContentProviderExternal(String name) { - return getContentProviderImpl(null, name); + 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); + } + + private ContentProviderHolder getContentProviderExternalUnchecked(String name,IBinder token) { + return getContentProviderImpl(null, name, token); } /** @@ -6144,8 +6358,10 @@ public final class ActivityManagerService extends ActivityManagerNative * @param cpr */ public void removeContentProvider(IApplicationThread caller, String name) { + enforceNotIsolatedCaller("removeContentProvider"); synchronized (this) { - ContentProviderRecord cpr = mProvidersByName.get(name); + int userId = UserId.getUserId(Binder.getCallingUid()); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId); if(cpr == null) { // remove from mProvidersByClass if (DEBUG_PROVIDER) Slog.v(TAG, name + @@ -6160,23 +6376,33 @@ public final class ActivityManagerService extends ActivityManagerNative } //update content provider record entry info ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name); - ContentProviderRecord localCpr = mProvidersByClass.get(comp); - if (localCpr.proc == r) { + ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId); + if (DEBUG_PROVIDER) Slog.v(TAG, "Removing provider requested by " + + r.info.processName + " from process " + + localCpr.appInfo.processName); + if (localCpr.launchingApp == r) { //should not happen. taken care of as a local provider Slog.w(TAG, "removeContentProvider called on local provider: " + cpr.info.name + " in process " + r.processName); return; } else { - if (decProviderCount(r, localCpr)) { + if (decProviderCount(r, localCpr, null)) { updateOomAdjLocked(); } } } } - private void removeContentProviderExternal(String name) { + 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); + } + + private void removeContentProviderExternalUnchecked(String name, IBinder token) { synchronized (this) { - ContentProviderRecord cpr = mProvidersByName.get(name); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, + Binder.getOrigCallingUser()); if(cpr == null) { //remove from mProvidersByClass if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list"); @@ -6185,12 +6411,20 @@ public final class ActivityManagerService extends ActivityManagerNative //update content provider record entry info ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name); - ContentProviderRecord localCpr = mProvidersByClass.get(comp); - localCpr.externals--; - if (localCpr.externals < 0) { - Slog.e(TAG, "Externals < 0 for content provider " + localCpr); + ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, + Binder.getOrigCallingUser()); + if (localCpr.hasExternalProcessHandles()) { + if (localCpr.removeExternalProcessHandleLocked(token)) { + updateOomAdjLocked(); + } else { + Slog.e(TAG, "Attmpt to remove content provider " + localCpr + + " with no external reference for token: " + + token + "."); + } + } else { + Slog.e(TAG, "Attmpt to remove content provider: " + localCpr + + " with no external references."); } - updateOomAdjLocked(); } } @@ -6200,8 +6434,11 @@ public final class ActivityManagerService extends ActivityManagerNative return; } + enforceNotIsolatedCaller("publishContentProviders"); synchronized(this) { final ProcessRecord r = getRecordForAppLocked(caller); + if (DEBUG_MU) + Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid); if (r == null) { throw new SecurityException( "Unable to find app for caller " + caller @@ -6218,12 +6455,14 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } ContentProviderRecord dst = r.pubProviders.get(src.info.name); + if (DEBUG_MU) + Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid); if (dst != null) { ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name); - mProvidersByClass.put(comp, dst); + mProviderMap.putProviderByClass(comp, dst); String names[] = dst.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - mProvidersByName.put(names[j], dst); + mProviderMap.putProviderByName(names[j], dst); } int NL = mLaunchingProviders.size(); @@ -6283,12 +6522,13 @@ public final class ActivityManagerService extends ActivityManagerNative * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java */ public String getProviderMimeType(Uri uri) { + enforceNotIsolatedCaller("getProviderMimeType"); final String name = uri.getAuthority(); final long ident = Binder.clearCallingIdentity(); ContentProviderHolder holder = null; try { - holder = getContentProviderExternal(name); + holder = getContentProviderExternalUnchecked(name, null); if (holder != null) { return holder.provider.getType(uri); } @@ -6297,7 +6537,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } finally { if (holder != null) { - removeContentProviderExternal(name); + removeContentProviderExternalUnchecked(name, null); } Binder.restoreCallingIdentity(ident); } @@ -6310,29 +6550,59 @@ public final class ActivityManagerService extends ActivityManagerNative // ========================================================= final ProcessRecord newProcessRecordLocked(IApplicationThread thread, - ApplicationInfo info, String customProcess) { + ApplicationInfo info, String customProcess, boolean isolated) { String proc = customProcess != null ? customProcess : info.processName; BatteryStatsImpl.Uid.Proc ps = null; BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + int uid = info.uid; + if (isolated) { + int userId = UserId.getUserId(uid); + int stepsLeft = Process.LAST_ISOLATED_UID - Process.FIRST_ISOLATED_UID + 1; + uid = 0; + while (true) { + if (mNextIsolatedProcessUid < Process.FIRST_ISOLATED_UID + || mNextIsolatedProcessUid > Process.LAST_ISOLATED_UID) { + mNextIsolatedProcessUid = Process.FIRST_ISOLATED_UID; + } + uid = UserId.getUid(userId, mNextIsolatedProcessUid); + mNextIsolatedProcessUid++; + if (mIsolatedProcesses.indexOfKey(uid) < 0) { + // No process for this uid, use it. + break; + } + stepsLeft--; + if (stepsLeft <= 0) { + return null; + } + } + } synchronized (stats) { ps = stats.getProcessStatsLocked(info.uid, proc); } - return new ProcessRecord(ps, thread, info, proc); + return new ProcessRecord(ps, thread, info, proc, uid); } - final ProcessRecord addAppLocked(ApplicationInfo info) { - ProcessRecord app = getProcessRecordLocked(info.processName, info.uid); + final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) { + ProcessRecord app; + if (!isolated) { + app = getProcessRecordLocked(info.processName, info.uid); + } else { + app = null; + } if (app == null) { - app = newProcessRecordLocked(null, info, null); - mProcessNames.put(info.processName, info.uid, app); + app = newProcessRecordLocked(null, info, null, isolated); + mProcessNames.put(info.processName, app.uid, app); + if (isolated) { + mIsolatedProcesses.put(app.uid, app); + } updateLruProcessLocked(app, true, true); } // This package really, really can not be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( - info.packageName, false); + info.packageName, false, UserId.getUserId(app.uid)); } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " @@ -6370,8 +6640,9 @@ public final class ActivityManagerService extends ActivityManagerNative } public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException { + enforceNotIsolatedCaller("openContentUri"); String name = uri.getAuthority(); - ContentProviderHolder cph = getContentProviderExternal(name); + ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null); ParcelFileDescriptor pfd = null; if (cph != null) { // We record the binder invoker's uid in thread-local storage before @@ -6393,7 +6664,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // We've got the fd now, so we're done with the provider. - removeContentProviderExternal(name); + removeContentProviderExternalUnchecked(name, null); } else { Slog.d(TAG, "Failed to get provider for authority '" + name + "'"); } @@ -6407,17 +6678,26 @@ public final class ActivityManagerService extends ActivityManagerNative } public void goingToSleep() { + if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.DEVICE_POWER); + } + synchronized(this) { - mSleeping = true; + mWentToSleep = true; mWindowManager.setEventDispatching(false); - mMainStack.stopIfSleepingLocked(); + if (!mSleeping) { + mSleeping = true; + mMainStack.stopIfSleepingLocked(); - // Initialize the wake times of all processes. - checkExcessivePowerUsageLocked(false); - mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); - Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); - mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY); + // Initialize the wake times of all processes. + checkExcessivePowerUsageLocked(false); + mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY); + } } } @@ -6477,12 +6757,40 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(origId); } + private void comeOutOfSleepIfNeededLocked() { + if (!mWentToSleep && !mLockScreenShown) { + if (mSleeping) { + mSleeping = false; + mMainStack.awakeFromSleepingLocked(); + mMainStack.resumeTopActivityLocked(null); + } + } + } + public void wakingUp() { + if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.DEVICE_POWER); + } + synchronized(this) { + mWentToSleep = false; mWindowManager.setEventDispatching(true); - mSleeping = false; - mMainStack.awakeFromSleepingLocked(); - mMainStack.resumeTopActivityLocked(null); + comeOutOfSleepIfNeededLocked(); + } + } + + public void setLockScreenShown(boolean shown) { + if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.DEVICE_POWER); + } + + synchronized(this) { + mLockScreenShown = shown; + comeOutOfSleepIfNeededLocked(); } } @@ -6563,12 +6871,25 @@ public final class ActivityManagerService extends ActivityManagerNative mDebugTransient = !persistent; if (packageName != null) { final long origId = Binder.clearCallingIdentity(); - forceStopPackageLocked(packageName, -1, false, false, true, true); + forceStopPackageLocked(packageName, -1, false, false, true, true, 0); Binder.restoreCallingIdentity(origId); } } } + void setOpenGlTraceApp(ApplicationInfo app, String processName) { + synchronized (this) { + boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); + if (!isDebuggable) { + if ((app.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) { + throw new SecurityException("Process not debuggable: " + app.packageName); + } + } + + mOpenGlTraceApp = processName; + } + } + void setProfileApp(ApplicationInfo app, String processName, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { synchronized (this) { @@ -6622,26 +6943,20 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public void registerActivityWatcher(IActivityWatcher watcher) { + public void registerProcessObserver(IProcessObserver observer) { + enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, + "registerProcessObserver()"); synchronized (this) { - mWatchers.register(watcher); + mProcessObservers.register(observer); } } - public void unregisterActivityWatcher(IActivityWatcher watcher) { + public void unregisterProcessObserver(IProcessObserver observer) { synchronized (this) { - mWatchers.unregister(watcher); + mProcessObservers.unregister(observer); } } - public void registerProcessObserver(IProcessObserver observer) { - mProcessObservers.register(observer); - } - - public void unregisterProcessObserver(IProcessObserver observer) { - mProcessObservers.unregister(observer); - } - public void setImmersive(IBinder token, boolean immersive) { synchronized(this) { ActivityRecord r = mMainStack.isInStackLocked(token); @@ -6663,6 +6978,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public boolean isTopActivityImmersive() { + enforceNotIsolatedCaller("startActivity"); synchronized (this) { ActivityRecord r = mMainStack.topRunningActivityLocked(null); return (r != null) ? r.immersive : false; @@ -6770,7 +7086,43 @@ public final class ActivityManagerService extends ActivityManagerNative } return killed; } - + + @Override + public boolean killProcessesBelowForeground(String reason) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("killProcessesBelowForeground() only available to system"); + } + + return killProcessesBelowAdj(ProcessList.FOREGROUND_APP_ADJ, reason); + } + + private boolean killProcessesBelowAdj(int belowAdj, String reason) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("killProcessesBelowAdj() only available to system"); + } + + boolean killed = false; + synchronized (mPidsSelfLocked) { + final int size = mPidsSelfLocked.size(); + for (int i = 0; i < size; i++) { + final int pid = mPidsSelfLocked.keyAt(i); + final ProcessRecord proc = mPidsSelfLocked.valueAt(i); + if (proc == null) continue; + + final int adj = proc.setAdj; + if (adj > belowAdj && !proc.killedBackground) { + Slog.w(TAG, "Killing " + proc + " (adj " + adj + "): " + reason); + EventLog.writeEvent( + EventLogTags.AM_KILL, proc.pid, proc.processName, adj, reason); + killed = true; + proc.killedBackground = true; + Process.killProcessQuiet(pid); + } + } + } + return killed; + } + public final void startRunning(String pkg, String cls, String action, String data) { synchronized(this) { @@ -6914,7 +7266,7 @@ public final class ActivityManagerService extends ActivityManagerNative List<ResolveInfo> ris = null; try { ris = AppGlobals.getPackageManager().queryIntentReceivers( - intent, null, 0); + intent, null, 0, 0); } catch (RemoteException e) { } if (ris != null) { @@ -6968,8 +7320,10 @@ public final class ActivityManagerService extends ActivityManagerNative }; } 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, null, null, null, true, false, MY_PID, Process.SYSTEM_UID, + 0 /* UserId zero */); if (finisher != null) { mWaitingUpdate = true; } @@ -7072,7 +7426,7 @@ public final class ActivityManagerService extends ActivityManagerNative = (ApplicationInfo)apps.get(i); if (info != null && !info.packageName.equals("android")) { - addAppLocked(info); + addAppLocked(info, false); } } } @@ -7169,16 +7523,24 @@ public final class ActivityManagerService extends ActivityManagerNative } private boolean handleAppCrashLocked(ProcessRecord app) { + if (mHeadless) { + Log.e(TAG, "handleAppCrashLocked: " + app.processName); + return false; + } long now = SystemClock.uptimeMillis(); - Long crashTime = mProcessCrashTimes.get(app.info.processName, - app.info.uid); + Long crashTime; + if (!app.isolated) { + crashTime = mProcessCrashTimes.get(app.info.processName, app.uid); + } else { + crashTime = null; + } if (crashTime != null && now < crashTime+ProcessList.MIN_CRASH_INTERVAL) { // This process loses! Slog.w(TAG, "Process " + app.info.processName + " has crashed too many times: killing!"); EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, - app.info.processName, app.info.uid); + app.info.processName, app.uid); for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); if (r.app == app) { @@ -7192,11 +7554,15 @@ public final class ActivityManagerService extends ActivityManagerNative // explicitly does so... but for persistent process, we really // need to keep it running. If a persistent process is actually // repeatedly crashing, then badness for everyone. - EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.info.uid, + EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.uid, app.info.processName); - mBadProcesses.put(app.info.processName, app.info.uid, now); + if (!app.isolated) { + // XXX We don't have a way to mark isolated processes + // as bad, since they don't have a peristent identity. + mBadProcesses.put(app.info.processName, app.uid, now); + mProcessCrashTimes.remove(app.info.processName, app.uid); + } app.bad = true; - mProcessCrashTimes.remove(app.info.processName, app.info.uid); app.removed = true; // Don't let services in this process be restarted and potentially // annoy the user repeatedly. Unless it is persistent, since those @@ -7208,7 +7574,7 @@ public final class ActivityManagerService extends ActivityManagerNative mMainStack.resumeTopActivityLocked(null); } else { ActivityRecord r = mMainStack.topRunningActivityLocked(null); - if (r.app == app) { + if (r != null && r.app == app) { // If the top running activity is from this crashing // process, then terminate it to avoid getting in a loop. Slog.w(TAG, " Force finishing activity " @@ -7268,7 +7634,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } - mProcessCrashTimes.put(app.info.processName, app.info.uid, now); + if (!app.isolated) { + // XXX Can't keep track of crash times for isolated processes, + // because they don't have a perisistent identity. + mProcessCrashTimes.put(app.info.processName, app.uid, now); + } + return true; } @@ -7279,28 +7650,8 @@ public final class ActivityManagerService extends ActivityManagerNative } void skipCurrentReceiverLocked(ProcessRecord app) { - boolean reschedule = false; - BroadcastRecord r = app.curReceiver; - if (r != null) { - // The current broadcast is waiting for this app's receiver - // to be finished. Looks like that's not going to happen, so - // let the broadcast continue. - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - reschedule = true; - } - r = mPendingBroadcast; - if (r != null && r.curApp == app) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "skip & discard pending app " + r); - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - reschedule = true; - } - if (reschedule) { - scheduleBroadcastsLocked(); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.skipCurrentReceiverLocked(app); } } @@ -7585,7 +7936,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (String pkg : process.pkgList) { sb.append("Package: ").append(pkg); try { - PackageInfo pi = pm.getPackageInfo(pkg, 0); + PackageInfo pi = pm.getPackageInfo(pkg, 0, 0); if (pi != null) { sb.append(" v").append(pi.versionCode); if (pi.versionName != null) { @@ -7705,8 +8056,10 @@ public final class ActivityManagerService extends ActivityManagerNative } }; - if (process == null || process.pid == MY_PID) { - worker.run(); // We may be about to die -- need to run this synchronously + if (process == null) { + // If process is null, we are being called from some internal code + // and may be about to die -- run this synchronously. + worker.run(); } else { worker.start(); } @@ -7786,8 +8139,10 @@ public final class ActivityManagerService extends ActivityManagerNative Intent appErrorIntent = null; synchronized (this) { - if (r != null) { - mProcessCrashTimes.put(r.info.processName, r.info.uid, + if (r != null && !r.isolated) { + // XXX Can't keep track of crash time for isolated processes, + // since they don't have a persistent identity. + mProcessCrashTimes.put(r.info.processName, r.uid, SystemClock.uptimeMillis()); } if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) { @@ -7850,6 +8205,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() { + enforceNotIsolatedCaller("getProcessesInErrorState"); // assume our apps are happy - lazy create the list List<ActivityManager.ProcessErrorStateInfo> errList = null; @@ -7911,7 +8267,24 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private void fillInProcMemInfo(ProcessRecord app, + ActivityManager.RunningAppProcessInfo outInfo) { + outInfo.pid = app.pid; + outInfo.uid = app.info.uid; + if (mHeavyWeightProcess == app) { + outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE; + } + if (app.persistent) { + outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT; + } + outInfo.lastTrimLevel = app.trimMemoryLevel; + int adj = app.curAdj; + outInfo.importance = oomAdjToImportance(adj, outInfo); + outInfo.importanceReasonCode = app.adjTypeCode; + } + public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() { + enforceNotIsolatedCaller("getRunningAppProcesses"); // Lazy instantiation of list List<ActivityManager.RunningAppProcessInfo> runList = null; synchronized (this) { @@ -7923,16 +8296,7 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityManager.RunningAppProcessInfo currApp = new ActivityManager.RunningAppProcessInfo(app.processName, app.pid, app.getPackageList()); - currApp.uid = app.info.uid; - if (mHeavyWeightProcess == app) { - currApp.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE; - } - if (app.persistent) { - currApp.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT; - } - int adj = app.curAdj; - currApp.importance = oomAdjToImportance(adj, currApp); - currApp.importanceReasonCode = app.adjTypeCode; + fillInProcMemInfo(app, currApp); if (app.adjSource instanceof ProcessRecord) { currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid; currApp.importanceReasonImportance = oomAdjToImportance( @@ -7957,6 +8321,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public List<ApplicationInfo> getRunningExternalApplications() { + enforceNotIsolatedCaller("getRunningExternalApplications"); List<ActivityManager.RunningAppProcessInfo> runningApps = getRunningAppProcesses(); List<ApplicationInfo> retList = new ArrayList<ApplicationInfo>(); if (runningApps != null && runningApps.size() > 0) { @@ -7971,7 +8336,7 @@ public final class ActivityManagerService extends ActivityManagerNative IPackageManager pm = AppGlobals.getPackageManager(); for (String pkg : extList) { try { - ApplicationInfo info = pm.getApplicationInfo(pkg, 0); + ApplicationInfo info = pm.getApplicationInfo(pkg, 0, UserId.getCallingUserId()); if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { retList.add(info); } @@ -7983,6 +8348,18 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outInfo) { + enforceNotIsolatedCaller("getMyMemoryState"); + synchronized (this) { + ProcessRecord proc; + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(Binder.getCallingPid()); + } + fillInProcMemInfo(proc, outInfo); + } + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (checkCallingPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -7993,7 +8370,7 @@ public final class ActivityManagerService extends ActivityManagerNative + android.Manifest.permission.DUMP); return; } - + boolean dumpAll = false; boolean dumpClient = false; String dumpPackage = null; @@ -8019,6 +8396,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" p[rocesses] [PACKAGE_NAME]: process state"); pw.println(" o[om]: out of memory management"); pw.println(" prov[iders] [COMP_SPEC ...]: content provider state"); + pw.println(" provider [COMP_SPEC]: provider client-side state"); pw.println(" s[ervices] [COMP_SPEC ...]: service state"); pw.println(" service [COMP_SPEC]: service client-side state"); pw.println(" package [PACKAGE_NAME]: all state related to given package"); @@ -8035,7 +8413,9 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println("Unknown argument: " + opt + "; use -h for help"); } } - + + long origId = Binder.clearCallingIdentity(); + boolean more = false; // Is the caller requesting to dump a particular piece of data? if (opti < args.length) { String cmd = args[opti]; @@ -8044,7 +8424,6 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { dumpActivitiesLocked(fd, pw, args, opti, true, dumpClient, null); } - return; } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) { String[] newArgs; String name; @@ -8061,7 +8440,6 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { dumpBroadcastsLocked(fd, pw, args, opti, true, name); } - return; } else if ("intents".equals(cmd) || "i".equals(cmd)) { String[] newArgs; String name; @@ -8078,7 +8456,6 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { dumpPendingIntentsLocked(fd, pw, args, opti, true, name); } - return; } else if ("processes".equals(cmd) || "p".equals(cmd)) { String[] newArgs; String name; @@ -8095,17 +8472,30 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { dumpProcessesLocked(fd, pw, args, opti, true, name); } - return; } else if ("oom".equals(cmd) || "o".equals(cmd)) { synchronized (this) { dumpOomLocked(fd, pw, args, opti, true); } - return; + } else if ("provider".equals(cmd)) { + String[] newArgs; + String name; + if (opti >= args.length) { + name = null; + newArgs = EMPTY_STRING_ARRAY; + } else { + name = args[opti]; + opti++; + newArgs = new String[args.length - opti]; + if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); + } + if (!dumpProvider(fd, pw, name, newArgs, 0, dumpAll)) { + pw.println("No providers match: " + name); + pw.println("Use -h for help."); + } } else if ("providers".equals(cmd) || "prov".equals(cmd)) { synchronized (this) { dumpProvidersLocked(fd, pw, args, opti, true, null); } - return; } else if ("service".equals(cmd)) { String[] newArgs; String name; @@ -8123,13 +8513,11 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println("No services match: " + name); pw.println("Use -h for help."); } - return; } else if ("package".equals(cmd)) { String[] newArgs; if (opti >= args.length) { pw.println("package: no package name specified"); pw.println("Use -h for help."); - return; } else { dumpPackage = args[opti]; opti++; @@ -8138,22 +8526,25 @@ public final class ActivityManagerService extends ActivityManagerNative args.length - opti); args = newArgs; opti = 0; + more = true; } } else if ("services".equals(cmd) || "s".equals(cmd)) { synchronized (this) { dumpServicesLocked(fd, pw, args, opti, true, dumpClient, null); } - return; } else { // Dumping a single activity? if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll)) { pw.println("Bad activity command, or no activities match: " + cmd); pw.println("Use -h for help."); } + } + if (!more) { + Binder.restoreCallingIdentity(origId); return; } } - + // No piece of data specified, dump everything. synchronized (this) { boolean needSep; @@ -8194,8 +8585,9 @@ public final class ActivityManagerService extends ActivityManagerNative } dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage); } + Binder.restoreCallingIdentity(origId); } - + boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)"); @@ -8302,7 +8694,21 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + if (mIsolatedProcesses.size() > 0) { + if (needSep) pw.println(" "); + needSep = true; + pw.println(" Isolated process list (sorted by uid):"); + for (int i=0; i<mIsolatedProcesses.size(); i++) { + ProcessRecord r = mIsolatedProcesses.valueAt(i); + if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) { + continue; + } + pw.println(String.format("%sIsolated #%2d: %s", + " ", i, r.toString())); + } + } + if (mLruProcesses.size() > 0) { if (needSep) pw.println(" "); needSep = true; @@ -8404,8 +8810,8 @@ public final class ActivityManagerService extends ActivityManagerNative pw.print(" Process "); pw.print(pname); pw.print(" uid "); pw.print(puid); pw.print(": last crashed "); - pw.print((now-uids.valueAt(i))); - pw.println(" ms ago"); + TimeUtils.formatDuration(now-uids.valueAt(i), pw); + pw.println(" ago"); } } } @@ -8470,13 +8876,22 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - pw.println(" mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown); + if (mSleeping || mWentToSleep || mLockScreenShown) { + pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep + + " mLockScreenShown " + mLockScreenShown); + } + if (mShuttingDown) { + pw.println(" mShuttingDown=" + mShuttingDown); + } if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { pw.println(" mDebugApp=" + mDebugApp + "/orig=" + mOrigDebugApp + " mDebugTransient=" + mDebugTransient + " mOrigWaitForDebugger=" + mOrigWaitForDebugger); } + if (mOpenGlTraceApp != null) { + pw.println(" mOpenGlTraceApp=" + mOpenGlTraceApp); + } if (mProfileApp != null || mProfileProc != null || mProfileFile != null || mProfileFd != null) { pw.println(" mProfileApp=" + mProfileApp + " mProfileProc=" + mProfileProc); @@ -8597,8 +9012,14 @@ public final class ActivityManagerService extends ActivityManagerNative if ("all".equals(name)) { synchronized (this) { - for (ServiceRecord r1 : mServices.values()) { - services.add(r1); + 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 { @@ -8616,18 +9037,24 @@ public final class ActivityManagerService extends ActivityManagerNative } synchronized (this) { - for (ServiceRecord r1 : mServices.values()) { - if (componentName != null) { - if (r1.name.equals(componentName)) { - services.add(r1); - } - } else if (name != null) { - if (r1.name.flattenToString().contains(name)) { - services.add(r1); + 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); + } } - } else if (System.identityHashCode(r1) == objectId) { - services.add(r1); } + } catch (RemoteException re) { } } } @@ -8685,6 +9112,19 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * 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 + * - the first arg isn't the flattened component name of an existing provider: + * dump all providers whose component contains the first arg as a substring + */ + protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + return mProviderMap.dumpProvider(fd, pw, name, args, opti, dumpAll); + } + static class ItemMatcher { ArrayList<ComponentName> components; ArrayList<String> strings; @@ -8901,84 +9341,11 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = true; } } - - if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 - || mPendingBroadcast != null) { - boolean printed = false; - for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { - BroadcastRecord br = mParallelBroadcasts.get(i); - if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Active broadcasts:"); - } - pw.println(" Broadcast #" + i + ":"); - br.dump(pw, " "); - } - printed = false; - for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) { - BroadcastRecord br = mOrderedBroadcasts.get(i); - if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Active ordered broadcasts:"); - } - pw.println(" Ordered Broadcast #" + i + ":"); - mOrderedBroadcasts.get(i).dump(pw, " "); - } - if (dumpPackage == null || (mPendingBroadcast != null - && dumpPackage.equals(mPendingBroadcast.callerPackage))) { - if (needSep) { - pw.println(); - } - pw.println(" Pending broadcast:"); - if (mPendingBroadcast != null) { - mPendingBroadcast.dump(pw, " "); - } else { - pw.println(" (null)"); - } - needSep = true; - } - } - boolean printed = false; - for (int i=0; i<MAX_BROADCAST_HISTORY; i++) { - BroadcastRecord r = mBroadcastHistory[i]; - if (r == null) { - break; - } - if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Historical broadcasts:"); - printed = true; - } - if (dumpAll) { - pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); - r.dump(pw, " "); - } else { - if (i >= 50) { - pw.println(" ..."); - break; - } - pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r); - } + for (BroadcastQueue q : mBroadcastQueues) { + needSep = q.dumpLocked(fd, pw, args, opti, dumpAll, dumpPackage, needSep); } + needSep = true; if (mStickyBroadcasts != null && dumpPackage == null) { @@ -8998,7 +9365,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<N; i++) { sb.setLength(0); sb.append(" Intent: "); - intents.get(i).toShortString(sb, false, true, false); + intents.get(i).toShortString(sb, false, true, false, false); pw.println(sb.toString()); Bundle bundle = intents.get(i).getExtras(); if (bundle != null) { @@ -9015,7 +9382,10 @@ public final class ActivityManagerService extends ActivityManagerNative if (dumpAll) { pw.println(); - pw.println(" mBroadcastsScheduled=" + mBroadcastsScheduled); + for (BroadcastQueue queue : mBroadcastQueues) { + pw.println(" mBroadcastsScheduled [" + queue.mQueueName + "]=" + + queue.mBroadcastsScheduled); + } pw.println(" mHandler:"); mHandler.dump(new PrintWriterPrinter(pw), " "); needSep = true; @@ -9024,6 +9394,9 @@ 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; @@ -9032,75 +9405,87 @@ public final class ActivityManagerService extends ActivityManagerNative matcher.build(args, opti); pw.println("ACTIVITY MANAGER SERVICES (dumpsys activity services)"); - if (mServices.size() > 0) { - boolean printed = false; - long nowReal = SystemClock.elapsedRealtime(); - Iterator<ServiceRecord> it = mServices.values().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)); - pw.print(" -> "); - ProcessRecord proc = conn.binding.client; - pw.println(proc != null ? proc.toShortString() : "null"); + 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(); + 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; } - } 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; } } - needSep = printed; + } catch (RemoteException re) { + } if (mPendingServices.size() > 0) { @@ -9204,82 +9589,14 @@ public final class ActivityManagerService extends ActivityManagerNative boolean dumpProvidersLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { - boolean needSep = false; + boolean needSep = true; ItemMatcher matcher = new ItemMatcher(); matcher.build(args, opti); pw.println("ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)"); - if (mProvidersByClass.size() > 0) { - boolean printed = false; - Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it - = mProvidersByClass.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<ComponentName, ContentProviderRecord> e = it.next(); - ContentProviderRecord r = e.getValue(); - ComponentName comp = e.getKey(); - String cls = comp.getClassName(); - int end = cls.lastIndexOf('.'); - if (end > 0 && end < (cls.length()-2)) { - cls = cls.substring(end+1); - } - if (!matcher.match(r, comp)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(comp.getPackageName())) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Published content providers (by class):"); - printed = true; - } - pw.print(" * "); pw.print(cls); pw.print(" ("); - pw.print(comp.flattenToShortString()); pw.println(")"); - if (dumpAll) { - r.dump(pw, " "); - } else { - if (r.proc != null) { - pw.print(" "); pw.println(r.proc); - } else { - pw.println(); - } - if (r.clients.size() > 0) { - pw.println(" Clients:"); - for (ProcessRecord cproc : r.clients) { - pw.print(" - "); pw.println(cproc); - } - } - } - } - } - - if (dumpAll) { - if (mProvidersByName.size() > 0) { - boolean printed = false; - Iterator<Map.Entry<String, ContentProviderRecord>> it - = mProvidersByName.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<String, ContentProviderRecord> e = it.next(); - ContentProviderRecord r = e.getValue(); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.name.getPackageName())) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Authority to provider mappings:"); - printed = true; - } - pw.print(" "); pw.print(e.getKey()); pw.println(":"); - pw.print(" "); pw.println(r); - } - } - } + + mProviderMap.dumpProvidersLocked(pw, dumpAll); if (mLaunchingProviders.size() > 0) { boolean printed = false; @@ -9384,7 +9701,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Complete + brief == give a summary. Isn't that obvious?!? if (lastTask.intent != null) { pw.print(prefix); pw.print(" "); - pw.println(lastTask.intent.toInsecureString()); + pw.println(lastTask.intent.toInsecureStringWithClip()); } } } @@ -9690,6 +10007,38 @@ public final class ActivityManagerService extends ActivityManagerNative } } + final void dumpDbInfo(FileDescriptor fd, PrintWriter pw, String[] args) { + ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, args); + if (procs == null) { + return; + } + + pw.println("Applications Database Info:"); + + for (int i = procs.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = procs.get(i); + if (r.thread != null) { + pw.println("\n** Database info for pid " + r.pid + " [" + r.processName + "] **"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.thread.dumpDbInfo(tp.getWriteFd().getFileDescriptor(), args); + tp.go(fd); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println("Failure while dumping the app: " + r); + pw.flush(); + } catch (RemoteException e) { + pw.println("Got a RemoteException while dumping the app " + r); + pw.flush(); + } + } + } + } + final static class MemItem { final String label; final String shortLabel; @@ -9988,6 +10337,29 @@ public final class ActivityManagerService extends ActivityManagerNative } pw.println(); pw.print("Total PSS: "); pw.print(totalPss); pw.println(" kB"); + final int[] SINGLE_LONG_FORMAT = new int[] { + Process.PROC_SPACE_TERM|Process.PROC_OUT_LONG + }; + long[] longOut = new long[1]; + Process.readProcFile("/sys/kernel/mm/ksm/pages_shared", + SINGLE_LONG_FORMAT, null, longOut, null); + long shared = longOut[0] * ProcessList.PAGE_SIZE / 1024; + longOut[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_sharing", + SINGLE_LONG_FORMAT, null, longOut, null); + long sharing = longOut[0] * ProcessList.PAGE_SIZE / 1024; + longOut[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_unshared", + SINGLE_LONG_FORMAT, null, longOut, null); + long unshared = longOut[0] * ProcessList.PAGE_SIZE / 1024; + longOut[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_volatile", + SINGLE_LONG_FORMAT, null, longOut, null); + long voltile = longOut[0] * ProcessList.PAGE_SIZE / 1024; + pw.print(" KSM: "); pw.print(sharing); pw.print(" kB saved from shared "); + pw.print(shared); pw.println(" kB"); + pw.print(" "); pw.print(unshared); pw.print(" kB unshared; "); + pw.print(voltile); pw.println(" kB volatile"); } } @@ -10062,6 +10434,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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); @@ -10133,10 +10506,10 @@ public final class ActivityManagerService extends ActivityManagerNative cpr.notifyAll(); } - mProvidersByClass.remove(cpr.name); + mProviderMap.removeProviderByClass(cpr.name, UserId.getUserId(cpr.uid)); String names[] = cpr.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - mProvidersByName.remove(names[j]); + mProviderMap.removeProviderByName(names[j], UserId.getUserId(cpr.uid)); } Iterator<ProcessRecord> cit = cpr.clients.iterator(); @@ -10257,7 +10630,7 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<NL; i++) { ContentProviderRecord cpr = (ContentProviderRecord) mLaunchingProviders.get(i); - if (cpr.clients.size() <= 0 && cpr.externals <= 0) { + if (cpr.clients.size() <= 0 && !cpr.hasExternalProcessHandles()) { synchronized (cpr) { cpr.launchingApp = null; cpr.notifyAll(); @@ -10297,10 +10670,11 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - if (!app.persistent) { + if (!app.persistent || app.isolated) { if (DEBUG_PROCESSES) Slog.v(TAG, "Removing non-persistent process during cleanup: " + app); - mProcessNames.remove(app.processName, app.info.uid); + mProcessNames.remove(app.processName, app.uid); + mIsolatedProcesses.remove(app.uid); if (mHeavyWeightProcess == app) { mHeavyWeightProcess = null; mHandler.sendEmptyMessage(CANCEL_HEAVY_NOTIFICATION_MSG); @@ -10325,10 +10699,10 @@ public final class ActivityManagerService extends ActivityManagerNative mPreviousProcess = null; } - if (restart) { + if (restart && !app.isolated) { // We have components that still need to be running in the // process, so re-launch it. - mProcessNames.put(app.processName, app.info.uid, app); + mProcessNames.put(app.processName, app.uid, app); startProcessLocked(app, "restart", app.processName); } else if (app.pid > 0 && app.pid != MY_PID) { // Goodbye! @@ -10408,12 +10782,15 @@ public final class ActivityManagerService extends ActivityManagerNative public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags) { + enforceNotIsolatedCaller("getServices"); synchronized (this) { ArrayList<ActivityManager.RunningServiceInfo> res = new ArrayList<ActivityManager.RunningServiceInfo>(); - if (mServices.size() > 0) { - Iterator<ServiceRecord> it = mServices.values().iterator(); + 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())); } @@ -10432,8 +10809,10 @@ public final class ActivityManagerService extends ActivityManagerNative } public PendingIntent getRunningServiceControlPanel(ComponentName name) { + enforceNotIsolatedCaller("getRunningServiceControlPanel"); synchronized (this) { - ServiceRecord r = mServices.get(name); + 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++) { @@ -10449,7 +10828,7 @@ public final class ActivityManagerService extends ActivityManagerNative private final ServiceRecord findServiceLocked(ComponentName name, IBinder token) { - ServiceRecord r = mServices.get(name); + ServiceRecord r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); return r == token ? r : null; } @@ -10464,21 +10843,21 @@ public final class ActivityManagerService extends ActivityManagerNative }; private ServiceLookupResult findServiceLocked(Intent service, - String resolvedType) { + String resolvedType, int userId) { ServiceRecord r = null; if (service.getComponent() != null) { - r = mServices.get(service.getComponent()); + r = mServiceMap.getServiceByName(service.getComponent(), userId); } if (r == null) { Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServicesByIntent.get(filter); + r = mServiceMap.getServiceByIntent(filter, userId); } if (r == null) { try { ResolveInfo rInfo = AppGlobals.getPackageManager().resolveService( - service, resolvedType, 0); + service, resolvedType, 0, userId); ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; if (sInfo == null) { @@ -10487,7 +10866,7 @@ public final class ActivityManagerService extends ActivityManagerNative ComponentName name = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); - r = mServices.get(name); + r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); } catch (RemoteException ex) { // pm is in same process, this will never happen. } @@ -10532,18 +10911,24 @@ public final class ActivityManagerService extends ActivityManagerNative } private ServiceLookupResult retrieveServiceLocked(Intent service, - String resolvedType, int callingPid, int callingUid) { + 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 = mServices.get(service.getComponent()); + r = mServiceMap.getServiceByName(service.getComponent(), userId); + } + if (r == null) { + Intent.FilterComparison filter = new Intent.FilterComparison(service); + r = mServiceMap.getServiceByIntent(filter, userId); } - Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServicesByIntent.get(filter); if (r == null) { try { ResolveInfo rInfo = AppGlobals.getPackageManager().resolveService( - service, resolvedType, STOCK_PM_FLAGS); + service, resolvedType, STOCK_PM_FLAGS, userId); ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; if (sInfo == null) { @@ -10551,12 +10936,18 @@ public final class ActivityManagerService extends ActivityManagerNative ": 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 = mServices.get(name); + r = mServiceMap.getServiceByName(name, userId); if (r == null) { - filter = new Intent.FilterComparison(service.cloneFilter()); + Intent.FilterComparison filter = new Intent.FilterComparison( + service.cloneFilter()); ServiceRestarter res = new ServiceRestarter(); BatteryStatsImpl.Uid.Pkg.Serv ss = null; BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); @@ -10567,8 +10958,8 @@ public final class ActivityManagerService extends ActivityManagerNative } r = new ServiceRecord(this, ss, name, filter, sInfo, res); res.setService(r); - mServices.put(name, r); - mServicesByIntent.put(filter, 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(); @@ -10648,9 +11039,9 @@ public final class ActivityManagerService extends ActivityManagerNative si.deliveredTime = SystemClock.uptimeMillis(); r.deliveredStarts.add(si); si.deliveryCount++; - if (si.targetPermissionUid >= 0 && si.intent != null) { - grantUriPermissionUncheckedFromIntentLocked(si.targetPermissionUid, - r.packageName, si.intent, si.getUriPermissionsLocked()); + if (si.neededGrants != null) { + grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, + si.getUriPermissionsLocked()); } bumpServiceExecutingLocked(r, "start"); if (!oomAdjusted) { @@ -10715,7 +11106,9 @@ public final class ActivityManagerService extends ActivityManagerNative 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(); @@ -10726,7 +11119,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean created = false; try { mStringBuilder.setLength(0); - r.intent.getIntent().toShortString(mStringBuilder, true, false, true); + 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); @@ -10752,7 +11145,7 @@ public final class ActivityManagerService extends ActivityManagerNative // be called. if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), - null, -1)); + null, null)); } sendServiceArgsLocked(r, true); @@ -10903,40 +11296,60 @@ public final class ActivityManagerService extends ActivityManagerNative // Service is now being launched, its package can't be stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( - r.packageName, false); + 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 = getProcessRecordLocked(appName, r.appInfo.uid); - 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); - } + ProcessRecord app; - // If a dead object exception was thrown -- fall through to - // restart the application. + 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 (startProcessLocked(appName, r.appInfo, true, intentFlags, - "service", r.name, false) == 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 (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); } @@ -11016,8 +11429,8 @@ public final class ActivityManagerService extends ActivityManagerNative System.identityHashCode(r), r.shortName, (r.app != null) ? r.app.pid : -1); - mServices.remove(r.name); - mServicesByIntent.remove(r.intent); + mServiceMap.removeServiceByName(r.name, r.userId); + mServiceMap.removeServiceByIntent(r.intent, r.userId); r.totalRestartCount = 0; unscheduleServiceRestartLocked(r); @@ -11095,7 +11508,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, - callingPid, callingUid); + callingPid, callingUid, UserId.getUserId(callingUid)); if (res == null) { return null; } @@ -11104,15 +11517,15 @@ public final class ActivityManagerService extends ActivityManagerNative ? res.permission : "private to package"); } ServiceRecord r = res.record; - int targetPermissionUid = checkGrantUriPermissionFromIntentLocked( - callingUid, r.packageName, service); + 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, targetPermissionUid)); + service, neededGrants)); r.lastActivity = SystemClock.uptimeMillis(); synchronized (r.stats.getBatteryStats()) { r.stats.startRunningLocked(); @@ -11126,11 +11539,14 @@ public final class ActivityManagerService extends ActivityManagerNative public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType) { + enforceNotIsolatedCaller("startService"); // Refuse possible leaked file descriptors if (service != null && service.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); } + if (DEBUG_SERVICE) + Slog.v(TAG, "startService: " + service + " type=" + resolvedType); synchronized(this) { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -11145,6 +11561,8 @@ public final class ActivityManagerService extends ActivityManagerNative ComponentName startServiceInPackage(int uid, Intent service, String resolvedType) { 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); @@ -11164,6 +11582,7 @@ public final class ActivityManagerService extends ActivityManagerNative public int stopService(IApplicationThread caller, Intent service, String resolvedType) { + enforceNotIsolatedCaller("stopService"); // Refuse possible leaked file descriptors if (service != null && service.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -11182,7 +11601,8 @@ public final class ActivityManagerService extends ActivityManagerNative } // If this service is active, make sure it is stopped. - ServiceLookupResult r = findServiceLocked(service, resolvedType); + ServiceLookupResult r = findServiceLocked(service, resolvedType, + callerApp == null ? UserId.getCallingUserId() : callerApp.userId); if (r != null) { if (r.record != null) { final long origId = Binder.clearCallingIdentity(); @@ -11201,6 +11621,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public IBinder peekService(Intent service, String resolvedType) { + enforceNotIsolatedCaller("peekService"); // Refuse possible leaked file descriptors if (service != null && service.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -11209,7 +11630,8 @@ public final class ActivityManagerService extends ActivityManagerNative IBinder ret = null; synchronized(this) { - ServiceLookupResult r = findServiceLocked(service, resolvedType); + ServiceLookupResult r = findServiceLocked(service, resolvedType, + UserId.getCallingUserId()); if (r != null) { // r.record is null if findServiceLocked() failed the caller permission check @@ -11335,19 +11757,40 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + boolean isSingleton(String componentProcessName, ApplicationInfo aInfo) { + boolean result = false; + if (UserId.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) { + result = false; + } 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); + } + return result; + } + public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, - IServiceConnection connection, int flags) { + IServiceConnection connection, int flags, int userId) { + enforceNotIsolatedCaller("bindService"); // Refuse possible leaked file descriptors if (service != null && service.hasFileDescriptors() == true) { 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( @@ -11390,13 +11833,18 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, - Binder.getCallingPid(), Binder.getCallingUid()); + 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(); @@ -11736,7 +12184,9 @@ public final class ActivityManagerService extends ActivityManagerNative 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); @@ -11830,7 +12280,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); + app.packageName, false, UserId.getUserId(app.uid)); } catch (RemoteException e) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " @@ -11843,7 +12293,7 @@ public final class ActivityManagerService extends ActivityManagerNative : new ComponentName("android", "FullBackupAgent"); // startProcessLocked() returns existing proc's record if it's already running ProcessRecord proc = startProcessLocked(app.processName, app, - false, 0, "backup", hostingName, false); + false, 0, "backup", hostingName, false, false); if (proc == null) { Slog.e(TAG, "Unable to start backup agent process " + r); return false; @@ -11967,19 +12417,30 @@ public final class ActivityManagerService extends ActivityManagerNative return cur; } - private final void scheduleBroadcastsLocked() { - if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts: current=" - + mBroadcastsScheduled); + boolean isPendingBroadcastProcessLocked(int pid) { + return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid) + || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid); + } + + void skipPendingBroadcastLocked(int pid) { + Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping"); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.skipPendingBroadcastLocked(pid); + } + } - if (mBroadcastsScheduled) { - return; + // The app just attached; send any pending broadcasts that it should receive + boolean sendPendingBroadcastsLocked(ProcessRecord app) { + boolean didSomething = false; + for (BroadcastQueue queue : mBroadcastQueues) { + didSomething |= queue.sendPendingBroadcastsLocked(app); } - mHandler.sendEmptyMessage(BROADCAST_INTENT_MSG); - mBroadcastsScheduled = true; + return didSomething; } public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String permission) { + enforceNotIsolatedCaller("registerReceiver"); synchronized(this) { ProcessRecord callerApp = null; if (caller != null) { @@ -12057,13 +12518,12 @@ public final class ActivityManagerService extends ActivityManagerNative int N = allSticky.size(); for (int i=0; i<N; i++) { Intent intent = (Intent)allSticky.get(i); - BroadcastRecord r = new BroadcastRecord(intent, null, + BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, null, null, -1, -1, null, receivers, null, 0, null, null, false, true, true); - if (mParallelBroadcasts.size() == 0) { - scheduleBroadcastsLocked(); - } - mParallelBroadcasts.add(r); + queue.enqueueParallelBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } } @@ -12074,38 +12534,46 @@ public final class ActivityManagerService extends ActivityManagerNative public void unregisterReceiver(IIntentReceiver receiver) { if (DEBUG_BROADCAST) Slog.v(TAG, "Unregister receiver: " + receiver); - boolean doNext = false; + final long origId = Binder.clearCallingIdentity(); + try { + boolean doTrim = false; - synchronized(this) { - ReceiverList rl + synchronized(this) { + ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); - if (rl != null) { - if (rl.curBroadcast != null) { - BroadcastRecord r = rl.curBroadcast; - doNext = finishReceiverLocked( - receiver.asBinder(), r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - } + if (rl != null) { + if (rl.curBroadcast != null) { + BroadcastRecord r = rl.curBroadcast; + final boolean doNext = finishReceiverLocked( + receiver.asBinder(), r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + if (doNext) { + doTrim = true; + r.queue.processNextBroadcast(false); + } + } - if (rl.app != null) { - rl.app.receivers.remove(rl); - } - removeReceiverLocked(rl); - if (rl.linkedToDeath) { - rl.linkedToDeath = false; - rl.receiver.asBinder().unlinkToDeath(rl, 0); + if (rl.app != null) { + rl.app.receivers.remove(rl); + } + removeReceiverLocked(rl); + if (rl.linkedToDeath) { + rl.linkedToDeath = false; + rl.receiver.asBinder().unlinkToDeath(rl, 0); + } } } - } - if (!doNext) { - return; + // If we actually concluded any broadcasts, we might now be able + // to trim the recipients' apps from our working set + if (doTrim) { + trimApplications(); + return; + } + + } finally { + Binder.restoreCallingIdentity(origId); } - - final long origId = Binder.clearCallingIdentity(); - processNextBroadcast(false); - trimApplications(); - Binder.restoreCallingIdentity(origId); } void removeReceiverLocked(ReceiverList rl) { @@ -12132,7 +12600,8 @@ public final class ActivityManagerService extends ActivityManagerNative String callerPackage, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, - boolean ordered, boolean sticky, int callingPid, int callingUid) { + boolean ordered, boolean sticky, int callingPid, int callingUid, + int userId) { intent = new Intent(intent); // By default broadcasts do not go to stopped apps. @@ -12140,7 +12609,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_BROADCAST_LIGHT) Slog.v( TAG, (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent - + " ordered=" + ordered); + + " ordered=" + ordered + " userid=" + userId); if ((resultTo != null) && !ordered) { Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!"); } @@ -12175,7 +12644,7 @@ public final class ActivityManagerService extends ActivityManagerNative String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); if (list != null && (list.length > 0)) { for (String pkg : list) { - forceStopPackageLocked(pkg, -1, false, true, true, false); + forceStopPackageLocked(pkg, -1, false, true, true, false, userId); } sendPackageBroadcastLocked( IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list); @@ -12186,7 +12655,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (data != null && (ssp=data.getSchemeSpecificPart()) != null) { if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) { forceStopPackageLocked(ssp, - intent.getIntExtra(Intent.EXTRA_UID, -1), false, true, true, false); + intent.getIntExtra(Intent.EXTRA_UID, -1), false, true, true, + false, userId); } if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED, @@ -12253,7 +12723,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } catch (RemoteException e) { Slog.w(TAG, "Remote exception", e); - return BROADCAST_SUCCESS; + return ActivityManager.BROADCAST_SUCCESS; } } @@ -12271,7 +12741,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (requiredPermission != null) { Slog.w(TAG, "Can't broadcast sticky intent " + intent + " and enforce permission " + requiredPermission); - return BROADCAST_STICKY_CANT_HAVE_PERMISSION; + return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION; } if (intent.getComponent() != null) { throw new SecurityException( @@ -12303,11 +12773,15 @@ public final class ActivityManagerService extends ActivityManagerNative if (intent.getComponent() != null) { // Broadcast is going to one specific receiver class... ActivityInfo ai = AppGlobals.getPackageManager(). - getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS); + getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS, userId); if (ai != null) { receivers = new ArrayList(); ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = ai; + if (isSingleton(ai.processName, ai.applicationInfo)) { + ri.activityInfo = getActivityInfoForUser(ai, 0); + } else { + ri.activityInfo = getActivityInfoForUser(ai, userId); + } receivers.add(ri); } } else { @@ -12316,9 +12790,10 @@ public final class ActivityManagerService extends ActivityManagerNative == 0) { receivers = AppGlobals.getPackageManager().queryIntentReceivers( - intent, resolvedType, STOCK_PM_FLAGS); + intent, resolvedType, STOCK_PM_FLAGS, userId); } - registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false); + registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, + userId); } } catch (RemoteException ex) { // pm is in same process, this will never happen. @@ -12335,28 +12810,17 @@ public final class ActivityManagerService extends ActivityManagerNative // If we are not serializing this broadcast, then send the // registered receivers separately so they don't wait for the // components to be launched. - BroadcastRecord r = new BroadcastRecord(intent, callerApp, + final BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, ordered, sticky, false); if (DEBUG_BROADCAST) Slog.v( - TAG, "Enqueueing parallel broadcast " + r - + ": prev had " + mParallelBroadcasts.size()); - boolean replaced = false; - if (replacePending) { - for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { - if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "***** DROPPING PARALLEL: " + intent); - mParallelBroadcasts.set(i, r); - replaced = true; - break; - } - } - } + TAG, "Enqueueing parallel broadcast " + r); + final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r); if (!replaced) { - mParallelBroadcasts.add(r); - scheduleBroadcastsLocked(); + queue.enqueueParallelBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } registeredReceivers = null; NR = 0; @@ -12436,36 +12900,26 @@ public final class ActivityManagerService extends ActivityManagerNative if ((receivers != null && receivers.size() > 0) || resultTo != null) { - BroadcastRecord r = new BroadcastRecord(intent, callerApp, + BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, receivers, resultTo, resultCode, resultData, map, ordered, sticky, false); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r - + ": prev had " + mOrderedBroadcasts.size()); + + ": prev had " + queue.mOrderedBroadcasts.size()); if (DEBUG_BROADCAST) { int seq = r.intent.getIntExtra("seq", -1); Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq); } - boolean replaced = false; - if (replacePending) { - for (int i=mOrderedBroadcasts.size()-1; i>0; i--) { - if (intent.filterEquals(mOrderedBroadcasts.get(i).intent)) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "***** DROPPING ORDERED: " + intent); - mOrderedBroadcasts.set(i, r); - replaced = true; - break; - } - } - } + boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { - mOrderedBroadcasts.add(r); - scheduleBroadcastsLocked(); + queue.enqueueOrderedBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } } - return BROADCAST_SUCCESS; + return ActivityManager.BROADCAST_SUCCESS; } final Intent verifyBroadcastLocked(Intent intent) { @@ -12500,7 +12954,8 @@ public final class ActivityManagerService extends ActivityManagerNative public final int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, - String requiredPermission, boolean serialized, boolean sticky) { + String requiredPermission, boolean serialized, boolean sticky, int userId) { + enforceNotIsolatedCaller("broadcastIntent"); synchronized(this) { intent = verifyBroadcastLocked(intent); @@ -12511,8 +12966,8 @@ public final class ActivityManagerService extends ActivityManagerNative int res = broadcastIntentLocked(callerApp, callerApp != null ? callerApp.info.packageName : null, intent, resolvedType, resultTo, - resultCode, resultData, map, requiredPermission, serialized, - sticky, callingPid, callingUid); + resultCode, resultData, map, requiredPermission, serialized, sticky, + callingPid, callingUid, userId); Binder.restoreCallingIdentity(origId); return res; } @@ -12521,21 +12976,21 @@ public final class ActivityManagerService extends ActivityManagerNative int broadcastIntentInPackage(String packageName, int uid, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, - String requiredPermission, boolean serialized, boolean sticky) { + String requiredPermission, boolean serialized, boolean sticky, int userId) { synchronized(this) { intent = verifyBroadcastLocked(intent); final long origId = Binder.clearCallingIdentity(); int res = broadcastIntentLocked(null, packageName, intent, resolvedType, resultTo, resultCode, resultData, map, requiredPermission, - serialized, sticky, -1, uid); + serialized, sticky, -1, uid, userId); Binder.restoreCallingIdentity(origId); return res; } } - public final void unbroadcastIntent(IApplicationThread caller, - Intent intent) { + // 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"); @@ -12568,54 +13023,14 @@ public final class ActivityManagerService extends ActivityManagerNative private final boolean finishReceiverLocked(IBinder receiver, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort, boolean explicit) { - if (mOrderedBroadcasts.size() == 0) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but no pending broadcasts"); - } - return false; - } - BroadcastRecord r = mOrderedBroadcasts.get(0); - if (r.receiver == null) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but none active"); - } - return false; - } - if (r.receiver != receiver) { - Slog.w(TAG, "finishReceiver called but active receiver is different"); + final BroadcastRecord r = broadcastRecordForReceiverLocked(receiver); + if (r == null) { + Slog.w(TAG, "finishReceiver called but not found on queue"); return false; } - int state = r.state; - r.state = BroadcastRecord.IDLE; - if (state == BroadcastRecord.IDLE) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but state is IDLE"); - } - } - r.receiver = null; - r.intent.setComponent(null); - if (r.curApp != null) { - r.curApp.curReceiver = null; - } - if (r.curFilter != null) { - r.curFilter.receiverList.curBroadcast = null; - } - r.curFilter = null; - r.curApp = null; - r.curComponent = null; - r.curReceiver = null; - mPendingBroadcast = null; - r.resultCode = resultCode; - r.resultData = resultData; - r.resultExtras = resultExtras; - r.resultAbort = resultAbort; - - // We will process the next receiver right now if this is finishing - // an app receiver (which is always asynchronous) or after we have - // come back from calling a receiver. - return state == BroadcastRecord.APP_RECEIVE - || state == BroadcastRecord.CALL_DONE_RECEIVE; + return r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, + explicit); } public void finishReceiver(IBinder who, int resultCode, String resultData, @@ -12627,615 +13042,28 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Bundle"); } - boolean doNext; - final long origId = Binder.clearCallingIdentity(); - - synchronized(this) { - doNext = finishReceiverLocked( - who, resultCode, resultData, resultExtras, resultAbort, true); - } - - if (doNext) { - processNextBroadcast(false); - } - trimApplications(); - - Binder.restoreCallingIdentity(origId); - } - - private final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { - if (r.nextReceiver > 0) { - Object curReceiver = r.receivers.get(r.nextReceiver-1); - if (curReceiver instanceof BroadcastFilter) { - BroadcastFilter bf = (BroadcastFilter) curReceiver; - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver - 1, - System.identityHashCode(bf)); - } else { - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver - 1, - ((ResolveInfo)curReceiver).toString()); - } - } else { - Slog.w(TAG, "Discarding broadcast before first receiver is invoked: " - + r); - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver, - "NONE"); - } - } - - private final void setBroadcastTimeoutLocked(long timeoutTime) { - if (! mPendingBroadcastTimeoutMessage) { - Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG); - mHandler.sendMessageAtTime(msg, timeoutTime); - mPendingBroadcastTimeoutMessage = true; - } - } - - private final void cancelBroadcastTimeoutLocked() { - if (mPendingBroadcastTimeoutMessage) { - mHandler.removeMessages(BROADCAST_TIMEOUT_MSG); - mPendingBroadcastTimeoutMessage = false; - } - } - - private final void broadcastTimeoutLocked(boolean fromMsg) { - if (fromMsg) { - mPendingBroadcastTimeoutMessage = false; - } - - if (mOrderedBroadcasts.size() == 0) { - return; - } - - long now = SystemClock.uptimeMillis(); - BroadcastRecord r = mOrderedBroadcasts.get(0); - if (fromMsg) { - if (mDidDexOpt) { - // Delay timeouts until dexopt finishes. - mDidDexOpt = false; - long timeoutTime = SystemClock.uptimeMillis() + BROADCAST_TIMEOUT; - setBroadcastTimeoutLocked(timeoutTime); - return; - } - if (! mProcessesReady) { - // Only process broadcast timeouts if the system is ready. That way - // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended - // to do heavy lifting for system up. - return; - } - - long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT; - if (timeoutTime > now) { - // We can observe premature timeouts because we do not cancel and reset the - // broadcast timeout message after each receiver finishes. Instead, we set up - // an initial timeout then kick it down the road a little further as needed - // when it expires. - if (DEBUG_BROADCAST) Slog.v(TAG, - "Premature timeout @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for " - + timeoutTime); - setBroadcastTimeoutLocked(timeoutTime); - return; - } - } - - Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver - + ", started " + (now - r.receiverTime) + "ms ago"); - r.receiverTime = now; - r.anrCount++; - - // Current receiver has passed its expiration date. - if (r.nextReceiver <= 0) { - Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0"); - return; - } - - ProcessRecord app = null; - String anrMessage = null; - - Object curReceiver = r.receivers.get(r.nextReceiver-1); - Slog.w(TAG, "Receiver during timeout: " + curReceiver); - logBroadcastReceiverDiscardLocked(r); - if (curReceiver instanceof BroadcastFilter) { - BroadcastFilter bf = (BroadcastFilter)curReceiver; - if (bf.receiverList.pid != 0 - && bf.receiverList.pid != MY_PID) { - synchronized (this.mPidsSelfLocked) { - app = this.mPidsSelfLocked.get( - bf.receiverList.pid); - } - } - } else { - app = r.curApp; - } - - if (app != null) { - anrMessage = "Broadcast of " + r.intent.toString(); - } - - if (mPendingBroadcast == r) { - mPendingBroadcast = null; - } - - // Move on to the next receiver. - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - scheduleBroadcastsLocked(); - - if (anrMessage != null) { - // Post the ANR to the handler since we do not want to process ANRs while - // potentially holding our lock. - mHandler.post(new AppNotResponding(app, anrMessage)); - } - } - - private final void processCurBroadcastLocked(BroadcastRecord r, - ProcessRecord app) throws RemoteException { - if (DEBUG_BROADCAST) Slog.v(TAG, - "Process cur broadcast " + r + " for app " + app); - if (app.thread == null) { - throw new RemoteException(); - } - r.receiver = app.thread.asBinder(); - r.curApp = app; - app.curReceiver = r; - updateLruProcessLocked(app, true, true); - - // Tell the application to launch this receiver. - r.intent.setComponent(r.curComponent); - - boolean started = false; try { - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, - "Delivering to component " + r.curComponent - + ": " + r); - ensurePackageDexOpt(r.intent.getComponent().getPackageName()); - app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver, - compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo), - r.resultCode, r.resultData, r.resultExtras, r.ordered); - if (DEBUG_BROADCAST) Slog.v(TAG, - "Process cur broadcast " + r + " DELIVERED for app " + app); - started = true; - } finally { - if (!started) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "Process cur broadcast " + r + ": NOT STARTED!"); - r.receiver = null; - r.curApp = null; - app.curReceiver = null; - } - } - - } - - static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, - Intent intent, int resultCode, String data, Bundle extras, - boolean ordered, boolean sticky) 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); - } else { - receiver.performReceive(intent, resultCode, data, extras, ordered, sticky); - } - } - - private final void deliverToRegisteredReceiverLocked(BroadcastRecord r, - BroadcastFilter filter, boolean ordered) { - boolean skip = false; - if (filter.requiredPermission != null) { - int perm = checkComponentPermission(filter.requiredPermission, - r.callingPid, r.callingUid, -1, true); - if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" - + r.callingPid + ", uid=" + r.callingUid + ")" - + " requires " + filter.requiredPermission - + " due to registered receiver " + filter); - skip = true; - } - } - if (r.requiredPermission != null) { - int perm = checkComponentPermission(r.requiredPermission, - filter.receiverList.pid, filter.receiverList.uid, -1, true); - if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: receiving " - + r.intent.toString() - + " to " + filter.receiverList.app - + " (pid=" + filter.receiverList.pid - + ", uid=" + filter.receiverList.uid + ")" - + " requires " + r.requiredPermission - + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - skip = true; - } - } - - if (!skip) { - // If this is not being sent as an ordered broadcast, then we - // don't want to touch the fields that keep track of the current - // state of ordered broadcasts. - if (ordered) { - r.receiver = filter.receiverList.receiver.asBinder(); - r.curFilter = filter; - filter.receiverList.curBroadcast = r; - r.state = BroadcastRecord.CALL_IN_RECEIVE; - if (filter.receiverList.app != null) { - // Bump hosting application to no longer be in background - // scheduling class. Note that we can't do that if there - // isn't an app... but we can only be in that case for - // things that directly call the IActivityManager API, which - // are already core system stuff so don't matter for this. - r.curApp = filter.receiverList.app; - filter.receiverList.app.curReceiver = r; - updateOomAdjLocked(); - } - } - try { - if (DEBUG_BROADCAST_LIGHT) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Delivering to " + filter - + " (seq=" + seq + "): " + r); - } - performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, - new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, r.ordered, r.initialSticky); - if (ordered) { - r.state = BroadcastRecord.CALL_DONE_RECEIVE; - } - } catch (RemoteException e) { - Slog.w(TAG, "Failure sending broadcast " + r.intent, e); - if (ordered) { - r.receiver = null; - r.curFilter = null; - filter.receiverList.curBroadcast = null; - if (filter.receiverList.app != null) { - filter.receiverList.app.curReceiver = null; - } - } - } - } - } - - private final void addBroadcastToHistoryLocked(BroadcastRecord r) { - if (r.callingUid < 0) { - // This was from a registerReceiver() call; ignore it. - return; - } - System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1, - MAX_BROADCAST_HISTORY-1); - r.finishTime = SystemClock.uptimeMillis(); - mBroadcastHistory[0] = r; - } - - private final void processNextBroadcast(boolean fromMsg) { - synchronized(this) { - BroadcastRecord r; - - if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast: " - + mParallelBroadcasts.size() + " broadcasts, " - + mOrderedBroadcasts.size() + " ordered broadcasts"); - - updateCpuStats(); - - if (fromMsg) { - mBroadcastsScheduled = false; - } - - // First, deliver any non-serialized broadcasts right away. - while (mParallelBroadcasts.size() > 0) { - r = mParallelBroadcasts.remove(0); - r.dispatchTime = SystemClock.uptimeMillis(); - r.dispatchClockTime = System.currentTimeMillis(); - final int N = r.receivers.size(); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast " - + r); - for (int i=0; i<N; i++) { - Object target = r.receivers.get(i); - if (DEBUG_BROADCAST) Slog.v(TAG, - "Delivering non-ordered to registered " - + target + ": " + r); - deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); - } - addBroadcastToHistoryLocked(r); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast " - + r); - } - - // Now take care of the next serialized one... - - // If we are waiting for a process to come up to handle the next - // broadcast, then do nothing at this point. Just in case, we - // check that the process we're waiting for still exists. - if (mPendingBroadcast != null) { - if (DEBUG_BROADCAST_LIGHT) { - Slog.v(TAG, "processNextBroadcast: waiting for " - + mPendingBroadcast.curApp); - } - - boolean isDead; - synchronized (mPidsSelfLocked) { - isDead = (mPidsSelfLocked.get(mPendingBroadcast.curApp.pid) == null); - } - if (!isDead) { - // It's still alive, so keep waiting - return; - } else { - Slog.w(TAG, "pending app " + mPendingBroadcast.curApp - + " died before responding to broadcast"); - mPendingBroadcast.state = BroadcastRecord.IDLE; - mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; - mPendingBroadcast = null; - } - } - - boolean looped = false; - - do { - if (mOrderedBroadcasts.size() == 0) { - // No more broadcasts pending, so all done! - scheduleAppGcsLocked(); - if (looped) { - // If we had finished the last ordered broadcast, then - // make sure all processes have correct oom and sched - // adjustments. - updateOomAdjLocked(); - } - return; - } - r = mOrderedBroadcasts.get(0); - boolean forceReceive = false; - - // Ensure that even if something goes awry with the timeout - // detection, we catch "hung" broadcasts here, discard them, - // and continue to make progress. - // - // This is only done if the system is ready so that PRE_BOOT_COMPLETED - // receivers don't get executed with timeouts. They're intended for - // one time heavy lifting after system upgrades and can take - // significant amounts of time. - int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; - if (mProcessesReady && r.dispatchTime > 0) { - long now = SystemClock.uptimeMillis(); - if ((numReceivers > 0) && - (now > r.dispatchTime + (2*BROADCAST_TIMEOUT*numReceivers))) { - Slog.w(TAG, "Hung broadcast discarded after timeout failure:" - + " now=" + now - + " dispatchTime=" + r.dispatchTime - + " startTime=" + r.receiverTime - + " intent=" + r.intent - + " numReceivers=" + numReceivers - + " nextReceiver=" + r.nextReceiver - + " state=" + r.state); - broadcastTimeoutLocked(false); // forcibly finish this broadcast - forceReceive = true; - r.state = BroadcastRecord.IDLE; - } - } - - if (r.state != BroadcastRecord.IDLE) { - if (DEBUG_BROADCAST) Slog.d(TAG, - "processNextBroadcast() called when not idle (state=" - + r.state + ")"); - return; - } - - if (r.receivers == null || r.nextReceiver >= numReceivers - || r.resultAbort || forceReceive) { - // No more receivers for this broadcast! Send the final - // result if requested... - if (r.resultTo != null) { - try { - if (DEBUG_BROADCAST) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Finishing broadcast " + r.intent.getAction() - + " seq=" + seq + " app=" + r.callerApp); - } - performReceiveLocked(r.callerApp, r.resultTo, - new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, false, false); - // Set this to null so that the reference - // (local and remote) isnt kept in the mBroadcastHistory. - r.resultTo = null; - } catch (RemoteException e) { - Slog.w(TAG, "Failure sending broadcast result of " + r.intent, e); - } - } - - if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG"); - cancelBroadcastTimeoutLocked(); - - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast " - + r); - - // ... and on to the next... - addBroadcastToHistoryLocked(r); - mOrderedBroadcasts.remove(0); - r = null; - looped = true; - continue; - } - } while (r == null); - - // Get the next receiver... - int recIdx = r.nextReceiver++; - - // Keep track of when this receiver started, and make sure there - // is a timeout message pending to kill it if need be. - r.receiverTime = SystemClock.uptimeMillis(); - if (recIdx == 0) { - r.dispatchTime = r.receiverTime; - r.dispatchClockTime = System.currentTimeMillis(); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast " - + r); - } - if (! mPendingBroadcastTimeoutMessage) { - long timeoutTime = r.receiverTime + BROADCAST_TIMEOUT; - if (DEBUG_BROADCAST) Slog.v(TAG, - "Submitting BROADCAST_TIMEOUT_MSG for " + r + " at " + timeoutTime); - setBroadcastTimeoutLocked(timeoutTime); - } - - Object nextReceiver = r.receivers.get(recIdx); - if (nextReceiver instanceof BroadcastFilter) { - // Simple case: this is a registered receiver who gets - // a direct call. - BroadcastFilter filter = (BroadcastFilter)nextReceiver; - if (DEBUG_BROADCAST) Slog.v(TAG, - "Delivering ordered to registered " - + filter + ": " + r); - deliverToRegisteredReceiverLocked(r, filter, r.ordered); - if (r.receiver == null || !r.ordered) { - // The receiver has already finished, so schedule to - // process the next one. - if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing: ordered=" - + r.ordered + " receiver=" + r.receiver); - r.state = BroadcastRecord.IDLE; - scheduleBroadcastsLocked(); - } - return; - } - - // Hard case: need to instantiate the receiver, possibly - // starting its application process to host it. - - ResolveInfo info = - (ResolveInfo)nextReceiver; - - boolean skip = false; - int perm = checkComponentPermission(info.activityInfo.permission, - r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, - info.activityInfo.exported); - if (perm != PackageManager.PERMISSION_GRANTED) { - if (!info.activityInfo.exported) { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " 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); - } 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); - } - skip = true; - } - if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && - r.requiredPermission != null) { - try { - perm = AppGlobals.getPackageManager(). - checkPermission(r.requiredPermission, - info.activityInfo.applicationInfo.packageName); - } catch (RemoteException e) { - perm = PackageManager.PERMISSION_DENIED; - } - if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: receiving " - + r.intent + " to " - + info.activityInfo.applicationInfo.packageName - + " requires " + r.requiredPermission - + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - skip = true; - } - } - if (r.curApp != null && r.curApp.crashing) { - // If the target process is crashing, just skip it. - if (DEBUG_BROADCAST) Slog.v(TAG, - "Skipping deliver ordered " + r + " to " + r.curApp - + ": process crashing"); - skip = true; - } - - if (skip) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "Skipping delivery of ordered " + r + " for whatever reason"); - r.receiver = null; - r.curFilter = null; - r.state = BroadcastRecord.IDLE; - scheduleBroadcastsLocked(); - return; - } - - r.state = BroadcastRecord.APP_RECEIVE; - String targetProcess = info.activityInfo.processName; - r.curComponent = new ComponentName( - info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - r.curReceiver = info.activityInfo; - - // Broadcast is being executed, its package can't be stopped. - try { - AppGlobals.getPackageManager().setPackageStoppedState( - r.curComponent.getPackageName(), false); - } catch (RemoteException e) { - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Failed trying to unstop package " - + r.curComponent.getPackageName() + ": " + e); - } + boolean doNext = false; + BroadcastRecord r = null; - // Is this receiver's application already running? - ProcessRecord app = getProcessRecordLocked(targetProcess, - info.activityInfo.applicationInfo.uid); - if (app != null && app.thread != null) { - try { - app.addPackage(info.activityInfo.packageName); - processCurBroadcastLocked(r, app); - return; - } catch (RemoteException e) { - Slog.w(TAG, "Exception when sending broadcast to " - + r.curComponent, e); + synchronized(this) { + r = broadcastRecordForReceiverLocked(who); + if (r != null) { + doNext = r.queue.finishReceiverLocked(r, resultCode, + resultData, resultExtras, resultAbort, true); } - - // If a dead object exception was thrown -- fall through to - // restart the application. } - // Not running -- get it started, to be executed when the app comes up. - if (DEBUG_BROADCAST) Slog.v(TAG, - "Need to start app " + targetProcess + " for broadcast " + r); - if ((r.curApp=startProcessLocked(targetProcess, - info.activityInfo.applicationInfo, true, - r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, - "broadcast", r.curComponent, - (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0)) - == null) { - // Ah, this recipient is unavailable. Finish it if necessary, - // and mark the broadcast record as ready for the next. - Slog.w(TAG, "Unable to launch app " - + info.activityInfo.applicationInfo.packageName + "/" - + info.activityInfo.applicationInfo.uid + " for broadcast " - + r.intent + ": process is bad"); - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - scheduleBroadcastsLocked(); - r.state = BroadcastRecord.IDLE; - return; + if (doNext) { + r.queue.processNextBroadcast(false); } - - mPendingBroadcast = r; - mPendingBroadcastRecvIndex = recIdx; + trimApplications(); + } finally { + Binder.restoreCallingIdentity(origId); } } - + // ========================================================= // INSTRUMENTATION // ========================================================= @@ -13243,6 +13071,7 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher) { + enforceNotIsolatedCaller("startInstrumentation"); // Refuse possible leaked file descriptors if (arguments != null && arguments.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Bundle"); @@ -13255,7 +13084,7 @@ public final class ActivityManagerService extends ActivityManagerNative ii = mContext.getPackageManager().getInstrumentationInfo( className, STOCK_PM_FLAGS); ai = mContext.getPackageManager().getApplicationInfo( - ii.targetPackage, STOCK_PM_FLAGS); + ii.targetPackage, STOCK_PM_FLAGS); } catch (PackageManager.NameNotFoundException e) { } if (ii == null) { @@ -13283,10 +13112,11 @@ 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); - ProcessRecord app = addAppLocked(ai); + forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId); + ProcessRecord app = addAppLocked(ai, false); app.instrumentationClass = className; app.instrumentationInfo = ai; app.instrumentationProfileFile = profileFile; @@ -13340,11 +13170,12 @@ public final class ActivityManagerService extends ActivityManagerNative app.instrumentationProfileFile = null; app.instrumentationArguments = null; - forceStopPackageLocked(app.processName, -1, false, false, true, true); + forceStopPackageLocked(app.processName, -1, false, false, true, true, app.userId); } public void finishInstrumentation(IApplicationThread target, int resultCode, Bundle results) { + int userId = UserId.getCallingUserId(); // Refuse possible leaked file descriptors if (results != null && results.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -13440,8 +13271,11 @@ public final class ActivityManagerService extends ActivityManagerNative * configuration. * @param persistent TODO */ - public boolean updateConfigurationLocked(Configuration values, + boolean updateConfigurationLocked(Configuration values, ActivityRecord starting, boolean persistent, boolean initLocale) { + // do nothing if we are headless + if (mHeadless) return true; + int changes = 0; boolean kept = true; @@ -13471,6 +13305,10 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.i(TAG, "Config changed: " + newConfig); final Configuration configCopy = new Configuration(mConfiguration); + + // TODO: If our config changes, should we auto dismiss any currently + // showing dialogs? + mShowDialogs = shouldShowDialogs(newConfig); AttributeCache ac = AttributeCache.instance(); if (ac != null) { @@ -13507,12 +13345,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); + null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); 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); + null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); } } } @@ -13537,6 +13375,19 @@ public final class ActivityManagerService extends ActivityManagerNative return kept; } + + /** + * Decide based on the configuration whether we should shouw the ANR, + * crash, etc dialogs. The idea is that if there is no affordnace to + * press the on-screen buttons, we shouldn't show the dialog. + * + * A thought: SystemUI might also want to get told about this, the Power + * dialog / global actions also might want different behaviors. + */ + private static final boolean shouldShowDialogs(Configuration config) { + return !(config.keyboard == Configuration.KEYBOARD_NOKEYS + && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH); + } /** * Save the locale. You must be inside a synchronized (this) block. @@ -13554,10 +13405,132 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity) { + ActivityRecord srec = ActivityRecord.forToken(token); + return srec != null && srec.task.affinity != null && + srec.task.affinity.equals(destAffinity); + } + + public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode, + Intent resultData) { + ComponentName dest = destIntent.getComponent(); + + synchronized (this) { + ActivityRecord srec = ActivityRecord.forToken(token); + if (srec == null) { + return false; + } + ArrayList<ActivityRecord> history = srec.stack.mHistory; + final int start = history.indexOf(srec); + if (start < 0) { + // Current activity is not in history stack; do nothing. + return false; + } + int finishTo = start - 1; + ActivityRecord parent = null; + boolean foundParentInTask = false; + if (dest != null) { + TaskRecord tr = srec.task; + for (int i = start - 1; i >= 0; i--) { + ActivityRecord r = history.get(i); + if (tr != r.task) { + // Couldn't find parent in the same task; stop at the one above this. + // (Root of current task; in-app "home" behavior) + // Always at least finish the current activity. + finishTo = Math.min(start - 1, i + 1); + parent = history.get(finishTo); + break; + } else if (r.info.packageName.equals(dest.getPackageName()) && + r.info.name.equals(dest.getClassName())) { + finishTo = i; + parent = r; + foundParentInTask = true; + break; + } + } + } + + if (mController != null) { + ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0); + if (next != null) { + // ask watcher if this is allowed + boolean resumeOK = true; + try { + resumeOK = mController.activityResuming(next.packageName); + } catch (RemoteException e) { + mController = null; + } + + if (!resumeOK) { + return false; + } + } + } + final long origId = Binder.clearCallingIdentity(); + for (int i = start; i > finishTo; i--) { + ActivityRecord r = history.get(i); + mMainStack.requestFinishActivityLocked(r.appToken, resultCode, resultData, + "navigate-up"); + // Only return the supplied result for the first activity finished + resultCode = Activity.RESULT_CANCELED; + resultData = null; + } + + if (parent != null && foundParentInTask) { + final int parentLaunchMode = parent.info.launchMode; + final int destIntentFlags = destIntent.getFlags(); + if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE || + parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK || + parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP || + (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + parent.deliverNewIntentLocked(srec.app.uid, destIntent); + } else { + try { + ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( + destIntent.getComponent(), 0, UserId.getCallingUserId()); + int res = mMainStack.startActivityLocked(srec.app.thread, destIntent, + null, aInfo, parent.appToken, null, + 0, -1, parent.launchedFromUid, 0, null, true, null); + foundParentInTask = res == ActivityManager.START_SUCCESS; + } catch (RemoteException e) { + foundParentInTask = false; + } + mMainStack.requestFinishActivityLocked(parent.appToken, resultCode, + resultData, "navigate-up"); + } + } + Binder.restoreCallingIdentity(origId); + return foundParentInTask; + } + } + // ========================================================= // LIFETIME MANAGEMENT // ========================================================= + // Returns which broadcast queue the app is the current [or imminent] receiver + // on, or 'null' if the app is not an active broadcast recipient. + private BroadcastQueue isReceivingBroadcast(ProcessRecord app) { + BroadcastRecord r = app.curReceiver; + if (r != null) { + return r.queue; + } + + // It's not the current receiver, but it might be starting up to become one + synchronized (this) { + for (BroadcastQueue queue : mBroadcastQueues) { + r = queue.mPendingBroadcast; + if (r != null && r.curApp == app) { + // found it; report which queue it's in + return queue; + } + } + } + + return null; + } + private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP, boolean recursed, boolean doingAll) { if (mAdjSeq == app.adjSeq) { @@ -13566,7 +13539,7 @@ public final class ActivityManagerService extends ActivityManagerNative // 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 = hiddenAdj; + app.curAdj = app.curRawAdj = app.nonStoppingAdj = hiddenAdj; } return app.curRawAdj; } @@ -13590,7 +13563,7 @@ public final class ActivityManagerService extends ActivityManagerNative // below foreground, so it is not worth doing work for it. app.adjType = "fixed"; app.adjSeq = mAdjSeq; - app.curRawAdj = app.maxAdj; + app.curRawAdj = app.nonStoppingAdj = app.maxAdj; app.foregroundActivities = false; app.keeping = true; app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; @@ -13623,6 +13596,7 @@ public final class ActivityManagerService extends ActivityManagerNative // important to least, and assign an appropriate OOM adjustment. int adj; int schedGroup; + BroadcastQueue queue; if (app == TOP_APP) { // The last app on the list is the foreground app. adj = ProcessList.FOREGROUND_APP_ADJ; @@ -13634,12 +13608,14 @@ public final class ActivityManagerService extends ActivityManagerNative adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "instrumentation"; - } else if (app.curReceiver != null || - (mPendingBroadcast != null && mPendingBroadcast.curApp == app)) { + } else if ((queue = isReceivingBroadcast(app)) != null) { // An app that is currently receiving a broadcast also - // counts as being in the foreground. + // counts as being in the foreground for OOM killer purposes. + // It's placed in a sched group based on the nature of the + // broadcast as reflected by which queue it's active in. adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; + schedGroup = (queue == mFgBroadcastQueue) + ? Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE; app.adjType = "broadcast"; } else if (app.executingServices.size() > 0) { // An app that is currently executing a service callback also @@ -13664,6 +13640,8 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "bg-empty"; } + boolean hasStoppingActivities = false; + // Examine all activities if not already foreground. if (!app.foregroundActivities && activitiesSize > 0) { for (int j = 0; j < activitiesSize; j++) { @@ -13678,15 +13656,20 @@ public final class ActivityManagerService extends ActivityManagerNative app.hidden = false; app.foregroundActivities = true; break; - } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED - || r.state == ActivityState.STOPPING) { - // Only upgrade adjustment. + } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; - app.adjType = "stopping"; + app.adjType = "pausing"; } app.hidden = false; app.foregroundActivities = true; + } else if (r.state == ActivityState.STOPPING) { + // We will apply the actual adjustment later, because + // we want to allow this process to immediately go through + // any memory trimming that is in effect. + app.hidden = false; + app.foregroundActivities = true; + hasStoppingActivities = true; } } } @@ -13744,7 +13727,7 @@ public final class ActivityManagerService extends ActivityManagerNative // this gives us a baseline and makes sure we don't get into an // infinite recursion. app.adjSeq = mAdjSeq; - app.curRawAdj = adj; + app.curRawAdj = app.nonStoppingAdj = adj; if (mBackupTarget != null && app == mBackupTarget.app) { // If possible we want to avoid killing apps while they're being backed up @@ -13988,7 +13971,7 @@ public final class ActivityManagerService extends ActivityManagerNative // If the provider has external (non-framework) process // dependencies, ensure that its adjustment is at least // FOREGROUND_APP_ADJ. - if (cpr.externals != 0) { + if (cpr.hasExternalProcessHandles()) { if (adj > ProcessList.FOREGROUND_APP_ADJ) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; @@ -14001,6 +13984,28 @@ public final class ActivityManagerService extends ActivityManagerNative } } + if (adj == ProcessList.SERVICE_ADJ) { + if (doingAll) { + app.serviceb = mNewNumServiceProcs > (mNumServiceProcs/3); + mNewNumServiceProcs++; + } + if (app.serviceb) { + adj = ProcessList.SERVICE_B_ADJ; + } + } else { + app.serviceb = false; + } + + app.nonStoppingAdj = adj; + + if (hasStoppingActivities) { + // Only upgrade adjustment. + if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + app.adjType = "stopping"; + } + } + app.curRawAdj = adj; //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid + @@ -14034,18 +14039,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - if (adj == ProcessList.SERVICE_ADJ) { - if (doingAll) { - app.serviceb = mNewNumServiceProcs > (mNumServiceProcs/3); - mNewNumServiceProcs++; - } - if (app.serviceb) { - adj = ProcessList.SERVICE_B_ADJ; - } - } else { - app.serviceb = false; - } - app.curAdj = adj; app.curSchedGroup = schedGroup; @@ -14080,8 +14073,13 @@ public final class ActivityManagerService extends ActivityManagerNative * Returns true if things are idle enough to perform GCs. */ private final boolean canGcNowLocked() { - return mParallelBroadcasts.size() == 0 - && mOrderedBroadcasts.size() == 0 + boolean processingBroadcasts = false; + for (BroadcastQueue q : mBroadcastQueues) { + if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) { + processingBroadcasts = true; + } + } + return !processingBroadcasts && (mSleeping || (mMainStack.mResumedActivity != null && mMainStack.mResumedActivity.idle)); } @@ -14252,7 +14250,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // If a process has held a wake lock for more // than 50% of the time during this period, - // that sounds pad. Kill! + // that sounds bad. Kill! if (doWakeKills && realtimeSince > 0 && ((wtimeUsed*100)/realtimeSince) >= 50) { synchronized (stats) { @@ -14300,23 +14298,6 @@ public final class ActivityManagerService extends ActivityManagerNative computeOomAdjLocked(app, hiddenAdj, TOP_APP, false, doingAll); if (app.curRawAdj != app.setRawAdj) { - if (false) { - // Removing for now. Forcing GCs is not so useful anymore - // with Dalvik, and the new memory level hint facility is - // better for what we need to do these days. - if (app.curRawAdj > ProcessList.FOREGROUND_APP_ADJ - && app.setRawAdj <= ProcessList.FOREGROUND_APP_ADJ) { - // If this app is transitioning from foreground to - // non-foreground, have it do a gc. - scheduleAppGcLocked(app); - } else if (app.curRawAdj >= ProcessList.HIDDEN_APP_MIN_ADJ - && app.setRawAdj < ProcessList.HIDDEN_APP_MIN_ADJ) { - // Likewise do a gc when an app is moving in to the - // background (such as a service stopping). - scheduleAppGcLocked(app); - } - } - if (wasKeeping && !app.keeping) { // This app is no longer something we want to keep. Note // its current wake lock time to later know to kill it if @@ -14433,6 +14414,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (factor < 1) factor = 1; int step = 0; int numHidden = 0; + int numTrimming = 0; // First update the OOM adjustment for each of the // application processes based on their current state. @@ -14463,6 +14445,25 @@ public final class ActivityManagerService extends ActivityManagerNative Process.killProcessQuiet(app.pid); } } + if (!app.killedBackground && 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 + // definition not re-use the same process again, and it is + // good to avoid having whatever code was running in them + // left sitting around after no longer needed. + Slog.i(TAG, "Isolated process " + app.processName + + " (pid " + app.pid + ") no longer needed"); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "isolated not needed"); + app.killedBackground = true; + Process.killProcessQuiet(app.pid); + } + if (app.nonStoppingAdj >= ProcessList.HOME_APP_ADJ + && app.nonStoppingAdj != ProcessList.SERVICE_B_ADJ + && !app.killedBackground) { + numTrimming++; + } } } @@ -14476,17 +14477,25 @@ public final class ActivityManagerService extends ActivityManagerNative // memory they want. if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/2)) { final int N = mLruProcesses.size(); - factor = numHidden/3; + factor = numTrimming/3; int minFactor = 2; if (mHomeProcess != null) minFactor++; if (mPreviousProcess != null) minFactor++; if (factor < minFactor) factor = minFactor; step = 0; + int fgTrimLevel; + if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/5)) { + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; + } else if (numHidden <= (ProcessList.MAX_HIDDEN_APPS/3)) { + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; + } else { + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; + } int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; for (i=0; i<N; i++) { ProcessRecord app = mLruProcesses.get(i); - if (app.curAdj >= ProcessList.HOME_APP_ADJ - && app.curAdj != ProcessList.SERVICE_B_ADJ + if (app.nonStoppingAdj >= ProcessList.HOME_APP_ADJ + && app.nonStoppingAdj != ProcessList.SERVICE_B_ADJ && !app.killedBackground) { if (app.trimMemoryLevel < curLevel && app.thread != null) { try { @@ -14508,6 +14517,7 @@ public final class ActivityManagerService extends ActivityManagerNative app.trimMemoryLevel = curLevel; step++; if (step >= factor) { + step = 0; switch (curLevel) { case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE; @@ -14517,7 +14527,7 @@ public final class ActivityManagerService extends ActivityManagerNative break; } } - } else if (app.curAdj == ProcessList.HEAVY_WEIGHT_APP_ADJ) { + } else if (app.nonStoppingAdj == ProcessList.HEAVY_WEIGHT_APP_ADJ) { if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND && app.thread != null) { try { @@ -14527,27 +14537,35 @@ public final class ActivityManagerService extends ActivityManagerNative } } app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; - } else if ((app.curAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi) - && app.pendingUiClean) { - if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN - && app.thread != null) { + } else { + if ((app.nonStoppingAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi) + && app.pendingUiClean) { + // If this application is now in the background and it + // had done UI, then give it the special trim level to + // have it free UI resources. + final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; + if (app.trimMemoryLevel < level && app.thread != null) { + try { + app.thread.scheduleTrimMemory(level); + } catch (RemoteException e) { + } + } + app.pendingUiClean = false; + } + if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) { try { - app.thread.scheduleTrimMemory( - ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + app.thread.scheduleTrimMemory(fgTrimLevel); } catch (RemoteException e) { } } - app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; - app.pendingUiClean = false; - } else { - app.trimMemoryLevel = 0; + app.trimMemoryLevel = fgTrimLevel; } } } else { final int N = mLruProcesses.size(); for (i=0; i<N; i++) { ProcessRecord app = mLruProcesses.get(i); - if ((app.curAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi) + if ((app.nonStoppingAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi) && app.pendingUiClean) { if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN && app.thread != null) { @@ -14557,11 +14575,9 @@ public final class ActivityManagerService extends ActivityManagerNative } catch (RemoteException e) { } } - app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; app.pendingUiClean = false; - } else { - app.trimMemoryLevel = 0; } + app.trimMemoryLevel = 0; } } @@ -14601,7 +14617,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.persistent) { if (app.persistent) { - addAppLocked(app.info); + addAppLocked(app.info, false); } } } @@ -14810,7 +14826,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { } } - public void onCoreSettingsChange(Bundle settings) { + void onCoreSettingsChange(Bundle settings) { for (int i = mLruProcesses.size() - 1; i >= 0; i--) { ProcessRecord processRecord = mLruProcesses.get(i); try { @@ -14825,8 +14841,199 @@ public final class ActivityManagerService extends ActivityManagerNative // Multi-user methods - public boolean switchUser(int userid) { - // TODO + private int mCurrentUserId; + private SparseIntArray mLoggedInUsers = new SparseIntArray(5); + + 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 (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)) { + return false; + } + mLoggedInUsers.append(userId, userId); + } + + mCurrentUserId = userId; + boolean haveActivities = mMainStack.switchUser(userId); + if (!haveActivities) { + startHomeActivityLocked(userId); + } + + } + + // 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); + return true; } + + @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; + } + return AppGlobals.getPackageManager().getUser(mCurrentUserId); + } + + private void onUserRemoved(Intent intent) { + int extraUserId = intent.getIntExtra(Intent.EXTRA_USERID, -1); + if (extraUserId < 1) return; + + // Kill all the processes for the user + ArrayList<Pair<String, Integer>> pkgAndUids = new ArrayList<Pair<String,Integer>>(); + 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))); + } + } + } + + for (Pair<String,Integer> pkgAndUid : pkgAndUids) { + forceStopPackageLocked(pkgAndUid.first, pkgAndUid.second, + false, false, true, true, extraUserId); + } + } + } + + private boolean userExists(int userId) { + try { + UserInfo user = AppGlobals.getPackageManager().getUser(userId); + return user != null; + } catch (RemoteException re) { + // Won't happen, in same process + } + + return false; + } + + private void checkValidCaller(int uid, int userId) { + if (UserId.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); + } + + private ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { + if (info == null) return null; + ApplicationInfo newInfo = new ApplicationInfo(info); + newInfo.uid = applyUserId(info.uid, userId); + newInfo.dataDir = USER_DATA_DIR + userId + "/" + + info.packageName; + return newInfo; + } + + ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) { + if (aInfo == null + || (userId < 1 && aInfo.applicationInfo.uid < UserId.PER_USER_RANGE)) { + return aInfo; + } + + ActivityInfo info = new ActivityInfo(aInfo); + 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 6ef36eb..cce8e7a 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -16,10 +16,12 @@ package com.android.server.am; +import com.android.internal.app.ResolverActivity; import com.android.server.AttributeCache; import com.android.server.am.ActivityStack.ActivityState; import android.app.Activity; +import android.app.ActivityOptions; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -34,6 +36,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -55,6 +58,7 @@ final class ActivityRecord { final IApplicationToken.Stub appToken; // window manager token final ActivityInfo info; // all about me final int launchedFromUid; // always the uid who started the activity. + final int userId; // Which user is this running for? final Intent intent; // the original intent that generated us final ComponentName realActivity; // the intent component, or target of an alias. final String shortComponentName; // the short component name of the intent @@ -92,6 +96,7 @@ final class ActivityRecord { ArrayList results; // pending ActivityResult objs we have received HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act ArrayList newIntents; // any pending new intents for single-top mode + ActivityOptions pendingOptions; // most recently given options HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold UriPermissionOwner uriPermissions; // current special URI access perms. ProcessRecord app; // if non-null, hosting application @@ -126,25 +131,27 @@ final class ActivityRecord { pw.print(prefix); pw.print("packageName="); pw.print(packageName); pw.print(" processName="); pw.println(processName); pw.print(prefix); pw.print("launchedFromUid="); pw.print(launchedFromUid); - pw.print(" app="); pw.println(app); - pw.print(prefix); pw.println(intent.toInsecureString()); + pw.print(" userId="); pw.println(userId); + pw.print(prefix); pw.print("app="); pw.println(app); + pw.print(prefix); pw.println(intent.toInsecureStringWithClip()); pw.print(prefix); pw.print("frontOfTask="); pw.print(frontOfTask); pw.print(" task="); pw.println(task); pw.print(prefix); pw.print("taskAffinity="); pw.println(taskAffinity); pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); - pw.print(prefix); pw.print("base="); pw.print(baseDir); - if (!resDir.equals(baseDir)) pw.print(" res="); pw.print(resDir); - pw.print(" data="); pw.println(dataDir); - pw.print(prefix); pw.print("labelRes=0x"); - pw.print(Integer.toHexString(labelRes)); - pw.print(" icon=0x"); pw.print(Integer.toHexString(icon)); - pw.print(" theme=0x"); pw.println(Integer.toHexString(theme)); + pw.print(prefix); pw.print("baseDir="); pw.println(baseDir); + if (!resDir.equals(baseDir)) { + pw.print(prefix); pw.print("resDir="); pw.println(resDir); + } + pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded); pw.print(" componentSpecified="); pw.print(componentSpecified); pw.print(" isHomeActivity="); pw.println(isHomeActivity); + pw.print(prefix); pw.print("compat="); pw.print(compat); + pw.print(" labelRes=0x"); pw.print(Integer.toHexString(labelRes)); + pw.print(" icon=0x"); pw.print(Integer.toHexString(icon)); + pw.print(" theme=0x"); pw.println(Integer.toHexString(theme)); pw.print(prefix); pw.print("config="); pw.println(configuration); - pw.print(prefix); pw.print("compat="); pw.println(compat); if (resultTo != null || resultWho != null) { pw.print(prefix); pw.print("resultTo="); pw.print(resultTo); pw.print(" resultWho="); pw.print(resultWho); @@ -191,13 +198,11 @@ final class ActivityRecord { TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime="); TimeUtils.formatDuration(startTime, pw); pw.println(""); } - if (lastVisibleTime != 0) { - pw.print(prefix); pw.print("lastVisibleTime="); - TimeUtils.formatDuration(lastVisibleTime, pw); pw.println(""); - } - if (waitingVisible || nowVisible) { + if (lastVisibleTime != 0 || waitingVisible || nowVisible) { pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible); - pw.print(" nowVisible="); pw.println(nowVisible); + pw.print(" nowVisible="); pw.print(nowVisible); + pw.print("lastVisibleTime="); + TimeUtils.formatDuration(lastVisibleTime, pw); pw.println(""); } if (configDestroy || configChangeFlags != 0) { pw.print(prefix); pw.print("configDestroy="); pw.print(configDestroy); @@ -283,6 +288,7 @@ final class ActivityRecord { appToken = new Token(this); info = aInfo; launchedFromUid = _launchedFromUid; + userId = UserId.getUserId(aInfo.applicationInfo.uid); intent = _intent; shortComponentName = _intent.getComponent().flattenToShortString(); resolvedType = _resolvedType; @@ -377,7 +383,7 @@ final class ActivityRecord { _intent.getData() == null && _intent.getType() == null && (intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && - !"android".equals(realActivity.getClassName())) { + !ResolverActivity.class.getName().equals(realActivity.getClassName())) { // This sure looks like a home activity! // Note the last check is so we don't count the resolver // activity as being home... really, we don't care about @@ -534,6 +540,47 @@ final class ActivityRecord { } } + void updateOptionsLocked(Bundle options) { + if (options != null) { + if (pendingOptions != null) { + pendingOptions.abort(); + } + pendingOptions = new ActivityOptions(options); + } + } + + void applyOptionsLocked() { + if (pendingOptions != null) { + switch (pendingOptions.getAnimationType()) { + case ActivityOptions.ANIM_CUSTOM: + service.mWindowManager.overridePendingAppTransition( + pendingOptions.getPackageName(), + pendingOptions.getCustomEnterResId(), + pendingOptions.getCustomExitResId()); + break; + case ActivityOptions.ANIM_SCALE_UP: + service.mWindowManager.overridePendingAppTransitionScaleUp( + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getStartWidth(), pendingOptions.getStartHeight()); + break; + case ActivityOptions.ANIM_THUMBNAIL: + service.mWindowManager.overridePendingAppTransitionThumb( + pendingOptions.getThumbnail(), + pendingOptions.getStartX(), pendingOptions.getStartY(), + pendingOptions.getOnAnimationStartListener()); + break; + } + pendingOptions = null; + } + } + + void clearOptionsLocked() { + if (pendingOptions != null) { + pendingOptions.abort(); + pendingOptions = null; + } + } + void removeUriPermissionsLocked() { if (uriPermissions != null) { uriPermissions.removeUriPermissionsLocked(); diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 86d3a1a..25fae83 100755 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -16,24 +16,19 @@ package com.android.server.am; +import static android.Manifest.permission.START_ANY_ACTIVITY; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.os.BatteryStatsImpl; import com.android.server.am.ActivityManagerService.PendingActivityLaunch; import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IThumbnailRetriever; -import static android.app.IActivityManager.START_CLASS_NOT_FOUND; -import static android.app.IActivityManager.START_DELIVERED_TO_TOP; -import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; -import static android.app.IActivityManager.START_INTENT_NOT_RESOLVED; -import static android.app.IActivityManager.START_PERMISSION_DENIED; -import static android.app.IActivityManager.START_RETURN_INTENT_TO_CALLER; -import static android.app.IActivityManager.START_SUCCESS; -import static android.app.IActivityManager.START_SWITCHES_CANCELED; -import static android.app.IActivityManager.START_TASK_TO_FRONT; import android.app.IApplicationThread; import android.app.PendingIntent; import android.app.ResultInfo; @@ -60,6 +55,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -104,6 +100,11 @@ final class ActivityStack { // next activity. static final int PAUSE_TIMEOUT = 500; + // How long we wait for the activity to tell us it has stopped before + // giving up. This is a good amount of time because we really need this + // from the application in order to get its saved state. + static final int STOP_TIMEOUT = 10*1000; + // How long we can hold the sleep wake lock before giving up. static final int SLEEP_TIMEOUT = 5*1000; @@ -277,15 +278,18 @@ final class ActivityStack { int mThumbnailWidth = -1; int mThumbnailHeight = -1; - static final int SLEEP_TIMEOUT_MSG = 8; - static final int PAUSE_TIMEOUT_MSG = 9; - static final int IDLE_TIMEOUT_MSG = 10; - static final int IDLE_NOW_MSG = 11; - static final int LAUNCH_TICK_MSG = 12; - static final int LAUNCH_TIMEOUT_MSG = 16; - static final int DESTROY_TIMEOUT_MSG = 17; - static final int RESUME_TOP_ACTIVITY_MSG = 19; - + private int mCurrentUser; + + static final int SLEEP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG; + static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1; + static final int IDLE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2; + static final int IDLE_NOW_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3; + static final int LAUNCH_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 4; + static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 5; + static final int RESUME_TOP_ACTIVITY_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 6; + static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 7; + static final int STOP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 8; + final Handler mHandler = new Handler() { //public Handler() { // if (localLOGV) Slog.v(TAG, "Handler started!"); @@ -369,6 +373,17 @@ final class ActivityStack { resumeTopActivityLocked(null); } } break; + case STOP_TIMEOUT_MSG: { + ActivityRecord r = (ActivityRecord)msg.obj; + // We don't at this point know if the activity is fullscreen, + // so we need to be conservative and assume it isn't. + Slog.w(TAG, "Activity stop timeout for " + r); + synchronized (mService) { + if (r.isInHistory()) { + activityStoppedLocked(r, null, null, null); + } + } + } break; } } }; @@ -385,6 +400,7 @@ 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); @@ -397,6 +413,7 @@ 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); @@ -418,6 +435,7 @@ 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); @@ -464,10 +482,11 @@ final class ActivityStack { TaskRecord cp = null; + final int userId = UserId.getUserId(info.applicationInfo.uid); final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { ActivityRecord r = mHistory.get(i); - if (!r.finishing && r.task != cp + if (!r.finishing && r.task != cp && r.userId == userId && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { cp = r.task; //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString() @@ -507,12 +526,13 @@ final class ActivityStack { if (info.targetActivity != null) { cls = new ComponentName(info.packageName, info.targetActivity); } + final int userId = UserId.getUserId(info.applicationInfo.uid); final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { ActivityRecord r = mHistory.get(i); if (!r.finishing) { - if (r.intent.getComponent().equals(cls)) { + if (r.intent.getComponent().equals(cls) && r.userId == userId) { //Slog.i(TAG, "Found matching class!"); //dump(); //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); @@ -531,6 +551,43 @@ final class ActivityStack { mService.mHandler.sendMessage(msg); } + /* + * 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; + + // 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++; + } + } + // Transition from the old top to the new top + resumeTopActivityLocked(top); + return haveActivities; + } + } + final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app, boolean andResume, boolean checkConfig) throws RemoteException { @@ -733,7 +790,7 @@ final class ActivityStack { } mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, - "activity", r.intent.getComponent(), false); + "activity", r.intent.getComponent(), false, false); } void stopIfSleepingLocked() { @@ -963,31 +1020,38 @@ final class ActivityStack { final void activityStoppedLocked(ActivityRecord r, Bundle icicle, Bitmap thumbnail, CharSequence description) { if (DEBUG_SAVED_STATE) Slog.i(TAG, "Saving icicle of " + r + ": " + icicle); - r.icicle = icicle; - r.haveState = true; - r.updateThumbnail(thumbnail, description); - r.stopped = true; - if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (stop complete)"); - r.state = ActivityState.STOPPED; - if (!r.finishing) { - if (r.configDestroy) { - destroyActivityLocked(r, true, false, "stop-config"); - resumeTopActivityLocked(null); - } else { - // Now that this process has stopped, we may want to consider - // it to be the previous app to try to keep around in case - // the user wants to return to it. - ProcessRecord fgApp = null; - if (mResumedActivity != null) { - fgApp = mResumedActivity.app; - } else if (mPausingActivity != null) { - fgApp = mPausingActivity.app; - } - if (r.app != null && fgApp != null && r.app != fgApp - && r.lastVisibleTime > mService.mPreviousProcessVisibleTime - && r.app != mService.mHomeProcess) { - mService.mPreviousProcess = r.app; - mService.mPreviousProcessVisibleTime = r.lastVisibleTime; + if (icicle != null) { + // If icicle is null, this is happening due to a timeout, so we + // haven't really saved the state. + r.icicle = icicle; + r.haveState = true; + r.updateThumbnail(thumbnail, description); + } + if (!r.stopped) { + if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (stop complete)"); + mHandler.removeMessages(STOP_TIMEOUT_MSG, r); + r.stopped = true; + r.state = ActivityState.STOPPED; + if (!r.finishing) { + if (r.configDestroy) { + destroyActivityLocked(r, true, false, "stop-config"); + resumeTopActivityLocked(null); + } else { + // Now that this process has stopped, we may want to consider + // it to be the previous app to try to keep around in case + // the user wants to return to it. + ProcessRecord fgApp = null; + if (mResumedActivity != null) { + fgApp = mResumedActivity.app; + } else if (mPausingActivity != null) { + fgApp = mPausingActivity.app; + } + if (r.app != null && fgApp != null && r.app != fgApp + && r.lastVisibleTime > mService.mPreviousProcessVisibleTime + && r.app != mService.mHomeProcess) { + mService.mPreviousProcess = r.app; + mService.mPreviousProcessVisibleTime = r.lastVisibleTime; + } } } } @@ -1296,7 +1360,7 @@ final class ActivityStack { // There are no more activities! Let's just start up the // Launcher... if (mMainStack) { - return mService.startHomeActivityLocked(); + return mService.startHomeActivityLocked(0); } } @@ -1412,7 +1476,7 @@ final class ActivityStack { // considered stopped. try { AppGlobals.getPackageManager().setPackageStoppedState( - next.packageName, false); + next.packageName, false, next.userId); /* TODO: Verify if correct userid */ } catch (RemoteException e1) { } catch (IllegalArgumentException e) { Slog.w(TAG, "Failed trying to unstop package " @@ -1422,6 +1486,7 @@ final class ActivityStack { // We are starting up the next activity, so tell the window manager // that the previous one will be hidden soon. This way it can know // to ignore it when computing the desired screen orientation. + boolean noAnim = false; if (prev != null) { if (prev.finishing) { if (DEBUG_TRANSITION) Slog.v(TAG, @@ -1440,6 +1505,7 @@ final class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: prev=" + prev); if (mNoAnimActivities.contains(next)) { + noAnim = true; mService.mWindowManager.prepareAppTransition( WindowManagerPolicy.TRANSIT_NONE, false); } else { @@ -1456,6 +1522,7 @@ final class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: no previous"); if (mNoAnimActivities.contains(next)) { + noAnim = true; mService.mWindowManager.prepareAppTransition( WindowManagerPolicy.TRANSIT_NONE, false); } else { @@ -1463,6 +1530,11 @@ final class ActivityStack { WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, false); } } + if (!noAnim) { + next.applyOptionsLocked(); + } else { + next.clearOptionsLocked(); + } if (next.app != null && next.app.thread != null) { if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next); @@ -1621,7 +1693,7 @@ final class ActivityStack { } private final void startActivityLocked(ActivityRecord r, boolean newTask, - boolean doResume, boolean keepCurTransition) { + boolean doResume, boolean keepCurTransition, Bundle options) { final int NH = mHistory.size(); int addPos = -1; @@ -1653,6 +1725,7 @@ final class ActivityStack { if (VALIDATE_TOKENS) { validateAppTokensLocked(); } + ActivityOptions.abort(options); return; } break; @@ -1714,6 +1787,7 @@ final class ActivityStack { : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, keepCurTransition); mNoAnimActivities.remove(r); } + r.updateOptionsLocked(options); mService.mWindowManager.addAppToken( addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen); boolean doShow = true; @@ -1754,6 +1828,7 @@ final class ActivityStack { // because there is nothing for it to animate on top of. mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen); + ActivityOptions.abort(options); } if (VALIDATE_TOKENS) { validateAppTokensLocked(); @@ -1805,6 +1880,10 @@ final class ActivityStack { if (below != null && below.finishing) { continue; } + // Don't check any lower in the stack if we're crossing a user boundary. + if (below != null && below.userId != taskTop.userId) { + break; + } if (target == null) { target = below; targetI = i; @@ -2250,14 +2329,12 @@ final class ActivityStack { } final int startActivityLocked(IApplicationThread caller, - Intent intent, String resolvedType, - Uri[] grantedUriPermissions, - int grantedMode, ActivityInfo aInfo, IBinder resultTo, + Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo, String resultWho, int requestCode, - int callingPid, int callingUid, boolean onlyIfNeeded, + int callingPid, int callingUid, int startFlags, Bundle options, boolean componentSpecified, ActivityRecord[] outActivity) { - int err = START_SUCCESS; + int err = ActivityManager.START_SUCCESS; ProcessRecord callerApp = null; if (caller != null) { @@ -2269,13 +2346,14 @@ final class ActivityStack { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); - err = START_PERMISSION_DENIED; + err = ActivityManager.START_PERMISSION_DENIED; } } - if (err == START_SUCCESS) { - Slog.i(TAG, "START {" + intent.toShortString(true, true, true) + "} from pid " - + (callerApp != null ? callerApp.pid : callingPid)); + if (err == ActivityManager.START_SUCCESS) { + final int userId = aInfo != null ? UserId.getUserId(aInfo.applicationInfo.uid) : 0; + Slog.i(TAG, "START {" + intent.toShortString(true, true, true, false) + + " u=" + userId + "} from pid " + (callerApp != null ? callerApp.pid : callingPid)); } ActivityRecord sourceRecord = null; @@ -2299,7 +2377,8 @@ final class ActivityStack { // Transfer the result target from the source activity to the new // one being started, including any failures. if (requestCode >= 0) { - return START_FORWARD_AND_REQUEST_CONFLICT; + ActivityOptions.abort(options); + return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; } resultRecord = sourceRecord.resultTo; resultWho = sourceRecord.resultWho; @@ -2311,31 +2390,34 @@ final class ActivityStack { } } - if (err == START_SUCCESS && intent.getComponent() == null) { + if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { // We couldn't find a class that can handle the given Intent. // That's the end of that! - err = START_INTENT_NOT_RESOLVED; + err = ActivityManager.START_INTENT_NOT_RESOLVED; } - if (err == START_SUCCESS && aInfo == null) { + if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. - err = START_CLASS_NOT_FOUND; + err = ActivityManager.START_CLASS_NOT_FOUND; } - if (err != START_SUCCESS) { + if (err != ActivityManager.START_SUCCESS) { if (resultRecord != null) { sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, Activity.RESULT_CANCELED, null); } mDismissKeyguardOnNextActivity = false; + ActivityOptions.abort(options); return err; } - final int perm = mService.checkComponentPermission(aInfo.permission, callingPid, + final int startAnyPerm = mService.checkPermission( + START_ANY_ACTIVITY, callingPid, callingUid); + final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid, callingUid, aInfo.applicationInfo.uid, aInfo.exported); - if (perm != PackageManager.PERMISSION_GRANTED) { + if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) { if (resultRecord != null) { sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, @@ -2380,11 +2462,12 @@ final class ActivityStack { // We pretend to the caller that it was really started, but // they will just get a cancel result. mDismissKeyguardOnNextActivity = false; - return START_SUCCESS; + ActivityOptions.abort(options); + return ActivityManager.START_SUCCESS; } } } - + ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); @@ -2399,12 +2482,11 @@ final class ActivityStack { PendingActivityLaunch pal = new PendingActivityLaunch(); pal.r = r; pal.sourceRecord = sourceRecord; - pal.grantedUriPermissions = grantedUriPermissions; - pal.grantedMode = grantedMode; - pal.onlyIfNeeded = onlyIfNeeded; + pal.startFlags = startFlags; mService.mPendingActivityLaunches.add(pal); mDismissKeyguardOnNextActivity = false; - return START_SWITCHES_CANCELED; + ActivityOptions.abort(options); + return ActivityManager.START_SWITCHES_CANCELED; } } @@ -2423,7 +2505,7 @@ final class ActivityStack { } err = startActivityUncheckedLocked(r, sourceRecord, - grantedUriPermissions, grantedMode, onlyIfNeeded, true); + startFlags, true, options); if (mDismissKeyguardOnNextActivity && mPausingActivity == null) { // Someone asked to have the keyguard dismissed on the next // activity start, but we are not actually doing an activity @@ -2446,11 +2528,12 @@ final class ActivityStack { } final int startActivityUncheckedLocked(ActivityRecord r, - ActivityRecord sourceRecord, Uri[] grantedUriPermissions, - int grantedMode, boolean onlyIfNeeded, boolean doResume) { + ActivityRecord sourceRecord, int startFlags, boolean doResume, + Bundle options) { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; - + final int userId = r.userId; + int launchFlags = intent.getFlags(); // We'll invoke onUserLeaving before onPause only if the launching @@ -2473,14 +2556,14 @@ final class ActivityStack { // being launched is the same as the one making the call... or, as // a special case, if we do not know the caller then we count the // current top activity as the caller. - if (onlyIfNeeded) { + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { ActivityRecord checkedCaller = sourceRecord; if (checkedCaller == null) { checkedCaller = topRunningNonDelayedActivityLocked(notTop); } if (!checkedCaller.realActivity.equals(r.realActivity)) { // Caller is not the same as launcher, so always needed. - onlyIfNeeded = false; + startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; } } @@ -2557,7 +2640,7 @@ final class ActivityStack { // We really do want to push this one into the // user's face, right now. moveHomeToFrontFromLaunchLocked(launchFlags); - moveTaskToFrontLocked(taskTop.task, r); + moveTaskToFrontLocked(taskTop.task, r, options); } } // If the caller has requested that the target task be @@ -2565,7 +2648,7 @@ final class ActivityStack { if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { taskTop = resetTaskIfNeededLocked(taskTop, r); } - if (onlyIfNeeded) { + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { // We don't need to start a new activity, and // the client said not to do anything if that // is the case, so this is it! And for paranoia, make @@ -2573,7 +2656,8 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - return START_RETURN_INTENT_TO_CALLER; + ActivityOptions.abort(options); + return ActivityManager.START_RETURN_INTENT_TO_CALLER; } if ((launchFlags & (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) @@ -2660,7 +2744,8 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - return START_TASK_TO_FRONT; + ActivityOptions.abort(options); + return ActivityManager.START_TASK_TO_FRONT; } } } @@ -2678,7 +2763,7 @@ final class ActivityStack { // once. ActivityRecord top = topRunningNonDelayedActivityLocked(notTop); if (top != null && r.resultTo == null) { - if (top.realActivity.equals(r.realActivity)) { + if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { if (top.app != null && top.app.thread != null) { if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP @@ -2689,14 +2774,15 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - if (onlyIfNeeded) { + ActivityOptions.abort(options); + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { // We don't need to start a new activity, and // the client said not to do anything if that // is the case, so this is it! - return START_RETURN_INTENT_TO_CALLER; + return ActivityManager.START_RETURN_INTENT_TO_CALLER; } top.deliverNewIntentLocked(callingUid, r.intent); - return START_DELIVERED_TO_TOP; + return ActivityManager.START_DELIVERED_TO_TOP; } } } @@ -2708,7 +2794,8 @@ final class ActivityStack { r.resultTo, r.resultWho, r.requestCode, Activity.RESULT_CANCELED, null); } - return START_CLASS_NOT_FOUND; + ActivityOptions.abort(options); + return ActivityManager.START_CLASS_NOT_FOUND; } boolean newTask = false; @@ -2749,7 +2836,8 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - return START_DELIVERED_TO_TOP; + ActivityOptions.abort(options); + return ActivityManager.START_DELIVERED_TO_TOP; } } else if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { @@ -2760,11 +2848,12 @@ final class ActivityStack { if (where >= 0) { ActivityRecord top = moveActivityToFrontLocked(where); logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.updateOptionsLocked(options); top.deliverNewIntentLocked(callingUid, r.intent); if (doResume) { resumeTopActivityLocked(null); } - return START_DELIVERED_TO_TOP; + return ActivityManager.START_DELIVERED_TO_TOP; } } // An existing activity is starting this new activity, so we want @@ -2788,13 +2877,6 @@ final class ActivityStack { + " in new guessed " + r.task); } - if (grantedUriPermissions != null && callingUid > 0) { - for (int i=0; i<grantedUriPermissions.length; i++) { - mService.grantUriPermissionLocked(callingUid, r.packageName, - grantedUriPermissions[i], grantedMode, r.getUriPermissionsLocked()); - } - } - mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, intent, r.getUriPermissionsLocked()); @@ -2802,12 +2884,12 @@ final class ActivityStack { EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId); } logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); - startActivityLocked(r, newTask, doResume, keepCurTransition); - return START_SUCCESS; + startActivityLocked(r, newTask, doResume, keepCurTransition, options); + return ActivityManager.START_SUCCESS; } - ActivityInfo resolveActivity(Intent intent, String resolvedType, boolean debug, - String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags, + String profileFile, ParcelFileDescriptor profileFd, int userId) { // Collect information about the target of the Intent. ActivityInfo aInfo; try { @@ -2815,7 +2897,7 @@ final class ActivityStack { AppGlobals.getPackageManager().resolveIntent( intent, resolvedType, PackageManager.MATCH_DEFAULT_ONLY - | ActivityManagerService.STOCK_PM_FLAGS); + | ActivityManagerService.STOCK_PM_FLAGS, userId); aInfo = rInfo != null ? rInfo.activityInfo : null; } catch (RemoteException e) { aInfo = null; @@ -2830,16 +2912,23 @@ final class ActivityStack { aInfo.applicationInfo.packageName, aInfo.name)); // Don't debug things in the system process - if (debug) { + if ((startFlags&ActivityManager.START_FLAG_DEBUG) != 0) { if (!aInfo.processName.equals("system")) { mService.setDebugApp(aInfo.processName, true, false); } } + if ((startFlags&ActivityManager.START_FLAG_OPENGL_TRACES) != 0) { + if (!aInfo.processName.equals("system")) { + mService.setOpenGlTraceApp(aInfo.applicationInfo, aInfo.processName); + } + } + if (profileFile != null) { if (!aInfo.processName.equals("system")) { mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, - profileFile, profileFd, autoStopProfiler); + profileFile, profileFd, + (startFlags&ActivityManager.START_FLAG_AUTO_STOP_PROFILER) != 0); } } } @@ -2847,24 +2936,26 @@ final class ActivityStack { } final int startActivityMayWait(IApplicationThread caller, int callingUid, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler, WaitResult outResult, Configuration config) { + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, + Bundle options, int userId) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } - boolean componentSpecified = intent.getComponent() != null; // Don't modify the client's object! intent = new Intent(intent); // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug, - profileFile, profileFd, autoStopProfiler); + ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, + profileFile, profileFd, userId); + if (aInfo != null && mService.isSingleton(aInfo.processName, aInfo.applicationInfo)) { + userId = 0; + } + aInfo = mService.getActivityInfoForUser(aInfo, userId); synchronized (mService) { int callingPid; @@ -2903,15 +2994,16 @@ final class ActivityStack { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + realCallingPid + ") when starting: " + intent.toString()); - return START_PERMISSION_DENIED; + ActivityOptions.abort(options); + return ActivityManager.START_PERMISSION_DENIED; } } IIntentSender target = mService.getIntentSenderLocked( - IActivityManager.INTENT_SENDER_ACTIVITY, "android", + ActivityManager.INTENT_SENDER_ACTIVITY, "android", realCallingUid, null, null, 0, new Intent[] { intent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT - | PendingIntent.FLAG_ONE_SHOT); + | PendingIntent.FLAG_ONE_SHOT, null); Intent newIntent = new Intent(); if (requestCode >= 0) { @@ -2943,8 +3035,9 @@ final class ActivityStack { AppGlobals.getPackageManager().resolveIntent( intent, null, PackageManager.MATCH_DEFAULT_ONLY - | ActivityManagerService.STOCK_PM_FLAGS); + | ActivityManagerService.STOCK_PM_FLAGS, userId); aInfo = rInfo != null ? rInfo.activityInfo : null; + aInfo = mService.getActivityInfoForUser(aInfo, userId); } catch (RemoteException e) { aInfo = null; } @@ -2953,9 +3046,8 @@ final class ActivityStack { } int res = startActivityLocked(caller, intent, resolvedType, - grantedUriPermissions, grantedMode, aInfo, - resultTo, resultWho, requestCode, callingPid, callingUid, - onlyIfNeeded, componentSpecified, null); + aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, + startFlags, options, componentSpecified, null); if (mConfigWillChange && mMainStack) { // If the caller also wants to switch to a new configuration, @@ -2974,7 +3066,7 @@ final class ActivityStack { if (outResult != null) { outResult.result = res; - if (res == IActivityManager.START_SUCCESS) { + if (res == ActivityManager.START_SUCCESS) { mWaitingActivityLaunched.add(outResult); do { try { @@ -2982,7 +3074,7 @@ final class ActivityStack { } catch (InterruptedException e) { } } while (!outResult.timeout && outResult.who == null); - } else if (res == IActivityManager.START_TASK_TO_FRONT) { + } else if (res == ActivityManager.START_TASK_TO_FRONT) { ActivityRecord r = this.topRunningActivityLocked(null); if (r.nowVisible) { outResult.timeout = false; @@ -3007,7 +3099,8 @@ final class ActivityStack { } final int startActivities(IApplicationThread caller, int callingUid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options, int userId) { if (intents == null) { throw new NullPointerException("intents is null"); } @@ -3050,8 +3143,10 @@ final class ActivityStack { intent = new Intent(intent); // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false, - null, null, false); + ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], + 0, null, null, userId); + // TODO: New, check if this is correct + aInfo = mService.getActivityInfoForUser(aInfo, userId); if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { @@ -3059,9 +3154,15 @@ final class ActivityStack { "FLAG_CANT_SAVE_STATE not supported here"); } + Bundle theseOptions; + if (options != null && i == intents.length-1) { + theseOptions = options; + } else { + theseOptions = null; + } int res = startActivityLocked(caller, intent, resolvedTypes[i], - null, 0, aInfo, resultTo, null, -1, callingPid, callingUid, - false, componentSpecified, outActivity); + aInfo, resultTo, null, -1, callingPid, callingUid, + 0, theseOptions, componentSpecified, outActivity); if (res < 0) { return res; } @@ -3073,7 +3174,7 @@ final class ActivityStack { Binder.restoreCallingIdentity(origId); } - return IActivityManager.START_SUCCESS; + return ActivityManager.START_SUCCESS; } void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, @@ -3163,6 +3264,9 @@ final class ActivityStack { if (mService.isSleeping()) { r.setSleeping(true); } + Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG); + msg.obj = r; + mHandler.sendMessageDelayed(msg, STOP_TIMEOUT); } catch (Exception e) { // Maybe just ignore exceptions here... if the process // has crashed, our death notification will clean things @@ -3393,6 +3497,51 @@ final class ActivityStack { return true; } + final void finishSubActivityLocked(IBinder token, String resultWho, int requestCode) { + ActivityRecord self = isInStackLocked(token); + if (self == null) { + return; + } + + int i; + for (i=mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.resultTo == self && r.requestCode == requestCode) { + if ((r.resultWho == null && resultWho == null) || + (r.resultWho != null && r.resultWho.equals(resultWho))) { + finishActivityLocked(r, i, + Activity.RESULT_CANCELED, null, "request-sub"); + } + } + } + } + + final boolean finishActivityAffinityLocked(IBinder token) { + int index = indexOfTokenLocked(token); + if (DEBUG_RESULTS) Slog.v( + TAG, "Finishing activity affinity @" + index + ": token=" + token); + if (index < 0) { + return false; + } + ActivityRecord r = mHistory.get(index); + + while (index > 0) { + ActivityRecord cur = mHistory.get(index); + if (cur.task != r.task) { + break; + } + if (cur.taskAffinity == null && r.taskAffinity != null) { + break; + } + if (cur.taskAffinity != null && !cur.taskAffinity.equals(r.taskAffinity)) { + break; + } + finishActivityLocked(cur, index, Activity.RESULT_CANCELED, null, "request-affinity"); + index--; + } + return true; + } + final void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) { // send the result ActivityRecord resultTo = r.resultTo; @@ -3426,6 +3575,15 @@ final class ActivityStack { */ final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode, Intent resultData, String reason) { + return finishActivityLocked(r, index, resultCode, resultData, reason, false); + } + + /** + * @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) { if (r.finishing) { Slog.w(TAG, "Duplicate finish request for " + r); return false; @@ -3467,7 +3625,10 @@ final class ActivityStack { mService.mCancelledThumbnails.add(r); } - if (mResumedActivity == r) { + if (immediate) { + return finishCurrentActivityLocked(r, index, + FINISH_IMMEDIATELY) == null; + } else if (mResumedActivity == r) { boolean endTask = index <= 0 || (mHistory.get(index-1)).task != r.task; if (DEBUG_TRANSITION) Slog.v(TAG, @@ -3617,6 +3778,7 @@ final class ActivityStack { // Get rid of any pending idle timeouts. mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); + mHandler.removeMessages(STOP_TIMEOUT_MSG, r); mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); r.finishLaunchTickingLocked(); @@ -3833,12 +3995,23 @@ final class ActivityStack { } } if (homeTask != null) { - moveTaskToFrontLocked(homeTask, null); + moveTaskToFrontLocked(homeTask, null, null); } } + final void updateTransitLocked(int transit, Bundle options) { + if (options != null) { + ActivityRecord r = topRunningActivityLocked(null); + if (r != null && r.state != ActivityState.RESUMED) { + r.updateOptionsLocked(options); + } else { + ActivityOptions.abort(options); + } + } + mService.mWindowManager.prepareAppTransition(transit, false); + } - final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) { + final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason, Bundle options) { if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); final int task = tr.taskId; @@ -3846,6 +4019,12 @@ final class ActivityStack { if (top < 0 || (mHistory.get(top)).task.taskId == task) { // nothing to do! + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + ActivityOptions.abort(options); + } else { + updateTransitLocked(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, options); + } return; } @@ -3887,9 +4066,9 @@ final class ActivityStack { if (r != null) { mNoAnimActivities.add(r); } + ActivityOptions.abort(options); } else { - mService.mWindowManager.prepareAppTransition( - WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, false); + updateTransitLocked(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, options); } mService.mWindowManager.moveAppTokensToTop(moved); @@ -4004,10 +4183,13 @@ final class ActivityStack { return info; } - public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex) { + public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex, + boolean taskRequired) { TaskAccessInfo info = getTaskAccessInfoLocked(taskId, false); if (info.root == null) { - Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId); + if (taskRequired) { + Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId); + } return null; } @@ -4018,7 +4200,9 @@ final class ActivityStack { } if (subTaskIndex >= info.subtasks.size()) { - Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex); + if (taskRequired) { + Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex); + } return null; } diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java new file mode 100644 index 0000000..47b8c0a --- /dev/null +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -0,0 +1,1019 @@ +/* + * 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.PrintWriter; +import java.util.ArrayList; + +import android.app.AppGlobals; +import android.content.ComponentName; +import android.content.IIntentReceiver; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserId; +import android.util.EventLog; +import android.util.Slog; + +/** + * BROADCASTS + * + * We keep two broadcast queues and associated bookkeeping, one for those at + * foreground priority, and one for normal (background-priority) broadcasts. + */ +public class BroadcastQueue { + static final String TAG = "BroadcastQueue"; + static final String TAG_MU = ActivityManagerService.TAG_MU; + static final boolean DEBUG_BROADCAST = ActivityManagerService.DEBUG_BROADCAST; + static final boolean DEBUG_BROADCAST_LIGHT = ActivityManagerService.DEBUG_BROADCAST_LIGHT; + static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU; + + static final int MAX_BROADCAST_HISTORY = 25; + + final ActivityManagerService mService; + + /** + * Recognizable moniker for this queue + */ + final String mQueueName; + + /** + * Timeout period for this queue's broadcasts + */ + final long mTimeoutPeriod; + + /** + * Lists of all active broadcasts that are to be executed immediately + * (without waiting for another broadcast to finish). Currently this only + * contains broadcasts to registered receivers, to avoid spinning up + * a bunch of processes to execute IntentReceiver components. Background- + * and foreground-priority broadcasts are queued separately. + */ + final ArrayList<BroadcastRecord> mParallelBroadcasts + = new ArrayList<BroadcastRecord>(); + /** + * List of all active broadcasts that are to be executed one at a time. + * The object at the top of the list is the currently activity broadcasts; + * those after it are waiting for the top to finish. As with parallel + * broadcasts, separate background- and foreground-priority queues are + * maintained. + */ + final ArrayList<BroadcastRecord> mOrderedBroadcasts + = new ArrayList<BroadcastRecord>(); + + /** + * Historical data of past broadcasts, for debugging. + */ + final BroadcastRecord[] mBroadcastHistory + = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + + /** + * Set when we current have a BROADCAST_INTENT_MSG in flight. + */ + boolean mBroadcastsScheduled = false; + + /** + * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler. + */ + boolean mPendingBroadcastTimeoutMessage; + + /** + * Intent broadcasts that we have tried to start, but are + * waiting for the application's process to be created. We only + * need one per scheduling class (instead of a list) because we always + * process broadcasts one at a time, so no others can be started while + * waiting for this one. + */ + BroadcastRecord mPendingBroadcast = null; + + /** + * The receiver index that is pending, to restart the broadcast if needed. + */ + int mPendingBroadcastRecvIndex; + + static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG; + static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1; + + final Handler mHandler = new Handler() { + //public Handler() { + // if (localLOGV) Slog.v(TAG, "Handler started!"); + //} + + public void handleMessage(Message msg) { + switch (msg.what) { + case BROADCAST_INTENT_MSG: { + if (DEBUG_BROADCAST) Slog.v( + TAG, "Received BROADCAST_INTENT_MSG"); + processNextBroadcast(true); + } break; + case BROADCAST_TIMEOUT_MSG: { + synchronized (mService) { + broadcastTimeoutLocked(true); + } + } break; + } + } + }; + + private final class AppNotResponding implements Runnable { + private final ProcessRecord mApp; + private final String mAnnotation; + + public AppNotResponding(ProcessRecord app, String annotation) { + mApp = app; + mAnnotation = annotation; + } + + @Override + public void run() { + mService.appNotResponding(mApp, null, null, mAnnotation); + } + } + + BroadcastQueue(ActivityManagerService service, String name, long timeoutPeriod) { + mService = service; + mQueueName = name; + mTimeoutPeriod = timeoutPeriod; + } + + public boolean isPendingBroadcastProcessLocked(int pid) { + return mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid; + } + + public void enqueueParallelBroadcastLocked(BroadcastRecord r) { + mParallelBroadcasts.add(r); + } + + public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { + mOrderedBroadcasts.add(r); + } + + public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) { + for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { + if (r.intent.filterEquals(mParallelBroadcasts.get(i).intent)) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "***** DROPPING PARALLEL [" + + mQueueName + "]: " + r.intent); + mParallelBroadcasts.set(i, r); + return true; + } + } + return false; + } + + public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) { + for (int i=mOrderedBroadcasts.size()-1; i>0; i--) { + if (r.intent.filterEquals(mOrderedBroadcasts.get(i).intent)) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "***** DROPPING ORDERED [" + + mQueueName + "]: " + r.intent); + mOrderedBroadcasts.set(i, r); + return true; + } + } + return false; + } + + private final void processCurBroadcastLocked(BroadcastRecord r, + ProcessRecord app) throws RemoteException { + if (DEBUG_BROADCAST) Slog.v(TAG, + "Process cur broadcast " + r + " for app " + app); + if (app.thread == null) { + throw new RemoteException(); + } + r.receiver = app.thread.asBinder(); + r.curApp = app; + app.curReceiver = r; + mService.updateLruProcessLocked(app, true, true); + + // Tell the application to launch this receiver. + r.intent.setComponent(r.curComponent); + + boolean started = false; + try { + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, + "Delivering to component " + r.curComponent + + ": " + r); + 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); + if (DEBUG_BROADCAST) Slog.v(TAG, + "Process cur broadcast " + r + " DELIVERED for app " + app); + started = true; + } finally { + if (!started) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "Process cur broadcast " + r + ": NOT STARTED!"); + r.receiver = null; + r.curApp = null; + app.curReceiver = null; + } + } + } + + public boolean sendPendingBroadcastsLocked(ProcessRecord app) { + boolean didSomething = false; + final BroadcastRecord br = mPendingBroadcast; + if (br != null && br.curApp.pid == app.pid) { + try { + mPendingBroadcast = null; + processCurBroadcastLocked(br, app); + didSomething = true; + } catch (Exception e) { + Slog.w(TAG, "Exception in new application when starting receiver " + + br.curComponent.flattenToShortString(), e); + logBroadcastReceiverDiscardLocked(br); + 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. + br.state = BroadcastRecord.IDLE; + throw new RuntimeException(e.getMessage()); + } + } + return didSomething; + } + + public void skipPendingBroadcastLocked(int pid) { + final BroadcastRecord br = mPendingBroadcast; + if (br != null && br.curApp.pid == pid) { + br.state = BroadcastRecord.IDLE; + br.nextReceiver = mPendingBroadcastRecvIndex; + mPendingBroadcast = null; + scheduleBroadcastsLocked(); + } + } + + public void skipCurrentReceiverLocked(ProcessRecord app) { + boolean reschedule = false; + BroadcastRecord r = app.curReceiver; + if (r != null) { + // The current broadcast is waiting for this app's receiver + // to be finished. Looks like that's not going to happen, so + // let the broadcast continue. + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + reschedule = true; + } + + r = mPendingBroadcast; + if (r != null && r.curApp == app) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "[" + mQueueName + "] skip & discard pending app " + r); + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + reschedule = true; + } + if (reschedule) { + scheduleBroadcastsLocked(); + } + } + + public void scheduleBroadcastsLocked() { + if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts [" + + mQueueName + "]: current=" + + mBroadcastsScheduled); + + if (mBroadcastsScheduled) { + return; + } + mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this)); + mBroadcastsScheduled = true; + } + + public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) { + if (mOrderedBroadcasts.size() > 0) { + final BroadcastRecord r = mOrderedBroadcasts.get(0); + if (r != null && r.receiver == receiver) { + return r; + } + } + return null; + } + + public boolean finishReceiverLocked(BroadcastRecord r, int resultCode, + String resultData, Bundle resultExtras, boolean resultAbort, + boolean explicit) { + int state = r.state; + r.state = BroadcastRecord.IDLE; + if (state == BroadcastRecord.IDLE) { + if (explicit) { + Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE"); + } + } + r.receiver = null; + r.intent.setComponent(null); + if (r.curApp != null) { + r.curApp.curReceiver = null; + } + if (r.curFilter != null) { + r.curFilter.receiverList.curBroadcast = null; + } + r.curFilter = null; + r.curApp = null; + r.curComponent = null; + r.curReceiver = null; + mPendingBroadcast = null; + + r.resultCode = resultCode; + r.resultData = resultData; + r.resultExtras = resultExtras; + r.resultAbort = resultAbort; + + // We will process the next receiver right now if this is finishing + // an app receiver (which is always asynchronous) or after we have + // come back from calling a receiver. + return state == BroadcastRecord.APP_RECEIVE + || state == BroadcastRecord.CALL_DONE_RECEIVE; + } + + private static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, + Intent intent, int resultCode, String data, Bundle extras, + boolean ordered, boolean sticky) 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); + } else { + receiver.performReceive(intent, resultCode, data, extras, ordered, sticky); + } + } + + private final void deliverToRegisteredReceiverLocked(BroadcastRecord r, + BroadcastFilter filter, boolean ordered) { + boolean skip = false; + if (filter.requiredPermission != null) { + int perm = mService.checkComponentPermission(filter.requiredPermission, + r.callingPid, r.callingUid, -1, true); + if (perm != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + + r.callingPid + ", uid=" + r.callingUid + ")" + + " requires " + filter.requiredPermission + + " due to registered receiver " + filter); + skip = true; + } + } + if (r.requiredPermission != null) { + int perm = mService.checkComponentPermission(r.requiredPermission, + filter.receiverList.pid, filter.receiverList.uid, -1, true); + if (perm != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: receiving " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " requires " + r.requiredPermission + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + } + } + + if (!skip) { + // If this is not being sent as an ordered broadcast, then we + // don't want to touch the fields that keep track of the current + // state of ordered broadcasts. + if (ordered) { + r.receiver = filter.receiverList.receiver.asBinder(); + r.curFilter = filter; + filter.receiverList.curBroadcast = r; + r.state = BroadcastRecord.CALL_IN_RECEIVE; + if (filter.receiverList.app != null) { + // Bump hosting application to no longer be in background + // scheduling class. Note that we can't do that if there + // isn't an app... but we can only be in that case for + // things that directly call the IActivityManager API, which + // are already core system stuff so don't matter for this. + r.curApp = filter.receiverList.app; + filter.receiverList.app.curReceiver = r; + mService.updateOomAdjLocked(); + } + } + try { + if (DEBUG_BROADCAST_LIGHT) { + int seq = r.intent.getIntExtra("seq", -1); + Slog.i(TAG, "Delivering to " + filter + + " (seq=" + seq + "): " + r); + } + performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, + new Intent(r.intent), r.resultCode, + r.resultData, r.resultExtras, r.ordered, r.initialSticky); + if (ordered) { + r.state = BroadcastRecord.CALL_DONE_RECEIVE; + } + } catch (RemoteException e) { + Slog.w(TAG, "Failure sending broadcast " + r.intent, e); + if (ordered) { + r.receiver = null; + r.curFilter = null; + filter.receiverList.curBroadcast = null; + if (filter.receiverList.app != null) { + filter.receiverList.app.curReceiver = null; + } + } + } + } + } + + final void processNextBroadcast(boolean fromMsg) { + synchronized(mService) { + BroadcastRecord r; + + if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast [" + + mQueueName + "]: " + + mParallelBroadcasts.size() + " broadcasts, " + + mOrderedBroadcasts.size() + " ordered broadcasts"); + + mService.updateCpuStats(); + + if (fromMsg) { + mBroadcastsScheduled = false; + } + + // First, deliver any non-serialized broadcasts right away. + while (mParallelBroadcasts.size() > 0) { + r = mParallelBroadcasts.remove(0); + r.dispatchTime = SystemClock.uptimeMillis(); + r.dispatchClockTime = System.currentTimeMillis(); + final int N = r.receivers.size(); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast [" + + mQueueName + "] " + r); + for (int i=0; i<N; i++) { + Object target = r.receivers.get(i); + if (DEBUG_BROADCAST) Slog.v(TAG, + "Delivering non-ordered on [" + mQueueName + "] to registered " + + target + ": " + r); + deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); + } + addBroadcastToHistoryLocked(r); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast [" + + mQueueName + "] " + r); + } + + // Now take care of the next serialized one... + + // If we are waiting for a process to come up to handle the next + // broadcast, then do nothing at this point. Just in case, we + // check that the process we're waiting for still exists. + if (mPendingBroadcast != null) { + if (DEBUG_BROADCAST_LIGHT) { + Slog.v(TAG, "processNextBroadcast [" + + mQueueName + "]: waiting for " + + mPendingBroadcast.curApp); + } + + boolean isDead; + synchronized (mService.mPidsSelfLocked) { + isDead = (mService.mPidsSelfLocked.get( + mPendingBroadcast.curApp.pid) == null); + } + if (!isDead) { + // It's still alive, so keep waiting + return; + } else { + Slog.w(TAG, "pending app [" + + mQueueName + "]" + mPendingBroadcast.curApp + + " died before responding to broadcast"); + mPendingBroadcast.state = BroadcastRecord.IDLE; + mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; + mPendingBroadcast = null; + } + } + + boolean looped = false; + + do { + if (mOrderedBroadcasts.size() == 0) { + // No more broadcasts pending, so all done! + mService.scheduleAppGcsLocked(); + if (looped) { + // If we had finished the last ordered broadcast, then + // make sure all processes have correct oom and sched + // adjustments. + mService.updateOomAdjLocked(); + } + return; + } + r = mOrderedBroadcasts.get(0); + boolean forceReceive = false; + + // Ensure that even if something goes awry with the timeout + // detection, we catch "hung" broadcasts here, discard them, + // and continue to make progress. + // + // This is only done if the system is ready so that PRE_BOOT_COMPLETED + // receivers don't get executed with timeouts. They're intended for + // one time heavy lifting after system upgrades and can take + // significant amounts of time. + int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; + if (mService.mProcessesReady && r.dispatchTime > 0) { + long now = SystemClock.uptimeMillis(); + if ((numReceivers > 0) && + (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) { + Slog.w(TAG, "Hung broadcast [" + + mQueueName + "] discarded after timeout failure:" + + " now=" + now + + " dispatchTime=" + r.dispatchTime + + " startTime=" + r.receiverTime + + " intent=" + r.intent + + " numReceivers=" + numReceivers + + " nextReceiver=" + r.nextReceiver + + " state=" + r.state); + broadcastTimeoutLocked(false); // forcibly finish this broadcast + forceReceive = true; + r.state = BroadcastRecord.IDLE; + } + } + + if (r.state != BroadcastRecord.IDLE) { + if (DEBUG_BROADCAST) Slog.d(TAG, + "processNextBroadcast(" + + mQueueName + ") called when not idle (state=" + + r.state + ")"); + return; + } + + if (r.receivers == null || r.nextReceiver >= numReceivers + || r.resultAbort || forceReceive) { + // No more receivers for this broadcast! Send the final + // result if requested... + if (r.resultTo != null) { + try { + if (DEBUG_BROADCAST) { + int seq = r.intent.getIntExtra("seq", -1); + Slog.i(TAG, "Finishing broadcast [" + + mQueueName + "] " + r.intent.getAction() + + " seq=" + seq + " app=" + r.callerApp); + } + performReceiveLocked(r.callerApp, r.resultTo, + new Intent(r.intent), r.resultCode, + r.resultData, r.resultExtras, false, false); + // Set this to null so that the reference + // (local and remote) isnt kept in the mBroadcastHistory. + r.resultTo = null; + } catch (RemoteException e) { + Slog.w(TAG, "Failure [" + + mQueueName + "] sending broadcast result of " + + r.intent, e); + } + } + + if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG"); + cancelBroadcastTimeoutLocked(); + + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast " + + r); + + // ... and on to the next... + addBroadcastToHistoryLocked(r); + mOrderedBroadcasts.remove(0); + r = null; + looped = true; + continue; + } + } while (r == null); + + // Get the next receiver... + int recIdx = r.nextReceiver++; + + // Keep track of when this receiver started, and make sure there + // is a timeout message pending to kill it if need be. + r.receiverTime = SystemClock.uptimeMillis(); + if (recIdx == 0) { + r.dispatchTime = r.receiverTime; + r.dispatchClockTime = System.currentTimeMillis(); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast [" + + mQueueName + "] " + r); + } + if (! mPendingBroadcastTimeoutMessage) { + long timeoutTime = r.receiverTime + mTimeoutPeriod; + if (DEBUG_BROADCAST) Slog.v(TAG, + "Submitting BROADCAST_TIMEOUT_MSG [" + + mQueueName + "] for " + r + " at " + timeoutTime); + setBroadcastTimeoutLocked(timeoutTime); + } + + Object nextReceiver = r.receivers.get(recIdx); + if (nextReceiver instanceof BroadcastFilter) { + // Simple case: this is a registered receiver who gets + // a direct call. + BroadcastFilter filter = (BroadcastFilter)nextReceiver; + if (DEBUG_BROADCAST) Slog.v(TAG, + "Delivering ordered [" + + mQueueName + "] to registered " + + filter + ": " + r); + deliverToRegisteredReceiverLocked(r, filter, r.ordered); + if (r.receiver == null || !r.ordered) { + // The receiver has already finished, so schedule to + // process the next one. + if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing [" + + mQueueName + "]: ordered=" + + r.ordered + " receiver=" + r.receiver); + r.state = BroadcastRecord.IDLE; + scheduleBroadcastsLocked(); + } + return; + } + + // Hard case: need to instantiate the receiver, possibly + // starting its application process to host it. + + ResolveInfo info = + (ResolveInfo)nextReceiver; + + boolean skip = false; + int perm = mService.checkComponentPermission(info.activityInfo.permission, + r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, + info.activityInfo.exported); + if (perm != PackageManager.PERMISSION_GRANTED) { + if (!info.activityInfo.exported) { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " 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); + } 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); + } + skip = true; + } + if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && + r.requiredPermission != null) { + try { + perm = AppGlobals.getPackageManager(). + checkPermission(r.requiredPermission, + info.activityInfo.applicationInfo.packageName); + } catch (RemoteException e) { + perm = PackageManager.PERMISSION_DENIED; + } + if (perm != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: receiving " + + r.intent + " to " + + info.activityInfo.applicationInfo.packageName + + " requires " + r.requiredPermission + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + } + } + if (r.curApp != null && r.curApp.crashing) { + // If the target process is crashing, just skip it. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Skipping deliver ordered [" + + mQueueName + "] " + r + " to " + r.curApp + + ": process crashing"); + skip = true; + } + + if (skip) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "Skipping delivery of ordered [" + + mQueueName + "] " + r + " for whatever reason"); + r.receiver = null; + r.curFilter = null; + r.state = BroadcastRecord.IDLE; + scheduleBroadcastsLocked(); + return; + } + + 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.curReceiver = info.activityInfo; + if (DEBUG_MU && r.callingUid > UserId.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); + } + + // Broadcast is being executed, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + r.curComponent.getPackageName(), false, UserId.getUserId(r.callingUid)); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + r.curComponent.getPackageName() + ": " + e); + } + + // Is this receiver's application already running? + ProcessRecord app = mService.getProcessRecordLocked(targetProcess, + info.activityInfo.applicationInfo.uid); + if (app != null && app.thread != null) { + try { + app.addPackage(info.activityInfo.packageName); + processCurBroadcastLocked(r, app); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when sending broadcast to " + + r.curComponent, e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + + // Not running -- get it started, to be executed when the app comes up. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Need to start app [" + + mQueueName + "] " + targetProcess + " for broadcast " + r); + if ((r.curApp=mService.startProcessLocked(targetProcess, + info.activityInfo.applicationInfo, true, + r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, + "broadcast", r.curComponent, + (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false)) + == null) { + // Ah, this recipient is unavailable. Finish it if necessary, + // and mark the broadcast record as ready for the next. + Slog.w(TAG, "Unable to launch app " + + info.activityInfo.applicationInfo.packageName + "/" + + info.activityInfo.applicationInfo.uid + " for broadcast " + + r.intent + ": process is bad"); + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + scheduleBroadcastsLocked(); + r.state = BroadcastRecord.IDLE; + return; + } + + mPendingBroadcast = r; + mPendingBroadcastRecvIndex = recIdx; + } + } + + final void setBroadcastTimeoutLocked(long timeoutTime) { + if (! mPendingBroadcastTimeoutMessage) { + Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this); + mHandler.sendMessageAtTime(msg, timeoutTime); + mPendingBroadcastTimeoutMessage = true; + } + } + + final void cancelBroadcastTimeoutLocked() { + if (mPendingBroadcastTimeoutMessage) { + mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this); + mPendingBroadcastTimeoutMessage = false; + } + } + + final void broadcastTimeoutLocked(boolean fromMsg) { + if (fromMsg) { + mPendingBroadcastTimeoutMessage = false; + } + + if (mOrderedBroadcasts.size() == 0) { + return; + } + + long now = SystemClock.uptimeMillis(); + BroadcastRecord r = mOrderedBroadcasts.get(0); + if (fromMsg) { + if (mService.mDidDexOpt) { + // Delay timeouts until dexopt finishes. + mService.mDidDexOpt = false; + long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod; + setBroadcastTimeoutLocked(timeoutTime); + return; + } + if (!mService.mProcessesReady) { + // Only process broadcast timeouts if the system is ready. That way + // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended + // to do heavy lifting for system up. + return; + } + + long timeoutTime = r.receiverTime + mTimeoutPeriod; + if (timeoutTime > now) { + // We can observe premature timeouts because we do not cancel and reset the + // broadcast timeout message after each receiver finishes. Instead, we set up + // an initial timeout then kick it down the road a little further as needed + // when it expires. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Premature timeout [" + + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for " + + timeoutTime); + setBroadcastTimeoutLocked(timeoutTime); + return; + } + } + + Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver + + ", started " + (now - r.receiverTime) + "ms ago"); + r.receiverTime = now; + r.anrCount++; + + // Current receiver has passed its expiration date. + if (r.nextReceiver <= 0) { + Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0"); + return; + } + + ProcessRecord app = null; + String anrMessage = null; + + Object curReceiver = r.receivers.get(r.nextReceiver-1); + Slog.w(TAG, "Receiver during timeout: " + curReceiver); + logBroadcastReceiverDiscardLocked(r); + if (curReceiver instanceof BroadcastFilter) { + BroadcastFilter bf = (BroadcastFilter)curReceiver; + if (bf.receiverList.pid != 0 + && bf.receiverList.pid != ActivityManagerService.MY_PID) { + synchronized (mService.mPidsSelfLocked) { + app = mService.mPidsSelfLocked.get( + bf.receiverList.pid); + } + } + } else { + app = r.curApp; + } + + if (app != null) { + anrMessage = "Broadcast of " + r.intent.toString(); + } + + if (mPendingBroadcast == r) { + mPendingBroadcast = null; + } + + // Move on to the next receiver. + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + scheduleBroadcastsLocked(); + + if (anrMessage != null) { + // Post the ANR to the handler since we do not want to process ANRs while + // potentially holding our lock. + mHandler.post(new AppNotResponding(app, anrMessage)); + } + } + + private final void addBroadcastToHistoryLocked(BroadcastRecord r) { + if (r.callingUid < 0) { + // This was from a registerReceiver() call; ignore it. + return; + } + System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1, + MAX_BROADCAST_HISTORY-1); + r.finishTime = SystemClock.uptimeMillis(); + mBroadcastHistory[0] = r; + } + + final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { + if (r.nextReceiver > 0) { + Object curReceiver = r.receivers.get(r.nextReceiver-1); + if (curReceiver instanceof BroadcastFilter) { + BroadcastFilter bf = (BroadcastFilter) curReceiver; + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver - 1, + System.identityHashCode(bf)); + } else { + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver - 1, + ((ResolveInfo)curReceiver).toString()); + } + } else { + Slog.w(TAG, "Discarding broadcast before first receiver is invoked: " + + r); + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver, + "NONE"); + } + } + + final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll, String dumpPackage, boolean needSep) { + if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 + || mPendingBroadcast != null) { + boolean printed = false; + for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { + BroadcastRecord br = mParallelBroadcasts.get(i); + if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + needSep = false; + } + printed = true; + pw.println(" Active broadcasts [" + mQueueName + "]:"); + } + pw.println(" Broadcast #" + i + ":"); + br.dump(pw, " "); + } + printed = false; + needSep = true; + for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) { + BroadcastRecord br = mOrderedBroadcasts.get(i); + if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + } + needSep = true; + pw.println(" Active ordered broadcasts [" + mQueueName + "]:"); + } + pw.println(" Ordered Broadcast #" + i + ":"); + mOrderedBroadcasts.get(i).dump(pw, " "); + } + if (dumpPackage == null || (mPendingBroadcast != null + && dumpPackage.equals(mPendingBroadcast.callerPackage))) { + if (needSep) { + pw.println(); + } + pw.println(" Pending broadcast [" + mQueueName + "]:"); + if (mPendingBroadcast != null) { + mPendingBroadcast.dump(pw, " "); + } else { + pw.println(" (null)"); + } + needSep = true; + } + } + + boolean printed = false; + for (int i=0; i<MAX_BROADCAST_HISTORY; i++) { + BroadcastRecord r = mBroadcastHistory[i]; + if (r == null) { + break; + } + if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + } + needSep = true; + pw.println(" Historical broadcasts [" + mQueueName + "]:"); + printed = true; + } + if (dumpAll) { + pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); + r.dump(pw, " "); + } else { + if (i >= 50) { + pw.println(" ..."); + break; + } + pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r); + } + } + + return needSep; + } +} diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index bcb0134..dd560fc 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -59,6 +59,7 @@ class BroadcastRecord extends Binder { IBinder receiver; // who is currently running, null if none. int state; int anrCount; // has this broadcast record hit any ANRs? + BroadcastQueue queue; // the outbound queue handling this broadcast static final int IDLE = 0; static final int APP_RECEIVE = 1; @@ -161,11 +162,13 @@ class BroadcastRecord extends Binder { } } - BroadcastRecord(Intent _intent, ProcessRecord _callerApp, String _callerPackage, + BroadcastRecord(BroadcastQueue _queue, + Intent _intent, ProcessRecord _callerApp, String _callerPackage, int _callingPid, int _callingUid, String _requiredPermission, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky, boolean _initialSticky) { + queue = _queue; intent = _intent; callerApp = _callerApp; callerPackage = _callerPackage; diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java index f656486..3ba3fbb 100644 --- a/services/java/com/android/server/am/CompatModePackages.java +++ b/services/java/com/android/server/am/CompatModePackages.java @@ -41,7 +41,7 @@ public class CompatModePackages { private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>(); - private static final int MSG_WRITE = 1; + private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG; private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -121,7 +121,7 @@ public class CompatModePackages { public void handlePackageAddedLocked(String packageName, boolean updated) { ApplicationInfo ai = null; try { - ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0); + ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0); } catch (RemoteException e) { } if (ai == null) { @@ -220,7 +220,7 @@ public class CompatModePackages { public int getPackageScreenCompatModeLocked(String packageName) { ApplicationInfo ai = null; try { - ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0); + ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0); } catch (RemoteException e) { } if (ai == null) { @@ -232,7 +232,7 @@ public class CompatModePackages { public void setPackageScreenCompatModeLocked(String packageName, int mode) { ApplicationInfo ai = null; try { - ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0); + ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0); } catch (RemoteException e) { } if (ai == null) { @@ -365,7 +365,7 @@ public class CompatModePackages { } ApplicationInfo ai = null; try { - ai = pm.getApplicationInfo(pkg, 0); + ai = pm.getApplicationInfo(pkg, 0, 0); } catch (RemoteException e) { } if (ai == null) { diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java index 3835553..608b09a 100644 --- a/services/java/com/android/server/am/ContentProviderRecord.java +++ b/services/java/com/android/server/am/ContentProviderRecord.java @@ -20,24 +20,35 @@ import android.app.IActivityManager.ContentProviderHolder; import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.ProviderInfo; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; import android.os.Process; +import android.os.RemoteException; +import android.util.Slog; import java.io.PrintWriter; +import java.util.HashMap; import java.util.HashSet; class ContentProviderRecord extends ContentProviderHolder { // All attached clients final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>(); + // Handles for non-framework processes supported by this provider + HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle; + // Count for external process for which we have no handles. + int externalProcessNoHandleCount; + final ActivityManagerService service; final int uid; final ApplicationInfo appInfo; final ComponentName name; - int externals; // number of non-framework processes supported by this provider ProcessRecord proc; // if non-null, hosting process. ProcessRecord launchingApp; // if non-null, waiting for this app to be launched. String stringName; - - public ContentProviderRecord(ProviderInfo _info, ApplicationInfo ai, ComponentName _name) { + + public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info, + ApplicationInfo ai, ComponentName _name) { super(_info); + service = _service; uid = ai.uid; appInfo = ai; name = _name; @@ -50,6 +61,7 @@ class ContentProviderRecord extends ContentProviderHolder { appInfo = cpr.appInfo; name = cpr.name; noReleaseNeeded = cpr.noReleaseNeeded; + service = cpr.service; } public boolean canRunHere(ProcessRecord app) { @@ -57,6 +69,57 @@ class ContentProviderRecord extends ContentProviderHolder { && (uid == Process.SYSTEM_UID || uid == app.info.uid); } + public void addExternalProcessHandleLocked(IBinder token) { + if (token == null) { + externalProcessNoHandleCount++; + } else { + if (externalProcessTokenToHandle == null) { + externalProcessTokenToHandle = new HashMap<IBinder, ExternalProcessHandle>(); + } + ExternalProcessHandle handle = externalProcessTokenToHandle.get(token); + if (handle == null) { + handle = new ExternalProcessHandle(token); + externalProcessTokenToHandle.put(token, handle); + } + handle.mAcquisitionCount++; + } + } + + public boolean removeExternalProcessHandleLocked(IBinder token) { + if (hasExternalProcessHandles()) { + boolean hasHandle = false; + if (externalProcessTokenToHandle != null) { + ExternalProcessHandle handle = externalProcessTokenToHandle.get(token); + if (handle != null) { + hasHandle = true; + handle.mAcquisitionCount--; + if (handle.mAcquisitionCount == 0) { + removeExternalProcessHandleInternalLocked(token); + return true; + } + } + } + if (!hasHandle) { + externalProcessNoHandleCount--; + return true; + } + } + return false; + } + + private void removeExternalProcessHandleInternalLocked(IBinder token) { + ExternalProcessHandle handle = externalProcessTokenToHandle.get(token); + handle.unlinkFromOwnDeathLocked(); + externalProcessTokenToHandle.remove(token); + if (externalProcessTokenToHandle.size() == 0) { + externalProcessTokenToHandle = null; + } + } + + public boolean hasExternalProcessHandles() { + return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0); + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("package="); pw.print(info.applicationInfo.packageName); @@ -73,8 +136,9 @@ class ContentProviderRecord extends ContentProviderHolder { pw.print("multiprocess="); pw.print(info.multiprocess); pw.print(" initOrder="); pw.println(info.initOrder); } - if (externals != 0) { - pw.print(prefix); pw.print("externals="); pw.println(externals); + if (hasExternalProcessHandles()) { + pw.print(prefix); pw.print("externals="); + pw.println(externalProcessTokenToHandle.size()); } if (clients.size() > 0) { pw.print(prefix); pw.println("Clients:"); @@ -84,6 +148,7 @@ class ContentProviderRecord extends ContentProviderHolder { } } + @Override public String toString() { if (stringName != null) { return stringName; @@ -92,8 +157,39 @@ class ContentProviderRecord extends ContentProviderHolder { sb.append("ContentProviderRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(' '); - sb.append(info.name); + sb.append(name.flattenToShortString()); sb.append('}'); return stringName = sb.toString(); } + + // This class represents a handle from an external process to a provider. + private class ExternalProcessHandle implements DeathRecipient { + private static final String LOG_TAG = "ExternalProcessHanldle"; + + private final IBinder mToken; + private int mAcquisitionCount; + + public ExternalProcessHandle(IBinder token) { + mToken = token; + try { + token.linkToDeath(this, 0); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Couldn't register for death for token: " + mToken, re); + } + } + + public void unlinkFromOwnDeathLocked() { + mToken.unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + synchronized (service) { + if (hasExternalProcessHandles() && + externalProcessTokenToHandle.get(mToken) != null) { + removeExternalProcessHandleInternalLocked(mToken); + } + } + } + } } diff --git a/services/java/com/android/server/am/IntentBindRecord.java b/services/java/com/android/server/am/IntentBindRecord.java index 2618c77..c94f714 100644 --- a/services/java/com/android/server/am/IntentBindRecord.java +++ b/services/java/com/android/server/am/IntentBindRecord.java @@ -54,7 +54,7 @@ class IntentBindRecord { void dumpInService(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("intent={"); - pw.print(intent.getIntent().toShortString(false, true, false)); + pw.print(intent.getIntent().toShortString(false, true, false, false)); pw.println('}'); pw.print(prefix); pw.print("binder="); pw.println(binder); pw.print(prefix); pw.print("requested="); pw.print(requested); @@ -89,7 +89,7 @@ class IntentBindRecord { sb.append(service.shortName); sb.append(':'); if (intent != null) { - intent.getIntent().toShortString(sb, false, false, false); + intent.getIntent().toShortString(sb, false, false, false, false); } sb.append('}'); return stringName = sb.toString(); diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index abd2a1f..ad15da1 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -16,14 +16,16 @@ package com.android.server.am; -import android.app.IActivityManager; +import android.app.ActivityManager; import android.content.IIntentSender; import android.content.IIntentReceiver; import android.app.PendingIntent; import android.content.Intent; 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; @@ -47,6 +49,7 @@ class PendingIntentRecord extends IIntentSender.Stub { final int requestCode; final Intent requestIntent; final String requestResolvedType; + final Bundle options; Intent[] allIntents; String[] allResolvedTypes; final int flags; @@ -55,7 +58,7 @@ class PendingIntentRecord extends IIntentSender.Stub { 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) { + int _r, Intent[] _i, String[] _it, int _f, Bundle _o) { type = _t; packageName = _p; activity = _a; @@ -66,6 +69,7 @@ class PendingIntentRecord extends IIntentSender.Stub { allIntents = _i; allResolvedTypes = _it; flags = _f; + options = _o; int hash = 23; hash = (ODD_PRIME_NUMBER*hash) + _f; @@ -151,19 +155,19 @@ class PendingIntentRecord extends IIntentSender.Stub { return "Key{" + typeName() + " pkg=" + packageName + " intent=" + (requestIntent != null - ? requestIntent.toShortString(false, true, false) : "<null>") + ? requestIntent.toShortString(false, true, false, false) : "<null>") + " flags=0x" + Integer.toHexString(flags) + "}"; } String typeName() { switch (type) { - case IActivityManager.INTENT_SENDER_ACTIVITY: + case ActivityManager.INTENT_SENDER_ACTIVITY: return "startActivity"; - case IActivityManager.INTENT_SENDER_BROADCAST: + case ActivityManager.INTENT_SENDER_BROADCAST: return "broadcastIntent"; - case IActivityManager.INTENT_SENDER_SERVICE: + case ActivityManager.INTENT_SENDER_SERVICE: return "startService"; - case IActivityManager.INTENT_SENDER_ACTIVITY_RESULT: + case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT: return "activityResult"; } return Integer.toString(type); @@ -180,13 +184,13 @@ class PendingIntentRecord extends IIntentSender.Stub { public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission) { return sendInner(code, intent, resolvedType, finishedReceiver, - requiredPermission, null, null, 0, 0, 0); + requiredPermission, null, null, 0, 0, 0, null); } int sendInner(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, - int flagsMask, int flagsValues) { + int flagsMask, int flagsValues, Bundle options) { synchronized(owner) { if (!canceled) { sent = true; @@ -212,7 +216,14 @@ class PendingIntentRecord extends IIntentSender.Stub { boolean sendFinish = finishedReceiver != null; switch (key.type) { - case IActivityManager.INTENT_SENDER_ACTIVITY: + case ActivityManager.INTENT_SENDER_ACTIVITY: + if (options == null) { + options = key.options; + } else if (key.options != null) { + Bundle opts = new Bundle(key.options); + opts.putAll(options); + options = opts; + } try { if (key.allIntents != null && key.allIntents.length > 1) { Intent[] allIntents = new Intent[key.allIntents.length]; @@ -226,36 +237,37 @@ class PendingIntentRecord extends IIntentSender.Stub { allIntents[allIntents.length-1] = finalIntent; allResolvedTypes[allResolvedTypes.length-1] = resolvedType; owner.startActivitiesInPackage(uid, allIntents, - allResolvedTypes, resultTo); + allResolvedTypes, resultTo, options); } else { owner.startActivityInPackage(uid, finalIntent, resolvedType, - resultTo, resultWho, requestCode, false); + resultTo, resultWho, requestCode, 0, options); } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); } break; - case IActivityManager.INTENT_SENDER_ACTIVITY_RESULT: + case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT: key.activity.stack.sendActivityResultLocked(-1, key.activity, key.who, key.requestCode, code, finalIntent); break; - case IActivityManager.INTENT_SENDER_BROADCAST: + case ActivityManager.INTENT_SENDER_BROADCAST: try { // If a completion callback has been requested, require // that the broadcast be delivered synchronously owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType, finishedReceiver, code, null, null, - requiredPermission, (finishedReceiver != null), false); + requiredPermission, (finishedReceiver != null), false, UserId + .getUserId(uid)); sendFinish = false; } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); } break; - case IActivityManager.INTENT_SENDER_SERVICE: + case ActivityManager.INTENT_SENDER_SERVICE: try { owner.startServiceInPackage(uid, finalIntent, resolvedType); @@ -279,7 +291,7 @@ class PendingIntentRecord extends IIntentSender.Stub { return 0; } } - return IActivityManager.START_CANCELED; + return ActivityManager.START_CANCELED; } protected void finalize() throws Throwable { @@ -318,7 +330,7 @@ class PendingIntentRecord extends IIntentSender.Stub { } if (key.requestIntent != null) { pw.print(prefix); pw.print("requestIntent="); - pw.println(key.requestIntent.toShortString(false, true, true)); + pw.println(key.requestIntent.toShortString(false, true, true, true)); } if (sent || canceled) { pw.print(prefix); pw.print("sent="); pw.print(sent); diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 72292be..4529ecc 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -28,7 +28,9 @@ import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.os.Bundle; import android.os.IBinder; +import android.os.Process; import android.os.SystemClock; +import android.os.UserId; import android.util.PrintWriterPrinter; import android.util.TimeUtils; @@ -44,6 +46,9 @@ import java.util.HashSet; class ProcessRecord { final BatteryStatsImpl.Uid.Proc batteryStats; // where to collect runtime statistics final ApplicationInfo info; // all about the first app in the process + final boolean isolated; // true if this is a special isolated process + final int uid; // uid of process; may be different from 'info' if isolated + final int userId; // user of process. final String processName; // name of the process // List of packages running in the process final HashSet<String> pkgList = new HashSet<String>(); @@ -58,6 +63,7 @@ class ProcessRecord { int hiddenAdj; // If hidden, 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 int curAdj; // Current OOM adjustment for this process int setAdj; // Last set OOM adjustment for this process int curSchedGroup; // Currently desired scheduling class @@ -147,6 +153,12 @@ class ProcessRecord { void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); + pw.print(prefix); pw.print("user #"); pw.print(userId); + pw.print(" uid="); pw.print(info.uid); + if (uid != info.uid) { + pw.print(" ISOLATED uid="); pw.print(uid); + } + pw.println(); if (info.className != null) { pw.print(prefix); pw.print("class="); pw.println(info.className); } @@ -188,6 +200,7 @@ class ProcessRecord { pw.print(" hidden="); pw.print(hiddenAdj); pw.print(" curRaw="); pw.print(curRawAdj); pw.print(" setRaw="); pw.print(setRawAdj); + pw.print(" nonStopping="); pw.print(nonStoppingAdj); pw.print(" cur="); pw.print(curAdj); pw.print(" set="); pw.println(setAdj); pw.print(prefix); pw.print("curSchedGroup="); pw.print(curSchedGroup); @@ -267,9 +280,12 @@ class ProcessRecord { } ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, IApplicationThread _thread, - ApplicationInfo _info, String _processName) { + ApplicationInfo _info, String _processName, int _uid) { batteryStats = _batteryStats; info = _info; + isolated = _info.uid != _uid; + uid = _uid; + userId = UserId.getUserId(_uid); processName = _processName; pkgList.add(_info.packageName); thread = _thread; @@ -343,7 +359,18 @@ class ProcessRecord { sb.append(':'); sb.append(processName); sb.append('/'); - sb.append(info.uid); + if (info.uid < Process.FIRST_APPLICATION_UID) { + sb.append(uid); + } else { + sb.append('u'); + sb.append(userId); + sb.append('a'); + sb.append(info.uid%Process.FIRST_APPLICATION_UID); + if (uid != info.uid) { + sb.append('i'); + sb.append(UserId.getAppId(uid) - Process.FIRST_ISOLATED_UID); + } + } } public String toString() { diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java new file mode 100644 index 0000000..ccc928f --- /dev/null +++ b/services/java/com/android/server/am/ProviderMap.java @@ -0,0 +1,351 @@ +/* + * 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.am; + +import android.content.ComponentName; +import android.os.Binder; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserId; +import android.util.Slog; +import android.util.SparseArray; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +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 + * user and ones that are not user-specific (system providers). + */ +public class ProviderMap { + + private static final String TAG = "ProviderMap"; + + private static final boolean DBG = false; + + private final HashMap<String, ContentProviderRecord> mGlobalByName + = new HashMap<String, ContentProviderRecord>(); + private final HashMap<ComponentName, ContentProviderRecord> mGlobalByClass + = new HashMap<ComponentName, ContentProviderRecord>(); + + private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser + = new SparseArray<HashMap<String, ContentProviderRecord>>(); + private final SparseArray<HashMap<ComponentName, ContentProviderRecord>> mProvidersByClassPerUser + = new SparseArray<HashMap<ComponentName, ContentProviderRecord>>(); + + ContentProviderRecord getProviderByName(String name) { + return getProviderByName(name, -1); + } + + ContentProviderRecord getProviderByName(String name, int userId) { + if (DBG) { + Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid()); + } + // Try to find it in the global list + ContentProviderRecord record = mGlobalByName.get(name); + if (record != null) { + return record; + } + + // Check the current user's list + return getProvidersByName(userId).get(name); + } + + ContentProviderRecord getProviderByClass(ComponentName name) { + return getProviderByClass(name, -1); + } + + ContentProviderRecord getProviderByClass(ComponentName name, int userId) { + if (DBG) { + Slog.i(TAG, "getProviderByClass: " + name + ", callingUid = " + Binder.getCallingUid()); + } + // Try to find it in the global list + ContentProviderRecord record = mGlobalByClass.get(name); + if (record != null) { + return record; + } + + // Check the current user's list + return getProvidersByClass(userId).get(name); + } + + void putProviderByName(String name, ContentProviderRecord record) { + if (DBG) { + 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); + } else { + final int userId = UserId.getUserId(record.appInfo.uid); + getProvidersByName(userId).put(name, record); + } + } + + void putProviderByClass(ComponentName name, ContentProviderRecord record) { + if (DBG) { + 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); + } else { + final int userId = UserId.getUserId(record.appInfo.uid); + getProvidersByClass(userId).put(name, record); + } + } + + void removeProviderByName(String name, int optionalUserId) { + if (mGlobalByName.containsKey(name)) { + if (DBG) + Slog.i(TAG, "Removing from globalByName name=" + name); + mGlobalByName.remove(name); + } else { + // TODO: Verify this works, i.e., the caller happens to be from the correct user + if (DBG) + Slog.i(TAG, + "Removing from providersByName name=" + name + " user=" + + (optionalUserId == -1 ? Binder.getOrigCallingUser() : optionalUserId)); + getProvidersByName(optionalUserId).remove(name); + } + } + + void removeProviderByClass(ComponentName name, int optionalUserId) { + if (mGlobalByClass.containsKey(name)) { + if (DBG) + Slog.i(TAG, "Removing from globalByClass name=" + name); + mGlobalByClass.remove(name); + } else { + if (DBG) + Slog.i(TAG, + "Removing from providersByClass name=" + name + " user=" + + (optionalUserId == -1 ? Binder.getOrigCallingUser() : optionalUserId)); + getProvidersByClass(optionalUserId).remove(name); + } + } + + private HashMap<String, ContentProviderRecord> getProvidersByName(int optionalUserId) { + final int userId = optionalUserId >= 0 + ? optionalUserId : Binder.getOrigCallingUser(); + final HashMap<String, ContentProviderRecord> map = mProvidersByNamePerUser.get(userId); + if (map == null) { + HashMap<String, ContentProviderRecord> newMap = new HashMap<String, ContentProviderRecord>(); + mProvidersByNamePerUser.put(userId, newMap); + return newMap; + } else { + return map; + } + } + + HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int optionalUserId) { + final int userId = optionalUserId >= 0 + ? optionalUserId : Binder.getOrigCallingUser(); + final HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.get(userId); + if (map == null) { + HashMap<ComponentName, ContentProviderRecord> newMap = new HashMap<ComponentName, ContentProviderRecord>(); + mProvidersByClassPerUser.put(userId, newMap); + return newMap; + } else { + return map; + } + } + + private void dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll, + HashMap<ComponentName, ContentProviderRecord> map) { + Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<ComponentName, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); + if (dumpAll) { + pw.print(" * "); + pw.println(r); + r.dump(pw, " "); + } else { + pw.print(" * "); + pw.println(r); + if (r.proc != null) { + pw.print(" proc="); + pw.println(r.proc); + } + if (r.launchingApp != null) { + pw.print(" launchingApp="); + pw.println(r.launchingApp); + } + if (r.clients.size() > 0 || r.externalProcessNoHandleCount > 0) { + pw.print(" "); pw.print(r.clients.size()); + pw.print(" clients, "); pw.print(r.externalProcessNoHandleCount); + pw.println(" external handles"); + } + } + } + } + + private void dumpProvidersByNameLocked(PrintWriter pw, + HashMap<String, ContentProviderRecord> map) { + Iterator<Map.Entry<String, ContentProviderRecord>> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); + pw.print(" "); + pw.print(e.getKey()); + pw.print(": "); + pw.println(r); + } + } + + 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); + pw.println(""); + } + + if (mProvidersByClassPerUser.size() > 1) { + 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); + dumpProvidersByClassLocked(pw, dumpAll, map); + } + needSep = true; + + if (dumpAll) { + pw.println(" "); + pw.println(" Authority to provider mappings:"); + dumpProvidersByNameLocked(pw, mGlobalByName); + + for (int i = 0; i < mProvidersByNamePerUser.size(); i++) { + if (i > 0) { + pw.println(" User " + mProvidersByNamePerUser.keyAt(i) + ":"); + } + dumpProvidersByNameLocked(pw, mProvidersByNamePerUser.valueAt(i)); + } + } + } + + protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); + + if ("all".equals(name)) { + synchronized (this) { + for (ContentProviderRecord r1 : getProvidersByClass(-1).values()) { + providers.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) { + } + } + + synchronized (this) { + for (ContentProviderRecord r1 : getProvidersByClass(-1).values()) { + if (componentName != null) { + if (r1.name.equals(componentName)) { + providers.add(r1); + } + } else if (name != null) { + if (r1.name.flattenToString().contains(name)) { + providers.add(r1); + } + } else if (System.identityHashCode(r1) == objectId) { + providers.add(r1); + } + } + } + } + + if (providers.size() <= 0) { + return false; + } + + boolean needSep = false; + for (int i=0; i<providers.size(); i++) { + if (needSep) { + pw.println(); + } + needSep = true; + dumpProvider("", fd, pw, providers.get(i), args, dumpAll); + } + return true; + } + + /** + * Invokes IApplicationThread.dumpProvider() on the thread of the specified provider if + * there is a thread associated with the provider. + */ + private void dumpProvider(String prefix, FileDescriptor fd, PrintWriter pw, + final ContentProviderRecord r, String[] args, boolean dumpAll) { + String innerPrefix = prefix + " "; + synchronized (this) { + pw.print(prefix); pw.print("PROVIDER "); + pw.print(r); + pw.print(" pid="); + if (r.proc != null) pw.println(r.proc.pid); + else pw.println("(not running)"); + if (dumpAll) { + r.dump(pw, innerPrefix); + } + } + if (r.proc != null && r.proc.thread != null) { + pw.println(" Client:"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.proc.thread.dumpProvider( + tp.getWriteFd().getFileDescriptor(), r.provider.asBinder(), args); + tp.setBufferPrefix(" "); + // Short timeout, since blocking here can + // deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException ex) { + pw.println(" Failure while dumping the provider: " + ex); + } catch (RemoteException ex) { + pw.println(" Got a RemoteException while dumping the service"); + } + } + } + + +} diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 257113b..828eef7 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -25,11 +25,13 @@ import android.app.NotificationManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.Slog; import android.util.TimeUtils; @@ -60,6 +62,7 @@ class ServiceRecord extends Binder { // all information about the service. final ApplicationInfo appInfo; // information about service's app. + final int userId; // user that this service is running as final String packageName; // the package implementing intent's component final String processName; // process where this component wants to run final String permission;// permission needed to access service @@ -77,6 +80,7 @@ class ServiceRecord extends Binder { // IBinder -> ConnectionRecord of all bound clients ProcessRecord app; // where this service is running or null. + ProcessRecord isolatedProc; // keep track of isolated process, if requested boolean isForeground; // is service currently in foreground mode? int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. @@ -102,7 +106,7 @@ class ServiceRecord extends Binder { final boolean taskRemoved; final int id; final Intent intent; - final int targetPermissionUid; + final ActivityManagerService.NeededUriGrants neededGrants; long deliveredTime; int deliveryCount; int doneExecutingCount; @@ -111,12 +115,12 @@ class ServiceRecord extends Binder { String stringName; // caching of toString StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent, - int _targetPermissionUid) { + ActivityManagerService.NeededUriGrants _neededGrants) { sr = _sr; taskRemoved = _taskRemoved; id = _id; intent = _intent; - targetPermissionUid = _targetPermissionUid; + neededGrants = _neededGrants; } UriPermissionOwner getUriPermissionsLocked() { @@ -173,9 +177,9 @@ class ServiceRecord extends Binder { pw.print(prefix); pw.print(" intent="); if (si.intent != null) pw.println(si.intent.toString()); else pw.println("null"); - if (si.targetPermissionUid >= 0) { - pw.print(prefix); pw.print(" targetPermissionUid="); - pw.println(si.targetPermissionUid); + if (si.neededGrants != null) { + pw.print(prefix); pw.print(" neededGrants="); + pw.println(si.neededGrants); } if (si.uriPermissions != null) { if (si.uriPermissions.readUriPermissions != null) { @@ -192,7 +196,7 @@ class ServiceRecord extends Binder { void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("intent={"); - pw.print(intent.getIntent().toShortString(false, true, false)); + pw.print(intent.getIntent().toShortString(false, true, false, true)); pw.println('}'); pw.print(prefix); pw.print("packageName="); pw.println(packageName); pw.print(prefix); pw.print("processName="); pw.println(processName); @@ -207,6 +211,9 @@ class ServiceRecord extends Binder { } pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); + if (isolatedProc != null) { + pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc); + } if (isForeground || foregroundId != 0) { pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); pw.print(" foregroundId="); pw.print(foregroundId); @@ -289,6 +296,7 @@ class ServiceRecord extends Binder { this.restarter = restarter; createTime = SystemClock.elapsedRealtime(); lastActivity = SystemClock.uptimeMillis(); + userId = UserId.getUserId(appInfo.uid); } public AppBindRecord retrieveAppBindingLocked(Intent intent, diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index de3129b..e3ebcc6 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -19,7 +19,9 @@ package com.android.server.am; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.os.UserId; import java.io.PrintWriter; @@ -37,6 +39,7 @@ class TaskRecord extends ThumbnailHolder { boolean askedCompatMode;// Have asked the user about compat mode for this task. String stringName; // caching of toString() result. + int userId; // user for which this task was created TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; @@ -84,13 +87,17 @@ class TaskRecord extends ThumbnailHolder { origActivity = new ComponentName(info.packageName, info.name); } } - + if (intent != null && (intent.getFlags()&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { // Once we are set to an Intent with this flag, we count this // task as having a true root activity. rootWasReset = true; } + + if (info.applicationInfo != null) { + userId = UserId.getUserId(info.applicationInfo.uid); + } } void dump(PrintWriter pw, String prefix) { @@ -104,14 +111,14 @@ class TaskRecord extends ThumbnailHolder { if (intent != null) { StringBuilder sb = new StringBuilder(128); sb.append(prefix); sb.append("intent={"); - intent.toShortString(sb, false, true, false); + intent.toShortString(sb, false, true, false, true); sb.append('}'); pw.println(sb.toString()); } if (affinityIntent != null) { StringBuilder sb = new StringBuilder(128); sb.append(prefix); sb.append("affinityIntent={"); - affinityIntent.toShortString(sb, false, true, false); + affinityIntent.toShortString(sb, false, true, false, true); sb.append('}'); pw.println(sb.toString()); } @@ -154,6 +161,8 @@ class TaskRecord extends ThumbnailHolder { } else { sb.append(" ??"); } + sb.append(" U "); + sb.append(userId); sb.append('}'); return stringName = sb.toString(); } diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index e810e3c..ba65f39 100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -656,7 +656,7 @@ public final class UsageStatsService extends IUsageStats.Stub { } } }; - mPackageMonitor.register(mContext, true); + mPackageMonitor.register(mContext, null, true); filterHistoryStats(); } diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index cc1df4f..88a0ccb 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -546,14 +546,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { ifcg = mNMService.getInterfaceConfig(iface); if (ifcg != null) { InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); - ifcg.addr = new LinkAddress(addr, USB_PREFIX_LENGTH); + ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); if (enabled) { - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); + ifcg.setInterfaceUp(); } else { - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down"); + ifcg.setInterfaceDown(); } - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", ""); - ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" "," "); + ifcg.clearFlag("running"); mNMService.setInterfaceConfig(iface, ifcg); } } catch (Exception e) { @@ -1216,6 +1215,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return retValue; } protected boolean turnOffUpstreamMobileConnection() { + // ignore pending renewal requests + ++mCurrentConnectionSequence; if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) { try { mConnService.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, @@ -1305,6 +1306,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { if (upType == ConnectivityManager.TYPE_MOBILE_DUN || upType == ConnectivityManager.TYPE_MOBILE_HIPRI) { turnOnUpstreamMobileConnection(upType); + } else if (upType != ConnectivityManager.TYPE_NONE) { + /* If we've found an active upstream connection that's not DUN/HIPRI + * we should stop any outstanding DUN/HIPRI start requests. + * + * If we found NONE we don't want to do this as we want any previous + * requests to keep trying to bring up something we can use. + */ + turnOffUpstreamMobileConnection(); } if (upType == ConnectivityManager.TYPE_NONE) { diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index a76e70f..c4f9ce1 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -33,6 +33,7 @@ import android.net.INetworkManagementEventObserver; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Binder; +import android.os.FileUtils; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; @@ -47,7 +48,6 @@ import com.android.internal.net.VpnConfig; import com.android.server.ConnectivityService.VpnCallback; import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charsets; @@ -573,11 +573,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } // Now we are connected. Read and parse the new state. - byte[] buffer = new byte[(int) state.length()]; - if (new FileInputStream(state).read(buffer) != buffer.length) { - throw new IllegalStateException("Cannot read the state"); - } - String[] parameters = new String(buffer, Charsets.UTF_8).split("\n", -1); + String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1); if (parameters.length != 6) { throw new IllegalStateException("Cannot parse the state"); } diff --git a/services/java/com/android/server/wm/InputApplicationHandle.java b/services/java/com/android/server/input/InputApplicationHandle.java index 1812f11..42c1052 100644 --- a/services/java/com/android/server/wm/InputApplicationHandle.java +++ b/services/java/com/android/server/input/InputApplicationHandle.java @@ -14,8 +14,7 @@ * limitations under the License. */ -package com.android.server.wm; - +package com.android.server.input; /** * Functions as a handle for an application that can receive input. @@ -30,7 +29,7 @@ public final class InputApplicationHandle { private int ptr; // The window manager's application window token. - public final AppWindowToken appWindowToken; + public final Object appWindowToken; // Application name. public String name; @@ -40,7 +39,7 @@ public final class InputApplicationHandle { private native void nativeDispose(); - public InputApplicationHandle(AppWindowToken appWindowToken) { + public InputApplicationHandle(Object appWindowToken) { this.appWindowToken = appWindowToken; } diff --git a/services/java/com/android/server/wm/InputFilter.java b/services/java/com/android/server/input/InputFilter.java index 8f0001a..2ce0a02 100644 --- a/services/java/com/android/server/wm/InputFilter.java +++ b/services/java/com/android/server/input/InputFilter.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.server.wm; +package com.android.server.input; + +import com.android.server.wm.WindowManagerService; import android.os.Handler; import android.os.Looper; @@ -33,7 +35,7 @@ import android.view.WindowManagerPolicy; * system's behavior changes as follows: * <ul> * <li>Input events are first delivered to the {@link WindowManagerPolicy} - * interception methods before queueing as usual. This critical step takes care of managing + * 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 @@ -79,7 +81,7 @@ import android.view.WindowManagerPolicy; * {@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 cancelation events to the dispatcher if needed. + * 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. @@ -89,8 +91,8 @@ import android.view.WindowManagerPolicy; * 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 synthetically generated - * keys by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag. + * 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 { diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java new file mode 100644 index 0000000..aece7d2 --- /dev/null +++ b/services/java/com/android/server/input/InputManagerService.java @@ -0,0 +1,1495 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input; + +import com.android.internal.os.AtomicFile; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; +import com.android.server.Watchdog; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.Manifest; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; +import android.content.res.Resources; +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.InputManager; +import android.hardware.input.KeyboardLayout; +import android.os.Binder; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.MessageQueue; +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.InputChannel; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.PointerIcon; +import android.view.Surface; +import android.view.ViewConfiguration; +import android.view.WindowManagerPolicy; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import libcore.io.IoUtils; +import libcore.io.Streams; +import libcore.util.Objects; + +/* + * Wraps the C++ InputManager and provides its callbacks. + */ +public class InputManagerService extends IInputManager.Stub implements Watchdog.Monitor { + static final String TAG = "InputManager"; + static final boolean DEBUG = true; + + private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; + + private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1; + + // Pointer to native input manager service object. + private final int mPtr; + + private final Context mContext; + private final Callbacks mCallbacks; + private final InputManagerHandler mHandler; + private boolean mSystemReady; + private BluetoothService mBluetoothService; + + // Persistent data store. Must be locked each time during use. + private final PersistentDataStore mDataStore = new PersistentDataStore(); + + // List of currently registered input devices changed listeners by process id. + private Object mInputDevicesLock = new Object(); + private boolean mInputDevicesChangedPending; // guarded by mInputDevicesLock + private InputDevice[] mInputDevices = new InputDevice[0]; + private final SparseArray<InputDevicesChangedListenerRecord> mInputDevicesChangedListeners = + new SparseArray<InputDevicesChangedListenerRecord>(); // guarded by mInputDevicesLock + private final ArrayList<InputDevicesChangedListenerRecord> + mTempInputDevicesChangedListenersToNotify = + new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only + + // State for vibrator tokens. + private Object mVibratorLock = new Object(); + private HashMap<IBinder, VibratorToken> mVibratorTokens = + new HashMap<IBinder, VibratorToken>(); + private int mNextVibratorTokenValue; + + // State for the currently installed input filter. + final Object mInputFilterLock = new Object(); + InputFilter 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); + + private static native int nativeGetScanCodeState(int ptr, + int deviceId, int sourceMask, int scanCode); + private static native int nativeGetKeyCodeState(int ptr, + int deviceId, int sourceMask, int keyCode); + private static native int nativeGetSwitchState(int ptr, + int deviceId, int sourceMask, int sw); + private static native boolean nativeHasKeys(int ptr, + int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); + private static native void nativeRegisterInputChannel(int ptr, InputChannel inputChannel, + InputWindowHandle inputWindowHandle, boolean monitor); + private static native void nativeUnregisterInputChannel(int ptr, InputChannel inputChannel); + private static native void nativeSetInputFilterEnabled(int ptr, boolean enable); + private static native int nativeInjectInputEvent(int ptr, InputEvent event, + int injectorPid, int injectorUid, int syncMode, int timeoutMillis, + int policyFlags); + private static native void nativeSetInputWindows(int ptr, InputWindowHandle[] windowHandles); + private static native void nativeSetInputDispatchMode(int ptr, boolean enabled, boolean frozen); + private static native void nativeSetSystemUiVisibility(int ptr, int visibility); + private static native void nativeSetFocusedApplication(int ptr, + InputApplicationHandle application); + private static native void nativeGetInputConfiguration(int ptr, Configuration configuration); + private static native boolean nativeTransferTouchFocus(int ptr, + InputChannel fromChannel, InputChannel toChannel); + private static native void nativeSetPointerSpeed(int ptr, int speed); + private static native void nativeSetShowTouches(int ptr, boolean enabled); + private static native void nativeVibrate(int ptr, int deviceId, long[] pattern, + int repeat, int token); + private static native void nativeCancelVibrate(int ptr, int deviceId, int token); + private static native void nativeReloadKeyboardLayouts(int ptr); + private static native void nativeReloadDeviceAliases(int ptr); + private static native String nativeDump(int ptr); + private static native void nativeMonitor(int ptr); + + // Input event injection constants defined in InputDispatcher.h. + private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0; + private static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1; + private static final int INPUT_EVENT_INJECTION_FAILED = 2; + private static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3; + + // Maximum number of milliseconds to wait for input event injection. + private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; + + // Key states (may be returned by queries about the current state of a + // particular key code, scan code or switch). + + /** The key state is unknown or the requested key itself is not supported. */ + public static final int KEY_STATE_UNKNOWN = -1; + + /** The key is up. /*/ + public static final int KEY_STATE_UP = 0; + + /** The key is down. */ + public static final int KEY_STATE_DOWN = 1; + + /** The key is down but is a virtual key press that is being emulated by the system. */ + public static final int KEY_STATE_VIRTUAL = 2; + + /** Scan code: Mouse / trackball button. */ + public static final int BTN_MOUSE = 0x110; + + /** Switch code: Lid switch. When set, lid is shut. */ + public static final int SW_LID = 0x00; + + /** Switch code: Keypad slide. When set, keyboard is exposed. */ + public static final int SW_KEYPAD_SLIDE = 0x0a; + + public InputManagerService(Context context, Callbacks callbacks) { + this.mContext = context; + this.mCallbacks = callbacks; + this.mHandler = new InputManagerHandler(); + + Slog.i(TAG, "Initializing input manager"); + mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); + } + + public void start() { + Slog.i(TAG, "Starting input manager"); + nativeStart(mPtr); + + // Add ourself to the Watchdog monitors. + Watchdog.getInstance().addMonitor(this); + + registerPointerSpeedSettingObserver(); + registerShowTouchesSettingObserver(); + + updatePointerSpeedFromSettings(); + updateShowTouchesFromSettings(); + } + + public void systemReady(BluetoothService bluetoothService) { + if (DEBUG) { + Slog.d(TAG, "System ready."); + } + mBluetoothService = bluetoothService; + mSystemReady = true; + + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "Packages changed, reloading keyboard layouts."); + } + reloadKeyboardLayouts(); + } + }, filter, null, mHandler); + + filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "Bluetooth alias changed, reloading device names."); + } + reloadDeviceAliases(); + } + }, filter, null, mHandler); + + reloadKeyboardLayouts(); + reloadDeviceAliases(); + } + + private void reloadKeyboardLayouts() { + nativeReloadKeyboardLayouts(mPtr); + } + + private void reloadDeviceAliases() { + 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."); + } + + if (DEBUG) { + Slog.d(TAG, "Setting display #" + displayId + " size to " + width + "x" + height + + " external size " + externalWidth + "x" + externalHeight); + } + nativeSetDisplaySize(mPtr, displayId, width, height, externalWidth, externalHeight); + } + + public void setDisplayOrientation(int displayId, int rotation) { + 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); + } + nativeSetDisplayOrientation(mPtr, displayId, rotation); + } + + public void getInputConfiguration(Configuration config) { + if (config == null) { + throw new IllegalArgumentException("config must not be null."); + } + + nativeGetInputConfiguration(mPtr, config); + } + + /** + * Gets the current state of a key or button by key code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param keyCode The key code to check. + * @return The key state. + */ + public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) { + return nativeGetKeyCodeState(mPtr, deviceId, sourceMask, keyCode); + } + + /** + * Gets the current state of a key or button by scan code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param scanCode The scan code to check. + * @return The key state. + */ + public int getScanCodeState(int deviceId, int sourceMask, int scanCode) { + return nativeGetScanCodeState(mPtr, deviceId, sourceMask, scanCode); + } + + /** + * Gets the current state of a switch by switch code. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param switchCode The switch code to check. + * @return The switch state. + */ + public int getSwitchState(int deviceId, int sourceMask, int switchCode) { + return nativeGetSwitchState(mPtr, deviceId, sourceMask, switchCode); + } + + /** + * Determines whether the specified key codes are supported by a particular device. + * @param deviceId The input device id, or -1 to consult all devices. + * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to + * consider all input sources. An input device is consulted if at least one of its + * non-class input source bits matches the specified source mask. + * @param keyCodes The array of key codes to check. + * @param keyExists An array at least as large as keyCodes whose entries will be set + * to true or false based on the presence or absence of support for the corresponding + * key codes. + * @return True if the lookup was successful, false otherwise. + */ + @Override // Binder call + public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) { + if (keyCodes == null) { + throw new IllegalArgumentException("keyCodes must not be null."); + } + if (keyExists == null || keyExists.length < keyCodes.length) { + throw new IllegalArgumentException("keyExists must not be null and must be at " + + "least as large as keyCodes."); + } + + return nativeHasKeys(mPtr, deviceId, sourceMask, keyCodes, keyExists); + } + + /** + * Creates an input channel that will receive all input from the input dispatcher. + * @param inputChannelName The input channel name. + * @return The input channel. + */ + public InputChannel monitorInput(String inputChannelName) { + if (inputChannelName == null) { + throw new IllegalArgumentException("inputChannelName must not be null."); + } + + InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); + nativeRegisterInputChannel(mPtr, inputChannels[0], null, true); + inputChannels[0].dispose(); // don't need to retain the Java object reference + return inputChannels[1]; + } + + /** + * Registers an input channel so that it can be used as an input event target. + * @param inputChannel The input channel to register. + * @param inputWindowHandle The handle of the input window associated with the + * input channel, or null if none. + */ + public void registerInputChannel(InputChannel inputChannel, + InputWindowHandle inputWindowHandle) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null."); + } + + nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false); + } + + /** + * Unregisters an input channel. + * @param inputChannel The input channel to unregister. + */ + public void unregisterInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null."); + } + + nativeUnregisterInputChannel(mPtr, inputChannel); + } + + /** + * Sets an input filter that will receive all input events before they are dispatched. + * The input filter may then reinterpret input events or inject new ones. + * + * To ensure consistency, the input dispatcher automatically drops all events + * in progress whenever an input filter is installed or uninstalled. After an input + * filter is uninstalled, it can no longer send input events unless it is reinstalled. + * Any events it attempts to send after it has been uninstalled will be dropped. + * + * @param filter The input filter, or null to remove the current filter. + */ + public void setInputFilter(InputFilter filter) { + synchronized (mInputFilterLock) { + final InputFilter oldFilter = mInputFilter; + if (oldFilter == filter) { + return; // nothing to do + } + + if (oldFilter != null) { + mInputFilter = null; + mInputFilterHost.disconnectLocked(); + mInputFilterHost = null; + oldFilter.uninstall(); + } + + if (filter != null) { + mInputFilter = filter; + mInputFilterHost = new InputFilterHost(); + filter.install(mInputFilterHost); + } + + nativeSetInputFilterEnabled(mPtr, filter != null); + } + } + + @Override // Binder call + public boolean injectInputEvent(InputEvent event, int mode) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mode != InputManager.INJECT_INPUT_EVENT_MODE_ASYNC + && mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH + && mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) { + throw new IllegalArgumentException("mode is invalid"); + } + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + final int result; + try { + result = nativeInjectInputEvent(mPtr, event, pid, uid, mode, + INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); + } finally { + Binder.restoreCallingIdentity(ident); + } + switch (result) { + case INPUT_EVENT_INJECTION_PERMISSION_DENIED: + Slog.w(TAG, "Input event injection from pid " + pid + " permission denied."); + throw new SecurityException( + "Injecting to another application requires INJECT_EVENTS permission"); + case INPUT_EVENT_INJECTION_SUCCEEDED: + return true; + case INPUT_EVENT_INJECTION_TIMED_OUT: + Slog.w(TAG, "Input event injection from pid " + pid + " timed out."); + return false; + case INPUT_EVENT_INJECTION_FAILED: + default: + Slog.w(TAG, "Input event injection from pid " + pid + " failed."); + return false; + } + } + + /** + * Gets information about the input device with the specified id. + * @param id The device id. + * @return The input device or null if not found. + */ + @Override // Binder call + public InputDevice getInputDevice(int deviceId) { + synchronized (mInputDevicesLock) { + final int count = mInputDevices.length; + for (int i = 0; i < count; i++) { + final InputDevice inputDevice = mInputDevices[i]; + if (inputDevice.getId() == deviceId) { + return inputDevice; + } + } + } + return null; + } + + /** + * Gets the ids of all input devices in the system. + * @return The input device ids. + */ + @Override // Binder call + public int[] getInputDeviceIds() { + synchronized (mInputDevicesLock) { + final int count = mInputDevices.length; + int[] ids = new int[count]; + for (int i = 0; i < count; i++) { + ids[i] = mInputDevices[i].getId(); + } + return ids; + } + } + + @Override // Binder call + public void registerInputDevicesChangedListener(IInputDevicesChangedListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mInputDevicesLock) { + int callingPid = Binder.getCallingPid(); + if (mInputDevicesChangedListeners.get(callingPid) != null) { + throw new SecurityException("The calling process has already " + + "registered an InputDevicesChangedListener."); + } + + InputDevicesChangedListenerRecord record = + new InputDevicesChangedListenerRecord(callingPid, listener); + try { + IBinder binder = listener.asBinder(); + binder.linkToDeath(record, 0); + } catch (RemoteException ex) { + // give up + throw new RuntimeException(ex); + } + + mInputDevicesChangedListeners.put(callingPid, record); + } + } + + private void onInputDevicesChangedListenerDied(int pid) { + synchronized (mInputDevicesLock) { + mInputDevicesChangedListeners.remove(pid); + } + } + + // Must be called on handler. + private void deliverInputDevicesChanged() { + mTempInputDevicesChangedListenersToNotify.clear(); + + final int numListeners; + final int[] deviceIdAndGeneration; + synchronized (mInputDevicesLock) { + if (!mInputDevicesChangedPending) { + return; + } + mInputDevicesChangedPending = false; + + numListeners = mInputDevicesChangedListeners.size(); + for (int i = 0; i < numListeners; i++) { + mTempInputDevicesChangedListenersToNotify.add( + mInputDevicesChangedListeners.valueAt(i)); + } + + final int numDevices = mInputDevices.length; + deviceIdAndGeneration = new int[numDevices * 2]; + for (int i = 0; i < numDevices; i++) { + final InputDevice inputDevice = mInputDevices[i]; + deviceIdAndGeneration[i * 2] = inputDevice.getId(); + deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration(); + } + } + + for (int i = 0; i < numListeners; i++) { + mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged( + deviceIdAndGeneration); + } + } + + @Override // Binder call + public KeyboardLayout[] getKeyboardLayouts() { + final ArrayList<KeyboardLayout> list = new ArrayList<KeyboardLayout>(); + visitAllKeyboardLayouts(new KeyboardLayoutVisitor() { + @Override + public void visitKeyboardLayout(Resources resources, + String descriptor, String label, int kcmResId) { + list.add(new KeyboardLayout(descriptor, label)); + } + }); + return list.toArray(new KeyboardLayout[list.size()]); + } + + @Override // Binder call + public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) { + if (keyboardLayoutDescriptor == null) { + throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); + } + + final KeyboardLayout[] result = new KeyboardLayout[1]; + visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() { + @Override + public void visitKeyboardLayout(Resources resources, + String descriptor, String label, int kcmResId) { + result[0] = new KeyboardLayout(descriptor, label); + } + }); + if (result[0] == null) { + Log.w(TAG, "Could not get keyboard layout with descriptor '" + + keyboardLayoutDescriptor + "'."); + } + return result[0]; + } + + private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) { + final PackageManager pm = mContext.getPackageManager(); + Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS); + for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent, + PackageManager.GET_META_DATA)) { + visitKeyboardLayoutsInPackage(pm, resolveInfo.activityInfo, null, visitor); + } + } + + private void visitKeyboardLayout(String keyboardLayoutDescriptor, + KeyboardLayoutVisitor visitor) { + KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(keyboardLayoutDescriptor); + if (d != null) { + final PackageManager pm = mContext.getPackageManager(); + try { + ActivityInfo receiver = pm.getReceiverInfo( + new ComponentName(d.packageName, d.receiverName), + PackageManager.GET_META_DATA); + visitKeyboardLayoutsInPackage(pm, receiver, d.keyboardLayoutName, visitor); + } catch (NameNotFoundException ex) { + } + } + } + + private void visitKeyboardLayoutsInPackage(PackageManager pm, ActivityInfo receiver, + String keyboardName, KeyboardLayoutVisitor visitor) { + Bundle metaData = receiver.metaData; + if (metaData == null) { + return; + } + + int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_LAYOUTS); + if (configResId == 0) { + Log.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS + + "' on receiver " + receiver.packageName + "/" + receiver.name); + return; + } + + try { + Resources resources = pm.getResourcesForApplication(receiver.applicationInfo); + XmlResourceParser parser = resources.getXml(configResId); + try { + XmlUtils.beginDocument(parser, "keyboard-layouts"); + + for (;;) { + XmlUtils.nextElement(parser); + String element = parser.getName(); + if (element == null) { + break; + } + if (element.equals("keyboard-layout")) { + TypedArray a = resources.obtainAttributes( + parser, com.android.internal.R.styleable.KeyboardLayout); + try { + String name = a.getString( + com.android.internal.R.styleable.KeyboardLayout_name); + String label = a.getString( + com.android.internal.R.styleable.KeyboardLayout_label); + int kcmResId = a.getResourceId( + com.android.internal.R.styleable.KeyboardLayout_kcm, 0); + if (name == null || label == null || kcmResId == 0) { + Log.w(TAG, "Missing required 'name', 'label' or 'kcm' " + + "attributes in keyboard layout " + + "resource from receiver " + + receiver.packageName + "/" + receiver.name); + } else { + String descriptor = KeyboardLayoutDescriptor.format( + receiver.packageName, receiver.name, name); + if (keyboardName == null || name.equals(keyboardName)) { + visitor.visitKeyboardLayout(resources, descriptor, + label, kcmResId); + } + } + } finally { + a.recycle(); + } + } else { + Log.w(TAG, "Skipping unrecognized element '" + element + + "' in keyboard layout resource from receiver " + + receiver.packageName + "/" + receiver.name); + } + } + } finally { + parser.close(); + } + } catch (Exception ex) { + Log.w(TAG, "Could not parse keyboard layout resource from receiver " + + receiver.packageName + "/" + receiver.name, ex); + } + } + + @Override // Binder call + public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) { + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); + } + + synchronized (mDataStore) { + return mDataStore.getKeyboardLayout(inputDeviceDescriptor); + } + } + + @Override // Binder call + public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor, + String keyboardLayoutDescriptor) { + if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT, + "setKeyboardLayoutForInputDevice()")) { + throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission"); + } + + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); + } + + final boolean changed; + synchronized (mDataStore) { + try { + changed = mDataStore.setKeyboardLayout( + inputDeviceDescriptor, keyboardLayoutDescriptor); + } finally { + mDataStore.saveIfNeeded(); + } + } + + if (changed) { + if (DEBUG) { + Slog.d(TAG, "Keyboard layout changed, reloading keyboard layouts."); + } + reloadKeyboardLayouts(); + } + } + + public void setInputWindows(InputWindowHandle[] windowHandles) { + nativeSetInputWindows(mPtr, windowHandles); + } + + public void setFocusedApplication(InputApplicationHandle application) { + nativeSetFocusedApplication(mPtr, application); + } + + public void setInputDispatchMode(boolean enabled, boolean frozen) { + nativeSetInputDispatchMode(mPtr, enabled, frozen); + } + + public void setSystemUiVisibility(int visibility) { + nativeSetSystemUiVisibility(mPtr, visibility); + } + + /** + * Atomically transfers touch focus from one window to another as identified by + * their input channels. It is possible for multiple windows to have + * touch focus if they support split touch dispatch + * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this + * method only transfers touch focus of the specified window without affecting + * other windows that may also have touch focus at the same time. + * @param fromChannel The channel of a window that currently has touch focus. + * @param toChannel The channel of the window that should receive touch focus in + * place of the first. + * @return True if the transfer was successful. False if the window with the + * specified channel did not actually have touch focus at the time of the request. + */ + public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { + if (fromChannel == null) { + throw new IllegalArgumentException("fromChannel must not be null."); + } + if (toChannel == null) { + throw new IllegalArgumentException("toChannel must not be null."); + } + return nativeTransferTouchFocus(mPtr, fromChannel, toChannel); + } + + @Override // Binder call + public void tryPointerSpeed(int speed) { + if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED, + "tryPointerSpeed()")) { + throw new SecurityException("Requires SET_POINTER_SPEED permission"); + } + + if (speed < InputManager.MIN_POINTER_SPEED || speed > InputManager.MAX_POINTER_SPEED) { + throw new IllegalArgumentException("speed out of range"); + } + + setPointerSpeedUnchecked(speed); + } + + public void updatePointerSpeedFromSettings() { + int speed = getPointerSpeedSetting(); + setPointerSpeedUnchecked(speed); + } + + private void setPointerSpeedUnchecked(int speed) { + speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED), + InputManager.MAX_POINTER_SPEED); + nativeSetPointerSpeed(mPtr, speed); + } + + private void registerPointerSpeedSettingObserver() { + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.POINTER_SPEED), true, + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + updatePointerSpeedFromSettings(); + } + }); + } + + private int getPointerSpeedSetting() { + int speed = InputManager.DEFAULT_POINTER_SPEED; + try { + speed = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.POINTER_SPEED); + } catch (SettingNotFoundException snfe) { + } + return speed; + } + + public void updateShowTouchesFromSettings() { + int setting = getShowTouchesSetting(0); + nativeSetShowTouches(mPtr, setting != 0); + } + + private void registerShowTouchesSettingObserver() { + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true, + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + updateShowTouchesFromSettings(); + } + }); + } + + private int getShowTouchesSetting(int defaultValue) { + int result = defaultValue; + try { + result = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.SHOW_TOUCHES); + } catch (SettingNotFoundException snfe) { + } + return result; + } + + // Binder call + @Override + public void vibrate(int deviceId, long[] pattern, int repeat, IBinder token) { + if (repeat >= pattern.length) { + throw new ArrayIndexOutOfBoundsException(); + } + + VibratorToken v; + synchronized (mVibratorLock) { + v = mVibratorTokens.get(token); + if (v == null) { + v = new VibratorToken(deviceId, token, mNextVibratorTokenValue++); + try { + token.linkToDeath(v, 0); + } catch (RemoteException ex) { + // give up + throw new RuntimeException(ex); + } + mVibratorTokens.put(token, v); + } + } + + synchronized (v) { + v.mVibrating = true; + nativeVibrate(mPtr, deviceId, pattern, repeat, v.mTokenValue); + } + } + + // Binder call + @Override + public void cancelVibrate(int deviceId, IBinder token) { + VibratorToken v; + synchronized (mVibratorLock) { + v = mVibratorTokens.get(token); + if (v == null || v.mDeviceId != deviceId) { + return; // nothing to cancel + } + } + + cancelVibrateIfNeeded(v); + } + + void onVibratorTokenDied(VibratorToken v) { + synchronized (mVibratorLock) { + mVibratorTokens.remove(v.mToken); + } + + cancelVibrateIfNeeded(v); + } + + private void cancelVibrateIfNeeded(VibratorToken v) { + synchronized (v) { + if (v.mVibrating) { + nativeCancelVibrate(mPtr, v.mDeviceId, v.mTokenValue); + v.mVibrating = false; + } + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump InputManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("INPUT MANAGER (dumpsys input)\n"); + String dumpStr = nativeDump(mPtr); + if (dumpStr != null) { + pw.println(dumpStr); + } + } + + private boolean checkCallingPermission(String permission, String func) { + // Quick check: if the calling permission is me, it's all okay. + if (Binder.getCallingPid() == Process.myPid()) { + return true; + } + + if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) { + return true; + } + String msg = "Permission Denial: " + func + " from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + permission; + Slog.w(TAG, msg); + return false; + } + + // Called by the heartbeat to ensure locks are not held indefinitely (for deadlock detection). + public void monitor() { + synchronized (mInputFilterLock) { } + nativeMonitor(mPtr); + } + + // Native callback. + private void notifyConfigurationChanged(long whenNanos) { + mCallbacks.notifyConfigurationChanged(); + } + + // Native callback. + private void notifyInputDevicesChanged(InputDevice[] inputDevices) { + synchronized (mInputDevicesLock) { + mInputDevices = inputDevices; + + if (!mInputDevicesChangedPending) { + mInputDevicesChangedPending = true; + mHandler.sendEmptyMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED); + } + } + } + + // Native callback. + private void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + mCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen); + } + + // Native callback. + private void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { + mCallbacks.notifyInputChannelBroken(inputWindowHandle); + } + + // Native callback. + private long notifyANR(InputApplicationHandle inputApplicationHandle, + InputWindowHandle inputWindowHandle) { + return mCallbacks.notifyANR(inputApplicationHandle, inputWindowHandle); + } + + // Native callback. + final boolean filterInputEvent(InputEvent event, int policyFlags) { + synchronized (mInputFilterLock) { + if (mInputFilter != null) { + mInputFilter.filterInputEvent(event, policyFlags); + return false; + } + } + event.recycle(); + return true; + } + + // Native callback. + private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { + return mCallbacks.interceptKeyBeforeQueueing( + event, policyFlags, isScreenOn); + } + + // Native callback. + private int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags) { + return mCallbacks.interceptMotionBeforeQueueingWhenScreenOff(policyFlags); + } + + // Native callback. + private long interceptKeyBeforeDispatching(InputWindowHandle focus, + KeyEvent event, int policyFlags) { + return mCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags); + } + + // Native callback. + private KeyEvent dispatchUnhandledKey(InputWindowHandle focus, + KeyEvent event, int policyFlags) { + return mCallbacks.dispatchUnhandledKey(focus, event, policyFlags); + } + + // Native callback. + private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) { + return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS, + injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED; + } + + // Native callback. + private int getVirtualKeyQuietTimeMillis() { + return mContext.getResources().getInteger( + com.android.internal.R.integer.config_virtualKeyQuietTimeMillis); + } + + // Native callback. + private String[] getExcludedDeviceNames() { + ArrayList<String> names = new ArrayList<String>(); + + // Read partner-provided list of excluded input devices + XmlPullParser parser = null; + // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". + File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); + FileReader confreader = null; + try { + confreader = new FileReader(confFile); + parser = Xml.newPullParser(); + parser.setInput(confreader); + XmlUtils.beginDocument(parser, "devices"); + + while (true) { + XmlUtils.nextElement(parser); + if (!"device".equals(parser.getName())) { + break; + } + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + names.add(name); + } + } + } catch (FileNotFoundException e) { + // It's ok if the file does not exist. + } catch (Exception e) { + Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); + } finally { + try { if (confreader != null) confreader.close(); } catch (IOException e) { } + } + + return names.toArray(new String[names.size()]); + } + + // Native callback. + private int getKeyRepeatTimeout() { + return ViewConfiguration.getKeyRepeatTimeout(); + } + + // Native callback. + private int getKeyRepeatDelay() { + return ViewConfiguration.getKeyRepeatDelay(); + } + + // Native callback. + private int getHoverTapTimeout() { + return ViewConfiguration.getHoverTapTimeout(); + } + + // Native callback. + private int getHoverTapSlop() { + return ViewConfiguration.getHoverTapSlop(); + } + + // Native callback. + private int getDoubleTapTimeout() { + return ViewConfiguration.getDoubleTapTimeout(); + } + + // Native callback. + private int getLongPressTimeout() { + return ViewConfiguration.getLongPressTimeout(); + } + + // Native callback. + private int getPointerLayer() { + return mCallbacks.getPointerLayer(); + } + + // Native callback. + private PointerIcon getPointerIcon() { + return PointerIcon.getDefaultIcon(mContext); + } + + // Native callback. + private String[] getKeyboardLayoutOverlay(String inputDeviceDescriptor) { + if (!mSystemReady) { + return null; + } + + String keyboardLayoutDescriptor = getKeyboardLayoutForInputDevice(inputDeviceDescriptor); + if (keyboardLayoutDescriptor == null) { + return null; + } + + final String[] result = new String[2]; + visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() { + @Override + public void visitKeyboardLayout(Resources resources, + String descriptor, String label, int kcmResId) { + try { + result[0] = descriptor; + result[1] = Streams.readFully(new InputStreamReader( + resources.openRawResource(kcmResId))); + } catch (IOException ex) { + } catch (NotFoundException ex) { + } + } + }); + if (result[0] == null) { + Log.w(TAG, "Could not get keyboard layout with descriptor '" + + keyboardLayoutDescriptor + "'."); + return null; + } + return result; + } + + // Native callback. + private String getDeviceAlias(String uniqueId) { + if (mBluetoothService != null && + BluetoothAdapter.checkBluetoothAddress(uniqueId)) { + return mBluetoothService.getRemoteAlias(uniqueId); + } + return null; + } + + + /** + * Callback interface implemented by the Window Manager. + */ + public interface Callbacks { + public void notifyConfigurationChanged(); + + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen); + + public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle); + + public long notifyANR(InputApplicationHandle inputApplicationHandle, + InputWindowHandle inputWindowHandle); + + public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn); + + public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags); + + public long interceptKeyBeforeDispatching(InputWindowHandle focus, + KeyEvent event, int policyFlags); + + public KeyEvent dispatchUnhandledKey(InputWindowHandle focus, + KeyEvent event, int policyFlags); + + public int getPointerLayer(); + } + + /** + * Private handler for the input manager. + */ + private final class InputManagerHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DELIVER_INPUT_DEVICES_CHANGED: + deliverInputDevicesChanged(); + break; + } + } + } + + /** + * Hosting interface for input filters to call back into the input manager. + */ + private final class InputFilterHost implements InputFilter.Host { + private boolean mDisconnected; + + public void disconnectLocked() { + mDisconnected = true; + } + + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + + synchronized (mInputFilterLock) { + if (!mDisconnected) { + nativeInjectInputEvent(mPtr, event, 0, 0, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0, + policyFlags | WindowManagerPolicy.FLAG_FILTERED); + } + } + } + } + + private static final class KeyboardLayoutDescriptor { + public String packageName; + public String receiverName; + public String keyboardLayoutName; + + public static String format(String packageName, + String receiverName, String keyboardName) { + return packageName + "/" + receiverName + "/" + keyboardName; + } + + public static KeyboardLayoutDescriptor parse(String descriptor) { + int pos = descriptor.indexOf('/'); + if (pos < 0 || pos + 1 == descriptor.length()) { + return null; + } + int pos2 = descriptor.indexOf('/', pos + 1); + if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) { + return null; + } + + KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor(); + result.packageName = descriptor.substring(0, pos); + result.receiverName = descriptor.substring(pos + 1, pos2); + result.keyboardLayoutName = descriptor.substring(pos2 + 1); + return result; + } + } + + private interface KeyboardLayoutVisitor { + void visitKeyboardLayout(Resources resources, + String descriptor, String label, int kcmResId); + } + + private final class InputDevicesChangedListenerRecord implements DeathRecipient { + private final int mPid; + private final IInputDevicesChangedListener mListener; + + public InputDevicesChangedListenerRecord(int pid, IInputDevicesChangedListener listener) { + mPid = pid; + mListener = listener; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Input devices changed listener for pid " + mPid + " died."); + } + onInputDevicesChangedListenerDied(mPid); + } + + public void notifyInputDevicesChanged(int[] info) { + try { + mListener.onInputDevicesChanged(info); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + + mPid + " that input devices changed, assuming it died.", ex); + binderDied(); + } + } + } + + private final class VibratorToken implements DeathRecipient { + public final int mDeviceId; + public final IBinder mToken; + public final int mTokenValue; + + public boolean mVibrating; + + public VibratorToken(int deviceId, IBinder token, int tokenValue) { + mDeviceId = deviceId; + mToken = token; + mTokenValue = tokenValue; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Vibrator token died."); + } + onVibratorTokenDied(this); + } + } + + /** + * Manages persistent state recorded by the input manager service as an XML file. + * Caller must acquire lock on the data store before accessing it. + * + * File format: + * <code> + * <input-mananger-state> + * <input-devices> + * <input-device descriptor="xxxxx" keyboard-layout="yyyyy" /> + * >input-devices> + * >/input-manager-state> + * </code> + */ + private static final class PersistentDataStore { + // Input device state by descriptor. + private final HashMap<String, InputDeviceState> mInputDevices = + new HashMap<String, InputDeviceState>(); + private final AtomicFile mAtomicFile; + + // True if the data has been loaded. + private boolean mLoaded; + + // True if there are changes to be saved. + private boolean mDirty; + + public PersistentDataStore() { + mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml")); + } + + public void saveIfNeeded() { + if (mDirty) { + save(); + mDirty = false; + } + } + + public String getKeyboardLayout(String inputDeviceDescriptor) { + InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false); + return state != null ? state.keyboardLayoutDescriptor : null; + } + + public boolean setKeyboardLayout(String inputDeviceDescriptor, + String keyboardLayoutDescriptor) { + InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true); + if (!Objects.equal(state.keyboardLayoutDescriptor, keyboardLayoutDescriptor)) { + state.keyboardLayoutDescriptor = keyboardLayoutDescriptor; + setDirty(); + return true; + } + return false; + } + + private InputDeviceState getInputDeviceState(String inputDeviceDescriptor, + boolean createIfAbsent) { + loadIfNeeded(); + InputDeviceState state = mInputDevices.get(inputDeviceDescriptor); + if (state == null && createIfAbsent) { + state = new InputDeviceState(); + mInputDevices.put(inputDeviceDescriptor, state); + setDirty(); + } + return state; + } + + private void loadIfNeeded() { + if (!mLoaded) { + load(); + mLoaded = true; + } + } + + private void setDirty() { + mDirty = true; + } + + private void clearState() { + mInputDevices.clear(); + } + + private void load() { + clearState(); + + final InputStream is; + try { + is = mAtomicFile.openRead(); + } catch (FileNotFoundException ex) { + return; + } + + XmlPullParser parser; + try { + parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream(is), null); + loadFromXml(parser); + } catch (IOException ex) { + Slog.w(TAG, "Failed to load input manager persistent store data.", ex); + clearState(); + } catch (XmlPullParserException ex) { + Slog.w(TAG, "Failed to load input manager persistent store data.", ex); + clearState(); + } finally { + IoUtils.closeQuietly(is); + } + } + + private void save() { + final FileOutputStream os; + try { + os = mAtomicFile.startWrite(); + boolean success = false; + try { + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(new BufferedOutputStream(os), "utf-8"); + saveToXml(serializer); + serializer.flush(); + success = true; + } finally { + if (success) { + mAtomicFile.finishWrite(os); + } else { + mAtomicFile.failWrite(os); + } + } + } catch (IOException ex) { + Slog.w(TAG, "Failed to save input manager persistent store data.", ex); + } + } + + private void loadFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + XmlUtils.beginDocument(parser, "input-manager-state"); + final int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (parser.getName().equals("input-devices")) { + loadInputDevicesFromXml(parser); + } + } + } + + private void loadInputDevicesFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + final int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (parser.getName().equals("input-device")) { + String descriptor = parser.getAttributeValue(null, "descriptor"); + if (descriptor == null) { + throw new XmlPullParserException( + "Missing descriptor attribute on input-device"); + } + InputDeviceState state = new InputDeviceState(); + state.keyboardLayoutDescriptor = + parser.getAttributeValue(null, "keyboard-layout"); + mInputDevices.put(descriptor, state); + } + } + } + + private void saveToXml(XmlSerializer serializer) throws IOException { + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startTag(null, "input-manager-state"); + serializer.startTag(null, "input-devices"); + for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) { + final String descriptor = entry.getKey(); + final InputDeviceState state = entry.getValue(); + serializer.startTag(null, "input-device"); + serializer.attribute(null, "descriptor", descriptor); + if (state.keyboardLayoutDescriptor != null) { + serializer.attribute(null, "keyboard-layout", state.keyboardLayoutDescriptor); + } + serializer.endTag(null, "input-device"); + } + serializer.endTag(null, "input-devices"); + serializer.endTag(null, "input-manager-state"); + serializer.endDocument(); + } + } + + private static final class InputDeviceState { + public String keyboardLayoutDescriptor; + } +} diff --git a/services/java/com/android/server/wm/InputWindowHandle.java b/services/java/com/android/server/input/InputWindowHandle.java index 264877c..03d66af 100644 --- a/services/java/com/android/server/wm/InputWindowHandle.java +++ b/services/java/com/android/server/input/InputWindowHandle.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package com.android.server.wm; +package com.android.server.input; import android.graphics.Region; import android.view.InputChannel; -import android.view.WindowManagerPolicy; /** * Functions as a handle for a window that can receive input. @@ -35,7 +34,7 @@ public final class InputWindowHandle { public final InputApplicationHandle inputApplicationHandle; // The window manager's window state. - public final WindowManagerPolicy.WindowState windowState; + public final Object windowState; // The input channel associated with the window. public InputChannel inputChannel; @@ -91,7 +90,7 @@ public final class InputWindowHandle { private native void nativeDispose(); public InputWindowHandle(InputApplicationHandle inputApplicationHandle, - WindowManagerPolicy.WindowState windowState) { + Object windowState) { this.inputApplicationHandle = inputApplicationHandle; this.windowState = windowState; } diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index 588fa93..65b9627 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -554,8 +554,13 @@ public class GpsLocationProvider implements LocationProviderInterface { long delay; - // GPS requires fresh NTP time - if (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(); diff --git a/services/java/com/android/server/net/NetworkIdentitySet.java b/services/java/com/android/server/net/NetworkIdentitySet.java index af03fb3..397f9f4 100644 --- a/services/java/com/android/server/net/NetworkIdentitySet.java +++ b/services/java/com/android/server/net/NetworkIdentitySet.java @@ -21,7 +21,6 @@ import android.net.NetworkIdentity; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.net.ProtocolException; import java.util.HashSet; /** @@ -33,48 +32,46 @@ import java.util.HashSet; public class NetworkIdentitySet extends HashSet<NetworkIdentity> { private static final int VERSION_INIT = 1; private static final int VERSION_ADD_ROAMING = 2; + private static final int VERSION_ADD_NETWORK_ID = 3; public NetworkIdentitySet() { } public NetworkIdentitySet(DataInputStream in) throws IOException { final int version = in.readInt(); - switch (version) { - case VERSION_INIT: { - final int size = in.readInt(); - for (int i = 0; i < size; i++) { - final int ignoredVersion = in.readInt(); - final int type = in.readInt(); - final int subType = in.readInt(); - final String subscriberId = readOptionalString(in); - add(new NetworkIdentity(type, subType, subscriberId, false)); - } - break; + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + if (version <= VERSION_INIT) { + final int ignored = in.readInt(); } - case VERSION_ADD_ROAMING: { - final int size = in.readInt(); - for (int i = 0; i < size; i++) { - final int type = in.readInt(); - final int subType = in.readInt(); - final String subscriberId = readOptionalString(in); - final boolean roaming = in.readBoolean(); - add(new NetworkIdentity(type, subType, subscriberId, roaming)); - } - break; + final int type = in.readInt(); + final int subType = in.readInt(); + final String subscriberId = readOptionalString(in); + final String networkId; + if (version >= VERSION_ADD_NETWORK_ID) { + networkId = readOptionalString(in); + } else { + networkId = null; } - default: { - throw new ProtocolException("unexpected version: " + version); + final boolean roaming; + if (version >= VERSION_ADD_ROAMING) { + roaming = in.readBoolean(); + } else { + roaming = false; } + + add(new NetworkIdentity(type, subType, subscriberId, networkId, false)); } } public void writeToStream(DataOutputStream out) throws IOException { - out.writeInt(VERSION_ADD_ROAMING); + out.writeInt(VERSION_ADD_NETWORK_ID); out.writeInt(size()); for (NetworkIdentity ident : this) { out.writeInt(ident.getType()); out.writeInt(ident.getSubType()); writeOptionalString(out, ident.getSubscriberId()); + writeOptionalString(out, ident.getNetworkId()); out.writeBoolean(ident.getRoaming()); } } diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index 8c0f1e0..8ebe224 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -30,6 +30,8 @@ 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.isNetworkTypeMobile; +import static android.net.NetworkPolicy.CYCLE_NONE; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; @@ -41,14 +43,24 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.dumpPolicy; import static android.net.NetworkPolicyManager.dumpRules; -import static android.net.NetworkPolicyManager.isUidValidForPolicy; import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER; import static android.net.NetworkTemplate.MATCH_MOBILE_4G; import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.net.wifi.WifiInfo.removeDoubleQuotes; +import static android.net.wifi.WifiManager.CHANGE_REASON_ADDED; +import static android.net.wifi.WifiManager.CHANGE_REASON_REMOVED; +import static android.net.wifi.WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION; +import static android.net.wifi.WifiManager.EXTRA_CHANGE_REASON; +import static android.net.wifi.WifiManager.EXTRA_NETWORK_INFO; +import static android.net.wifi.WifiManager.EXTRA_WIFI_CONFIGURATION; +import static android.net.wifi.WifiManager.EXTRA_WIFI_INFO; +import static android.telephony.TelephonyManager.SIM_STATE_READY; import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static com.android.internal.util.ArrayUtils.appendInt; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.readBooleanAttribute; @@ -73,6 +85,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.IConnectivityManager; @@ -81,10 +94,14 @@ import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.NetworkIdentity; +import android.net.NetworkInfo; import android.net.NetworkPolicy; import android.net.NetworkQuotaInfo; import android.net.NetworkState; import android.net.NetworkTemplate; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Environment; import android.os.Handler; @@ -95,6 +112,7 @@ import android.os.Message; import android.os.MessageQueue.IdleHandler; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.UserId; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.format.Formatter; @@ -111,6 +129,7 @@ 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; import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -153,10 +172,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int VERSION_INIT = 1; private static final int VERSION_ADDED_SNOOZE = 2; private static final int VERSION_ADDED_RESTRICT_BACKGROUND = 3; - - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; + private static final int VERSION_ADDED_METERED = 4; + private static final int VERSION_SPLIT_SNOOZE = 5; + private static final int VERSION_ADDED_TIMEZONE = 6; + 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; // @VisibleForTesting public static final int TYPE_WARNING = 0x1; @@ -166,23 +188,33 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final String TAG_POLICY_LIST = "policy-list"; private static final String TAG_NETWORK_POLICY = "network-policy"; private static final String TAG_UID_POLICY = "uid-policy"; + private static final String TAG_APP_POLICY = "app-policy"; private static final String ATTR_VERSION = "version"; private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground"; private static final String ATTR_NETWORK_TEMPLATE = "networkTemplate"; private static final String ATTR_SUBSCRIBER_ID = "subscriberId"; + private static final String ATTR_NETWORK_ID = "networkId"; private static final String ATTR_CYCLE_DAY = "cycleDay"; + private static final String ATTR_CYCLE_TIMEZONE = "cycleTimezone"; private static final String ATTR_WARNING_BYTES = "warningBytes"; private static final String ATTR_LIMIT_BYTES = "limitBytes"; private static final String ATTR_LAST_SNOOZE = "lastSnooze"; + private static final String ATTR_LAST_WARNING_SNOOZE = "lastWarningSnooze"; + private static final String ATTR_LAST_LIMIT_SNOOZE = "lastLimitSnooze"; + private static final String ATTR_METERED = "metered"; + private static final String ATTR_INFERRED = "inferred"; private static final String ATTR_UID = "uid"; + private static final String ATTR_APP_ID = "appId"; private static final String ATTR_POLICY = "policy"; private static final String TAG_ALLOW_BACKGROUND = TAG + ":allowBackground"; // @VisibleForTesting public static final String ACTION_ALLOW_BACKGROUND = - "com.android.server.action.ACTION_ALLOW_BACKGROUND"; + "com.android.server.net.action.ALLOW_BACKGROUND"; + public static final String ACTION_SNOOZE_WARNING = + "com.android.server.net.action.SNOOZE_WARNING"; private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS; @@ -191,6 +223,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 3; private static final int MSG_PROCESS_DIED = 4; private static final int MSG_LIMIT_REACHED = 5; + private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6; private final Context mContext; private final IActivityManager mActivityManager; @@ -214,8 +247,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** Currently active network rules for ifaces. */ private HashMap<NetworkPolicy, String[]> mNetworkRules = Maps.newHashMap(); - /** Defined UID policies. */ - private SparseIntArray mUidPolicy = new SparseIntArray(); + /** Defined app policies. */ + private SparseIntArray mAppPolicy = new SparseIntArray(); /** Currently derived rules for each UID. */ private SparseIntArray mUidRules = new SparseIntArray(); @@ -331,6 +364,22 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND); mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler); + // listen for snooze warning from notifications + final IntentFilter snoozeWarningFilter = new IntentFilter(ACTION_SNOOZE_WARNING); + mContext.registerReceiver(mSnoozeWarningReceiver, snoozeWarningFilter, + MANAGE_NETWORK_POLICY, mHandler); + + // listen for configured wifi networks to be removed + final IntentFilter wifiConfigFilter = new IntentFilter(CONFIGURED_NETWORKS_CHANGED_ACTION); + mContext.registerReceiver( + mWifiConfigReceiver, wifiConfigFilter, CONNECTIVITY_INTERNAL, mHandler); + + // listen for wifi state changes to catch metered hint + final IntentFilter wifiStateFilter = new IntentFilter( + WifiManager.NETWORK_STATE_CHANGED_ACTION); + mContext.registerReceiver( + mWifiStateReceiver, wifiStateFilter, CONNECTIVITY_INTERNAL, mHandler); + } private IProcessObserver mProcessObserver = new IProcessObserver.Stub() { @@ -365,18 +414,26 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final String action = intent.getAction(); final int uid = intent.getIntExtra(EXTRA_UID, 0); + final int appId = UserId.getAppId(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. + // 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); - updateRulesForUidLocked(uid); + updateRulesForAppLocked(appId); } else if (ACTION_UID_REMOVED.equals(action)) { + // NOTE: UID_REMOVED is currently only sent once, and is not + // broadcast when users are removed. + // remove any policy and update rules to clean up. if (LOGV) Slog.v(TAG, "ACTION_UID_REMOVED for uid=" + uid); - mUidPolicy.delete(uid); - updateRulesForUidLocked(uid); + + mAppPolicy.delete(appId); + updateRulesForAppLocked(appId); writePolicyLocked(); } } @@ -416,6 +473,90 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { }; /** + * Receiver that watches for {@link Notification} control of + * {@link NetworkPolicy#lastWarningSnooze}. + */ + private BroadcastReceiver mSnoozeWarningReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // on background handler thread, and verified MANAGE_NETWORK_POLICY + // permission above. + + final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE); + performSnooze(template, TYPE_WARNING); + } + }; + + /** + * Receiver that watches for {@link WifiConfiguration} to be changed. + */ + private BroadcastReceiver mWifiConfigReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // on background handler thread, and verified CONNECTIVITY_INTERNAL + // permission above. + + final int reason = intent.getIntExtra(EXTRA_CHANGE_REASON, CHANGE_REASON_ADDED); + if (reason == CHANGE_REASON_REMOVED) { + final WifiConfiguration config = intent.getParcelableExtra( + EXTRA_WIFI_CONFIGURATION); + if (config.SSID != null) { + final NetworkTemplate template = NetworkTemplate.buildTemplateWifi( + removeDoubleQuotes(config.SSID)); + synchronized (mRulesLock) { + if (mNetworkPolicy.containsKey(template)) { + mNetworkPolicy.remove(template); + writePolicyLocked(); + } + } + } + } + } + }; + + /** + * Receiver that watches {@link WifiInfo} state changes to infer metered + * state. Ignores hints when policy is user-defined. + */ + private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // on background handler thread, and verified CONNECTIVITY_INTERNAL + // permission above. + + // ignore when not connected + final NetworkInfo netInfo = intent.getParcelableExtra(EXTRA_NETWORK_INFO); + if (!netInfo.isConnected()) return; + + final WifiInfo info = intent.getParcelableExtra(EXTRA_WIFI_INFO); + final boolean meteredHint = info.getMeteredHint(); + + final NetworkTemplate template = NetworkTemplate.buildTemplateWifi( + removeDoubleQuotes(info.getSSID())); + synchronized (mRulesLock) { + NetworkPolicy policy = mNetworkPolicy.get(template); + if (policy == null && meteredHint) { + // policy doesn't exist, and AP is hinting that it's + // metered: create an inferred policy. + policy = new NetworkPolicy(template, CYCLE_NONE, Time.TIMEZONE_UTC, + WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, + meteredHint, true); + addNetworkPolicyLocked(policy); + + } else if (policy != null && policy.inferred) { + // policy exists, and was inferred: update its current + // metered state. + policy.metered = meteredHint; + + // since this is inferred for each wifi session, just update + // rules without persisting. + updateNetworkRulesLocked(); + } + } + } + }; + + /** * Observer that watches for {@link INetworkManagementService} alerts. */ private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() { @@ -450,13 +591,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (NetworkPolicy policy : mNetworkPolicy.values()) { // ignore policies that aren't relevant to user if (!isTemplateRelevant(policy.template)) continue; + if (!policy.hasCycle()) continue; final long start = computeLastCycleBoundary(currentTime, policy); final long end = currentTime; final long totalBytes = getTotalBytes(policy.template, start, end); if (policy.isOverLimit(totalBytes)) { - if (policy.lastSnooze >= start) { + if (policy.lastLimitSnooze >= start) { enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes); } else { enqueueNotification(policy, TYPE_LIMIT, totalBytes); @@ -466,7 +608,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else { notifyUnderLimitLocked(policy.template); - if (policy.warningBytes != WARNING_DISABLED && totalBytes >= policy.warningBytes) { + if (policy.isOverWarning(totalBytes) && policy.lastWarningSnooze < start) { enqueueNotification(policy, TYPE_WARNING, totalBytes); } } @@ -487,16 +629,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** * Test if given {@link NetworkTemplate} is relevant to user based on - * current device state, such as when {@link #getActiveSubscriberId()} - * matches. This is regardless of data connection status. + * current device state, such as when + * {@link TelephonyManager#getSubscriberId()} matches. This is regardless of + * data connection status. */ private boolean isTemplateRelevant(NetworkTemplate template) { + final TelephonyManager tele = TelephonyManager.from(mContext); + switch (template.getMatchRule()) { case MATCH_MOBILE_3G_LOWER: case MATCH_MOBILE_4G: case MATCH_MOBILE_ALL: - // mobile templates are relevant when subscriberid is active - return Objects.equal(getActiveSubscriberId(), template.getSubscriberId()); + // mobile templates are relevant when SIM is ready and + // subscriberId matches. + if (tele.getSimState() == SIM_STATE_READY) { + return Objects.equal(tele.getSubscriberId(), template.getSubscriberId()); + } else { + return false; + } } return true; } @@ -532,7 +682,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final String tag = buildNotificationTag(policy, type); final Notification.Builder builder = new Notification.Builder(mContext); builder.setOnlyAlertOnce(true); - builder.setOngoing(true); + builder.setWhen(0L); final Resources res = mContext.getResources(); switch (type) { @@ -545,9 +695,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { builder.setContentTitle(title); builder.setContentText(body); - final Intent intent = buildViewDataUsageIntent(policy.template); + final Intent snoozeIntent = buildSnoozeWarningIntent(policy.template); + builder.setDeleteIntent(PendingIntent.getBroadcast( + mContext, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + + final Intent viewIntent = buildViewDataUsageIntent(policy.template); builder.setContentIntent(PendingIntent.getActivity( - mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + break; } case TYPE_LIMIT: { @@ -572,6 +727,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { break; } + builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_disabled); builder.setTicker(title); builder.setContentTitle(title); @@ -606,6 +762,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { break; } + builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_error); builder.setTicker(title); builder.setContentTitle(title); @@ -708,7 +865,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long currentTime = currentTimeMillis(); for (NetworkPolicy policy : mNetworkPolicy.values()) { // shortcut when policy has no limit - if (policy.limitBytes == LIMIT_DISABLED) { + if (policy.limitBytes == LIMIT_DISABLED || !policy.hasCycle()) { setNetworkTemplateEnabled(policy.template, true); continue; } @@ -718,10 +875,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long totalBytes = getTotalBytes(policy.template, start, end); // disable data connection when over limit and not snoozed - final boolean overLimit = policy.isOverLimit(totalBytes) && policy.lastSnooze < start; - final boolean enabled = !overLimit; + final boolean overLimitWithoutSnooze = policy.isOverLimit(totalBytes) + && policy.lastLimitSnooze < start; + final boolean networkEnabled = !overLimitWithoutSnooze; - setNetworkTemplateEnabled(policy.template, enabled); + setNetworkTemplateEnabled(policy.template, networkEnabled); } } @@ -730,13 +888,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * for the given {@link NetworkTemplate}. */ private void setNetworkTemplateEnabled(NetworkTemplate template, boolean enabled) { + final TelephonyManager tele = TelephonyManager.from(mContext); + switch (template.getMatchRule()) { case MATCH_MOBILE_3G_LOWER: case MATCH_MOBILE_4G: case MATCH_MOBILE_ALL: // TODO: offer more granular control over radio states once // 4965893 is available. - if (Objects.equal(getActiveSubscriberId(), template.getSubscriberId())) { + if (tele.getSimState() == SIM_STATE_READY + && Objects.equal(tele.getSubscriberId(), template.getSubscriberId())) { setPolicyDataEnable(TYPE_MOBILE, enabled); setPolicyDataEnable(TYPE_WIMAX, enabled); } @@ -809,9 +970,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (NetworkPolicy policy : mNetworkRules.keySet()) { final String[] ifaces = mNetworkRules.get(policy); - final long start = computeLastCycleBoundary(currentTime, policy); - final long end = currentTime; - final long totalBytes = getTotalBytes(policy.template, start, end); + final long start; + final long totalBytes; + if (policy.hasCycle()) { + start = computeLastCycleBoundary(currentTime, policy); + totalBytes = getTotalBytes(policy.template, start, currentTime); + } else { + start = Long.MAX_VALUE; + totalBytes = 0; + } if (LOGD) { Slog.d(TAG, "applying policy " + policy.toString() + " to ifaces " @@ -819,9 +986,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED; - if (hasLimit) { + if (hasLimit || policy.metered) { final long quotaBytes; - if (policy.lastSnooze >= start) { + if (!hasLimit) { + // metered network, but no policy limit; we still need to + // restrict apps, so push really high quota. + quotaBytes = Long.MAX_VALUE; + } else if (policy.lastLimitSnooze >= start) { // snoozing past quota, but we still need to restrict apps, // so push really high quota. quotaBytes = Long.MAX_VALUE; @@ -865,9 +1036,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyLocked()"); if (mSuppressDefaultPolicy) return; - final String subscriberId = getActiveSubscriberId(); + final TelephonyManager tele = TelephonyManager.from(mContext); + + // avoid creating policy when SIM isn't ready + if (tele.getSimState() != SIM_STATE_READY) return; + + final String subscriberId = tele.getSubscriberId(); final NetworkIdentity probeIdent = new NetworkIdentity( - TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, false); + TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false); // examine to see if any policy is defined for active mobile boolean mobileDefined = false; @@ -885,14 +1061,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { com.android.internal.R.integer.config_networkPolicyDefaultWarning) * MB_IN_BYTES; - final Time time = new Time(Time.TIMEZONE_UTC); + final Time time = new Time(); time.setToNow(); + final int cycleDay = time.monthDay; + final String cycleTimezone = time.timezone; final NetworkTemplate template = buildTemplateMobileAll(subscriberId); - mNetworkPolicy.put(template, new NetworkPolicy( - template, cycleDay, warningBytes, LIMIT_DISABLED, SNOOZE_NEVER)); - writePolicyLocked(); + final NetworkPolicy policy = new NetworkPolicy(template, cycleDay, cycleTimezone, + warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, true); + addNetworkPolicyLocked(policy); } } @@ -901,7 +1079,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // clear any existing policy and read from disk mNetworkPolicy.clear(); - mUidPolicy.clear(); + mAppPolicy.clear(); FileInputStream fis = null; try { @@ -926,30 +1104,81 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else if (TAG_NETWORK_POLICY.equals(tag)) { final int networkTemplate = readIntAttribute(in, ATTR_NETWORK_TEMPLATE); final String subscriberId = in.getAttributeValue(null, ATTR_SUBSCRIBER_ID); + final String networkId; + if (version >= VERSION_ADDED_NETWORK_ID) { + networkId = in.getAttributeValue(null, ATTR_NETWORK_ID); + } else { + networkId = null; + } final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY); + final String cycleTimezone; + if (version >= VERSION_ADDED_TIMEZONE) { + cycleTimezone = in.getAttributeValue(null, ATTR_CYCLE_TIMEZONE); + } else { + cycleTimezone = Time.TIMEZONE_UTC; + } final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES); final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES); - final long lastSnooze; - if (version >= VERSION_ADDED_SNOOZE) { - lastSnooze = readLongAttribute(in, ATTR_LAST_SNOOZE); + final long lastLimitSnooze; + if (version >= VERSION_SPLIT_SNOOZE) { + lastLimitSnooze = readLongAttribute(in, ATTR_LAST_LIMIT_SNOOZE); + } else if (version >= VERSION_ADDED_SNOOZE) { + lastLimitSnooze = readLongAttribute(in, ATTR_LAST_SNOOZE); + } else { + lastLimitSnooze = SNOOZE_NEVER; + } + final boolean metered; + if (version >= VERSION_ADDED_METERED) { + metered = readBooleanAttribute(in, ATTR_METERED); + } else { + switch (networkTemplate) { + case MATCH_MOBILE_3G_LOWER: + case MATCH_MOBILE_4G: + case MATCH_MOBILE_ALL: + metered = true; + break; + default: + metered = false; + } + } + final long lastWarningSnooze; + if (version >= VERSION_SPLIT_SNOOZE) { + lastWarningSnooze = readLongAttribute(in, ATTR_LAST_WARNING_SNOOZE); + } else { + lastWarningSnooze = SNOOZE_NEVER; + } + final boolean inferred; + if (version >= VERSION_ADDED_INFERRED) { + inferred = readBooleanAttribute(in, ATTR_INFERRED); } else { - lastSnooze = SNOOZE_NEVER; + inferred = false; } final NetworkTemplate template = new NetworkTemplate( - networkTemplate, subscriberId); - mNetworkPolicy.put(template, new NetworkPolicy( - template, cycleDay, warningBytes, limitBytes, lastSnooze)); + networkTemplate, subscriberId, networkId); + mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay, + cycleTimezone, warningBytes, limitBytes, lastWarningSnooze, + lastLimitSnooze, metered, inferred)); - } else if (TAG_UID_POLICY.equals(tag)) { + } else if (TAG_UID_POLICY.equals(tag) && version < VERSION_SWITCH_APP_ID) { final int uid = readIntAttribute(in, ATTR_UID); final int policy = readIntAttribute(in, ATTR_POLICY); - if (isUidValidForPolicy(mContext, uid)) { - setUidPolicyUnchecked(uid, policy, false); + final int appId = UserId.getAppId(uid); + if (UserId.isApp(appId)) { + setAppPolicyUnchecked(appId, 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) { + final int appId = readIntAttribute(in, ATTR_APP_ID); + final int policy = readIntAttribute(in, ATTR_POLICY); + + if (UserId.isApp(appId)) { + setAppPolicyUnchecked(appId, policy, false); + } else { + Slog.w(TAG, "unable to apply policy to appId " + appId + "; ignoring"); + } } } } @@ -994,7 +1223,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { out.startDocument(null, true); out.startTag(null, TAG_POLICY_LIST); - writeIntAttribute(out, ATTR_VERSION, VERSION_ADDED_RESTRICT_BACKGROUND); + writeIntAttribute(out, ATTR_VERSION, VERSION_LATEST); writeBooleanAttribute(out, ATTR_RESTRICT_BACKGROUND, mRestrictBackground); // write all known network policies @@ -1007,25 +1236,33 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (subscriberId != null) { out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId); } + final String networkId = template.getNetworkId(); + if (networkId != null) { + out.attribute(null, ATTR_NETWORK_ID, networkId); + } writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay); + out.attribute(null, ATTR_CYCLE_TIMEZONE, policy.cycleTimezone); writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes); writeLongAttribute(out, ATTR_LIMIT_BYTES, policy.limitBytes); - writeLongAttribute(out, ATTR_LAST_SNOOZE, policy.lastSnooze); + writeLongAttribute(out, ATTR_LAST_WARNING_SNOOZE, policy.lastWarningSnooze); + writeLongAttribute(out, ATTR_LAST_LIMIT_SNOOZE, policy.lastLimitSnooze); + writeBooleanAttribute(out, ATTR_METERED, policy.metered); + writeBooleanAttribute(out, ATTR_INFERRED, policy.inferred); out.endTag(null, TAG_NETWORK_POLICY); } // write all known uid policies - for (int i = 0; i < mUidPolicy.size(); i++) { - final int uid = mUidPolicy.keyAt(i); - final int policy = mUidPolicy.valueAt(i); + for (int i = 0; i < mAppPolicy.size(); i++) { + final int appId = mAppPolicy.keyAt(i); + final int policy = mAppPolicy.valueAt(i); // skip writing empty policies if (policy == POLICY_NONE) continue; - out.startTag(null, TAG_UID_POLICY); - writeIntAttribute(out, ATTR_UID, uid); + out.startTag(null, TAG_APP_POLICY); + writeIntAttribute(out, ATTR_APP_ID, appId); writeIntAttribute(out, ATTR_POLICY, policy); - out.endTag(null, TAG_UID_POLICY); + out.endTag(null, TAG_APP_POLICY); } out.endTag(null, TAG_POLICY_LIST); @@ -1040,24 +1277,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void setUidPolicy(int uid, int policy) { + public void setAppPolicy(int appId, int policy) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - if (!isUidValidForPolicy(mContext, uid)) { - throw new IllegalArgumentException("cannot apply policy to UID " + uid); + if (!UserId.isApp(appId)) { + throw new IllegalArgumentException("cannot apply policy to appId " + appId); } - setUidPolicyUnchecked(uid, policy, true); + setAppPolicyUnchecked(appId, policy, true); } - private void setUidPolicyUnchecked(int uid, int policy, boolean persist) { + private void setAppPolicyUnchecked(int appId, int policy, boolean persist) { final int oldPolicy; synchronized (mRulesLock) { - oldPolicy = getUidPolicy(uid); - mUidPolicy.put(uid, policy); + oldPolicy = getAppPolicy(appId); + mAppPolicy.put(appId, policy); // uid policy changed, recompute rules and persist policy. - updateRulesForUidLocked(uid); + updateRulesForAppLocked(appId); if (persist) { writePolicyLocked(); } @@ -1065,15 +1302,32 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public int getUidPolicy(int uid) { + public int getAppPolicy(int appId) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); synchronized (mRulesLock) { - return mUidPolicy.get(uid, POLICY_NONE); + return mAppPolicy.get(appId, POLICY_NONE); } } @Override + public int[] getAppsWithPolicy(int policy) { + mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + + int[] appIds = 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); + } + } + } + return appIds; + } + + @Override public void registerListener(INetworkPolicyListener listener) { // TODO: create permission for observing network policy mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); @@ -1109,6 +1363,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + private void addNetworkPolicyLocked(NetworkPolicy policy) { + mNetworkPolicy.put(policy.template, policy); + + updateNetworkEnabledLocked(); + updateNetworkRulesLocked(); + updateNotificationsLocked(); + writePolicyLocked(); + } + @Override public NetworkPolicy[] getNetworkPolicies() { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); @@ -1120,9 +1383,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void snoozePolicy(NetworkTemplate template) { + public void snoozeLimit(NetworkTemplate template) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + performSnooze(template, TYPE_LIMIT); + } + private void performSnooze(NetworkTemplate template, int type) { maybeRefreshTrustedTime(); final long currentTime = currentTimeMillis(); synchronized (mRulesLock) { @@ -1132,7 +1398,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { throw new IllegalArgumentException("unable to find policy for " + template); } - policy.lastSnooze = currentTime; + switch (type) { + case TYPE_WARNING: + policy.lastWarningSnooze = currentTime; + break; + case TYPE_LIMIT: + policy.lastLimitSnooze = currentTime; + break; + default: + throw new IllegalArgumentException("unexpected type"); + } updateNetworkEnabledLocked(); updateNetworkRulesLocked(); @@ -1152,6 +1427,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { updateNotificationsLocked(); writePolicyLocked(); } + + mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED, restrictBackground ? 1 : 0, 0) + .sendToTarget(); } @Override @@ -1194,7 +1472,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { policy = findPolicyForNetworkLocked(ident); } - if (policy == null) { + if (policy == null || !policy.hasCycle()) { // missing policy means we can't derive useful quota info return null; } @@ -1216,51 +1494,90 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + public boolean isNetworkMetered(NetworkState state) { + final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state); + + // roaming networks are always considered metered + if (ident.getRoaming()) { + return true; + } + + final NetworkPolicy policy; + synchronized (mRulesLock) { + policy = findPolicyForNetworkLocked(ident); + } + + if (policy != null) { + return policy.metered; + } else { + final int type = state.networkInfo.getType(); + if (isNetworkTypeMobile(type) || type == TYPE_WIMAX) { + return true; + } + return false; + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); + final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); + final HashSet<String> argSet = new HashSet<String>(); for (String arg : args) { argSet.add(arg); } synchronized (mRulesLock) { - if (argSet.contains("unsnooze")) { + if (argSet.contains("--unsnooze")) { for (NetworkPolicy policy : mNetworkPolicy.values()) { - policy.lastSnooze = SNOOZE_NEVER; + policy.clearSnooze(); } + + updateNetworkEnabledLocked(); + updateNetworkRulesLocked(); + updateNotificationsLocked(); writePolicyLocked(); - fout.println("Wiped snooze timestamps"); + + fout.println("Cleared snooze timestamps"); return; } fout.print("Restrict background: "); fout.println(mRestrictBackground); fout.println("Network policies:"); + fout.increaseIndent(); for (NetworkPolicy policy : mNetworkPolicy.values()) { - fout.print(" "); fout.println(policy.toString()); + fout.println(policy.toString()); } + fout.decreaseIndent(); - fout.println("Policy status for known UIDs:"); + fout.println("Policy for apps:"); + fout.increaseIndent(); + int size = mAppPolicy.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); + fout.print(" policy="); + dumpPolicy(fout, policy); + fout.println(); + } + fout.decreaseIndent(); final SparseBooleanArray knownUids = new SparseBooleanArray(); - collectKeys(mUidPolicy, knownUids); collectKeys(mUidForeground, knownUids); collectKeys(mUidRules, knownUids); - final int size = knownUids.size(); + fout.println("Status for known UIDs:"); + fout.increaseIndent(); + size = knownUids.size(); for (int i = 0; i < size; i++) { final int uid = knownUids.keyAt(i); - fout.print(" UID="); + fout.print("UID="); fout.print(uid); - fout.print(" policy="); - final int policyIndex = mUidPolicy.indexOfKey(uid); - if (policyIndex < 0) { - fout.print("UNKNOWN"); - } else { - dumpPolicy(fout, mUidPolicy.valueAt(policyIndex)); - } - fout.print(" foreground="); final int foregroundIndex = mUidPidForeground.indexOfKey(uid); if (foregroundIndex < 0) { @@ -1279,6 +1596,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.println(); } + fout.decreaseIndent(); } } @@ -1350,32 +1668,42 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final PackageManager pm = mContext.getPackageManager(); final List<ApplicationInfo> apps = pm.getInstalledApplications(0); for (ApplicationInfo app : apps) { - updateRulesForUidLocked(app.uid); + final int appId = UserId.getAppId(app.uid); + updateRulesForAppLocked(appId); } - // and catch system UIDs - // TODO: keep in sync with android_filesystem_config.h - for (int uid = 1000; uid <= 1025; uid++) { - updateRulesForUidLocked(uid); - } - for (int uid = 2000; uid <= 2002; uid++) { - updateRulesForUidLocked(uid); - } - for (int uid = 3000; uid <= 3007; uid++) { + // limit data usage for some internal system services + updateRulesForUidLocked(android.os.Process.MEDIA_UID); + 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); } - for (int uid = 9998; uid <= 9999; uid++) { - 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)) { + return true; } + + return false; } private void updateRulesForUidLocked(int uid) { - final int uidPolicy = getUidPolicy(uid); + if (!isUidValidForRules(uid)) return; + + final int appId = UserId.getAppId(uid); + final int appPolicy = getAppPolicy(appId); final boolean uidForeground = isUidForeground(uid); // derive active rules based on policy and active state int uidRules = RULE_ALLOW_ALL; - if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) { + if (!uidForeground && (appPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) { // uid in background, and policy says to block metered data uidRules = RULE_REJECT_METERED; } @@ -1407,7 +1735,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } private Handler.Callback mHandlerCallback = new Handler.Callback() { - /** {@inheritDoc} */ + @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_RULES_CHANGED: { @@ -1495,6 +1823,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } return true; } + case MSG_RESTRICT_BACKGROUND_CHANGED: { + final boolean restrictBackground = msg.arg1 != 0; + final int length = mListeners.beginBroadcast(); + for (int i = 0; i < length; i++) { + final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); + if (listener != null) { + try { + listener.onRestrictBackgroundChanged(restrictBackground); + } catch (RemoteException e) { + } + } + } + mListeners.finishBroadcast(); + } default: { return false; } @@ -1543,15 +1885,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private String getActiveSubscriberId() { - final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService( - Context.TELEPHONY_SERVICE); - return telephony.getSubscriberId(); - } - private long getTotalBytes(NetworkTemplate template, long start, long end) { try { - return mNetworkStats.getSummaryForNetwork(template, start, end).getTotalBytes(); + return mNetworkStats.getNetworkTotalBytes(template, start, end); + } catch (RuntimeException e) { + Slog.w(TAG, "problem reading network stats: " + e); + return 0; } catch (RemoteException e) { // ignored; service lives in system_server return 0; @@ -1575,6 +1914,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return new Intent(ACTION_ALLOW_BACKGROUND); } + private static Intent buildSnoozeWarningIntent(NetworkTemplate template) { + final Intent intent = new Intent(ACTION_SNOOZE_WARNING); + intent.putExtra(EXTRA_NETWORK_TEMPLATE, template); + return intent; + } + private static Intent buildNetworkOverLimitIntent(NetworkTemplate template) { final Intent intent = new Intent(); intent.setComponent(new ComponentName( diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java new file mode 100644 index 0000000..2892a74 --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsCollection.java @@ -0,0 +1,508 @@ +/* + * 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.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.TrafficStats.UID_REMOVED; + +import android.net.NetworkIdentity; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.text.format.DateUtils; + +import com.android.internal.os.AtomicFile; +import com.android.internal.util.FileRotator; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Objects; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.ProtocolException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import libcore.io.IoUtils; + +/** + * Collection of {@link NetworkStatsHistory}, stored based on combined key of + * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. + */ +public class NetworkStatsCollection implements FileRotator.Reader { + /** File header magic number: "ANET" */ + private static final int FILE_MAGIC = 0x414E4554; + + private static final int VERSION_NETWORK_INIT = 1; + + private static final int VERSION_UID_INIT = 1; + private static final int VERSION_UID_WITH_IDENT = 2; + private static final int VERSION_UID_WITH_TAG = 3; + private static final int VERSION_UID_WITH_SET = 4; + + private static final int VERSION_UNIFIED_INIT = 16; + + private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap(); + + private long mBucketDuration; + + private long mStartMillis; + private long mEndMillis; + private long mTotalBytes; + private boolean mDirty; + + public NetworkStatsCollection(long bucketDuration) { + mBucketDuration = bucketDuration; + reset(); + } + + public void reset() { + mStats.clear(); + mStartMillis = Long.MAX_VALUE; + mEndMillis = Long.MIN_VALUE; + mTotalBytes = 0; + mDirty = false; + } + + public long getStartMillis() { + return mStartMillis; + } + + public long getEndMillis() { + return mEndMillis; + } + + public long getTotalBytes() { + return mTotalBytes; + } + + public boolean isDirty() { + return mDirty; + } + + public void clearDirty() { + mDirty = false; + } + + public boolean isEmpty() { + return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; + } + + /** + * Combine all {@link NetworkStatsHistory} in this collection which match + * the requested parameters. + */ + public NetworkStatsHistory getHistory( + NetworkTemplate template, int uid, int set, int tag, int fields) { + final NetworkStatsHistory combined = new NetworkStatsHistory( + mBucketDuration, estimateBuckets(), fields); + for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) { + final Key key = entry.getKey(); + final boolean setMatches = set == SET_ALL || key.set == set; + if (key.uid == uid && setMatches && key.tag == tag + && templateMatches(template, key.ident)) { + combined.recordEntireHistory(entry.getValue()); + } + } + return combined; + } + + /** + * Summarize all {@link NetworkStatsHistory} in this collection which match + * the requested parameters. + */ + public NetworkStats getSummary(NetworkTemplate template, long start, long end) { + final long now = System.currentTimeMillis(); + + final NetworkStats stats = new NetworkStats(end - start, 24); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + NetworkStatsHistory.Entry historyEntry = null; + + for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) { + final Key key = mapEntry.getKey(); + if (templateMatches(template, key.ident)) { + final NetworkStatsHistory history = mapEntry.getValue(); + historyEntry = history.getValues(start, end, now, historyEntry); + + entry.iface = IFACE_ALL; + entry.uid = key.uid; + entry.set = key.set; + entry.tag = key.tag; + entry.rxBytes = historyEntry.rxBytes; + entry.rxPackets = historyEntry.rxPackets; + entry.txBytes = historyEntry.txBytes; + entry.txPackets = historyEntry.txPackets; + entry.operations = historyEntry.operations; + + if (!entry.isEmpty()) { + stats.combineValues(entry); + } + } + } + + return stats; + } + + /** + * Record given {@link android.net.NetworkStats.Entry} into this collection. + */ + public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, + long end, NetworkStats.Entry entry) { + noteRecordedHistory(start, end, entry.rxBytes + entry.txBytes); + findOrCreateHistory(ident, uid, set, tag).recordData(start, end, entry); + } + + /** + * Record given {@link NetworkStatsHistory} into this collection. + */ + private void recordHistory(Key key, NetworkStatsHistory history) { + if (history.size() == 0) return; + noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes()); + + final NetworkStatsHistory existing = mStats.get(key); + if (existing != null) { + existing.recordEntireHistory(history); + } else { + mStats.put(key, history); + } + } + + /** + * Record all {@link NetworkStatsHistory} contained in the given collection + * into this collection. + */ + public void recordCollection(NetworkStatsCollection another) { + for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) { + recordHistory(entry.getKey(), entry.getValue()); + } + } + + private NetworkStatsHistory findOrCreateHistory( + NetworkIdentitySet ident, int uid, int set, int tag) { + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory existing = mStats.get(key); + + // update when no existing, or when bucket duration changed + NetworkStatsHistory updated = null; + if (existing == null) { + updated = new NetworkStatsHistory(mBucketDuration, 10); + } else if (existing.getBucketDuration() != mBucketDuration) { + updated = new NetworkStatsHistory(existing, mBucketDuration); + } + + if (updated != null) { + mStats.put(key, updated); + return updated; + } else { + return existing; + } + } + + @Override + public void read(InputStream in) throws IOException { + read(new DataInputStream(in)); + } + + public void read(DataInputStream in) throws IOException { + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_UNIFIED_INIT: { + // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) + final int identSize = in.readInt(); + for (int i = 0; i < identSize; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + + final int size = in.readInt(); + for (int j = 0; j < size; j++) { + final int uid = in.readInt(); + final int set = in.readInt(); + final int tag = in.readInt(); + + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + recordHistory(key, history); + } + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } + + public void write(DataOutputStream out) throws IOException { + // cluster key lists grouped by ident + final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); + for (Key key : mStats.keySet()) { + ArrayList<Key> keys = keysByIdent.get(key.ident); + if (keys == null) { + keys = Lists.newArrayList(); + keysByIdent.put(key.ident, keys); + } + keys.add(key); + } + + out.writeInt(FILE_MAGIC); + out.writeInt(VERSION_UNIFIED_INIT); + + out.writeInt(keysByIdent.size()); + for (NetworkIdentitySet ident : keysByIdent.keySet()) { + final ArrayList<Key> keys = keysByIdent.get(ident); + ident.writeToStream(out); + + out.writeInt(keys.size()); + for (Key key : keys) { + final NetworkStatsHistory history = mStats.get(key); + out.writeInt(key.uid); + out.writeInt(key.set); + out.writeInt(key.tag); + history.writeToStream(out); + } + } + + out.flush(); + } + + @Deprecated + public void readLegacyNetwork(File file) throws IOException { + final AtomicFile inputFile = new AtomicFile(file); + + DataInputStream in = null; + try { + in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); + + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_NETWORK_INIT: { + // network := size *(NetworkIdentitySet NetworkStatsHistory) + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + + final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE); + recordHistory(key, history); + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } catch (FileNotFoundException e) { + // missing stats is okay, probably first boot + } finally { + IoUtils.closeQuietly(in); + } + } + + @Deprecated + public void readLegacyUid(File file, boolean onlyTags) throws IOException { + final AtomicFile inputFile = new AtomicFile(file); + + DataInputStream in = null; + try { + in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); + + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_UID_INIT: { + // uid := size *(UID NetworkStatsHistory) + + // drop this data version, since we don't have a good + // mapping into NetworkIdentitySet. + break; + } + case VERSION_UID_WITH_IDENT: { + // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) + + // drop this data version, since this version only existed + // for a short time. + break; + } + case VERSION_UID_WITH_TAG: + case VERSION_UID_WITH_SET: { + // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) + final int identSize = in.readInt(); + for (int i = 0; i < identSize; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + + final int size = in.readInt(); + for (int j = 0; j < size; j++) { + final int uid = in.readInt(); + final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() + : SET_DEFAULT; + final int tag = in.readInt(); + + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + + if ((tag == TAG_NONE) != onlyTags) { + recordHistory(key, history); + } + } + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } catch (FileNotFoundException e) { + // missing stats is okay, probably first boot + } finally { + IoUtils.closeQuietly(in); + } + } + + /** + * Remove any {@link NetworkStatsHistory} attributed to the requested UID, + * moving any {@link NetworkStats#TAG_NONE} series to + * {@link TrafficStats#UID_REMOVED}. + */ + public void removeUid(int uid) { + final ArrayList<Key> knownKeys = Lists.newArrayList(); + knownKeys.addAll(mStats.keySet()); + + // migrate all UID stats into special "removed" bucket + for (Key key : knownKeys) { + if (key.uid == uid) { + // only migrate combined TAG_NONE history + if (key.tag == TAG_NONE) { + final NetworkStatsHistory uidHistory = mStats.get(key); + final NetworkStatsHistory removedHistory = findOrCreateHistory( + key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); + removedHistory.recordEntireHistory(uidHistory); + } + mStats.remove(key); + mDirty = true; + } + } + } + + private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) { + if (startMillis < mStartMillis) mStartMillis = startMillis; + if (endMillis > mEndMillis) mEndMillis = endMillis; + mTotalBytes += totalBytes; + mDirty = true; + } + + private int estimateBuckets() { + return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5) + / mBucketDuration); + } + + public void dump(IndentingPrintWriter pw) { + final ArrayList<Key> keys = Lists.newArrayList(); + keys.addAll(mStats.keySet()); + Collections.sort(keys); + + for (Key key : keys) { + pw.print("ident="); pw.print(key.ident.toString()); + pw.print(" uid="); pw.print(key.uid); + pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); + pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); + + final NetworkStatsHistory history = mStats.get(key); + pw.increaseIndent(); + history.dump(pw, true); + pw.decreaseIndent(); + } + } + + /** + * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} + * in the given {@link NetworkIdentitySet}. + */ + private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) { + for (NetworkIdentity ident : identSet) { + if (template.matches(ident)) { + return true; + } + } + return false; + } + + private static class Key implements Comparable<Key> { + public final NetworkIdentitySet ident; + public final int uid; + public final int set; + public final int tag; + + private final int hashCode; + + public Key(NetworkIdentitySet ident, int uid, int set, int tag) { + this.ident = ident; + this.uid = uid; + this.set = set; + this.tag = tag; + hashCode = Objects.hashCode(ident, uid, set, tag); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Key) { + final Key key = (Key) obj; + return uid == key.uid && set == key.set && tag == key.tag + && Objects.equal(ident, key.ident); + } + return false; + } + + @Override + public int compareTo(Key another) { + return Integer.compare(uid, another.uid); + } + } +} diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java new file mode 100644 index 0000000..57ad158 --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsRecorder.java @@ -0,0 +1,347 @@ +/* + * 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.net.NetworkStats.TAG_NONE; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.net.NetworkStats; +import android.net.NetworkStats.NonMonotonicObserver; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.util.FileRotator; +import com.android.internal.util.IndentingPrintWriter; +import com.google.android.collect.Sets; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.WeakReference; +import java.util.HashSet; +import java.util.Map; + +/** + * Logic to record deltas between periodic {@link NetworkStats} snapshots into + * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}. + * Keeps pending changes in memory until they pass a specific threshold, in + * bytes. Uses {@link FileRotator} for persistence logic. + * <p> + * Not inherently thread safe. + */ +public class NetworkStatsRecorder { + private static final String TAG = "NetworkStatsRecorder"; + private static final boolean LOGD = false; + private static final boolean LOGV = false; + + private final FileRotator mRotator; + private final NonMonotonicObserver<String> mObserver; + private final String mCookie; + + private final long mBucketDuration; + private final long mPersistThresholdBytes; + private final boolean mOnlyTags; + + private NetworkStats mLastSnapshot; + + private final NetworkStatsCollection mPending; + private final NetworkStatsCollection mSinceBoot; + + private final CombiningRewriter mPendingRewriter; + + private WeakReference<NetworkStatsCollection> mComplete; + + public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, + String cookie, long bucketDuration, long persistThresholdBytes, boolean onlyTags) { + mRotator = checkNotNull(rotator, "missing FileRotator"); + mObserver = checkNotNull(observer, "missing NonMonotonicObserver"); + mCookie = cookie; + + mBucketDuration = bucketDuration; + mPersistThresholdBytes = persistThresholdBytes; + mOnlyTags = onlyTags; + + mPending = new NetworkStatsCollection(bucketDuration); + mSinceBoot = new NetworkStatsCollection(bucketDuration); + + mPendingRewriter = new CombiningRewriter(mPending); + } + + public void resetLocked() { + mLastSnapshot = null; + mPending.reset(); + mSinceBoot.reset(); + mComplete.clear(); + } + + public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) { + return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null); + } + + /** + * Load complete history represented by {@link FileRotator}. Caches + * internally as a {@link WeakReference}, and updated with future + * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long + * as reference is valid. + */ + public NetworkStatsCollection getOrLoadCompleteLocked() { + NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; + if (complete == null) { + if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie); + try { + complete = new NetworkStatsCollection(mBucketDuration); + mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE); + complete.recordCollection(mPending); + mComplete = new WeakReference<NetworkStatsCollection>(complete); + } catch (IOException e) { + Log.wtf(TAG, "problem completely reading network stats", e); + } + } + return complete; + } + + /** + * Record any delta that occurred since last {@link NetworkStats} snapshot, + * using the given {@link Map} to identify network interfaces. First + * snapshot is considered bootstrap, and is not counted as delta. + */ + public void recordSnapshotLocked(NetworkStats snapshot, + Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) { + final HashSet<String> unknownIfaces = Sets.newHashSet(); + + // assume first snapshot is bootstrap and don't record + if (mLastSnapshot == null) { + mLastSnapshot = snapshot; + return; + } + + final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; + + final NetworkStats delta = NetworkStats.subtract( + snapshot, mLastSnapshot, mObserver, mCookie); + final long end = currentTimeMillis; + final long start = end - delta.getElapsedRealtime(); + + NetworkStats.Entry entry = null; + for (int i = 0; i < delta.size(); i++) { + entry = delta.getValues(i, entry); + final NetworkIdentitySet ident = ifaceIdent.get(entry.iface); + if (ident == null) { + unknownIfaces.add(entry.iface); + continue; + } + + // skip when no delta occured + if (entry.isEmpty()) continue; + + // only record tag data when requested + if ((entry.tag == TAG_NONE) != mOnlyTags) { + mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); + + // also record against boot stats when present + if (mSinceBoot != null) { + mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); + } + + // also record against complete dataset when present + if (complete != null) { + complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); + } + } + } + + mLastSnapshot = snapshot; + + if (LOGV && unknownIfaces.size() > 0) { + Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats"); + } + } + + /** + * Consider persisting any pending deltas, if they are beyond + * {@link #mPersistThresholdBytes}. + */ + public void maybePersistLocked(long currentTimeMillis) { + final long pendingBytes = mPending.getTotalBytes(); + if (pendingBytes >= mPersistThresholdBytes) { + forcePersistLocked(currentTimeMillis); + } else { + mRotator.maybeRotate(currentTimeMillis); + } + } + + /** + * Force persisting any pending deltas. + */ + public void forcePersistLocked(long currentTimeMillis) { + if (mPending.isDirty()) { + if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie); + try { + mRotator.rewriteActive(mPendingRewriter, currentTimeMillis); + mRotator.maybeRotate(currentTimeMillis); + mPending.reset(); + } catch (IOException e) { + Log.wtf(TAG, "problem persisting pending stats", e); + } + } + } + + /** + * Remove the given UID from all {@link FileRotator} history, migrating it + * to {@link TrafficStats#UID_REMOVED}. + */ + public void removeUidLocked(int uid) { + try { + // process all existing data to migrate uid + mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid)); + } catch (IOException e) { + Log.wtf(TAG, "problem removing UID " + uid, e); + } + + // clear UID from current stats snapshot + if (mLastSnapshot != null) { + mLastSnapshot = mLastSnapshot.withoutUid(uid); + } + + final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; + if (complete != null) { + complete.removeUid(uid); + } + } + + /** + * Rewriter that will combine current {@link NetworkStatsCollection} values + * with anything read from disk, and write combined set to disk. Clears the + * original {@link NetworkStatsCollection} when finished writing. + */ + private static class CombiningRewriter implements FileRotator.Rewriter { + private final NetworkStatsCollection mCollection; + + public CombiningRewriter(NetworkStatsCollection collection) { + mCollection = checkNotNull(collection, "missing NetworkStatsCollection"); + } + + @Override + public void reset() { + // ignored + } + + @Override + public void read(InputStream in) throws IOException { + mCollection.read(in); + } + + @Override + public boolean shouldWrite() { + return true; + } + + @Override + public void write(OutputStream out) throws IOException { + mCollection.write(new DataOutputStream(out)); + mCollection.reset(); + } + } + + /** + * Rewriter that will remove any {@link NetworkStatsHistory} attributed to + * the requested UID, only writing data back when modified. + */ + public static class RemoveUidRewriter implements FileRotator.Rewriter { + private final NetworkStatsCollection mTemp; + private final int mUid; + + public RemoveUidRewriter(long bucketDuration, int uid) { + mTemp = new NetworkStatsCollection(bucketDuration); + mUid = uid; + } + + @Override + public void reset() { + mTemp.reset(); + } + + @Override + public void read(InputStream in) throws IOException { + mTemp.read(in); + mTemp.clearDirty(); + mTemp.removeUid(mUid); + } + + @Override + public boolean shouldWrite() { + return mTemp.isDirty(); + } + + @Override + public void write(OutputStream out) throws IOException { + mTemp.write(new DataOutputStream(out)); + } + } + + public void importLegacyNetworkLocked(File file) throws IOException { + // legacy file still exists; start empty to avoid double importing + mRotator.deleteAll(); + + final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration); + collection.readLegacyNetwork(file); + + final long startMillis = collection.getStartMillis(); + final long endMillis = collection.getEndMillis(); + + if (!collection.isEmpty()) { + // process legacy data, creating active file at starting time, then + // using end time to possibly trigger rotation. + mRotator.rewriteActive(new CombiningRewriter(collection), startMillis); + mRotator.maybeRotate(endMillis); + } + } + + public void importLegacyUidLocked(File file) throws IOException { + // legacy file still exists; start empty to avoid double importing + mRotator.deleteAll(); + + final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration); + collection.readLegacyUid(file, mOnlyTags); + + final long startMillis = collection.getStartMillis(); + final long endMillis = collection.getEndMillis(); + + if (!collection.isEmpty()) { + // process legacy data, creating active file at starting time, then + // using end time to possibly trigger rotation. + mRotator.rewriteActive(new CombiningRewriter(collection), startMillis); + mRotator.maybeRotate(endMillis); + } + } + + public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) { + pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes()); + if (fullHistory) { + pw.println("Complete history:"); + getOrLoadCompleteLocked().dump(pw); + } else { + pw.println("History since boot:"); + mSinceBoot.dump(pw); + } + } +} diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index f660520..1c3e24f 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -26,28 +26,37 @@ import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; +import static android.net.ConnectivityManager.isNetworkTypeMobile; +import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.SET_FOREGROUND; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; -import static android.net.NetworkTemplate.buildTemplateMobileAll; -import static android.net.NetworkTemplate.buildTemplateWifi; -import static android.net.TrafficStats.UID_REMOVED; -import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION; -import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY; -import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD; +import static android.net.NetworkTemplate.buildTemplateMobileWildcard; +import static android.net.NetworkTemplate.buildTemplateWifiWildcard; +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_TAG_MAX_HISTORY; +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_MAX_HISTORY; +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.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE; import static android.telephony.PhoneStateListener.LISTEN_NONE; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.SECOND_IN_MILLIS; +import static com.android.internal.util.ArrayUtils.appendElement; +import static com.android.internal.util.ArrayUtils.contains; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats; @@ -61,19 +70,19 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.INetworkStatsService; +import android.net.INetworkStatsSession; +import android.net.LinkProperties; import android.net.NetworkIdentity; import android.net.NetworkInfo; import android.net.NetworkState; import android.net.NetworkStats; -import android.net.NetworkStats.NonMonotonicException; +import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.TrafficStats; import android.os.Binder; import android.os.DropBoxManager; import android.os.Environment; @@ -94,32 +103,18 @@ import android.util.Slog; import android.util.SparseIntArray; import android.util.TrustedTime; -import com.android.internal.os.AtomicFile; -import com.android.internal.util.Objects; +import com.android.internal.util.FileRotator; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; import com.android.server.connectivity.Tethering; -import com.google.android.collect.Lists; import com.google.android.collect.Maps; -import com.google.android.collect.Sets; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.File; import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.net.ProtocolException; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Random; - -import libcore.io.IoUtils; /** * Collect and persist detailed network statistics, and provide this data to @@ -127,19 +122,11 @@ import libcore.io.IoUtils; */ public class NetworkStatsService extends INetworkStatsService.Stub { private static final String TAG = "NetworkStats"; - private static final boolean LOGD = false; private static final boolean LOGV = false; - /** File header magic number: "ANET" */ - private static final int FILE_MAGIC = 0x414E4554; - private static final int VERSION_NETWORK_INIT = 1; - private static final int VERSION_UID_INIT = 1; - private static final int VERSION_UID_WITH_IDENT = 2; - private static final int VERSION_UID_WITH_TAG = 3; - private static final int VERSION_UID_WITH_SET = 4; - private static final int MSG_PERFORM_POLL = 1; private static final int MSG_UPDATE_IFACES = 2; + private static final int MSG_REGISTER_GLOBAL_ALERT = 3; /** Flags to control detail level of poll event. */ private static final int FLAG_PERSIST_NETWORK = 0x1; @@ -147,9 +134,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID; private static final int FLAG_PERSIST_FORCE = 0x100; - /** Sample recent usage after each poll event. */ - private static final boolean ENABLE_SAMPLE_AFTER_POLL = true; - private static final String TAG_NETSTATS_ERROR = "netstats_error"; private final Context mContext; @@ -159,10 +143,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final TelephonyManager mTeleManager; private final NetworkStatsSettings mSettings; + private final File mSystemDir; + private final File mBaseDir; + private final PowerManager.WakeLock mWakeLock; private IConnectivityManager mConnManager; - private DropBoxManager mDropBox; // @VisibleForTesting public static final String ACTION_NETWORK_STATS_POLL = @@ -172,71 +158,76 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private PendingIntent mPollIntent; - // TODO: trim empty history objects entirely - - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES; - private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES; + private static final String PREFIX_DEV = "dev"; + private static final String PREFIX_UID = "uid"; + private static final String PREFIX_UID_TAG = "uid_tag"; /** * Settings that can be changed externally. */ public interface NetworkStatsSettings { public long getPollInterval(); - public long getPersistThreshold(); - public long getNetworkBucketDuration(); - public long getNetworkMaxHistory(); - public long getUidBucketDuration(); - public long getUidMaxHistory(); - public long getTagMaxHistory(); public long getTimeCacheMaxAge(); + public long getGlobalAlertBytes(); + public boolean getSampleEnabled(); + + public static class Config { + public final long bucketDuration; + public final long persistBytes; + public final long rotateAgeMillis; + public final long deleteAgeMillis; + + public Config(long bucketDuration, long persistBytes, long rotateAgeMillis, + long deleteAgeMillis) { + this.bucketDuration = bucketDuration; + this.persistBytes = persistBytes; + this.rotateAgeMillis = rotateAgeMillis; + this.deleteAgeMillis = deleteAgeMillis; + } + } + + public Config getDevConfig(); + public Config getUidConfig(); + public Config getUidTagConfig(); } private final Object mStatsLock = new Object(); /** Set of currently active ifaces. */ private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap(); - /** Set of historical {@code dev} stats for known networks. */ - private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkDevStats = Maps.newHashMap(); - /** Set of historical {@code xtables} stats for known networks. */ - private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkXtStats = Maps.newHashMap(); - /** Set of historical {@code xtables} stats for known UIDs. */ - private HashMap<UidStatsKey, NetworkStatsHistory> mUidStats = Maps.newHashMap(); - - /** Flag if {@link #mNetworkDevStats} have been loaded from disk. */ - private boolean mNetworkStatsLoaded = false; - /** Flag if {@link #mUidStats} have been loaded from disk. */ - private boolean mUidStatsLoaded = false; - - private NetworkStats mLastPollNetworkDevSnapshot; - private NetworkStats mLastPollNetworkXtSnapshot; - private NetworkStats mLastPollUidSnapshot; - private NetworkStats mLastPollOperationsSnapshot; - - private NetworkStats mLastPersistNetworkDevSnapshot; - private NetworkStats mLastPersistNetworkXtSnapshot; - private NetworkStats mLastPersistUidSnapshot; + /** Current default active iface. */ + private String mActiveIface; + /** Set of any ifaces associated with mobile networks since boot. */ + private String[] mMobileIfaces = new String[0]; + + private final DropBoxNonMonotonicObserver mNonMonotonicObserver = + new DropBoxNonMonotonicObserver(); + + private NetworkStatsRecorder mDevRecorder; + private NetworkStatsRecorder mUidRecorder; + private NetworkStatsRecorder mUidTagRecorder; + + /** Cached {@link #mDevRecorder} stats. */ + private NetworkStatsCollection mDevStatsCached; /** Current counter sets for each UID. */ private SparseIntArray mActiveUidCounterSet = new SparseIntArray(); /** Data layer operation counters for splicing into other structures. */ - private NetworkStats mOperations = new NetworkStats(0L, 10); + private NetworkStats mUidOperations = new NetworkStats(0L, 10); private final HandlerThread mHandlerThread; private final Handler mHandler; - private final AtomicFile mNetworkDevFile; - private final AtomicFile mNetworkXtFile; - private final AtomicFile mUidFile; + private boolean mSystemReady; public NetworkStatsService( Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) { this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context), - getSystemDir(), new DefaultNetworkStatsSettings(context)); + getDefaultSystemDir(), new DefaultNetworkStatsSettings(context)); } - private static File getSystemDir() { + private static File getDefaultSystemDir() { return new File(Environment.getDataDirectory(), "system"); } @@ -258,9 +249,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback); - mNetworkDevFile = new AtomicFile(new File(systemDir, "netstats.bin")); - mNetworkXtFile = new AtomicFile(new File(systemDir, "netstats_xt.bin")); - mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin")); + mSystemDir = checkNotNull(systemDir); + mBaseDir = new File(systemDir, "netstats"); + mBaseDir.mkdirs(); } public void bindConnectivityManager(IConnectivityManager connManager) { @@ -268,17 +259,29 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } public void systemReady() { + mSystemReady = true; + + if (!isBandwidthControlEnabled()) { + Slog.w(TAG, "bandwidth controls disabled, unable to track stats"); + return; + } + + // create data recorders along with historical rotators + mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false); + mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false); + mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true); + synchronized (mStatsLock) { + // upgrade any legacy stats, migrating them to rotated files + maybeUpgradeLegacyStatsLocked(); + // read historical network stats from disk, since policy service - // might need them right away. we delay loading detailed UID stats - // until actually needed. - readNetworkDevStatsLocked(); - readNetworkXtStatsLocked(); - mNetworkStatsLoaded = true; - } + // might need them right away. + mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked(); - // bootstrap initial stats to prevent double-counting later - bootstrapStats(); + // bootstrap initial stats to prevent double-counting later + bootstrapStatsLocked(); + } // watch for network interfaces to be claimed final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE); @@ -308,12 +311,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // watch for networkType changes that aren't broadcast through // CONNECTIVITY_ACTION_IMMEDIATE above. - mTeleManager.listen(mPhoneListener, LISTEN_DATA_CONNECTION_STATE); + if (!COMBINE_SUBTYPE_ENABLED) { + mTeleManager.listen(mPhoneListener, LISTEN_DATA_CONNECTION_STATE); + } registerPollAlarmLocked(); registerGlobalAlert(); + } - mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE); + private NetworkStatsRecorder buildRecorder( + String prefix, NetworkStatsSettings.Config config, boolean includeTags) { + return new NetworkStatsRecorder( + new FileRotator(mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis), + mNonMonotonicObserver, prefix, config.bucketDuration, config.persistBytes, + includeTags); } private void shutdownLocked() { @@ -323,20 +334,50 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContext.unregisterReceiver(mRemovedReceiver); mContext.unregisterReceiver(mShutdownReceiver); - mTeleManager.listen(mPhoneListener, LISTEN_NONE); - - if (mNetworkStatsLoaded) { - writeNetworkDevStatsLocked(); - writeNetworkXtStatsLocked(); + if (!COMBINE_SUBTYPE_ENABLED) { + mTeleManager.listen(mPhoneListener, LISTEN_NONE); } - if (mUidStatsLoaded) { - writeUidStatsLocked(); + + final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); + + // persist any pending stats + mDevRecorder.forcePersistLocked(currentTime); + mUidRecorder.forcePersistLocked(currentTime); + mUidTagRecorder.forcePersistLocked(currentTime); + + mDevRecorder = null; + mUidRecorder = null; + mUidTagRecorder = null; + + mDevStatsCached = null; + + mSystemReady = false; + } + + private void maybeUpgradeLegacyStatsLocked() { + File file; + try { + file = new File(mSystemDir, "netstats.bin"); + if (file.exists()) { + mDevRecorder.importLegacyNetworkLocked(file); + file.delete(); + } + + file = new File(mSystemDir, "netstats_xt.bin"); + if (file.exists()) { + file.delete(); + } + + file = new File(mSystemDir, "netstats_uid.bin"); + if (file.exists()) { + mUidRecorder.importLegacyUidLocked(file); + mUidTagRecorder.importLegacyUidLocked(file); + file.delete(); + } + } catch (IOException e) { + Log.wtf(TAG, "problem during legacy upgrade", e); } - mNetworkDevStats.clear(); - mNetworkXtStats.clear(); - mUidStats.clear(); - mNetworkStatsLoaded = false; - mUidStatsLoaded = false; } /** @@ -367,7 +408,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ private void registerGlobalAlert() { try { - final long alertBytes = mSettings.getPersistThreshold(); + final long alertBytes = mSettings.getGlobalAlertBytes(); mNetworkManager.setGlobalAlert(alertBytes); } catch (IllegalStateException e) { Slog.w(TAG, "problem registering for global alert: " + e); @@ -377,162 +418,77 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @Override - public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { + public INetworkStatsSession openSession() { mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - return getHistoryForNetworkDev(template, fields); - } + assertBandwidthControlEnabled(); - private NetworkStatsHistory getHistoryForNetworkDev(NetworkTemplate template, int fields) { - return getHistoryForNetwork(template, fields, mNetworkDevStats); - } + // return an IBinder which holds strong references to any loaded stats + // for its lifetime; when caller closes only weak references remain. - private NetworkStatsHistory getHistoryForNetworkXt(NetworkTemplate template, int fields) { - return getHistoryForNetwork(template, fields, mNetworkXtStats); - } + return new INetworkStatsSession.Stub() { + private NetworkStatsCollection mUidComplete; + private NetworkStatsCollection mUidTagComplete; - private NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields, - HashMap<NetworkIdentitySet, NetworkStatsHistory> source) { - synchronized (mStatsLock) { - // combine all interfaces that match template - final NetworkStatsHistory combined = new NetworkStatsHistory( - mSettings.getNetworkBucketDuration(), estimateNetworkBuckets(), fields); - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - if (history != null) { - combined.recordEntireHistory(history); - } + private NetworkStatsCollection getUidComplete() { + if (mUidComplete == null) { + mUidComplete = mUidRecorder.getOrLoadCompleteLocked(); } + return mUidComplete; } - return combined; - } - } - - @Override - public NetworkStatsHistory getHistoryForUid( - NetworkTemplate template, int uid, int set, int tag, int fields) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - synchronized (mStatsLock) { - ensureUidStatsLoadedLocked(); - - // combine all interfaces that match template - final NetworkStatsHistory combined = new NetworkStatsHistory( - mSettings.getUidBucketDuration(), estimateUidBuckets(), fields); - for (UidStatsKey key : mUidStats.keySet()) { - final boolean setMatches = set == SET_ALL || key.set == set; - if (templateMatches(template, key.ident) && key.uid == uid && setMatches - && key.tag == tag) { - final NetworkStatsHistory history = mUidStats.get(key); - combined.recordEntireHistory(history); + private NetworkStatsCollection getUidTagComplete() { + if (mUidTagComplete == null) { + mUidTagComplete = mUidTagRecorder.getOrLoadCompleteLocked(); } + return mUidTagComplete; } - return combined; - } - } - - @Override - public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - return getSummaryForNetworkDev(template, start, end); - } - - private NetworkStats getSummaryForNetworkDev(NetworkTemplate template, long start, long end) { - return getSummaryForNetwork(template, start, end, mNetworkDevStats); - } + @Override + public NetworkStats getSummaryForNetwork( + NetworkTemplate template, long start, long end) { + return mDevStatsCached.getSummary(template, start, end); + } - private NetworkStats getSummaryForNetworkXt(NetworkTemplate template, long start, long end) { - return getSummaryForNetwork(template, start, end, mNetworkXtStats); - } + @Override + public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { + return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields); + } - private NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end, - HashMap<NetworkIdentitySet, NetworkStatsHistory> source) { - synchronized (mStatsLock) { - // use system clock to be externally consistent - final long now = System.currentTimeMillis(); - - final NetworkStats stats = new NetworkStats(end - start, 1); - final NetworkStats.Entry entry = new NetworkStats.Entry(); - NetworkStatsHistory.Entry historyEntry = null; - - // combine total from all interfaces that match template - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - historyEntry = history.getValues(start, end, now, historyEntry); - - entry.iface = IFACE_ALL; - entry.uid = UID_ALL; - entry.tag = TAG_NONE; - entry.rxBytes = historyEntry.rxBytes; - entry.rxPackets = historyEntry.rxPackets; - entry.txBytes = historyEntry.txBytes; - entry.txPackets = historyEntry.txPackets; - - stats.combineValues(entry); + @Override + public NetworkStats getSummaryForAllUid( + NetworkTemplate template, long start, long end, boolean includeTags) { + final NetworkStats stats = getUidComplete().getSummary(template, start, end); + if (includeTags) { + final NetworkStats tagStats = getUidTagComplete() + .getSummary(template, start, end); + stats.combineAllValues(tagStats); } + return stats; } - return stats; - } - } + @Override + public NetworkStatsHistory getHistoryForUid( + NetworkTemplate template, int uid, int set, int tag, int fields) { + if (tag == TAG_NONE) { + return getUidComplete().getHistory(template, uid, set, tag, fields); + } else { + return getUidTagComplete().getHistory(template, uid, set, tag, fields); + } + } - private long getHistoryStartLocked( - NetworkTemplate template, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) { - long start = Long.MAX_VALUE; - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - start = Math.min(start, history.getStart()); + @Override + public void close() { + mUidComplete = null; + mUidTagComplete = null; } - } - return start; + }; } @Override - public NetworkStats getSummaryForAllUid( - NetworkTemplate template, long start, long end, boolean includeTags) { + public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - - synchronized (mStatsLock) { - ensureUidStatsLoadedLocked(); - - // use system clock to be externally consistent - final long now = System.currentTimeMillis(); - - final NetworkStats stats = new NetworkStats(end - start, 24); - final NetworkStats.Entry entry = new NetworkStats.Entry(); - NetworkStatsHistory.Entry historyEntry = null; - - for (UidStatsKey key : mUidStats.keySet()) { - if (templateMatches(template, key.ident)) { - // always include summary under TAG_NONE, and include - // other tags when requested. - if (key.tag == TAG_NONE || includeTags) { - final NetworkStatsHistory history = mUidStats.get(key); - historyEntry = history.getValues(start, end, now, historyEntry); - - entry.iface = IFACE_ALL; - entry.uid = key.uid; - entry.set = key.set; - entry.tag = key.tag; - entry.rxBytes = historyEntry.rxBytes; - entry.rxPackets = historyEntry.rxPackets; - entry.txBytes = historyEntry.txBytes; - entry.txPackets = historyEntry.txPackets; - entry.operations = historyEntry.operations; - - if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 - || entry.txPackets > 0 || entry.operations > 0) { - stats.combineValues(entry); - } - } - } - } - - return stats; - } + assertBandwidthControlEnabled(); + return mDevStatsCached.getSummary(template, start, end).getTotalBytes(); } @Override @@ -540,10 +496,21 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (Binder.getCallingUid() != uid) { mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG); } + assertBandwidthControlEnabled(); // TODO: switch to data layer stats once kernel exports // for now, read network layer stats and flatten across all ifaces - final NetworkStats networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid); + final long token = Binder.clearCallingIdentity(); + final NetworkStats networkLayer; + try { + networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid); + } finally { + Binder.restoreCallingIdentity(token); + } + + // splice in operation counts + networkLayer.spliceOperationsFrom(mUidOperations); + final NetworkStats dataLayer = new NetworkStats( networkLayer.getElapsedRealtime(), networkLayer.size()); @@ -554,12 +521,15 @@ public class NetworkStatsService extends INetworkStatsService.Stub { dataLayer.combineValues(entry); } - // splice in operation counts - dataLayer.spliceOperationsFrom(mOperations); return dataLayer; } @Override + public String[] getMobileIfaces() { + return mMobileIfaces; + } + + @Override public void incrementOperationCount(int uid, int tag, int operationCount) { if (Binder.getCallingUid() != uid) { mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG); @@ -574,8 +544,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { synchronized (mStatsLock) { final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT); - mOperations.combineValues(IFACE_ALL, uid, set, tag, 0L, 0L, 0L, 0L, operationCount); - mOperations.combineValues(IFACE_ALL, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount); + mUidOperations.combineValues( + mActiveIface, uid, set, tag, 0L, 0L, 0L, 0L, operationCount); + mUidOperations.combineValues( + mActiveIface, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount); } } @@ -596,7 +568,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void forceUpdate() { mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - performPoll(FLAG_PERSIST_ALL); + assertBandwidthControlEnabled(); + + final long token = Binder.clearCallingIdentity(); + try { + performPoll(FLAG_PERSIST_ALL); + } finally { + Binder.restoreCallingIdentity(token); + } } /** @@ -680,7 +659,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget(); // re-arm global alert for next update - registerGlobalAlert(); + mHandler.obtainMessage(MSG_REGISTER_GLOBAL_ALERT).sendToTarget(); } } }; @@ -732,6 +711,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * {@link NetworkIdentitySet}. */ private void updateIfacesLocked() { + if (!mSystemReady) return; if (LOGV) Slog.v(TAG, "updateIfacesLocked()"); // take one last stats snapshot before updating iface mapping. this @@ -743,13 +723,17 @@ public class NetworkStatsService extends INetworkStatsService.Stub { performPollLocked(FLAG_PERSIST_NETWORK); final NetworkState[] states; + final LinkProperties activeLink; try { states = mConnManager.getAllNetworkState(); + activeLink = mConnManager.getActiveLinkProperties(); } catch (RemoteException e) { // ignored; service lives in system_server return; } + mActiveIface = activeLink != null ? activeLink.getInterfaceName() : null; + // rebuild active interfaces based on connected networks mActiveIfaces.clear(); @@ -765,6 +749,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state)); + + // remember any ifaces associated with mobile networks + if (isNetworkTypeMobile(state.networkInfo.getType())) { + if (!contains(mMobileIfaces, iface)) { + mMobileIfaces = appendElement(String.class, mMobileIfaces, iface); + } + } } } } @@ -773,12 +764,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * Bootstrap initial stats snapshot, usually during {@link #systemReady()} * so we have baseline values without double-counting. */ - private void bootstrapStats() { + private void bootstrapStatsLocked() { + final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); + try { - mLastPollUidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - mLastPollNetworkDevSnapshot = mNetworkManager.getNetworkStatsSummary(); - mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot); - mLastPollOperationsSnapshot = new NetworkStats(0L, 0); + // snapshot and record current counters; read UID stats first to + // avoid overcounting dev stats. + final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); + final NetworkStats devSnapshot = getNetworkStatsSummary(); + + mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + } catch (IllegalStateException e) { Slog.w(TAG, "problem reading network stats: " + e); } catch (RemoteException e) { @@ -808,7 +807,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * {@link NetworkStatsHistory}. */ private void performPollLocked(int flags) { + if (!mSystemReady) return; if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")"); + final long startRealtime = SystemClock.elapsedRealtime(); final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0; @@ -818,27 +819,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // TODO: consider marking "untrusted" times in historical stats final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis(); - final long threshold = mSettings.getPersistThreshold(); - final NetworkStats uidSnapshot; - final NetworkStats networkXtSnapshot; - final NetworkStats networkDevSnapshot; try { - // collect any tethering stats - final NetworkStats tetherSnapshot = getNetworkStatsTethering(); - - // record uid stats, folding in tethering stats - uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - uidSnapshot.combineAllValues(tetherSnapshot); - performUidPollLocked(uidSnapshot, currentTime); - - // record dev network stats - networkDevSnapshot = mNetworkManager.getNetworkStatsSummary(); - performNetworkDevPollLocked(networkDevSnapshot, currentTime); + // snapshot and record current counters; read UID stats first to + // avoid overcounting dev stats. + final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); + final NetworkStats devSnapshot = getNetworkStatsSummary(); - // record xt network stats - networkXtSnapshot = computeNetworkXtSnapshotFromUid(uidSnapshot); - performNetworkXtPollLocked(networkXtSnapshot, currentTime); + mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); } catch (IllegalStateException e) { Log.wtf(TAG, "problem reading network stats", e); @@ -848,26 +838,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return; } - // persist when enough network data has occurred - final long persistNetworkDevDelta = computeStatsDelta( - mLastPersistNetworkDevSnapshot, networkDevSnapshot, true, "devp").getTotalBytes(); - final long persistNetworkXtDelta = computeStatsDelta( - mLastPersistNetworkXtSnapshot, networkXtSnapshot, true, "xtp").getTotalBytes(); - final boolean networkOverThreshold = persistNetworkDevDelta > threshold - || persistNetworkXtDelta > threshold; - if (persistForce || (persistNetwork && networkOverThreshold)) { - writeNetworkDevStatsLocked(); - writeNetworkXtStatsLocked(); - mLastPersistNetworkDevSnapshot = networkDevSnapshot; - mLastPersistNetworkXtSnapshot = networkXtSnapshot; - } - - // persist when enough uid data has occurred - final long persistUidDelta = computeStatsDelta( - mLastPersistUidSnapshot, uidSnapshot, true, "uidp").getTotalBytes(); - if (persistForce || (persistUid && persistUidDelta > threshold)) { - writeUidStatsLocked(); - mLastPersistUidSnapshot = uidSnapshot; + // persist any pending data depending on requested flags + if (persistForce) { + mDevRecorder.forcePersistLocked(currentTime); + mUidRecorder.forcePersistLocked(currentTime); + mUidTagRecorder.forcePersistLocked(currentTime); + } else { + if (persistNetwork) { + mDevRecorder.maybePersistLocked(currentTime); + } + if (persistUid) { + mUidRecorder.maybePersistLocked(currentTime); + mUidTagRecorder.maybePersistLocked(currentTime); + } } if (LOGV) { @@ -875,9 +858,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { Slog.v(TAG, "performPollLocked() took " + duration + "ms"); } - if (ENABLE_SAMPLE_AFTER_POLL) { + if (mSettings.getSampleEnabled()) { // sample stats after each full poll - performSample(); + performSampleLocked(); } // finally, dispatch updated event to any listeners @@ -887,511 +870,58 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Update {@link #mNetworkDevStats} historical usage. - */ - private void performNetworkDevPollLocked(NetworkStats networkDevSnapshot, long currentTime) { - final HashSet<String> unknownIface = Sets.newHashSet(); - - final NetworkStats delta = computeStatsDelta( - mLastPollNetworkDevSnapshot, networkDevSnapshot, false, "dev"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - unknownIface.add(entry.iface); - continue; - } - - final NetworkStatsHistory history = findOrCreateNetworkDevStatsLocked(ident); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollNetworkDevSnapshot = networkDevSnapshot; - - if (LOGD && unknownIface.size() > 0) { - Slog.w(TAG, "unknown dev interfaces " + unknownIface + ", ignoring those stats"); - } - } - - /** - * Update {@link #mNetworkXtStats} historical usage. - */ - private void performNetworkXtPollLocked(NetworkStats networkXtSnapshot, long currentTime) { - final HashSet<String> unknownIface = Sets.newHashSet(); - - final NetworkStats delta = computeStatsDelta( - mLastPollNetworkXtSnapshot, networkXtSnapshot, false, "xt"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - unknownIface.add(entry.iface); - continue; - } - - final NetworkStatsHistory history = findOrCreateNetworkXtStatsLocked(ident); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollNetworkXtSnapshot = networkXtSnapshot; - - if (LOGD && unknownIface.size() > 0) { - Slog.w(TAG, "unknown xt interfaces " + unknownIface + ", ignoring those stats"); - } - } - - /** - * Update {@link #mUidStats} historical usage. - */ - private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) { - ensureUidStatsLoadedLocked(); - - final NetworkStats delta = computeStatsDelta( - mLastPollUidSnapshot, uidSnapshot, false, "uid"); - final NetworkStats operationsDelta = computeStatsDelta( - mLastPollOperationsSnapshot, mOperations, false, "uidop"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - NetworkStats.Entry operationsEntry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 - || entry.txPackets > 0) { - Log.w(TAG, "dropping UID delta from unknown iface: " + entry); - } - continue; - } - - // splice in operation counts since last poll - final int j = operationsDelta.findIndex(IFACE_ALL, entry.uid, entry.set, entry.tag); - if (j != -1) { - operationsEntry = operationsDelta.getValues(j, operationsEntry); - entry.operations = operationsEntry.operations; - } - - final NetworkStatsHistory history = findOrCreateUidStatsLocked( - ident, entry.uid, entry.set, entry.tag); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollUidSnapshot = uidSnapshot; - mLastPollOperationsSnapshot = mOperations.clone(); - } - - /** * Sample recent statistics summary into {@link EventLog}. */ - private void performSample() { - final long largestBucketSize = Math.max( - mSettings.getNetworkBucketDuration(), mSettings.getUidBucketDuration()); - - // take sample as atomic buckets - final long now = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis(); - final long end = now - (now % largestBucketSize) + largestBucketSize; - final long start = end - largestBucketSize; - + private void performSampleLocked() { + // TODO: migrate trustedtime fixes to separate binary log events final long trustedTime = mTime.hasCache() ? mTime.currentTimeMillis() : -1; - long devHistoryStart = Long.MAX_VALUE; - NetworkTemplate template = null; - NetworkStats.Entry devTotal = null; - NetworkStats.Entry xtTotal = null; - NetworkStats.Entry uidTotal = null; + NetworkTemplate template; + NetworkStats.Entry devTotal; + NetworkStats.Entry xtTotal; + NetworkStats.Entry uidTotal; // collect mobile sample - template = buildTemplateMobileAll(getActiveSubscriberId(mContext)); - devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal); - devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats); - xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal); - uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal); + template = buildTemplateMobileWildcard(); + devTotal = mDevRecorder.getTotalSinceBootLocked(template); + xtTotal = new NetworkStats.Entry(); + uidTotal = mUidRecorder.getTotalSinceBootLocked(template); EventLogTags.writeNetstatsMobileSample( devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets, xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets, - trustedTime, devHistoryStart); + trustedTime); // collect wifi sample - template = buildTemplateWifi(); - devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal); - devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats); - xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal); - uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal); + template = buildTemplateWifiWildcard(); + devTotal = mDevRecorder.getTotalSinceBootLocked(template); + xtTotal = new NetworkStats.Entry(); + uidTotal = mUidRecorder.getTotalSinceBootLocked(template); + EventLogTags.writeNetstatsWifiSample( devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets, xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets, - trustedTime, devHistoryStart); + trustedTime); } /** - * Clean up {@link #mUidStats} after UID is removed. + * Clean up {@link #mUidRecorder} after UID is removed. */ private void removeUidLocked(int uid) { - ensureUidStatsLoadedLocked(); - // perform one last poll before removing performPollLocked(FLAG_PERSIST_ALL); - final ArrayList<UidStatsKey> knownKeys = Lists.newArrayList(); - knownKeys.addAll(mUidStats.keySet()); - - // migrate all UID stats into special "removed" bucket - for (UidStatsKey key : knownKeys) { - if (key.uid == uid) { - // only migrate combined TAG_NONE history - if (key.tag == TAG_NONE) { - final NetworkStatsHistory uidHistory = mUidStats.get(key); - final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked( - key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); - removedHistory.recordEntireHistory(uidHistory); - } - mUidStats.remove(key); - } - } - - // clear UID from current stats snapshot - if (mLastPollUidSnapshot != null) { - mLastPollUidSnapshot = mLastPollUidSnapshot.withoutUid(uid); - mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot); - } + mUidRecorder.removeUidLocked(uid); + mUidTagRecorder.removeUidLocked(uid); // clear kernel stats associated with UID resetKernelUidStats(uid); - - // since this was radical rewrite, push to disk - writeUidStatsLocked(); - } - - private NetworkStatsHistory findOrCreateNetworkXtStatsLocked(NetworkIdentitySet ident) { - return findOrCreateNetworkStatsLocked(ident, mNetworkXtStats); - } - - private NetworkStatsHistory findOrCreateNetworkDevStatsLocked(NetworkIdentitySet ident) { - return findOrCreateNetworkStatsLocked(ident, mNetworkDevStats); - } - - private NetworkStatsHistory findOrCreateNetworkStatsLocked( - NetworkIdentitySet ident, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) { - final NetworkStatsHistory existing = source.get(ident); - - // update when no existing, or when bucket duration changed - final long bucketDuration = mSettings.getNetworkBucketDuration(); - NetworkStatsHistory updated = null; - if (existing == null) { - updated = new NetworkStatsHistory(bucketDuration, 10); - } else if (existing.getBucketDuration() != bucketDuration) { - updated = new NetworkStatsHistory( - bucketDuration, estimateResizeBuckets(existing, bucketDuration)); - updated.recordEntireHistory(existing); - } - - if (updated != null) { - source.put(ident, updated); - return updated; - } else { - return existing; - } - } - - private NetworkStatsHistory findOrCreateUidStatsLocked( - NetworkIdentitySet ident, int uid, int set, int tag) { - ensureUidStatsLoadedLocked(); - - final UidStatsKey key = new UidStatsKey(ident, uid, set, tag); - final NetworkStatsHistory existing = mUidStats.get(key); - - // update when no existing, or when bucket duration changed - final long bucketDuration = mSettings.getUidBucketDuration(); - NetworkStatsHistory updated = null; - if (existing == null) { - updated = new NetworkStatsHistory(bucketDuration, 10); - } else if (existing.getBucketDuration() != bucketDuration) { - updated = new NetworkStatsHistory( - bucketDuration, estimateResizeBuckets(existing, bucketDuration)); - updated.recordEntireHistory(existing); - } - - if (updated != null) { - mUidStats.put(key, updated); - return updated; - } else { - return existing; - } - } - - private void readNetworkDevStatsLocked() { - if (LOGV) Slog.v(TAG, "readNetworkDevStatsLocked()"); - readNetworkStats(mNetworkDevFile, mNetworkDevStats); - } - - private void readNetworkXtStatsLocked() { - if (LOGV) Slog.v(TAG, "readNetworkXtStatsLocked()"); - readNetworkStats(mNetworkXtFile, mNetworkXtStats); - } - - private static void readNetworkStats( - AtomicFile inputFile, HashMap<NetworkIdentitySet, NetworkStatsHistory> output) { - // clear any existing stats and read from disk - output.clear(); - - DataInputStream in = null; - try { - in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); - - // verify file magic header intact - final int magic = in.readInt(); - if (magic != FILE_MAGIC) { - throw new ProtocolException("unexpected magic: " + magic); - } - - final int version = in.readInt(); - switch (version) { - case VERSION_NETWORK_INIT: { - // network := size *(NetworkIdentitySet NetworkStatsHistory) - final int size = in.readInt(); - for (int i = 0; i < size; i++) { - final NetworkIdentitySet ident = new NetworkIdentitySet(in); - final NetworkStatsHistory history = new NetworkStatsHistory(in); - output.put(ident, history); - } - break; - } - default: { - throw new ProtocolException("unexpected version: " + version); - } - } - } catch (FileNotFoundException e) { - // missing stats is okay, probably first boot - } catch (IOException e) { - Log.wtf(TAG, "problem reading network stats", e); - } finally { - IoUtils.closeQuietly(in); - } - } - - private void ensureUidStatsLoadedLocked() { - if (!mUidStatsLoaded) { - readUidStatsLocked(); - mUidStatsLoaded = true; - } - } - - private void readUidStatsLocked() { - if (LOGV) Slog.v(TAG, "readUidStatsLocked()"); - - // clear any existing stats and read from disk - mUidStats.clear(); - - DataInputStream in = null; - try { - in = new DataInputStream(new BufferedInputStream(mUidFile.openRead())); - - // verify file magic header intact - final int magic = in.readInt(); - if (magic != FILE_MAGIC) { - throw new ProtocolException("unexpected magic: " + magic); - } - - final int version = in.readInt(); - switch (version) { - case VERSION_UID_INIT: { - // uid := size *(UID NetworkStatsHistory) - - // drop this data version, since we don't have a good - // mapping into NetworkIdentitySet. - break; - } - case VERSION_UID_WITH_IDENT: { - // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) - - // drop this data version, since this version only existed - // for a short time. - break; - } - case VERSION_UID_WITH_TAG: - case VERSION_UID_WITH_SET: { - // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) - final int identSize = in.readInt(); - for (int i = 0; i < identSize; i++) { - final NetworkIdentitySet ident = new NetworkIdentitySet(in); - - final int size = in.readInt(); - for (int j = 0; j < size; j++) { - final int uid = in.readInt(); - final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() - : SET_DEFAULT; - final int tag = in.readInt(); - - final UidStatsKey key = new UidStatsKey(ident, uid, set, tag); - final NetworkStatsHistory history = new NetworkStatsHistory(in); - mUidStats.put(key, history); - } - } - break; - } - default: { - throw new ProtocolException("unexpected version: " + version); - } - } - } catch (FileNotFoundException e) { - // missing stats is okay, probably first boot - } catch (IOException e) { - Log.wtf(TAG, "problem reading uid stats", e); - } finally { - IoUtils.closeQuietly(in); - } - } - - private void writeNetworkDevStatsLocked() { - if (LOGV) Slog.v(TAG, "writeNetworkDevStatsLocked()"); - writeNetworkStats(mNetworkDevStats, mNetworkDevFile); - } - - private void writeNetworkXtStatsLocked() { - if (LOGV) Slog.v(TAG, "writeNetworkXtStatsLocked()"); - writeNetworkStats(mNetworkXtStats, mNetworkXtFile); - } - - private void writeNetworkStats( - HashMap<NetworkIdentitySet, NetworkStatsHistory> input, AtomicFile outputFile) { - // TODO: consider duplicating stats and releasing lock while writing - - // trim any history beyond max - if (mTime.hasCache()) { - final long systemCurrentTime = System.currentTimeMillis(); - final long trustedCurrentTime = mTime.currentTimeMillis(); - - final long currentTime = Math.min(systemCurrentTime, trustedCurrentTime); - final long maxHistory = mSettings.getNetworkMaxHistory(); - - for (NetworkStatsHistory history : input.values()) { - final int beforeSize = history.size(); - history.removeBucketsBefore(currentTime - maxHistory); - final int afterSize = history.size(); - - if (beforeSize > 24 && afterSize < beforeSize / 2) { - // yikes, dropping more than half of significant history - final StringBuilder builder = new StringBuilder(); - builder.append("yikes, dropping more than half of history").append('\n'); - builder.append("systemCurrentTime=").append(systemCurrentTime).append('\n'); - builder.append("trustedCurrentTime=").append(trustedCurrentTime).append('\n'); - builder.append("maxHistory=").append(maxHistory).append('\n'); - builder.append("beforeSize=").append(beforeSize).append('\n'); - builder.append("afterSize=").append(afterSize).append('\n'); - mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); - } - } - } - - FileOutputStream fos = null; - try { - fos = outputFile.startWrite(); - final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); - - out.writeInt(FILE_MAGIC); - out.writeInt(VERSION_NETWORK_INIT); - - out.writeInt(input.size()); - for (NetworkIdentitySet ident : input.keySet()) { - final NetworkStatsHistory history = input.get(ident); - ident.writeToStream(out); - history.writeToStream(out); - } - - out.flush(); - outputFile.finishWrite(fos); - } catch (IOException e) { - Log.wtf(TAG, "problem writing stats", e); - if (fos != null) { - outputFile.failWrite(fos); - } - } - } - - private void writeUidStatsLocked() { - if (LOGV) Slog.v(TAG, "writeUidStatsLocked()"); - - if (!mUidStatsLoaded) { - Slog.w(TAG, "asked to write UID stats when not loaded; skipping"); - return; - } - - // TODO: consider duplicating stats and releasing lock while writing - - // trim any history beyond max - if (mTime.hasCache()) { - final long currentTime = Math.min( - System.currentTimeMillis(), mTime.currentTimeMillis()); - final long maxUidHistory = mSettings.getUidMaxHistory(); - final long maxTagHistory = mSettings.getTagMaxHistory(); - for (UidStatsKey key : mUidStats.keySet()) { - final NetworkStatsHistory history = mUidStats.get(key); - - // detailed tags are trimmed sooner than summary in TAG_NONE - if (key.tag == TAG_NONE) { - history.removeBucketsBefore(currentTime - maxUidHistory); - } else { - history.removeBucketsBefore(currentTime - maxTagHistory); - } - } - } - - // build UidStatsKey lists grouped by ident - final HashMap<NetworkIdentitySet, ArrayList<UidStatsKey>> keysByIdent = Maps.newHashMap(); - for (UidStatsKey key : mUidStats.keySet()) { - ArrayList<UidStatsKey> keys = keysByIdent.get(key.ident); - if (keys == null) { - keys = Lists.newArrayList(); - keysByIdent.put(key.ident, keys); - } - keys.add(key); - } - - FileOutputStream fos = null; - try { - fos = mUidFile.startWrite(); - final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); - - out.writeInt(FILE_MAGIC); - out.writeInt(VERSION_UID_WITH_SET); - - out.writeInt(keysByIdent.size()); - for (NetworkIdentitySet ident : keysByIdent.keySet()) { - final ArrayList<UidStatsKey> keys = keysByIdent.get(ident); - ident.writeToStream(out); - - out.writeInt(keys.size()); - for (UidStatsKey key : keys) { - final NetworkStatsHistory history = mUidStats.get(key); - out.writeInt(key.uid); - out.writeInt(key.set); - out.writeInt(key.tag); - history.writeToStream(out); - } - } - - out.flush(); - mUidFile.finishWrite(fos); - } catch (IOException e) { - Log.wtf(TAG, "problem writing stats", e); - if (fos != null) { - mUidFile.failWrite(fos); - } - } } @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); final HashSet<String> argSet = new HashSet<String>(); @@ -1399,182 +929,80 @@ public class NetworkStatsService extends INetworkStatsService.Stub { argSet.add(arg); } - final boolean fullHistory = argSet.contains("full"); + // usage: dumpsys netstats --full --uid --tag --poll --checkin + final boolean poll = argSet.contains("--poll") || argSet.contains("poll"); + final boolean checkin = argSet.contains("--checkin"); + final boolean fullHistory = argSet.contains("--full") || argSet.contains("full"); + final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail"); + final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail"); + + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); synchronized (mStatsLock) { - // TODO: remove this testing code, since it corrupts stats - if (argSet.contains("generate")) { - generateRandomLocked(args); - pw.println("Generated stub stats"); + if (poll) { + performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE); + pw.println("Forced poll"); return; } - if (argSet.contains("poll")) { - performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE); - pw.println("Forced poll"); + if (checkin) { + // list current stats files to verify rotation + pw.println("Current files:"); + pw.increaseIndent(); + for (String file : mBaseDir.list()) { + pw.println(file); + } + pw.decreaseIndent(); return; } pw.println("Active interfaces:"); + pw.increaseIndent(); for (String iface : mActiveIfaces.keySet()) { final NetworkIdentitySet ident = mActiveIfaces.get(iface); - pw.print(" iface="); pw.print(iface); + pw.print("iface="); pw.print(iface); pw.print(" ident="); pw.println(ident.toString()); } - - pw.println("Known historical dev stats:"); - for (NetworkIdentitySet ident : mNetworkDevStats.keySet()) { - final NetworkStatsHistory history = mNetworkDevStats.get(ident); - pw.print(" ident="); pw.println(ident.toString()); - history.dump(" ", pw, fullHistory); + pw.decreaseIndent(); + + pw.println("Dev stats:"); + pw.increaseIndent(); + mDevRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); + + if (includeUid) { + pw.println("UID stats:"); + pw.increaseIndent(); + mUidRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); } - pw.println("Known historical xt stats:"); - for (NetworkIdentitySet ident : mNetworkXtStats.keySet()) { - final NetworkStatsHistory history = mNetworkXtStats.get(ident); - pw.print(" ident="); pw.println(ident.toString()); - history.dump(" ", pw, fullHistory); - } - - if (argSet.contains("detail")) { - // since explicitly requested with argument, we're okay to load - // from disk if not already in memory. - ensureUidStatsLoadedLocked(); - - final ArrayList<UidStatsKey> keys = Lists.newArrayList(); - keys.addAll(mUidStats.keySet()); - Collections.sort(keys); - - pw.println("Detailed UID stats:"); - for (UidStatsKey key : keys) { - pw.print(" ident="); pw.print(key.ident.toString()); - pw.print(" uid="); pw.print(key.uid); - pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); - pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); - - final NetworkStatsHistory history = mUidStats.get(key); - history.dump(" ", pw, fullHistory); - } + if (includeTag) { + pw.println("UID tag stats:"); + pw.increaseIndent(); + mUidTagRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); } } } - /** - * @deprecated only for temporary testing - */ - @Deprecated - private void generateRandomLocked(String[] args) { - final long totalBytes = Long.parseLong(args[1]); - final long totalTime = Long.parseLong(args[2]); - - final PackageManager pm = mContext.getPackageManager(); - final ArrayList<Integer> specialUidList = Lists.newArrayList(); - for (int i = 3; i < args.length; i++) { - try { - specialUidList.add(pm.getApplicationInfo(args[i], 0).uid); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - } - - final HashSet<Integer> otherUidSet = Sets.newHashSet(); - for (ApplicationInfo info : pm.getInstalledApplications(0)) { - if (pm.checkPermission(android.Manifest.permission.INTERNET, info.packageName) - == PackageManager.PERMISSION_GRANTED && !specialUidList.contains(info.uid)) { - otherUidSet.add(info.uid); - } - } - - final ArrayList<Integer> otherUidList = new ArrayList<Integer>(otherUidSet); - - final long end = System.currentTimeMillis(); - final long start = end - totalTime; - - mNetworkDevStats.clear(); - mNetworkXtStats.clear(); - mUidStats.clear(); - - final Random r = new Random(); - for (NetworkIdentitySet ident : mActiveIfaces.values()) { - final NetworkStatsHistory devHistory = findOrCreateNetworkDevStatsLocked(ident); - final NetworkStatsHistory xtHistory = findOrCreateNetworkXtStatsLocked(ident); - - final ArrayList<Integer> uidList = new ArrayList<Integer>(); - uidList.addAll(specialUidList); - - if (uidList.size() == 0) { - Collections.shuffle(otherUidList); - uidList.addAll(otherUidList); - } - - boolean first = true; - long remainingBytes = totalBytes; - for (int uid : uidList) { - final NetworkStatsHistory defaultHistory = findOrCreateUidStatsLocked( - ident, uid, SET_DEFAULT, TAG_NONE); - final NetworkStatsHistory foregroundHistory = findOrCreateUidStatsLocked( - ident, uid, SET_FOREGROUND, TAG_NONE); - - final long uidBytes = totalBytes / uidList.size(); - - final float fractionDefault = r.nextFloat(); - final long defaultBytes = (long) (uidBytes * fractionDefault); - final long foregroundBytes = (long) (uidBytes * (1 - fractionDefault)); - - defaultHistory.generateRandom(start, end, defaultBytes); - foregroundHistory.generateRandom(start, end, foregroundBytes); - - if (first) { - final long bumpTime = (start + end) / 2; - defaultHistory.recordData( - bumpTime, bumpTime + DAY_IN_MILLIS, 200 * MB_IN_BYTES, 0); - first = false; - } - - devHistory.recordEntireHistory(defaultHistory); - devHistory.recordEntireHistory(foregroundHistory); - xtHistory.recordEntireHistory(defaultHistory); - xtHistory.recordEntireHistory(foregroundHistory); - } - } + private NetworkStats getNetworkStatsSummary() throws RemoteException { + return mNetworkManager.getNetworkStatsSummary(); } /** - * Return the delta between two {@link NetworkStats} snapshots, where {@code - * before} can be {@code null}. + * Return snapshot of current UID statistics, including any + * {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values. */ - private NetworkStats computeStatsDelta( - NetworkStats before, NetworkStats current, boolean collectStale, String type) { - if (before != null) { - try { - return current.subtract(before, false); - } catch (NonMonotonicException e) { - Log.w(TAG, "found non-monotonic values; saving to dropbox"); - - // record error for debugging - final StringBuilder builder = new StringBuilder(); - builder.append("found non-monotonic " + type + " values at left[" + e.leftIndex - + "] - right[" + e.rightIndex + "]\n"); - builder.append("left=").append(e.left).append('\n'); - builder.append("right=").append(e.right).append('\n'); - mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); + private NetworkStats getNetworkStatsUidDetail() throws RemoteException { + final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - try { - // return clamped delta to help recover - return current.subtract(before, true); - } catch (NonMonotonicException e1) { - Log.wtf(TAG, "found non-monotonic values; returning empty delta", e1); - return new NetworkStats(0L, 10); - } - } - } else if (collectStale) { - // caller is okay collecting stale stats for first call. - return current; - } else { - // this is first snapshot; to prevent from double-counting we only - // observe traffic occuring between known snapshots. - return new NetworkStats(0L, 10); - } + // fold tethering stats and operations into uid snapshot + final NetworkStats tetherSnapshot = getNetworkStatsTethering(); + uidSnapshot.combineAllValues(tetherSnapshot); + uidSnapshot.combineAllValues(mUidOperations); + + return uidSnapshot; } /** @@ -1591,37 +1019,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - private static NetworkStats computeNetworkXtSnapshotFromUid(NetworkStats uidSnapshot) { - return uidSnapshot.groupedByIface(); - } - - private int estimateNetworkBuckets() { - return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration()); - } - - private int estimateUidBuckets() { - return (int) (mSettings.getUidMaxHistory() / mSettings.getUidBucketDuration()); - } - - private static int estimateResizeBuckets(NetworkStatsHistory existing, long newBucketDuration) { - return (int) (existing.size() * existing.getBucketDuration() / newBucketDuration); - } - - /** - * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} - * in the given {@link NetworkIdentitySet}. - */ - private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) { - for (NetworkIdentity ident : identSet) { - if (template.matches(ident)) { - return true; - } - } - return false; - } - private Handler.Callback mHandlerCallback = new Handler.Callback() { - /** {@inheritDoc} */ + @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_PERFORM_POLL: { @@ -1633,6 +1032,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { updateIfaces(); return true; } + case MSG_REGISTER_GLOBAL_ALERT: { + registerGlobalAlert(); + return true; + } default: { return false; } @@ -1640,51 +1043,46 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } }; - private static String getActiveSubscriberId(Context context) { - final TelephonyManager telephony = (TelephonyManager) context.getSystemService( - Context.TELEPHONY_SERVICE); - return telephony.getSubscriberId(); + private void assertBandwidthControlEnabled() { + if (!isBandwidthControlEnabled()) { + throw new IllegalStateException("Bandwidth module disabled"); + } } - /** - * Key uniquely identifying a {@link NetworkStatsHistory} for a UID. - */ - private static class UidStatsKey implements Comparable<UidStatsKey> { - public final NetworkIdentitySet ident; - public final int uid; - public final int set; - public final int tag; - - public UidStatsKey(NetworkIdentitySet ident, int uid, int set, int tag) { - this.ident = ident; - this.uid = uid; - this.set = set; - this.tag = tag; + private boolean isBandwidthControlEnabled() { + final long token = Binder.clearCallingIdentity(); + try { + return mNetworkManager.isBandwidthControlEnabled(); + } catch (RemoteException e) { + // ignored; service lives in system_server + return false; + } finally { + Binder.restoreCallingIdentity(token); } + } + private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> { @Override - public int hashCode() { - return Objects.hashCode(ident, uid, set, tag); - } + public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right, + int rightIndex, String cookie) { + Log.w(TAG, "found non-monotonic values; saving to dropbox"); - @Override - public boolean equals(Object obj) { - if (obj instanceof UidStatsKey) { - final UidStatsKey key = (UidStatsKey) obj; - return Objects.equal(ident, key.ident) && uid == key.uid && set == key.set - && tag == key.tag; - } - return false; - } + // record error for debugging + final StringBuilder builder = new StringBuilder(); + builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex + + "] - right[" + rightIndex + "]\n"); + builder.append("left=").append(left).append('\n'); + builder.append("right=").append(right).append('\n'); - /** {@inheritDoc} */ - public int compareTo(UidStatsKey another) { - return Integer.compare(uid, another.uid); + final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService( + Context.DROPBOX_SERVICE); + dropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); } } /** - * Default external settings that read from {@link Settings.Secure}. + * Default external settings that read from + * {@link android.provider.Settings.Secure}. */ private static class DefaultNetworkStatsSettings implements NetworkStatsSettings { private final ContentResolver mResolver; @@ -1702,29 +1100,45 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return Settings.Secure.getInt(mResolver, name, defInt) != 0; } + @Override public long getPollInterval() { return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); } - public long getPersistThreshold() { - return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 2 * MB_IN_BYTES); - } - public long getNetworkBucketDuration() { - return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS); + @Override + public long getTimeCacheMaxAge() { + return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS); } - public long getNetworkMaxHistory() { - return getSecureLong(NETSTATS_NETWORK_MAX_HISTORY, 90 * DAY_IN_MILLIS); + @Override + public long getGlobalAlertBytes() { + return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, 2 * MB_IN_BYTES); } - public long getUidBucketDuration() { - return getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS); + @Override + public boolean getSampleEnabled() { + return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true); } - public long getUidMaxHistory() { - return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS); + + @Override + public Config getDevConfig() { + return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), + getSecureLong(NETSTATS_DEV_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS)); } - public long getTagMaxHistory() { - return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * DAY_IN_MILLIS); + + @Override + public Config getUidConfig() { + return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), + getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS)); } - public long getTimeCacheMaxAge() { - return DAY_IN_MILLIS; + + @Override + public Config getUidTagConfig() { + return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), + getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_UID_ROTATE_AGE, 5 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_UID_DELETE_AGE, 15 * DAY_IN_MILLIS)); } } } diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index 11ccd60..9b1973e 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -277,6 +277,27 @@ class Installer { return execute(builder.toString()); } + /** + * Clone all the package data directories from srcUserId to targetUserId. If copyData is true, + * some of the data is also copied, otherwise just empty directories are created with the + * correct access rights. + * @param srcUserId user to copy the data directories from + * @param targetUserId user to copy the data directories to + * @param copyData whether the data itself is to be copied. If false, empty directories are + * created. + * @return success/error code + */ + public int cloneUserData(int srcUserId, int targetUserId, boolean copyData) { + StringBuilder builder = new StringBuilder("cloneuserdata"); + builder.append(' '); + builder.append(srcUserId); + builder.append(' '); + builder.append(targetUserId); + builder.append(' '); + builder.append(copyData ? '1' : '0'); + return execute(builder.toString()); + } + public boolean ping() { if (execute("ping") < 0) { return false; diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 938d93a..21ae624 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -16,10 +16,14 @@ package com.android.server.pm; +import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS; +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 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_DISABLED_USER; 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 com.android.internal.app.IMediaContainerService; @@ -92,6 +96,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserId; import android.security.SystemKeyStore; import android.util.DisplayMetrics; import android.util.EventLog; @@ -129,12 +134,14 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import libcore.io.ErrnoException; +import libcore.io.IoUtils; import libcore.io.Libcore; /** @@ -163,13 +170,9 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_APP_DIR_OBSERVER = false; private static final boolean DEBUG_VERIFY = false; - static final boolean MULTIPLE_APPLICATION_UIDS = true; private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; - static final int FIRST_APPLICATION_UID = - Process.FIRST_APPLICATION_UID; - static final int MAX_APPLICATION_UIDS = 1000; private static final boolean GET_CERTIFICATES = true; @@ -394,7 +397,7 @@ public class PackageManagerService extends IPackageManager.Stub { static final int MCS_GIVE_UP = 11; static final int UPDATED_MEDIA_STATUS = 12; static final int WRITE_SETTINGS = 13; - static final int WRITE_STOPPED_PACKAGES = 14; + static final int WRITE_PACKAGE_RESTRICTIONS = 14; static final int PACKAGE_VERIFIED = 15; static final int CHECK_PENDING_VERIFICATION = 16; @@ -403,7 +406,10 @@ public class PackageManagerService extends IPackageManager.Stub { // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; - final UserManager mUserManager; + static UserManager sUserManager; + + // Stores a list of users whose package restrictions file needs to be updated + private HashSet<Integer> mDirtyUsers = new HashSet<Integer>(); final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); @@ -620,15 +626,15 @@ public class PackageManagerService extends IPackageManager.Stub { packages = new String[size]; components = new ArrayList[size]; uids = new int[size]; - Iterator<HashMap.Entry<String, ArrayList<String>>> + Iterator<Map.Entry<String, ArrayList<String>>> it = mPendingBroadcasts.entrySet().iterator(); int i = 0; while (it.hasNext() && i < size) { - HashMap.Entry<String, ArrayList<String>> ent = it.next(); + Map.Entry<String, ArrayList<String>> ent = it.next(); packages[i] = ent.getKey(); components[i] = ent.getValue(); PackageSetting ps = mSettings.mPackages.get(ent.getKey()); - uids[i] = (ps != null) ? ps.userId : -1; + uids[i] = (ps != null) ? ps.appId : -1; i++; } size = i; @@ -672,14 +678,15 @@ public class PackageManagerService extends IPackageManager.Stub { } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName, - extras, null, null); + extras, null, null, UserId.USER_ALL); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, res.pkg.applicationInfo.packageName, - extras, null, null); + extras, null, null, UserId.USER_ALL); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, - res.pkg.applicationInfo.packageName, null); + res.pkg.applicationInfo.packageName, null, + UserId.USER_ALL); } if (res.removedInfo.args != null) { // Remove the replaced package's older resources safely now @@ -716,7 +723,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (msg.obj != null) { @SuppressWarnings("unchecked") - Set<SdInstallArgs> args = (Set<SdInstallArgs>) msg.obj; + Set<AsecInstallArgs> args = (Set<AsecInstallArgs>) msg.obj; if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading all containers"); // Unload containers unloadAllContainers(args); @@ -734,16 +741,20 @@ public class PackageManagerService extends IPackageManager.Stub { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mPackages) { removeMessages(WRITE_SETTINGS); - removeMessages(WRITE_STOPPED_PACKAGES); + removeMessages(WRITE_PACKAGE_RESTRICTIONS); mSettings.writeLPr(); + mDirtyUsers.clear(); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; - case WRITE_STOPPED_PACKAGES: { + case WRITE_PACKAGE_RESTRICTIONS: { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mPackages) { - removeMessages(WRITE_STOPPED_PACKAGES); - mSettings.writeStoppedLPr(); + removeMessages(WRITE_PACKAGE_RESTRICTIONS); + for (int userId : mDirtyUsers) { + mSettings.writePackageRestrictionsLPr(userId); + } + mDirtyUsers.clear(); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; @@ -810,22 +821,13 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.sendEmptyMessageDelayed(WRITE_SETTINGS, WRITE_SETTINGS_DELAY); } } - - void scheduleWriteStoppedPackagesLocked() { - if (!mHandler.hasMessages(WRITE_STOPPED_PACKAGES)) { - mHandler.sendEmptyMessageDelayed(WRITE_STOPPED_PACKAGES, WRITE_SETTINGS_DELAY); - } - } - static boolean installOnSd(int flags) { - if (((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) || - ((flags & PackageManager.INSTALL_INTERNAL) != 0)) { - return false; + void scheduleWritePackageRestrictionsLocked(int userId) { + if (!sUserManager.exists(userId)) return; + mDirtyUsers.add(userId); + if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) { + mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY); } - if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { - return true; - } - return false; } public static final IPackageManager main(Context context, boolean factoryTest, @@ -873,18 +875,9 @@ public class PackageManagerService extends IPackageManager.Stub { mSettings = new Settings(); mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.phone", - MULTIPLE_APPLICATION_UIDS - ? RADIO_UID : FIRST_APPLICATION_UID, - ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.log", - MULTIPLE_APPLICATION_UIDS - ? LOG_UID : FIRST_APPLICATION_UID, - ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.nfc", - MULTIPLE_APPLICATION_UIDS - ? NFC_UID : FIRST_APPLICATION_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); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { @@ -920,11 +913,11 @@ public class PackageManagerService extends IPackageManager.Stub { mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); - mUserManager = new UserManager(mInstaller, mUserAppDataDir); + sUserManager = new UserManager(mInstaller, mUserAppDataDir); readPermissions(); - mRestoredSettings = mSettings.readLPw(); + mRestoredSettings = mSettings.readLPw(getUsers()); long startTime = SystemClock.uptimeMillis(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, @@ -1086,7 +1079,7 @@ public class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); mInstaller.remove(ps.name, 0); - mUserManager.removePackageForAllUsers(ps.name); + sUserManager.removePackageForAllUsers(ps.name); } } } @@ -1139,7 +1132,31 @@ public class PackageManagerService extends IPackageManager.Stub { + "; regranting permissions for internal storage"); mSettings.mInternalSdkPlatform = mSdkVersion; - updatePermissionsLPw(null, null, true, regrantPermissions, regrantPermissions); + updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL + | (regrantPermissions + ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL) + : 0)); + + // Verify that all of the preferred activity components actually + // exist. It is possible for applications to be updated and at + // that point remove a previously declared activity component that + // had been set as a preferred activity. We try to clean this up + // the next time we encounter that preferred activity, but it is + // possible for the user flow to never be able to return to that + // situation so here we do a sanity check to make sure we haven't + // left any junk around. + ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>(); + for (PreferredActivity pa : mSettings.mPreferredActivities.filterSet()) { + if (mActivities.mActivities.get(pa.mPref.mComponent) == null) { + removed.add(pa); + } + } + for (int i=0; i<removed.size(); i++) { + PreferredActivity pa = removed.get(i); + Slog.w(TAG, "Removing dangling preferred activity: " + + pa.mPref.mComponent); + mSettings.mPreferredActivities.removeFilter(pa); + } // can downgrade to reader mSettings.writeLPr(); @@ -1164,7 +1181,7 @@ public class PackageManagerService extends IPackageManager.Stub { private String getRequiredVerifierLPr() { final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE, - PackageManager.GET_DISABLED_COMPONENTS); + PackageManager.GET_DISABLED_COMPONENTS, 0 /* TODO: Which userId? */); String requiredVerifier = null; @@ -1218,7 +1235,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); } else { - mUserManager.removePackageForAllUsers(ps.name); + sUserManager.removePackageForAllUsers(ps.name); } if (ps.codePath != null) { if (!ps.codePath.delete()) { @@ -1426,22 +1443,6 @@ public class PackageManagerService extends IPackageManager.Stub { } } - static int[] appendInt(int[] cur, int val) { - if (cur == null) { - return new int[] { val }; - } - final int N = cur.length; - for (int i=0; i<N; i++) { - if (cur[i] == val) { - return cur; - } - } - int[] ret = new int[N+1]; - System.arraycopy(cur, 0, ret, 0, N); - ret[N] = val; - return ret; - } - static int[] appendInts(int[] cur, int[] add) { if (add == null) return cur; if (cur == null) return add; @@ -1452,26 +1453,6 @@ public class PackageManagerService extends IPackageManager.Stub { return cur; } - static int[] removeInt(int[] cur, int val) { - if (cur == null) { - return null; - } - final int N = cur.length; - for (int i=0; i<N; i++) { - if (cur[i] == val) { - int[] ret = new int[N-1]; - if (i > 0) { - System.arraycopy(cur, 0, ret, 0, i); - } - if (i < (N-1)) { - System.arraycopy(cur, i + 1, ret, i, N - i - 1); - } - return ret; - } - } - return cur; - } - static int[] removeInts(int[] cur, int[] rem) { if (rem == null) return cur; if (cur == null) return cur; @@ -1482,31 +1463,42 @@ public class PackageManagerService extends IPackageManager.Stub { return cur; } - PackageInfo generatePackageInfo(PackageParser.Package p, int flags) { + 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. - return PackageParser.generatePackageInfo(p, null, flags, 0, 0); - } - final PackageSetting ps = (PackageSetting)p.mExtras; - if (ps == null) { - return null; + 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); + pi.applicationInfo.enabled = + pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_DEFAULT + || pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_ENABLED; } - final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; - return PackageParser.generatePackageInfo(p, gp.gids, flags, - ps.firstInstallTime, ps.lastUpdateTime); + return pi; } - public PackageInfo getPackageInfo(String packageName, int flags) { + @Override + public PackageInfo getPackageInfo(String packageName, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getPackageInfo " + packageName + ": " + p); if (p != null) { - return generatePackageInfo(p, flags); + return generatePackageInfo(p, flags, userId); } if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - return generatePackageInfoFromSettingsLPw(packageName, flags); + return generatePackageInfoFromSettingsLPw(packageName, flags, userId); } } return null; @@ -1535,20 +1527,22 @@ public class PackageManagerService extends IPackageManager.Stub { } return out; } - - public int getPackageUid(String packageName) { + + @Override + public int getPackageUid(String packageName, int userId) { + if (!sUserManager.exists(userId)) return -1; // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); if(p != null) { - return p.applicationInfo.uid; + return UserId.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 ? p.applicationInfo.uid : -1; + return p != null ? UserId.getUid(userId, p.applicationInfo.uid) : -1; } } @@ -1561,7 +1555,16 @@ public class PackageManagerService extends IPackageManager.Stub { if (p != null) { final PackageSetting ps = (PackageSetting)p.mExtras; final SharedUserSetting suid = ps.sharedUser; - return suid != null ? suid.gids : ps.gids; + int[] gids = suid != null ? suid.gids : ps.gids; + + // include GIDs for any unenforced permissions + if (!isPermissionEnforcedLocked(READ_EXTERNAL_STORAGE)) { + final BasePermission basePerm = mSettings.mPermissions.get( + READ_EXTERNAL_STORAGE); + gids = appendInts(gids, basePerm.gids); + } + + return gids; } } // stupid thing to indicate an error. @@ -1636,24 +1639,30 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags) { + private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags, + int userId) { + if (!sUserManager.exists(userId)) return null; PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { if (ps.pkg == null) { - PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, flags); + PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, flags, userId); if (pInfo != null) { return pInfo.applicationInfo; } return null; } - return PackageParser.generateApplicationInfo(ps.pkg, flags); + return PackageParser.generateApplicationInfo(ps.pkg, flags, ps.getStopped(userId), + ps.getEnabled(userId), userId); } return null; } - private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags) { + private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags, + int userId) { + 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; @@ -1663,15 +1672,17 @@ public class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg.packageName, 0).getPath(); ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; - ps.pkg.mSetEnabled = ps.enabled; - ps.pkg.mSetStopped = ps.stopped; } - return generatePackageInfo(ps.pkg, flags); + // ps.pkg.mSetEnabled = ps.getEnabled(userId); + // ps.pkg.mSetStopped = ps.getStopped(userId); + return generatePackageInfo(ps.pkg, flags, userId); } return null; } - public ApplicationInfo getApplicationInfo(String packageName, int flags) { + @Override + public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; // writer synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); @@ -1679,14 +1690,17 @@ public class PackageManagerService extends IPackageManager.Stub { TAG, "getApplicationInfo " + packageName + ": " + p); if (p != null) { + 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); + return PackageParser.generateApplicationInfo(p, flags, ps.getStopped(userId), + ps.getEnabled(userId)); } if ("android".equals(packageName)||"system".equals(packageName)) { return mAndroidApplication; } if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - return generateApplicationInfoFromSettingsLPw(packageName, flags); + return generateApplicationInfoFromSettingsLPw(packageName, flags, userId); } } return null; @@ -1742,13 +1756,18 @@ public class PackageManagerService extends IPackageManager.Stub { }); } - public ActivityInfo getActivityInfo(ComponentName component, int flags) { + @Override + public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; synchronized (mPackages) { PackageParser.Activity a = mActivities.mActivities.get(component); if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); - if (a != null && mSettings.isEnabledLPr(a.info, flags)) { - return PackageParser.generateActivityInfo(a, flags); + 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); } if (mResolveComponentName.equals(component)) { return mResolveActivity; @@ -1757,37 +1776,52 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - public ActivityInfo getReceiverInfo(ComponentName component, int flags) { + @Override + public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; synchronized (mPackages) { PackageParser.Activity a = mReceivers.mActivities.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getReceiverInfo " + component + ": " + a); - if (a != null && mSettings.isEnabledLPr(a.info, flags)) { - return PackageParser.generateActivityInfo(a, flags); + 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 null; } - public ServiceInfo getServiceInfo(ComponentName component, int flags) { + @Override + public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; synchronized (mPackages) { PackageParser.Service s = mServices.mServices.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getServiceInfo " + component + ": " + s); - if (s != null && mSettings.isEnabledLPr(s.info, flags)) { - return PackageParser.generateServiceInfo(s, flags); + 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 null; } - public ProviderInfo getProviderInfo(ComponentName component, int flags) { + @Override + public ProviderInfo getProviderInfo(ComponentName component, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; synchronized (mPackages) { PackageParser.Provider p = mProvidersByComponent.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); - if (p != null && mSettings.isEnabledLPr(p.info, flags)) { - return PackageParser.generateProviderInfo(p, flags); + 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 null; @@ -1831,6 +1865,14 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private void checkValidCaller(int uid, int userId) { + if (UserId.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0) + return; + + throw new SecurityException("Caller uid=" + uid + + " is not privileged to communicate with user=" + userId); + } + public int checkPermission(String permName, String pkgName) { synchronized (mPackages) { PackageParser.Package p = mPackages.get(pkgName); @@ -1844,13 +1886,16 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.PERMISSION_GRANTED; } } + if (!isPermissionEnforcedLocked(permName)) { + return PackageManager.PERMISSION_GRANTED; + } } return PackageManager.PERMISSION_DENIED; } public int checkUidPermission(String permName, int uid) { synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(uid); + Object obj = mSettings.getUserIdLPr(UserId.getAppId(uid)); if (obj != null) { GrantedPermissions gp = (GrantedPermissions)obj; if (gp.grantedPermissions.contains(permName)) { @@ -1862,6 +1907,9 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.PERMISSION_GRANTED; } } + if (!isPermissionEnforcedLocked(permName)) { + return PackageManager.PERMISSION_GRANTED; + } } return PackageManager.PERMISSION_DENIED; } @@ -1881,7 +1929,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (permName != null) { BasePermission bp = findPermissionTreeLP(permName); if (bp != null) { - if (bp.uid == Binder.getCallingUid()) { + if (bp.uid == UserId.getAppId(Binder.getCallingUid())) { return bp; } throw new SecurityException("Calling uid " @@ -1930,6 +1978,7 @@ public class PackageManagerService extends IPackageManager.Stub { BasePermission bp = mSettings.mPermissions.get(info.name); boolean added = bp == null; boolean changed = true; + int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel); if (added) { bp = new BasePermission(info.name, tree.sourcePackage, BasePermission.TYPE_DYNAMIC); @@ -1938,16 +1987,17 @@ public class PackageManagerService extends IPackageManager.Stub { "Not allowed to modify non-dynamic permission " + info.name); } else { - if (bp.protectionLevel == info.protectionLevel + if (bp.protectionLevel == fixedLevel && bp.perm.owner.equals(tree.perm.owner) && bp.uid == tree.uid && comparePermissionInfos(bp.perm.info, info)) { changed = false; } } - bp.protectionLevel = info.protectionLevel; - bp.perm = new PackageParser.Permission(tree.perm.owner, - new PermissionInfo(info)); + bp.protectionLevel = fixedLevel; + info = new PermissionInfo(info); + info.protectionLevel = fixedLevel; + bp.perm = new PackageParser.Permission(tree.perm.owner, info); bp.perm.info.packageName = tree.perm.info.packageName; bp.uid = tree.uid; if (added) { @@ -1957,7 +2007,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (!async) { mSettings.writeLPr(); } else { - scheduleWriteSettingsLocked(); + scheduleWriteSettingsLocked(); } } return added; @@ -1991,6 +2041,77 @@ public class PackageManagerService extends IPackageManager.Stub { } } + public void grantPermission(String packageName, String permissionName) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null); + synchronized (mPackages) { + final PackageParser.Package pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + final BasePermission bp = mSettings.mPermissions.get(permissionName); + if (bp == null) { + throw new IllegalArgumentException("Unknown permission: " + packageName); + } + if (!pkg.requestedPermissions.contains(permissionName)) { + throw new SecurityException("Package " + packageName + + " has not requested permission " + permissionName); + } + if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) == 0) { + throw new SecurityException("Permission " + permissionName + + " is not a development permission"); + } + final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (ps == null) { + return; + } + final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; + if (gp.grantedPermissions.add(permissionName)) { + if (ps.haveGids) { + gp.gids = appendInts(gp.gids, bp.gids); + } + mSettings.writeLPr(); + } + } + } + + public void revokePermission(String packageName, String permissionName) { + synchronized (mPackages) { + final PackageParser.Package pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + if (pkg.applicationInfo.uid != Binder.getCallingUid()) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null); + } + final BasePermission bp = mSettings.mPermissions.get(permissionName); + if (bp == null) { + throw new IllegalArgumentException("Unknown permission: " + packageName); + } + if (!pkg.requestedPermissions.contains(permissionName)) { + throw new SecurityException("Package " + packageName + + " has not requested permission " + permissionName); + } + if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) == 0) { + throw new SecurityException("Permission " + permissionName + + " is not a development permission"); + } + final PackageSetting ps = (PackageSetting) pkg.mExtras; + if (ps == null) { + return; + } + final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; + if (gp.grantedPermissions.remove(permissionName)) { + gp.grantedPermissions.remove(permissionName); + if (ps.haveGids) { + gp.gids = removeInts(gp.gids, bp.gids); + } + mSettings.writeLPr(); + } + } + } + public boolean isProtectedBroadcast(String actionName) { synchronized (mPackages) { return mProtectedBroadcasts.contains(actionName); @@ -2010,6 +2131,9 @@ 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); // reader synchronized (mPackages) { Signature[] s1; @@ -2067,6 +2191,7 @@ public class PackageManagerService extends IPackageManager.Stub { } public String[] getPackagesForUid(int uid) { + uid = UserId.getAppId(uid); // reader synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(uid); @@ -2091,7 +2216,7 @@ public class PackageManagerService extends IPackageManager.Stub { public String getNameForUid(int uid) { // reader synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(uid); + Object obj = mSettings.getUserIdLPr(UserId.getAppId(uid)); if (obj instanceof SharedUserSetting) { final SharedUserSetting sus = (SharedUserSetting) obj; return sus.name + ":" + sus.userId; @@ -2110,21 +2235,23 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, false); - if(suid == null) { + if (suid == null) { return -1; } return suid.userId; } } + @Override public ResolveInfo resolveIntent(Intent intent, String resolvedType, - int flags) { - List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags); - return chooseBestActivity(intent, resolvedType, flags, query); + int flags, int userId) { + if (!sUserManager.exists(userId)) return null; + List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId); + return chooseBestActivity(intent, resolvedType, flags, query, userId); } private ResolveInfo chooseBestActivity(Intent intent, String resolvedType, - int flags, List<ResolveInfo> query) { + int flags, List<ResolveInfo> query, int userId) { if (query != null) { final int N = query.size(); if (N == 1) { @@ -2148,7 +2275,7 @@ public class PackageManagerService extends IPackageManager.Stub { // If we have saved a preference for a preferred activity for // this Intent, use that. ResolveInfo ri = findPreferredActivity(intent, resolvedType, - flags, query, r0.priority); + flags, query, r0.priority, userId); if (ri != null) { return ri; } @@ -2159,7 +2286,8 @@ public class PackageManagerService extends IPackageManager.Stub { } ResolveInfo findPreferredActivity(Intent intent, String resolvedType, - int flags, List<ResolveInfo> query, int priority) { + int flags, List<ResolveInfo> query, int priority, int userId) { + if (!sUserManager.exists(userId)) return null; // writer synchronized (mPackages) { if (intent.getSelector() != null) { @@ -2168,7 +2296,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); List<PreferredActivity> prefs = mSettings.mPreferredActivities.queryIntent(intent, resolvedType, - (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0); + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId); if (prefs != null && prefs.size() > 0) { // First figure out how good the original match set is. // We will only allow preferred activities that came @@ -2202,7 +2330,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (pa.mPref.mMatch != match) { continue; } - final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags); + final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags, userId); if (DEBUG_PREFERRED) { Log.v(TAG, "Got preferred activity:"); if (ai != null) { @@ -2211,31 +2339,40 @@ public class PackageManagerService extends IPackageManager.Stub { Log.v(TAG, " null"); } } - if (ai != null) { - for (int j=0; j<N; j++) { - final ResolveInfo ri = query.get(j); - if (!ri.activityInfo.applicationInfo.packageName - .equals(ai.applicationInfo.packageName)) { - continue; - } - if (!ri.activityInfo.name.equals(ai.name)) { - continue; - } - - // Okay we found a previously set preferred app. - // If the result set is different from when this - // was created, we need to clear it and re-ask the - // user their preference. - if (!pa.mPref.sameSet(query, priority)) { - Slog.i(TAG, "Result set changed, dropping preferred activity for " - + intent + " type " + resolvedType); - mSettings.mPreferredActivities.removeFilter(pa); - return null; - } + if (ai == null) { + // This previously registered preferred activity + // component is no longer known. Most likely an update + // to the app was installed and in the new version this + // component no longer exists. Clean it up by removing + // it from the preferred activities list, and skip it. + Slog.w(TAG, "Removing dangling preferred activity: " + + pa.mPref.mComponent); + mSettings.mPreferredActivities.removeFilter(pa); + continue; + } + for (int j=0; j<N; j++) { + final ResolveInfo ri = query.get(j); + if (!ri.activityInfo.applicationInfo.packageName + .equals(ai.applicationInfo.packageName)) { + continue; + } + if (!ri.activityInfo.name.equals(ai.name)) { + continue; + } - // Yay! - return ri; + // Okay we found a previously set preferred app. + // If the result set is different from when this + // was created, we need to clear it and re-ask the + // user their preference. + if (!pa.mPref.sameSet(query, priority)) { + Slog.i(TAG, "Result set changed, dropping preferred activity for " + + intent + " type " + resolvedType); + mSettings.mPreferredActivities.removeFilter(pa); + return null; } + + // Yay! + return ri; } } } @@ -2243,8 +2380,10 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } + @Override public List<ResolveInfo> queryIntentActivities(Intent intent, - String resolvedType, int flags) { + String resolvedType, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -2252,9 +2391,10 @@ public class PackageManagerService extends IPackageManager.Stub { comp = intent.getComponent(); } } + if (comp != null) { final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); - final ActivityInfo ai = getActivityInfo(comp, flags); + final ActivityInfo ai = getActivityInfo(comp, flags, userId); if (ai != null) { final ResolveInfo ri = new ResolveInfo(); ri.activityInfo = ai; @@ -2267,24 +2407,26 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { final String pkgName = intent.getPackage(); if (pkgName == null) { - return mActivities.queryIntent(intent, resolvedType, flags); + return mActivities.queryIntent(intent, resolvedType, flags, userId); } final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { return mActivities.queryIntentForPackage(intent, resolvedType, flags, - pkg.activities); + pkg.activities, userId); } return new ArrayList<ResolveInfo>(); } } + @Override public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics, String[] specificTypes, Intent intent, - String resolvedType, int flags) { + String resolvedType, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; final String resultsAction = intent.getAction(); List<ResolveInfo> results = queryIntentActivities(intent, resolvedType, flags - | PackageManager.GET_RESOLVED_FILTER); + | PackageManager.GET_RESOLVED_FILTER, userId); if (DEBUG_INTENT_MATCHING) { Log.v(TAG, "Query " + intent + ": " + results); @@ -2327,7 +2469,7 @@ public class PackageManagerService extends IPackageManager.Stub { ri = resolveIntent( sintent, specificTypes != null ? specificTypes[i] : null, - flags); + flags, userId); if (ri == null) { continue; } @@ -2338,7 +2480,7 @@ public class PackageManagerService extends IPackageManager.Stub { comp = new ComponentName(ai.applicationInfo.packageName, ai.name); } else { - ai = getActivityInfo(comp, flags); + ai = getActivityInfo(comp, flags, userId); if (ai == null) { continue; } @@ -2447,7 +2589,10 @@ public class PackageManagerService extends IPackageManager.Stub { return results; } - public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags) { + @Override + public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags, + int userId) { + if (!sUserManager.exists(userId)) return null; ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -2457,7 +2602,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (comp != null) { List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); - ActivityInfo ai = getReceiverInfo(comp, flags); + ActivityInfo ai = getReceiverInfo(comp, flags, userId); if (ai != null) { ResolveInfo ri = new ResolveInfo(); ri.activityInfo = ai; @@ -2470,18 +2615,21 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { String pkgName = intent.getPackage(); if (pkgName == null) { - return mReceivers.queryIntent(intent, resolvedType, flags); + return mReceivers.queryIntent(intent, resolvedType, flags, userId); } final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers); + return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers, + userId); } return null; } } - public ResolveInfo resolveService(Intent intent, String resolvedType, int flags) { - List<ResolveInfo> query = queryIntentServices(intent, resolvedType, flags); + @Override + public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) { + List<ResolveInfo> query = queryIntentServices(intent, resolvedType, flags, userId); + if (!sUserManager.exists(userId)) return null; if (query != null) { if (query.size() >= 1) { // If there is more than one service with the same priority, @@ -2492,7 +2640,10 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags) { + @Override + public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags, + int userId) { + if (!sUserManager.exists(userId)) return null; ComponentName comp = intent.getComponent(); if (comp == null) { if (intent.getSelector() != null) { @@ -2502,7 +2653,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (comp != null) { final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); - final ServiceInfo si = getServiceInfo(comp, flags); + final ServiceInfo si = getServiceInfo(comp, flags, userId); if (si != null) { final ResolveInfo ri = new ResolveInfo(); ri.serviceInfo = si; @@ -2515,11 +2666,12 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { String pkgName = intent.getPackage(); if (pkgName == null) { - return mServices.queryIntent(intent, resolvedType, flags); + return mServices.queryIntent(intent, resolvedType, flags, userId); } final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return mServices.queryIntentForPackage(intent, resolvedType, flags, pkg.services); + return mServices.queryIntentForPackage(intent, resolvedType, flags, pkg.services, + userId); } return null; } @@ -2544,6 +2696,7 @@ public class PackageManagerService extends IPackageManager.Stub { final ParceledListSlice<PackageInfo> list = new ParceledListSlice<PackageInfo>(); final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; final String[] keys; + int userId = UserId.getCallingUserId(); // writer synchronized (mPackages) { @@ -2564,12 +2717,12 @@ public class PackageManagerService extends IPackageManager.Stub { if (listUninstalled) { final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { - pi = generatePackageInfoFromSettingsLPw(ps.name, flags); + pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId); } } else { final PackageParser.Package p = mPackages.get(packageName); if (p != null) { - pi = generatePackageInfo(p, flags); + pi = generatePackageInfo(p, flags, userId); } } @@ -2586,8 +2739,10 @@ public class PackageManagerService extends IPackageManager.Stub { return list; } + @Override public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, - String lastRead) { + String lastRead, int userId) { + if (!sUserManager.exists(userId)) return null; final ParceledListSlice<ApplicationInfo> list = new ParceledListSlice<ApplicationInfo>(); final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; final String[] keys; @@ -2608,15 +2763,16 @@ public class PackageManagerService extends IPackageManager.Stub { final String packageName = keys[i++]; ApplicationInfo ai = null; + final PackageSetting ps = mSettings.mPackages.get(packageName); if (listUninstalled) { - final PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { - ai = generateApplicationInfoFromSettingsLPw(ps.name, flags); + ai = generateApplicationInfoFromSettingsLPw(ps.name, flags, userId); } } else { final PackageParser.Package p = mPackages.get(packageName); - if (p != null) { - ai = PackageParser.generateApplicationInfo(p, flags); + if (p != null && ps != null) { + ai = PackageParser.generateApplicationInfo(p, flags, ps.getStopped(userId), + ps.getEnabled(userId), userId); } } @@ -2639,12 +2795,17 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Package> i = mPackages.values().iterator(); + final int userId = UserId.getCallingUserId(); while (i.hasNext()) { final PackageParser.Package p = i.next(); if (p.applicationInfo != null && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0 && (!mSafeMode || isSystemApp(p))) { - finalList.add(PackageParser.generateApplicationInfo(p, flags)); + 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)); } } } @@ -2652,15 +2813,23 @@ public class PackageManagerService extends IPackageManager.Stub { return finalList; } - public ProviderInfo resolveContentProvider(String name, int flags) { + @Override + public ProviderInfo resolveContentProvider(String name, int flags, int userId) { + if (!sUserManager.exists(userId)) return null; // reader synchronized (mPackages) { final PackageParser.Provider provider = mProviders.get(name); + PackageSetting ps = provider != null + ? mSettings.mPackages.get(provider.owner.packageName) + : null; return provider != null - && mSettings.isEnabledLPr(provider.info, flags) + && mSettings.isEnabledLPr(provider.info, flags, userId) && (!mSafeMode || (provider.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0) - ? PackageParser.generateProviderInfo(provider, flags) + ? PackageParser.generateProviderInfo(provider, flags, + ps != null ? ps.getStopped(userId) : false, + ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, + userId) : null; } } @@ -2674,16 +2843,20 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() .iterator(); - + final int userId = UserId.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 && (!mSafeMode || (p.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0)) { outNames.add(entry.getKey()); - outInfo.add(PackageParser.generateProviderInfo(p, 0)); + outInfo.add(PackageParser.generateProviderInfo(p, 0, + ps != null ? ps.getStopped(userId) : false, + ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, + userId)); } } } @@ -2696,19 +2869,25 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); + final int userId = processName != null ? + UserId.getUserId(uid) : UserId.getCallingUserId(); while (i.hasNext()) { final PackageParser.Provider p = i.next(); + PackageSetting ps = mSettings.mPackages.get(p.owner.packageName); if (p.info.authority != null && (processName == null || (p.info.processName.equals(processName) - && p.info.applicationInfo.uid == uid)) - && mSettings.isEnabledLPr(p.info, flags) + && UserId.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)); + finalList.add(PackageParser.generateProviderInfo(p, flags, + ps != null ? ps.getStopped(userId) : false, + ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT, + userId)); } } } @@ -3352,7 +3531,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } - pkg.applicationInfo.uid = pkgSetting.userId; + pkg.applicationInfo.uid = pkgSetting.appId; pkg.mExtras = pkgSetting; if (!verifySignaturesLP(pkgSetting, pkg)) { @@ -3459,7 +3638,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (ret >= 0) { // TODO: Kill the processes first // Remove the data directories for all users - mUserManager.removePackageForAllUsers(pkgName); + sUserManager.removePackageForAllUsers(pkgName); // Old data gone! String msg = "System package " + pkg.packageName + " has changed from uid: " @@ -3480,7 +3659,7 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } // Create data directories for all users - mUserManager.installPackageForAllUsers(pkgName, + sUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); } if (!recovered) { @@ -3522,7 +3701,7 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } // Create data directories for all users - mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); + sUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); if (dataPath.exists()) { pkg.applicationInfo.dataDir = dataPath.getPath(); @@ -3946,8 +4125,6 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { - clearPackagePreferredActivitiesLPw(pkg.packageName); - mPackages.remove(pkg.applicationInfo.packageName); if (pkg.mPath != null) { mAppDirs.remove(pkg.mPath); @@ -4103,10 +4280,13 @@ public class PackageManagerService extends IPackageManager.Stub { } return false; } - + + static final int UPDATE_PERMISSIONS_ALL = 1<<0; + static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1; + static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2; + private void updatePermissionsLPw(String changingPkg, - PackageParser.Package pkgInfo, boolean grantPermissions, - boolean replace, boolean replaceAll) { + PackageParser.Package pkgInfo, int flags) { // Make sure there are no dangling permission trees. Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator(); while (it.hasNext()) { @@ -4124,7 +4304,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) { Slog.i(TAG, "Removing old permission tree: " + bp.name + " from package " + bp.sourcePackage); - grantPermissions = true; + flags |= UPDATE_PERMISSIONS_ALL; it.remove(); } } @@ -4164,7 +4344,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) { Slog.i(TAG, "Removing old permission: " + bp.name + " from package " + bp.sourcePackage); - grantPermissions = true; + flags |= UPDATE_PERMISSIONS_ALL; it.remove(); } } @@ -4172,16 +4352,16 @@ public class PackageManagerService extends IPackageManager.Stub { // Now update the permissions for all packages, in particular // replace the granted permissions of the system packages. - if (grantPermissions) { + if ((flags&UPDATE_PERMISSIONS_ALL) != 0) { for (PackageParser.Package pkg : mPackages.values()) { if (pkg != pkgInfo) { - grantPermissionsLPw(pkg, replaceAll); + grantPermissionsLPw(pkg, (flags&UPDATE_PERMISSIONS_REPLACE_ALL) != 0); } } } if (pkgInfo != null) { - grantPermissionsLPw(pkgInfo, replace); + grantPermissionsLPw(pkgInfo, (flags&UPDATE_PERMISSIONS_REPLACE_PKG) != 0); } } @@ -4191,11 +4371,13 @@ public class PackageManagerService extends IPackageManager.Stub { return; } final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; + HashSet<String> origPermissions = gp.grantedPermissions; boolean changedPermission = false; if (replace) { ps.permissionsFixed = false; if (gp == ps) { + origPermissions = new HashSet<String>(gp.grantedPermissions); gp.grantedPermissions.clear(); gp.gids = mGlobalGids; } @@ -4208,6 +4390,7 @@ public class PackageManagerService extends IPackageManager.Stub { final int N = pkg.requestedPermissions.size(); for (int i=0; i<N; i++) { final String name = pkg.requestedPermissions.get(i); + //final boolean required = pkg.requestedPermssionsRequired.get(i); final BasePermission bp = mSettings.mPermissions.get(name); if (DEBUG_INSTALL) { if (gp != ps) { @@ -4218,23 +4401,23 @@ public class PackageManagerService extends IPackageManager.Stub { final String perm = bp.name; boolean allowed; boolean allowedSig = false; - if (bp.protectionLevel == PermissionInfo.PROTECTION_NORMAL - || bp.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { + final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; + if (level == PermissionInfo.PROTECTION_NORMAL + || level == PermissionInfo.PROTECTION_DANGEROUS) { allowed = true; } else if (bp.packageSetting == null) { // This permission is invalid; skip it. allowed = false; - } else if (bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE - || bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { + } else if (level == PermissionInfo.PROTECTION_SIGNATURE) { allowed = (compareSignatures( bp.packageSetting.signatures.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH) || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH); - if (!allowed && bp.protectionLevel - == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { + if (!allowed && (bp.protectionLevel + & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) { if (isSystemApp(pkg)) { - // For updated system applications, the signatureOrSystem permission + // For updated system applications, a system permission // is granted only if it had been defined by the original application. if (isUpdatedSystemApp(pkg)) { final PackageSetting sysPs = mSettings @@ -4251,6 +4434,16 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + if (!allowed && (bp.protectionLevel + & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) { + // For development permissions, a development permission + // is granted only if it was already granted. + if (origPermissions.contains(perm)) { + allowed = true; + } else { + allowed = false; + } + } if (allowed) { allowedSig = true; } @@ -4336,19 +4529,23 @@ public class PackageManagerService extends IPackageManager.Stub { private final class ActivityIntentResolver extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> { public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, - boolean defaultOnly) { + boolean defaultOnly, int userId) { + if (!sUserManager.exists(userId)) return null; mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; - return super.queryIntent(intent, resolvedType, defaultOnly); + return super.queryIntent(intent, resolvedType, defaultOnly, userId); } - public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, + int userId) { + if (!sUserManager.exists(userId)) return null; mFlags = flags; return super.queryIntent(intent, resolvedType, - (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0); + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId); } public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, - int flags, ArrayList<PackageParser.Activity> packageActivities) { + int flags, ArrayList<PackageParser.Activity> packageActivities, int userId) { + if (!sUserManager.exists(userId)) return null; if (packageActivities == null) { return null; } @@ -4365,7 +4562,7 @@ public class PackageManagerService extends IPackageManager.Stub { listCut.add(intentFilters); } } - return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut); + return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId); } public final void addActivity(PackageParser.Activity a, String type) { @@ -4430,7 +4627,8 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter) { + protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter, int userId) { + if (!sUserManager.exists(userId)) return true; PackageParser.Package p = filter.activity.owner; if (p != null) { PackageSetting ps = (PackageSetting)p.mExtras; @@ -4438,7 +4636,7 @@ 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.stopped && (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0; + return ps.getStopped(userId) && (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0; } } return false; @@ -4451,8 +4649,9 @@ public class PackageManagerService extends IPackageManager.Stub { @Override protected ResolveInfo newResult(PackageParser.ActivityIntentInfo info, - int match) { - if (!mSettings.isEnabledLPr(info.activity.info, mFlags)) { + int match, int userId) { + if (!sUserManager.exists(userId)) return null; + if (!mSettings.isEnabledLPr(info.activity.info, mFlags, userId)) { return null; } final PackageParser.Activity activity = info.activity; @@ -4461,8 +4660,11 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } final ResolveInfo res = new ResolveInfo(); - res.activityInfo = PackageParser.generateActivityInfo(activity, - mFlags); + 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 ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = info; } @@ -4516,19 +4718,22 @@ public class PackageManagerService extends IPackageManager.Stub { private final class ServiceIntentResolver extends IntentResolver<PackageParser.ServiceIntentInfo, ResolveInfo> { public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, - boolean defaultOnly) { + boolean defaultOnly, int userId) { mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; - return super.queryIntent(intent, resolvedType, defaultOnly); + return super.queryIntent(intent, resolvedType, defaultOnly, userId); } - public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, + int userId) { + if (!sUserManager.exists(userId)) return null; mFlags = flags; return super.queryIntent(intent, resolvedType, - (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0); + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId); } public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, - int flags, ArrayList<PackageParser.Service> packageServices) { + int flags, ArrayList<PackageParser.Service> packageServices, int userId) { + if (!sUserManager.exists(userId)) return null; if (packageServices == null) { return null; } @@ -4545,7 +4750,7 @@ public class PackageManagerService extends IPackageManager.Stub { listCut.add(intentFilters); } } - return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut); + return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId); } public final void addService(PackageParser.Service s) { @@ -4605,7 +4810,8 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override - protected boolean isFilterStopped(PackageParser.ServiceIntentInfo filter) { + protected boolean isFilterStopped(PackageParser.ServiceIntentInfo filter, int userId) { + if (!sUserManager.exists(userId)) return true; PackageParser.Package p = filter.service.owner; if (p != null) { PackageSetting ps = (PackageSetting)p.mExtras; @@ -4613,7 +4819,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.stopped && (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0; + return ps.getStopped(userId) + && (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0; } } return false; @@ -4626,9 +4833,10 @@ public class PackageManagerService extends IPackageManager.Stub { @Override protected ResolveInfo newResult(PackageParser.ServiceIntentInfo filter, - int match) { + int match, int userId) { + if (!sUserManager.exists(userId)) return null; final PackageParser.ServiceIntentInfo info = (PackageParser.ServiceIntentInfo)filter; - if (!mSettings.isEnabledLPr(info.service.info, mFlags)) { + if (!mSettings.isEnabledLPr(info.service.info, mFlags, userId)) { return null; } final PackageParser.Service service = info.service; @@ -4637,8 +4845,11 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } final ResolveInfo res = new ResolveInfo(); - res.serviceInfo = PackageParser.generateServiceInfo(service, - mFlags); + 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 ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = filter; } @@ -4729,21 +4940,32 @@ public class PackageManagerService extends IPackageManager.Stub { }; static final void sendPackageBroadcast(String action, String pkg, - Bundle extras, String targetPkg, IIntentReceiver finishedReceiver) { + Bundle extras, String targetPkg, IIntentReceiver finishedReceiver, int userId) { IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { try { - final Intent intent = new Intent(action, - pkg != null ? Uri.fromParts("package", pkg, null) : null); - if (extras != null) { - intent.putExtras(extras); - } - if (targetPkg != null) { - intent.setPackage(targetPkg); + int[] userIds = userId == UserId.USER_ALL + ? sUserManager.getUserIds() + : new int[] {userId}; + for (int id : userIds) { + final Intent intent = new Intent(action, + pkg != null ? Uri.fromParts("package", pkg, null) : null); + if (extras != null) { + intent.putExtras(extras); + } + if (targetPkg != null) { + intent.setPackage(targetPkg); + } + // 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)); + intent.putExtra(Intent.EXTRA_UID, uid); + } + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + am.broadcastIntent(null, intent, null, finishedReceiver, + 0, null, null, null, finishedReceiver != null, false, id); } - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - am.broadcastIntent(null, intent, null, finishedReceiver, - 0, null, null, null, finishedReceiver != null, false); } catch (RemoteException ex) { } } @@ -4867,7 +5089,7 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { updatePermissionsLPw(p.packageName, p, - p.permissions.size() > 0, false, false); + p.permissions.size() > 0 ? UPDATE_PERMISSIONS_ALL : 0); } addedPackage = p.applicationInfo.packageName; addedUid = p.applicationInfo.uid; @@ -4886,13 +5108,13 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putInt(Intent.EXTRA_UID, removedUid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false); sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, - extras, null, null); + extras, null, null, UserId.USER_ALL); } if (addedPackage != null) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, addedUid); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, - extras, null, null); + extras, null, null, UserId.USER_ALL); } } @@ -5163,7 +5385,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { installPackageLI(args, true, res); } - args.doPostInstall(res.returnCode); + args.doPostInstall(res.returnCode, res.uid); } // A restore should be performed at this point if (a) the install @@ -5413,7 +5635,6 @@ public class PackageManagerService extends IPackageManager.Stub { */ public void handleStartCopy() throws RemoteException { int ret = PackageManager.INSTALL_SUCCEEDED; - final boolean fwdLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0; final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0; PackageInfoLite pkgLite = null; @@ -5422,10 +5643,6 @@ public class PackageManagerService extends IPackageManager.Stub { // Check if both bits are set. Slog.w(TAG, "Conflicting flags specified for installing on both internal and external"); ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; - } else if (fwdLocked && onSd) { - // Check for forward locked apps - Slog.w(TAG, "Cannot install fwd locked apps on sdcard"); - ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { final long lowThreshold; @@ -5489,14 +5706,14 @@ public class PackageManagerService extends IPackageManager.Stub { * do, then we'll defer to them to verify the packages. */ final int requiredUid = mRequiredVerifierPackage == null ? -1 - : getPackageUid(mRequiredVerifierPackage); + : getPackageUid(mRequiredVerifierPackage, 0); if (requiredUid != -1 && isVerificationEnabled()) { final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION); verification.setDataAndType(packageURI, PACKAGE_MIME_TYPE); verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); final List<ResolveInfo> receivers = queryIntentReceivers(verification, null, - PackageManager.GET_DISABLED_COMPONENTS); + PackageManager.GET_DISABLED_COMPONENTS, 0 /* TODO: Which userId? */); if (DEBUG_VERIFY) { Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent " @@ -5602,6 +5819,10 @@ public class PackageManagerService extends IPackageManager.Stub { mArgs = createInstallArgs(this); mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR; } + + public boolean isForwardLocked() { + return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; + } } /* @@ -5617,14 +5838,16 @@ public class PackageManagerService extends IPackageManager.Stub { final String packageName; final InstallArgs srcArgs; final InstallArgs targetArgs; + int uid; int mRet; MoveParams(InstallArgs srcArgs, IPackageMoveObserver observer, int flags, - String packageName, String dataDir) { + String packageName, String dataDir, int uid) { this.srcArgs = srcArgs; this.observer = observer; this.flags = flags; this.packageName = packageName; + this.uid = uid; if (srcArgs != null) { Uri packageUri = Uri.fromFile(new File(srcArgs.getCodePath())); targetArgs = createInstallArgs(packageUri, flags, packageName, dataDir); @@ -5659,7 +5882,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override void handleReturnCode() { - targetArgs.doPostInstall(mRet); + targetArgs.doPostInstall(mRet, uid); int currentStatus = PackageManager.MOVE_FAILED_INTERNAL_ERROR; if (mRet == PackageManager.INSTALL_SUCCEEDED) { currentStatus = PackageManager.MOVE_SUCCEEDED; @@ -5675,9 +5898,35 @@ public class PackageManagerService extends IPackageManager.Stub { } } + /** + * Used during creation of InstallArgs + * + * @param flags package installation flags + * @return true if should be installed on external storage + */ + private static boolean installOnSd(int flags) { + if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { + return false; + } + if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { + return true; + } + return false; + } + + /** + * Used during creation of InstallArgs + * + * @param flags package installation flags + * @return true if should be installed as forward locked + */ + private static boolean installForwardLocked(int flags) { + return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; + } + private InstallArgs createInstallArgs(InstallParams params) { - if (installOnSd(params.flags)) { - return new SdInstallArgs(params); + if (installOnSd(params.flags) || params.isForwardLocked()) { + return new AsecInstallArgs(params); } else { return new FileInstallArgs(params); } @@ -5685,8 +5934,9 @@ public class PackageManagerService extends IPackageManager.Stub { private InstallArgs createInstallArgs(int flags, String fullCodePath, String fullResourcePath, String nativeLibraryPath) { - if (installOnSd(flags)) { - return new SdInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath); + if (installOnSd(flags) || installForwardLocked(flags)) { + return new AsecInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath, + (flags & PackageManager.INSTALL_EXTERNAL) != 0); } else { return new FileInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath); } @@ -5694,9 +5944,10 @@ public class PackageManagerService extends IPackageManager.Stub { // Used by package mover private InstallArgs createInstallArgs(Uri packageURI, int flags, String pkgName, String dataDir) { - if (installOnSd(flags)) { - String cid = getNextCodePath(null, pkgName, "/" + SdInstallArgs.RES_FILE_NAME); - return new SdInstallArgs(packageURI, cid); + if (installOnSd(flags) || installForwardLocked(flags)) { + String cid = getNextCodePath(null, pkgName, "/" + AsecInstallArgs.RES_FILE_NAME); + return new AsecInstallArgs(packageURI, cid, + (flags & PackageManager.INSTALL_EXTERNAL) != 0); } else { return new FileInstallArgs(packageURI, pkgName, dataDir); } @@ -5723,7 +5974,8 @@ public class PackageManagerService extends IPackageManager.Stub { abstract int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException; abstract int doPreInstall(int status); abstract boolean doRename(int status, String pkgName, String oldCodePath); - abstract int doPostInstall(int status); + + abstract int doPostInstall(int status, int uid); abstract String getCodePath(); abstract String getResourcePath(); abstract String getNativeLibraryPath(); @@ -5731,6 +5983,10 @@ public class PackageManagerService extends IPackageManager.Stub { abstract void cleanUpResourcesLI(); abstract boolean doPostDeleteLI(boolean delete); abstract boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException; + + protected boolean isFwdLocked() { + return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; + } } class FileInstallArgs extends InstallArgs { @@ -5783,7 +6039,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkInternalFreeStorage(packageURI, lowThreshold); + return imcs.checkInternalFreeStorage(packageURI, isFwdLocked(), lowThreshold); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5834,10 +6090,23 @@ public class PackageManagerService extends IPackageManager.Stub { Intent.FLAG_GRANT_READ_URI_PERMISSION); ret = imcs.copyResource(packageURI, out); } finally { - try { if (out != null) out.close(); } catch (IOException e) {} + IoUtils.closeQuietly(out); mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } + if (isFwdLocked()) { + final File destResourceFile = new File(getResourcePath()); + + // Copy the public files + try { + PackageHelper.extractPublicFiles(codeFileName, destResourceFile); + } catch (IOException e) { + Slog.e(TAG, "Couldn't create a new zip file for the public parts of a" + + " forward-locked app."); + destResourceFile.delete(); + return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } + } return ret; } @@ -5853,26 +6122,34 @@ public class PackageManagerService extends IPackageManager.Stub { cleanUp(); return false; } else { - // Rename based on packageName - File codeFile = new File(getCodePath()); - String apkName = getNextCodePath(oldCodePath, pkgName, ".apk"); - File desFile = new File(installDir, apkName + ".apk"); - if (!codeFile.renameTo(desFile)) { + final File oldCodeFile = new File(getCodePath()); + final File oldResourceFile = new File(getResourcePath()); + + // Rename APK file based on packageName + final String apkName = getNextCodePath(oldCodePath, pkgName, ".apk"); + final File newCodeFile = new File(installDir, apkName + ".apk"); + if (!oldCodeFile.renameTo(newCodeFile)) { + return false; + } + codeFileName = newCodeFile.getPath(); + + // Rename public resource file if it's forward-locked. + final File newResFile = new File(getResourcePathFromCodePath()); + if (isFwdLocked() && !oldResourceFile.renameTo(newResFile)) { return false; } - // Reset paths since the file has been renamed. - codeFileName = desFile.getPath(); resourceFileName = getResourcePathFromCodePath(); - // Set permissions + + // Attempt to set permissions if (!setPermissions()) { - // Failed setting permissions. return false; } + return true; } } - int doPostInstall(int status) { + int doPostInstall(int status, int uid) { if (status != PackageManager.INSTALL_SUCCEEDED) { cleanUp(); } @@ -5883,11 +6160,26 @@ public class PackageManagerService extends IPackageManager.Stub { return resourceFileName; } - String getResourcePathFromCodePath() { - String codePath = getCodePath(); - if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { - String apkNameOnly = getApkName(codePath); - return mAppInstallDir.getPath() + "/" + apkNameOnly + ".zip"; + private String getResourcePathFromCodePath() { + final String codePath = getCodePath(); + if (isFwdLocked()) { + final StringBuilder sb = new StringBuilder(); + + sb.append(mAppInstallDir.getPath()); + sb.append('/'); + sb.append(getApkName(codePath)); + sb.append(".zip"); + + /* + * If our APK is a temporary file, mark the resource as a + * temporary file as well so it can be cleaned up after + * catastrophic failure. + */ + if (codePath.endsWith(".tmp")) { + sb.append(".tmp"); + } + + return sb.toString(); } else { return codePath; } @@ -5960,10 +6252,6 @@ public class PackageManagerService extends IPackageManager.Stub { cleanUpResourcesLI(); return true; } - - private boolean isFwdLocked() { - return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; - } } /** @@ -5977,20 +6265,23 @@ public class PackageManagerService extends IPackageManager.Stub { return subStr1.substring(sidx+1, eidx); } - class SdInstallArgs extends InstallArgs { + class AsecInstallArgs extends InstallArgs { static final String RES_FILE_NAME = "pkg.apk"; + static final String PUBLIC_RES_FILE_NAME = "res.zip"; String cid; String packagePath; + String resourcePath; String libraryPath; - SdInstallArgs(InstallParams params) { + AsecInstallArgs(InstallParams params) { super(params.packageURI, params.observer, params.flags, params.installerPackageName, params.manifestDigest); } - SdInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) { - super(null, null, PackageManager.INSTALL_EXTERNAL, null, null); + AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath, + boolean isExternal) { + super(null, null, isExternal ? PackageManager.INSTALL_EXTERNAL : 0, null, null); // Extract cid from fullCodePath int eidx = fullCodePath.lastIndexOf("/"); String subStr1 = fullCodePath.substring(0, eidx); @@ -5999,14 +6290,14 @@ public class PackageManagerService extends IPackageManager.Stub { setCachePath(subStr1); } - SdInstallArgs(String cid) { - super(null, null, PackageManager.INSTALL_EXTERNAL, null, null); + AsecInstallArgs(String cid) { + super(null, null, 0, null, null); this.cid = cid; setCachePath(PackageHelper.getSdDir(cid)); } - SdInstallArgs(Uri packageURI, String cid) { - super(packageURI, null, PackageManager.INSTALL_EXTERNAL, null, null); + AsecInstallArgs(Uri packageURI, String cid, boolean isExternal) { + super(packageURI, null, isExternal ? PackageManager.INSTALL_EXTERNAL : 0, null, null); this.cid = cid; } @@ -6018,12 +6309,16 @@ public class PackageManagerService extends IPackageManager.Stub { try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkExternalFreeStorage(packageURI); + return imcs.checkExternalFreeStorage(packageURI, isFwdLocked()); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } } + private final boolean isExternal() { + return (flags & PackageManager.INSTALL_EXTERNAL) != 0; + } + int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException { if (temp) { createCopyFile(); @@ -6039,8 +6334,8 @@ public class PackageManagerService extends IPackageManager.Stub { try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - newCachePath = imcs.copyResourceToContainer(packageURI, cid, - getEncryptKey(), RES_FILE_NAME); + newCachePath = imcs.copyResourceToContainer(packageURI, cid, getEncryptKey(), + RES_FILE_NAME, PUBLIC_RES_FILE_NAME, isExternal(), isFwdLocked()); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -6060,7 +6355,7 @@ public class PackageManagerService extends IPackageManager.Stub { @Override String getResourcePath() { - return packagePath; + return resourcePath; } @Override @@ -6136,22 +6431,36 @@ public class PackageManagerService extends IPackageManager.Stub { File cachePath = new File(newCachePath); libraryPath = new File(cachePath, LIB_DIR_NAME).getPath(); packagePath = new File(cachePath, RES_FILE_NAME).getPath(); + + if (isFwdLocked()) { + resourcePath = new File(cachePath, PUBLIC_RES_FILE_NAME).getPath(); + } else { + resourcePath = packagePath; + } } - int doPostInstall(int status) { + int doPostInstall(int status, int uid) { if (status != PackageManager.INSTALL_SUCCEEDED) { cleanUp(); } else { + if (uid < Process.FIRST_APPLICATION_UID + || !PackageHelper.fixSdPermissions(cid, uid, RES_FILE_NAME)) { + Slog.e(TAG, "Failed to finalize " + cid); + PackageHelper.destroySdDir(cid); + return PackageManager.INSTALL_FAILED_CONTAINER_ERROR; + } + boolean mounted = PackageHelper.isContainerMounted(cid); if (!mounted) { - PackageHelper.mountSdDir(cid, - getEncryptKey(), Process.myUid()); + PackageHelper.mountSdDir(cid, getEncryptKey(), Process.myUid()); } } return status; } private void cleanUp() { + if (DEBUG_SD_INSTALL) Slog.i(TAG, "cleanUp"); + // Destroy secure container PackageHelper.destroySdDir(cid); } @@ -6348,6 +6657,7 @@ public class PackageManagerService extends IPackageManager.Stub { oldPackage = mPackages.get(pkgName); if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { + Slog.w(TAG, "New package has a different signature: " + pkgName); res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; return; } @@ -6415,10 +6725,6 @@ public class PackageManagerService extends IPackageManager.Stub { // package that we deleted. if(deletedPkg) { File restoreFile = new File(deletedPackage.mPath); - if (restoreFile == null) { - Slog.e(TAG, "Failed allocating storage when restoring pkg : " + pkgName); - return; - } // Parse old package boolean oldOnSd = isExternal(deletedPackage); int oldParseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | @@ -6435,7 +6741,7 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { updatePermissionsLPw(deletedPackage.packageName, deletedPackage, - true, false, false); + UPDATE_PERMISSIONS_ALL); // can downgrade to reader mSettings.writeLPr(); } @@ -6483,8 +6789,7 @@ public class PackageManagerService extends IPackageManager.Stub { // We didn't need to disable the .apk as a current system package, // which means we are replacing another update that is already // installed. We need to make sure to delete the older one's .apk. - res.removedInfo.args = createInstallArgs(isExternal(pkg) - ? PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL, + res.removedInfo.args = createInstallArgs(0, deletedPackage.applicationInfo.sourceDir, deletedPackage.applicationInfo.publicSourceDir, deletedPackage.applicationInfo.nativeLibraryDir); @@ -6570,16 +6875,13 @@ public class PackageManagerService extends IPackageManager.Stub { // Discontinue if moving dex files failed. return; } - if((res.returnCode = setPermissionsLI(newPackage)) - != PackageManager.INSTALL_SUCCEEDED) { - mInstaller.rmdex(newPackage.mScanPath); - return; - } else { - Log.d(TAG, "New package installed in " + newPackage.mPath); - } + + Log.d(TAG, "New package installed in " + newPackage.mPath); + synchronized (mPackages) { updatePermissionsLPw(newPackage.packageName, newPackage, - newPackage.permissions.size() > 0, true, false); + UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0 + ? UPDATE_PERMISSIONS_ALL : 0)); res.name = pkgName; res.uid = newPackage.applicationInfo.uid; res.pkg = newPackage; @@ -6605,10 +6907,9 @@ public class PackageManagerService extends IPackageManager.Stub { res.returnCode = PackageManager.INSTALL_SUCCEEDED; // Retrieve PackageSettings and parse package - int parseFlags = PackageParser.PARSE_CHATTY | - (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) | - (onSd ? PackageParser.PARSE_ON_SDCARD : 0); - parseFlags |= mDefParseFlags; + int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY + | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) + | (onSd ? PackageParser.PARSE_ON_SDCARD : 0); PackageParser pp = new PackageParser(tmpPackageFile.getPath()); pp.setSeparateProcesses(mSeparateProcesses); final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile, @@ -6705,37 +7006,6 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private int setPermissionsLI(PackageParser.Package newPackage) { - int retCode = 0; - // TODO Gross hack but fix later. Ideally move this to be a post installation - // check after alloting uid. - if (isForwardLocked(newPackage)) { - File destResourceFile = new File(newPackage.applicationInfo.publicSourceDir); - try { - extractPublicFiles(newPackage, destResourceFile); - } catch (IOException e) { - Slog.e(TAG, "Couldn't create a new zip file for the public parts of a" + - " forward-locked app."); - return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } finally { - //TODO clean up the extracted public files - } - retCode = mInstaller.setForwardLockPerm(getApkName(newPackage.mPath), - newPackage.applicationInfo.uid); - } else { - // The permissions on the resource file was set when it was copied for - // non forward locked apps and apps on sdcard - } - - if (retCode != 0) { - Slog.e(TAG, "Couldn't set new package file permissions for " + newPackage.mPath - + ". The return code was: " + retCode); - // TODO Define new internal error - return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } - return PackageManager.INSTALL_SUCCEEDED; - } - private static boolean isForwardLocked(PackageParser.Package pkg) { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0; } @@ -6744,6 +7014,10 @@ public class PackageManagerService extends IPackageManager.Stub { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; } + private static boolean isExternal(PackageSetting ps) { + return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0; + } + private static boolean isSystemApp(PackageParser.Package pkg) { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } @@ -6756,67 +7030,6 @@ public class PackageManagerService extends IPackageManager.Stub { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; } - private void extractPublicFiles(PackageParser.Package newPackage, - File publicZipFile) throws IOException { - final FileOutputStream fstr = new FileOutputStream(publicZipFile); - final ZipOutputStream publicZipOutStream = new ZipOutputStream(fstr); - final ZipFile privateZip = new ZipFile(newPackage.mPath); - - // Copy manifest, resources.arsc and res directory to public zip - - final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries(); - while (privateZipEntries.hasMoreElements()) { - final ZipEntry zipEntry = privateZipEntries.nextElement(); - final String zipEntryName = zipEntry.getName(); - if ("AndroidManifest.xml".equals(zipEntryName) - || "resources.arsc".equals(zipEntryName) - || zipEntryName.startsWith("res/")) { - try { - copyZipEntry(zipEntry, privateZip, publicZipOutStream); - } catch (IOException e) { - try { - publicZipOutStream.close(); - throw e; - } finally { - publicZipFile.delete(); - } - } - } - } - - publicZipOutStream.finish(); - publicZipOutStream.flush(); - FileUtils.sync(fstr); - publicZipOutStream.close(); - FileUtils.setPermissions( - publicZipFile.getAbsolutePath(), - FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP|FileUtils.S_IROTH, - -1, -1); - } - - private static void copyZipEntry(ZipEntry zipEntry, - ZipFile inZipFile, - ZipOutputStream outZipStream) throws IOException { - byte[] buffer = new byte[4096]; - int num; - - ZipEntry newEntry; - if (zipEntry.getMethod() == ZipEntry.STORED) { - // Preserve the STORED method of the input entry. - newEntry = new ZipEntry(zipEntry); - } else { - // Create a new entry so that the compressed len is recomputed. - newEntry = new ZipEntry(zipEntry.getName()); - } - outZipStream.putNextEntry(newEntry); - - InputStream data = inZipFile.getInputStream(zipEntry); - while ((num = data.read(buffer)) > 0) { - outZipStream.write(buffer, 0, num); - } - outZipStream.flush(); - } - private void deleteTempPackageFiles() { FilenameFilter filter = new FilenameFilter() { public boolean accept(File dir, String name) { @@ -6919,11 +7132,11 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putBoolean(Intent.EXTRA_REPLACING, true); sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, null, null); + extras, null, null, UserId.USER_ALL); sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, - extras, null, null); + extras, null, null, UserId.USER_ALL); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, - null, packageName, null); + null, packageName, null, UserId.USER_ALL); } } // Force a gc here. @@ -6956,14 +7169,15 @@ public class PackageManagerService extends IPackageManager.Stub { } if (removedPackage != null) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, - extras, null, null); + extras, null, null, UserId.USER_ALL); if (fullRemove && !replacing) { sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, - extras, null, null); + extras, null, null, UserId.USER_ALL); } } if (removedUid >= 0) { - sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null); + sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null, + UserId.getUserId(removedUid)); } } } @@ -6995,7 +7209,7 @@ public class PackageManagerService extends IPackageManager.Stub { // we don't consider this to be a failure of the core package deletion } else { // TODO: Kill the processes first - mUserManager.removePackageForAllUsers(packageName); + sUserManager.removePackageForAllUsers(packageName); } schedulePackageCleaning(packageName); } @@ -7007,22 +7221,13 @@ public class PackageManagerService extends IPackageManager.Stub { outInfo.removedUid = mSettings.removePackageLPw(packageName); } if (deletedPs != null) { - updatePermissionsLPw(deletedPs.name, null, false, false, false); + updatePermissionsLPw(deletedPs.name, null, 0); if (deletedPs.sharedUser != null) { // remove permissions associated with package mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids); } } - } - // remove from preferred activities. - ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>(); - for (PreferredActivity pa : mSettings.mPreferredActivities.filterSet()) { - if (pa.mPref.mComponent.getPackageName().equals(deletedPs.name)) { - removed.add(pa); - } - } - for (PreferredActivity pa : removed) { - mSettings.mPreferredActivities.removeFilter(pa); + clearPackagePreferredActivitiesLPw(deletedPs.name); } } // can downgrade to reader @@ -7090,7 +7295,8 @@ public class PackageManagerService extends IPackageManager.Stub { } // writer synchronized (mPackages) { - updatePermissionsLPw(newPkg.packageName, newPkg, true, true, false); + updatePermissionsLPw(newPkg.packageName, newPkg, + UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG); // can downgrade to reader here if (writeSettings) { mSettings.writeLPr(); @@ -7181,17 +7387,19 @@ public class PackageManagerService extends IPackageManager.Stub { return ret; } + @Override public void clearApplicationUserData(final String packageName, - final IPackageDataObserver observer) { + final IPackageDataObserver observer, final int userId) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CLEAR_APP_USER_DATA, null); + checkValidCaller(Binder.getCallingUid(), userId); // Queue up an async operation since the package deletion may take a little while. mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); final boolean succeeded; synchronized (mInstallLock) { - succeeded = clearApplicationUserDataLI(packageName); + succeeded = clearApplicationUserDataLI(packageName, userId); } if (succeeded) { // invoke DeviceStorageMonitor's update method to clear any notifications @@ -7212,7 +7420,7 @@ public class PackageManagerService extends IPackageManager.Stub { }); } - private boolean clearApplicationUserDataLI(String packageName) { + private boolean clearApplicationUserDataLI(String packageName, int userId) { if (packageName == null) { Slog.w(TAG, "Attempt to delete null packageName."); return false; @@ -7244,7 +7452,7 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } } - int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId + int retCode = mInstaller.clearUserData(packageName, userId); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -7258,12 +7466,13 @@ 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(); mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); final boolean succeded; synchronized (mInstallLock) { - succeded = deleteApplicationCacheFilesLI(packageName); + succeded = deleteApplicationCacheFilesLI(packageName, userId); } if(observer != null) { try { @@ -7276,7 +7485,7 @@ public class PackageManagerService extends IPackageManager.Stub { }); } - private boolean deleteApplicationCacheFilesLI(String packageName) { + private boolean deleteApplicationCacheFilesLI(String packageName, int userId) { if (packageName == null) { Slog.w(TAG, "Attempt to delete null packageName."); return false; @@ -7294,6 +7503,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package " + packageName + " has no applicationInfo."); return false; } + // TODO: Pass userId to deleteCacheFiles int retCode = mInstaller.deleteCacheFiles(packageName); if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " @@ -7463,17 +7673,27 @@ public class PackageManagerService extends IPackageManager.Stub { android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null); } + 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.getAction(0).equals(action) && pa.getCategory(0).equals(category)) { - it.remove(); - Log.i(TAG, "Removed preferred activity " + pa.mPref.mComponent + ":"); + if (removed == null) { + removed = new ArrayList<PreferredActivity>(); + } + removed.add(pa); + Log.i(TAG, "Removing preferred activity " + pa.mPref.mComponent + ":"); filter.dump(new LogPrinter(Log.INFO, TAG), " "); } } + if (removed != null) { + for (int i=0; i<removed.size(); i++) { + PreferredActivity pa = removed.get(i); + mSettings.mPreferredActivities.removeFilter(pa); + } + } addPreferredActivity(filter, match, set, activity); } } @@ -7505,16 +7725,25 @@ public class PackageManagerService extends IPackageManager.Stub { } boolean clearPackagePreferredActivitiesLPw(String packageName) { - boolean changed = false; + ArrayList<PreferredActivity> removed = null; Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); while (it.hasNext()) { PreferredActivity pa = it.next(); if (pa.mPref.mComponent.getPackageName().equals(packageName)) { - it.remove(); - changed = true; + if (removed == null) { + removed = new ArrayList<PreferredActivity>(); + } + removed.add(pa); } } - return changed; + if (removed != null) { + for (int i=0; i<removed.size(); i++) { + PreferredActivity pa = removed.get(i); + mSettings.mPreferredActivities.removeFilter(pa); + } + return true; + } + return false; } public int getPreferredActivities(List<IntentFilter> outFilters, @@ -7541,19 +7770,23 @@ public class PackageManagerService extends IPackageManager.Stub { return num; } + @Override public void setApplicationEnabledSetting(String appPackageName, - int newState, int flags) { - setEnabledSetting(appPackageName, null, newState, flags); + int newState, int flags, int userId) { + if (!sUserManager.exists(userId)) return; + setEnabledSetting(appPackageName, null, newState, flags, userId); } + @Override public void setComponentEnabledSetting(ComponentName componentName, - int newState, int flags) { + int newState, int flags, int userId) { + if (!sUserManager.exists(userId)) return; setEnabledSetting(componentName.getPackageName(), - componentName.getClassName(), newState, flags); + componentName.getClassName(), newState, flags, userId); } private void setEnabledSetting( - final String packageName, String className, int newState, final int flags) { + final String packageName, String className, int newState, final int flags, int userId) { if (!(newState == COMPONENT_ENABLED_STATE_DEFAULT || newState == COMPONENT_ENABLED_STATE_ENABLED || newState == COMPONENT_ENABLED_STATE_DISABLED @@ -7565,6 +7798,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); final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); boolean sendNow = false; boolean isApp = (className == null); @@ -7584,35 +7818,47 @@ public class PackageManagerService extends IPackageManager.Stub { "Unknown component: " + packageName + "/" + className); } - if (!allowedByPermission && (uid != pkgSetting.userId)) { + // Allow root and verify that userId is not being specified by a different user + if (!allowedByPermission && !UserId.isSameApp(uid, pkgSetting.appId)) { throw new SecurityException( "Permission Denial: attempt to change component state from pid=" + Binder.getCallingPid() - + ", uid=" + uid + ", package uid=" + pkgSetting.userId); + + ", uid=" + uid + ", package uid=" + pkgSetting.appId); } if (className == null) { // We're dealing with an application/package level state change - if (pkgSetting.enabled == newState) { + if (pkgSetting.getEnabled(userId) == newState) { // Nothing to do return; } - pkgSetting.enabled = newState; - pkgSetting.pkg.mSetEnabled = newState; + pkgSetting.setEnabled(newState, userId); + // pkgSetting.pkg.mSetEnabled = newState; } else { // We're dealing with a component level state change + // First, verify that this is a valid class name. + PackageParser.Package pkg = pkgSetting.pkg; + if (pkg == null || !pkg.hasComponentClassName(className)) { + if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { + throw new IllegalArgumentException("Component class " + className + + " does not exist in " + packageName); + } else { + Slog.w(TAG, "Failed setComponentEnabledSetting: component class " + + className + " does not exist in " + packageName); + } + } switch (newState) { case COMPONENT_ENABLED_STATE_ENABLED: - if (!pkgSetting.enableComponentLPw(className)) { + if (!pkgSetting.enableComponentLPw(className, userId)) { return; } break; case COMPONENT_ENABLED_STATE_DISABLED: - if (!pkgSetting.disableComponentLPw(className)) { + if (!pkgSetting.disableComponentLPw(className, userId)) { return; } break; case COMPONENT_ENABLED_STATE_DEFAULT: - if (!pkgSetting.restoreComponentLPw(className)) { + if (!pkgSetting.restoreComponentLPw(className, userId)) { return; } break; @@ -7621,8 +7867,8 @@ public class PackageManagerService extends IPackageManager.Stub { return; } } - mSettings.writeLPr(); - packageUid = pkgSetting.userId; + mSettings.writePackageRestrictionsLPr(userId); + packageUid = UserId.getUid(userId, pkgSetting.appId); components = mPendingBroadcasts.get(packageName); final boolean newPackage = components == null; if (newPackage) { @@ -7670,19 +7916,22 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag); extras.putInt(Intent.EXTRA_UID, packageUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null); + sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null, + UserId.getUserId(packageUid)); } - public void setPackageStoppedState(String packageName, boolean stopped) { + public void setPackageStoppedState(String packageName, boolean stopped, int userId) { + if (!sUserManager.exists(userId)) return; final int uid = Binder.getCallingUid(); final int permission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + checkValidCaller(uid, userId); // writer synchronized (mPackages) { if (mSettings.setPackageStoppedStateLPw(packageName, stopped, allowedByPermission, - uid)) { - scheduleWriteStoppedPackagesLocked(); + uid, userId)) { + scheduleWritePackageRestrictionsLocked(userId); } } } @@ -7694,17 +7943,25 @@ public class PackageManagerService extends IPackageManager.Stub { } } - public int getApplicationEnabledSetting(String packageName) { + @Override + public int getApplicationEnabledSetting(String packageName, int userId) { + if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED; + int uid = Binder.getCallingUid(); + checkValidCaller(uid, userId); // reader synchronized (mPackages) { - return mSettings.getApplicationEnabledSettingLPr(packageName); + return mSettings.getApplicationEnabledSettingLPr(packageName, userId); } } - public int getComponentEnabledSetting(ComponentName componentName) { + @Override + public int getComponentEnabledSetting(ComponentName componentName, int userId) { + if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED; + int uid = Binder.getCallingUid(); + checkValidCaller(uid, userId); // reader synchronized (mPackages) { - return mSettings.getComponentEnabledSettingLPr(componentName); + return mSettings.getComponentEnabledSettingLPr(componentName, userId); } } @@ -7908,7 +8165,7 @@ public class PackageManagerService extends IPackageManager.Stub { pw.print(" Required: "); pw.print(mRequiredVerifierPackage); pw.print(" (uid="); - pw.print(getPackageUid(mRequiredVerifierPackage)); + pw.print(getPackageUid(mRequiredVerifierPackage, 0)); pw.println(")"); } @@ -8119,8 +8376,6 @@ public class PackageManagerService extends IPackageManager.Stub { // little while. mHandler.post(new Runnable() { public void run() { - // TODO fix this; this does nothing. - mHandler.removeCallbacks(this); updateExternalMediaStatusInner(mediaStatus, reportStatus); } }); @@ -8132,13 +8387,13 @@ public class PackageManagerService extends IPackageManager.Stub { * Please note that we always have to report status if reportStatus has been * set to true especially when unloading packages. */ - private void updateExternalMediaStatusInner(boolean mediaStatus, boolean reportStatus) { + private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus) { // Collection of uids int uidArr[] = null; // Collection of stale containers HashSet<String> removeCids = new HashSet<String>(); // Collection of packages on external media with valid containers. - HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>(); + HashMap<AsecInstallArgs, String> processCids = new HashMap<AsecInstallArgs, String>(); // Get list of secure containers. final String list[] = PackageHelper.getSecureContainerList(); if (list == null || list.length == 0) { @@ -8151,7 +8406,7 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { for (String cid : list) { - SdInstallArgs args = new SdInstallArgs(cid); + AsecInstallArgs args = new AsecInstallArgs(cid); if (DEBUG_SD_INSTALL) Log.i(TAG, "Processing container " + cid); String pkgName = args.getPackageName(); @@ -8173,7 +8428,7 @@ public class PackageManagerService extends IPackageManager.Stub { + " at code path: " + ps.codePathString); // We do have a valid package installed on sdcard processCids.put(args, ps.codePathString); - int uid = ps.userId; + int uid = ps.appId; if (uid != -1) { uidList[num++] = uid; } @@ -8201,7 +8456,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } // Process packages with valid entries. - if (mediaStatus) { + if (isMounted) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); loadMediaPackages(processCids, uidArr, removeCids); @@ -8226,7 +8481,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); + sendPackageBroadcast(action, null, extras, null, finishedReceiver, UserId.USER_ALL); } } @@ -8236,12 +8491,12 @@ public class PackageManagerService extends IPackageManager.Stub { * the cid is added to list of removeCids. We currently don't delete stale * containers. */ - private void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[], + private void loadMediaPackages(HashMap<AsecInstallArgs, String> processCids, int uidArr[], HashSet<String> removeCids) { ArrayList<String> pkgList = new ArrayList<String>(); - Set<SdInstallArgs> keys = processCids.keySet(); + Set<AsecInstallArgs> keys = processCids.keySet(); boolean doGc = false; - for (SdInstallArgs args : keys) { + for (AsecInstallArgs args : keys) { String codePath = processCids.get(args); if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading container : " + args.cid); @@ -8277,7 +8532,8 @@ public class PackageManagerService extends IPackageManager.Stub { retCode = PackageManager.INSTALL_SUCCEEDED; pkgList.add(pkg.packageName); // Post process args - args.doPostInstall(PackageManager.INSTALL_SUCCEEDED); + args.doPostInstall(PackageManager.INSTALL_SUCCEEDED, + pkg.applicationInfo.uid); } } else { Slog.i(TAG, "Failed to install pkg from " + codePath + " from sdcard"); @@ -8308,7 +8564,10 @@ public class PackageManagerService extends IPackageManager.Stub { // Make sure group IDs have been assigned, and any permission // changes in other apps are accounted for - updatePermissionsLPw(null, null, true, regrantPermissions, regrantPermissions); + updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL + | (regrantPermissions + ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL) + : 0)); // can downgrade to reader // Persist settings mSettings.writeLPr(); @@ -8337,9 +8596,9 @@ public class PackageManagerService extends IPackageManager.Stub { /* * Utility method to unload a list of specified containers */ - private void unloadAllContainers(Set<SdInstallArgs> cidArgs) { + private void unloadAllContainers(Set<AsecInstallArgs> cidArgs) { // Just unmount all valid containers. - for (SdInstallArgs arg : cidArgs) { + for (AsecInstallArgs arg : cidArgs) { synchronized (mInstallLock) { arg.doPostDeleteLI(false); } @@ -8355,14 +8614,14 @@ public class PackageManagerService extends IPackageManager.Stub { * that we always have to post this message if status has been requested no * matter what. */ - private void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[], + private void unloadMediaPackages(HashMap<AsecInstallArgs, String> processCids, int uidArr[], final boolean reportStatus) { if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages"); ArrayList<String> pkgList = new ArrayList<String>(); - ArrayList<SdInstallArgs> failedList = new ArrayList<SdInstallArgs>(); - final Set<SdInstallArgs> keys = processCids.keySet(); - for (SdInstallArgs args : keys) { + ArrayList<AsecInstallArgs> failedList = new ArrayList<AsecInstallArgs>(); + final Set<AsecInstallArgs> keys = processCids.keySet(); + for (AsecInstallArgs args : keys) { String pkgName = args.getPackageName(); if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to unload pkg : " + pkgName); @@ -8423,9 +8682,6 @@ public class PackageManagerService extends IPackageManager.Stub { if (pkg.applicationInfo != null && isSystemApp(pkg)) { Slog.w(TAG, "Cannot move system application"); returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; - } else if (pkg.applicationInfo != null && isForwardLocked(pkg)) { - Slog.w(TAG, "Cannot move forward locked app."); - returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED; } else if (pkg.mOperationPending) { Slog.w(TAG, "Attempt to move package which has pending operations"); returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING; @@ -8457,13 +8713,14 @@ public class PackageManagerService extends IPackageManager.Stub { * anyway. */ if (returnCode != PackageManager.MOVE_SUCCEEDED) { - processPendingMove(new MoveParams(null, observer, 0, packageName, null), returnCode); + processPendingMove(new MoveParams(null, observer, 0, packageName, null, -1), + 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.dataDir, pkg.applicationInfo.uid); msg.obj = mp; mHandler.sendMessage(msg); } @@ -8588,7 +8845,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (returnCode != PackageManager.MOVE_SUCCEEDED) { // Clean up failed installation if (mp.targetArgs != null) { - mp.targetArgs.doPostInstall(PackageManager.INSTALL_FAILED_INTERNAL_ERROR); + mp.targetArgs.doPostInstall(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, + -1); } } else { // Force a gc to clear things up. @@ -8647,8 +8905,12 @@ public class PackageManagerService extends IPackageManager.Stub { // TODO(kroot): Add a real permission for creating users enforceSystemOrRoot("Only the system can create users"); - // TODO(kroot): fix this API - UserInfo userInfo = mUserManager.createUser(name, flags, new ArrayList<ApplicationInfo>()); + 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; } @@ -8656,13 +8918,34 @@ public class PackageManagerService extends IPackageManager.Stub { // TODO(kroot): Add a real permission for removing users enforceSystemOrRoot("Only the system can remove users"); - if (userId == 0) { + if (userId == 0 || !sUserManager.exists(userId)) { return false; } - mUserManager.removeUser(userId); + + 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); + } + 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); + } + if (mDirtyUsers.remove(userId)); + mSettings.removeUserLPr(userId); + } + } + @Override public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException { mContext.enforceCallingOrSelfPermission( @@ -8673,4 +8956,66 @@ public class PackageManagerService extends IPackageManager.Stub { return mSettings.getVerifierDeviceIdentityLPw(); } } + + @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)) { + synchronized (mPackages) { + if (mSettings.mReadExternalStorageEnforced != enforced) { + mSettings.mReadExternalStorageEnforced = enforced; + mSettings.writeLPr(); + + // kill any non-foreground processes so we restart them and + // grant/revoke the GID. + final IActivityManager am = ActivityManagerNative.getDefault(); + if (am != null) { + final long token = Binder.clearCallingIdentity(); + try { + am.killProcessesBelowForeground("setPermissionEnforcement"); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + } + } else { + throw new IllegalArgumentException("No selective enforcement for " + permission); + } + } + + @Override + public boolean isPermissionEnforced(String permission) { + mContext.enforceCallingOrSelfPermission(GRANT_REVOKE_PERMISSIONS, null); + synchronized (mPackages) { + return isPermissionEnforcedLocked(permission); + } + } + + private boolean isPermissionEnforcedLocked(String permission) { + if (READ_EXTERNAL_STORAGE.equals(permission)) { + return mSettings.mReadExternalStorageEnforced; + } else { + return true; + } + } } diff --git a/services/java/com/android/server/pm/PackageSetting.java b/services/java/com/android/server/pm/PackageSetting.java index efdc2b3..f7f0870 100644 --- a/services/java/com/android/server/pm/PackageSetting.java +++ b/services/java/com/android/server/pm/PackageSetting.java @@ -24,7 +24,7 @@ import java.io.File; * Settings data for a particular package we know about. */ final class PackageSetting extends PackageSettingBase { - int userId; + int appId; PackageParser.Package pkg; SharedUserSetting sharedUser; @@ -41,7 +41,7 @@ final class PackageSetting extends PackageSettingBase { PackageSetting(PackageSetting orig) { super(orig); - userId = orig.userId; + appId = orig.appId; pkg = orig.pkg; sharedUser = orig.sharedUser; } @@ -50,6 +50,6 @@ final class PackageSetting extends PackageSettingBase { public String toString() { return "PackageSetting{" + Integer.toHexString(System.identityHashCode(this)) - + " " + name + "/" + userId + "}"; + + " " + name + "/" + appId + "}"; } }
\ No newline at end of file diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java index e2f83ad..56f2166 100644 --- a/services/java/com/android/server/pm/PackageSettingBase.java +++ b/services/java/com/android/server/pm/PackageSettingBase.java @@ -20,6 +20,8 @@ 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.util.SparseArray; +import android.util.SparseIntArray; import java.io.File; import java.util.HashSet; @@ -62,20 +64,22 @@ class PackageSettingBase extends GrantedPermissions { // Whether this package is currently stopped, thus can not be // started until explicitly launched by the user. - public boolean stopped; + private SparseArray<Boolean> stopped = new SparseArray<Boolean>(); // Set to true if we have never launched this app. - public boolean notLaunched; + private SparseArray<Boolean> notLaunched = new SparseArray<Boolean>(); /* Explicitly disabled components */ - HashSet<String> disabledComponents = new HashSet<String>(0); + private SparseArray<HashSet<String>> disabledComponents = new SparseArray<HashSet<String>>(); /* Explicitly enabled components */ - HashSet<String> enabledComponents = new HashSet<String>(0); - int enabled = COMPONENT_ENABLED_STATE_DEFAULT; + private SparseArray<HashSet<String>> enabledComponents = new SparseArray<HashSet<String>>(); + /* Enabled state */ + private SparseIntArray enabled = new SparseIntArray(); + int installStatus = PKG_INSTALL_COMPLETE; PackageSettingBase origPackage; - + /* package name of the app that installed this package */ String installerPackageName; PackageSettingBase(String name, String realName, File codePath, File resourcePath, @@ -111,14 +115,12 @@ class PackageSettingBase extends GrantedPermissions { permissionsFixed = base.permissionsFixed; haveGids = base.haveGids; - stopped = base.stopped; notLaunched = base.notLaunched; - disabledComponents = (HashSet<String>) base.disabledComponents.clone(); - - enabledComponents = (HashSet<String>) base.enabledComponents.clone(); - - enabled = base.enabled; + 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(); installStatus = base.installStatus; origPackage = base.origPackage; @@ -177,31 +179,107 @@ class PackageSettingBase extends GrantedPermissions { installStatus = base.installStatus; } - boolean enableComponentLPw(String componentClassName) { - boolean changed = disabledComponents.remove(componentClassName); - changed |= enabledComponents.add(componentClassName); + void setEnabled(int state, int userId) { + enabled.put(userId, state); + } + + int getEnabled(int userId) { + return enabled.get(userId, COMPONENT_ENABLED_STATE_DEFAULT); + } + + boolean getStopped(int userId) { + return stopped.get(userId, false); + } + + void setStopped(boolean stop, int userId) { + stopped.put(userId, stop); + } + + boolean getNotLaunched(int userId) { + return notLaunched.get(userId, false); + } + + void setNotLaunched(boolean stop, int userId) { + notLaunched.put(userId, stop); + } + + HashSet<String> getEnabledComponents(int userId) { + return getComponentHashSet(enabledComponents, userId); + } + + HashSet<String> getDisabledComponents(int userId) { + return getComponentHashSet(disabledComponents, userId); + } + + void setEnabledComponents(HashSet<String> components, int userId) { + enabledComponents.put(userId, components); + } + + void setDisabledComponents(HashSet<String> components, int userId) { + disabledComponents.put(userId, components); + } + + 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); + } + return set; + } + + void addDisabledComponent(String componentClassName, int userId) { + HashSet<String> disabled = getComponentHashSet(disabledComponents, userId); + disabled.add(componentClassName); + } + + void addEnabledComponent(String componentClassName, int userId) { + HashSet<String> enabled = getComponentHashSet(enabledComponents, userId); + enabled.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); return changed; } - boolean disableComponentLPw(String componentClassName) { - boolean changed = enabledComponents.remove(componentClassName); - changed |= disabledComponents.add(componentClassName); + 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); return changed; } - boolean restoreComponentLPw(String componentClassName) { - boolean changed = enabledComponents.remove(componentClassName); - changed |= disabledComponents.remove(componentClassName); + 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); return changed; } - int getCurrentEnabledStateLPr(String componentName) { - if (enabledComponents.contains(componentName)) { + int getCurrentEnabledStateLPr(String componentName, int userId) { + HashSet<String> disabled = getComponentHashSet(disabledComponents, userId); + HashSet<String> enabled = getComponentHashSet(enabledComponents, userId); + if (enabled.contains(componentName)) { return COMPONENT_ENABLED_STATE_ENABLED; - } else if (disabledComponents.contains(componentName)) { + } else if (disabled.contains(componentName)) { return COMPONENT_ENABLED_STATE_DISABLED; } else { return COMPONENT_ENABLED_STATE_DEFAULT; } } -}
\ No newline at end of file + + void removeUser(int userId) { + enabled.delete(userId); + stopped.delete(userId); + enabledComponents.delete(userId); + disabledComponents.delete(userId); + notLaunched.delete(userId); + } + +} diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index f0f5414..c79f815 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -20,6 +20,7 @@ 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_DISABLED_USER; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; @@ -31,6 +32,7 @@ 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.Intent; import android.content.pm.ApplicationInfo; @@ -39,11 +41,14 @@ 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.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.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -62,6 +67,8 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; +import java.util.Map; import libcore.io.IoUtils; @@ -73,6 +80,20 @@ final class Settings { private static final boolean DEBUG_STOPPED = false; + private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage"; + private static final String ATTR_ENFORCEMENT = "enforcement"; + + private static final String TAG_ITEM = "item"; + private static final String TAG_DISABLED_COMPONENTS = "disabled-components"; + private static final String TAG_ENABLED_COMPONENTS = "enabled-components"; + private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions"; + private static final String TAG_PACKAGE = "pkg"; + + private static final String ATTR_NAME = "name"; + private static final String ATTR_NOT_LAUNCHED = "nl"; + private static final String ATTR_ENABLED = "enabled"; + private static final String ATTR_STOPPED = "stopped"; + private final File mSettingsFilename; private final File mBackupSettingsFilename; private final File mPackageListFilename; @@ -90,6 +111,8 @@ final class Settings { int mInternalSdkPlatform; int mExternalSdkPlatform; + boolean mReadExternalStorageEnforced = PackageManager.DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE; + /** Device identity for the purpose of package verification. */ private VerifierDeviceIdentity mVerifierDeviceIdentity; @@ -146,19 +169,24 @@ final class Settings { */ private final ArrayList<PendingPackage> mPendingPackages = new ArrayList<PendingPackage>(); + private final File mSystemDir; Settings() { - File dataDir = Environment.getDataDirectory(); - File systemDir = new File(dataDir, "system"); - systemDir.mkdirs(); - FileUtils.setPermissions(systemDir.toString(), + this(Environment.getDataDirectory()); + } + + Settings(File dataDir) { + mSystemDir = new File(dataDir, "system"); + mSystemDir.mkdirs(); + FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); - mSettingsFilename = new File(systemDir, "packages.xml"); - mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); - mPackageListFilename = new File(systemDir, "packages.list"); - mStoppedPackagesFilename = new File(systemDir, "packages-stopped.xml"); - mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml"); + mSettingsFilename = new File(mSystemDir, "packages.xml"); + mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); + mPackageListFilename = new File(mSystemDir, "packages.list"); + // Deprecated: Needed for migration + mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); + mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); } PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, @@ -199,11 +227,7 @@ final class Settings { return null; } s = new SharedUserSetting(name, pkgFlags); - if (PackageManagerService.MULTIPLE_APPLICATION_UIDS) { - s.userId = newUserIdLPw(s); - } else { - s.userId = PackageManagerService.FIRST_APPLICATION_UID; - } + s.userId = newUserIdLPw(s); Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId); // < 0 means we couldn't assign a userid; fall out and return // s, which is currently null @@ -251,7 +275,7 @@ final class Settings { p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath, - p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags); + p.nativeLibraryPathString, p.appId, p.versionCode, p.pkgFlags); mDisabledSysPackages.remove(name); return ret; } @@ -260,7 +284,7 @@ final class Settings { String nativeLibraryPathString, int uid, int vc, int pkgFlags) { PackageSetting p = mPackages.get(name); if (p != null) { - if (p.userId == uid) { + if (p.appId == uid) { return p; } PackageManagerService.reportSettingsProblem(Log.ERROR, @@ -269,7 +293,7 @@ final class Settings { } p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString, vc, pkgFlags); - p.userId = uid; + p.appId = uid; if (addUserIdLPw(uid, p, name)) { mPackages.put(name, p); return p; @@ -320,7 +344,7 @@ 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) { @@ -332,13 +356,13 @@ final class Settings { // This is an updated system app with versions in both system // and data partition. Just let the most recent version // take precedence. - Slog.w(PackageManagerService.TAG, "Trying to update system app code path from " + - p.codePathString + " to " + codePath.toString()); + Slog.w(PackageManagerService.TAG, "Trying to update system app code path from " + + p.codePathString + " to " + codePath.toString()); } else { // Just a change in the code path is not an issue, but // let's log a message about it. - Slog.i(PackageManagerService.TAG, "Package " + name + " codePath changed from " + p.codePath - + " to " + codePath + "; Retaining data and using new"); + Slog.i(PackageManagerService.TAG, "Package " + name + " codePath changed from " + + p.codePath + " to " + codePath + "; Retaining data and using new"); /* * Since we've changed paths, we need to prefer the new * native library path over the one stored in the @@ -375,15 +399,15 @@ final class Settings { // We are consuming the data from an existing package. p = new PackageSetting(origPackage.name, name, codePath, resourcePath, nativeLibraryPathString, vc, pkgFlags); - if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " + name - + " is adopting original package " + origPackage.name); + if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " + + name + " is adopting original package " + origPackage.name); // Note that we will retain the new package's signature so // that we can keep its data. PackageSignatures s = p.signatures; p.copyFrom(origPackage); p.signatures = s; p.sharedUser = origPackage.sharedUser; - p.userId = origPackage.userId; + p.appId = origPackage.appId; p.origPackage = origPackage; mRenamedPackages.put(name, origPackage.name); name = origPackage.name; @@ -401,12 +425,18 @@ final class Settings { e.fillInStackTrace(); Slog.i(PackageManagerService.TAG, "Stopping package " + name, e); } - p.stopped = true; - p.notLaunched = true; + List<UserInfo> users = getAllUsers(); + if (users != null) { + for (UserInfo user : users) { + p.setStopped(true, user.id); + p.setNotLaunched(true, user.id); + writePackageRestrictionsLPr(user.id); + } + } } if (sharedUser != null) { - p.userId = sharedUser.userId; - } else if (PackageManagerService.MULTIPLE_APPLICATION_UIDS) { + p.appId = sharedUser.userId; + } else { // Clone the setting here for disabled system packages PackageSetting dis = mDisabledSysPackages.get(name); if (dis != null) { @@ -417,23 +447,31 @@ final class Settings { if (dis.signatures.mSignatures != null) { p.signatures.mSignatures = dis.signatures.mSignatures.clone(); } - p.userId = dis.userId; + p.appId = dis.appId; // Clone permissions p.grantedPermissions = new HashSet<String>(dis.grantedPermissions); // Clone component info - p.disabledComponents = new HashSet<String>(dis.disabledComponents); - p.enabledComponents = new HashSet<String>(dis.enabledComponents); + List<UserInfo> users = getAllUsers(); + 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); + } + } // Add new setting to list of user ids - addUserIdLPw(p.userId, p, name); + addUserIdLPw(p.appId, p, name); } else { // Assign new user id - p.userId = newUserIdLPw(p); + p.appId = newUserIdLPw(p); } - } else { - p.userId = PackageManagerService.FIRST_APPLICATION_UID; } } - if (p.userId < 0) { + if (p.appId < 0) { PackageManagerService.reportSettingsProblem(Log.WARN, "Package " + name + " could not be assigned a valid uid"); return null; @@ -449,8 +487,8 @@ final class Settings { void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) { p.pkg = pkg; - pkg.mSetEnabled = p.enabled; - pkg.mSetStopped = p.stopped; + // pkg.mSetEnabled = p.getEnabled(userId); + // pkg.mSetStopped = p.getStopped(userId); final String codePath = pkg.applicationInfo.sourceDir; final String resourcePath = pkg.applicationInfo.publicSourceDir; // Update code path if needed @@ -474,18 +512,18 @@ final class Settings { p.nativeLibraryPathString = nativeLibraryPath; } // Update version code if needed - if (pkg.mVersionCode != p.versionCode) { + if (pkg.mVersionCode != p.versionCode) { p.versionCode = pkg.mVersionCode; } - // Update signatures if needed. - if (p.signatures.mSignatures == null) { - p.signatures.assignSignatures(pkg.mSignatures); - } - // If this app defines a shared user id initialize - // the shared user signatures as well. - if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { - p.sharedUser.signatures.assignSignatures(pkg.mSignatures); - } + // Update signatures if needed. + if (p.signatures.mSignatures == null) { + p.signatures.assignSignatures(pkg.mSignatures); + } + // If this app defines a shared user id initialize + // the shared user signatures as well. + if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { + p.sharedUser.signatures.assignSignatures(pkg.mSignatures); + } addPackageSettingLPw(p, pkg.packageName, p.sharedUser); } @@ -501,9 +539,9 @@ final class Settings { + p.sharedUser + " but is now " + sharedUser + "; I am not changing its files so it will probably fail!"); p.sharedUser.packages.remove(p); - } else if (p.userId != sharedUser.userId) { + } else if (p.appId != sharedUser.userId) { PackageManagerService.reportSettingsProblem(Log.ERROR, - "Package " + p.name + " was user id " + p.userId + "Package " + p.name + " was user id " + p.appId + " but is now user " + sharedUser + " with id " + sharedUser.userId + "; I am not changing its files so it will probably fail!"); @@ -511,7 +549,7 @@ final class Settings { sharedUser.packages.add(p); p.sharedUser = sharedUser; - p.userId = sharedUser.userId; + p.appId = sharedUser.userId; } } @@ -576,8 +614,8 @@ final class Settings { return p.sharedUser.userId; } } else { - removeUserIdLPw(p.userId); - return p.userId; + removeUserIdLPw(p.appId); + return p.appId; } } return -1; @@ -590,20 +628,20 @@ final class Settings { p.sharedUser.packages.remove(p); p.sharedUser.packages.add(newp); } else { - replaceUserIdLPw(p.userId, newp); + replaceUserIdLPw(p.appId, newp); } } mPackages.put(name, newp); } private boolean addUserIdLPw(int uid, Object obj, Object name) { - if (uid >= PackageManagerService.FIRST_APPLICATION_UID + PackageManagerService.MAX_APPLICATION_UIDS) { + if (uid > Process.LAST_APPLICATION_UID) { return false; } - if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + if (uid >= Process.FIRST_APPLICATION_UID) { int N = mUserIds.size(); - final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + final int index = uid - Process.FIRST_APPLICATION_UID; while (index >= N) { mUserIds.add(null); N++; @@ -628,9 +666,9 @@ final class Settings { } public Object getUserIdLPr(int uid) { - if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + if (uid >= Process.FIRST_APPLICATION_UID) { final int N = mUserIds.size(); - final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + final int index = uid - Process.FIRST_APPLICATION_UID; return index < N ? mUserIds.get(index) : null; } else { return mOtherUserIds.get(uid); @@ -638,9 +676,9 @@ final class Settings { } private void removeUserIdLPw(int uid) { - if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + if (uid >= Process.FIRST_APPLICATION_UID) { final int N = mUserIds.size(); - final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + final int index = uid - Process.FIRST_APPLICATION_UID; if (index < N) mUserIds.set(index, null); } else { mOtherUserIds.remove(uid); @@ -648,59 +686,278 @@ final class Settings { } private void replaceUserIdLPw(int uid, Object obj) { - if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + if (uid >= Process.FIRST_APPLICATION_UID) { final int N = mUserIds.size(); - final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + final int index = uid - Process.FIRST_APPLICATION_UID; if (index < N) mUserIds.set(index, obj); } else { mOtherUserIds.put(uid, obj); } } - void writeStoppedLPr() { + private File getUserPackagesStateFile(int userId) { + return new File(mSystemDir, + "users/" + userId + "/package-restrictions.xml"); + } + + private File getUserPackagesStateBackupFile(int userId) { + return new File(mSystemDir, + "users/" + userId + "/package-restrictions-backup.xml"); + } + + void writeAllUsersPackageRestrictionsLPr() { + List<UserInfo> users = getAllUsers(); + if (users == null) return; + + for (UserInfo user : users) { + writePackageRestrictionsLPr(user.id); + } + } + + void readAllUsersPackageRestrictionsLPr() { + List<UserInfo> users = getAllUsers(); + if (users == null) { + readPackageRestrictionsLPr(0); + return; + } + + for (UserInfo user : users) { + readPackageRestrictionsLPr(user.id); + } + } + + void readPackageRestrictionsLPr(int userId) { + FileInputStream str = null; + File userPackagesStateFile = getUserPackagesStateFile(userId); + File backupFile = getUserPackagesStateBackupFile(userId); + if (backupFile.exists()) { + try { + str = new FileInputStream(backupFile); + mReadMessages.append("Reading from backup stopped packages file\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, + "Need to read from backup stopped packages file"); + if (userPackagesStateFile.exists()) { + // If both the backup and normal file exist, we + // ignore the normal one since it might have been + // corrupted. + Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file " + + userPackagesStateFile); + userPackagesStateFile.delete(); + } + } catch (java.io.IOException e) { + // We'll try for the normal settings file. + } + } + + try { + if (str == null) { + if (!userPackagesStateFile.exists()) { + mReadMessages.append("No stopped packages file found\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, + "No stopped packages file; " + + "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. + for (PackageSetting pkg : mPackages.values()) { + pkg.setStopped(false, userId); + pkg.setNotLaunched(false, userId); + } + return; + } + str = new FileInputStream(userPackagesStateFile); + } + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(str, null); + + int type; + while ((type=parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + mReadMessages.append("No start tag found in package restrictions file\n"); + PackageManagerService.reportSettingsProblem(Log.WARN, + "No start tag found in package manager stopped packages"); + return; + } + + int outerDepth = parser.getDepth(); + PackageSetting ps = null; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals(TAG_PACKAGE)) { + String name = parser.getAttributeValue(null, ATTR_NAME); + ps = mPackages.get(name); + if (ps == null) { + Slog.w(PackageManagerService.TAG, "No package known for stopped package: " + + name); + 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); + + int packageDepth = parser.getDepth(); + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > packageDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + tagName = parser.getName(); + if (tagName.equals(TAG_ENABLED_COMPONENTS)) { + HashSet<String> components = readComponentsLPr(parser); + ps.setEnabledComponents(components, userId); + } else if (tagName.equals(TAG_DISABLED_COMPONENTS)) { + HashSet<String> components = readComponentsLPr(parser); + ps.setDisabledComponents(components, userId); + } + } + } else { + Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + str.close(); + + } catch (XmlPullParserException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Error reading stopped packages: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e); + + } catch (java.io.IOException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e); + } + } + + private HashSet<String> readComponentsLPr(XmlPullParser parser) + throws IOException, XmlPullParserException { + HashSet<String> components = new HashSet<String>(); + int type; + int outerDepth = parser.getDepth(); + String tagName; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + tagName = parser.getName(); + if (tagName.equals(TAG_ITEM)) { + String componentName = parser.getAttributeValue(null, ATTR_NAME); + if (componentName != null) { + components.add(componentName); + } + } + } + return components; + } + + void writePackageRestrictionsLPr(int userId) { // Keep the old stopped packages around until we know the new ones have // been successfully written. - if (mStoppedPackagesFilename.exists()) { + File userPackagesStateFile = getUserPackagesStateFile(userId); + File backupFile = getUserPackagesStateBackupFile(userId); + new File(userPackagesStateFile.getParent()).mkdirs(); + if (userPackagesStateFile.exists()) { // Presence of backup settings file indicates that we failed // to persist packages earlier. So preserve the older // backup for future reference since the current packages // might have been corrupted. - if (!mBackupStoppedPackagesFilename.exists()) { - if (!mStoppedPackagesFilename.renameTo(mBackupStoppedPackagesFilename)) { - Log.wtf(PackageManagerService.TAG, "Unable to backup package manager stopped packages, " + if (!backupFile.exists()) { + if (!userPackagesStateFile.renameTo(backupFile)) { + Log.wtf(PackageManagerService.TAG, "Unable to backup user packages state file, " + "current changes will be lost at reboot"); return; } } else { - mStoppedPackagesFilename.delete(); + userPackagesStateFile.delete(); Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup"); } } try { - final FileOutputStream fstr = new FileOutputStream(mStoppedPackagesFilename); + final FileOutputStream fstr = new FileOutputStream(userPackagesStateFile); final BufferedOutputStream str = new BufferedOutputStream(fstr); - //XmlSerializer serializer = XmlUtils.serializerInstance(); final XmlSerializer serializer = new FastXmlSerializer(); serializer.setOutput(str, "utf-8"); serializer.startDocument(null, true); serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - serializer.startTag(null, "stopped-packages"); + serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS); for (final PackageSetting pkg : mPackages.values()) { - if (pkg.stopped) { - serializer.startTag(null, "pkg"); - serializer.attribute(null, "name", pkg.name); - if (pkg.notLaunched) { - serializer.attribute(null, "nl", "1"); + if (pkg.getStopped(userId) + || pkg.getNotLaunched(userId) + || pkg.getEnabled(userId) != COMPONENT_ENABLED_STATE_DEFAULT + || pkg.getEnabledComponents(userId).size() > 0 + || pkg.getDisabledComponents(userId).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 (stopped) { + serializer.attribute(null, ATTR_STOPPED, "true"); + } + if (notLaunched) { + serializer.attribute(null, ATTR_NOT_LAUNCHED, "true"); + } + if (enabled != COMPONENT_ENABLED_STATE_DEFAULT) { + serializer.attribute(null, ATTR_ENABLED, Integer.toString(enabled)); + } + if (enabledComponents.size() > 0) { + serializer.startTag(null, TAG_ENABLED_COMPONENTS); + for (final String name : enabledComponents) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, name); + serializer.endTag(null, TAG_ITEM); + } + serializer.endTag(null, TAG_ENABLED_COMPONENTS); } - serializer.endTag(null, "pkg"); + if (disabledComponents.size() > 0) { + serializer.startTag(null, TAG_DISABLED_COMPONENTS); + for (final String name : disabledComponents) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, name); + serializer.endTag(null, TAG_ITEM); + } + serializer.endTag(null, TAG_DISABLED_COMPONENTS); + } + serializer.endTag(null, TAG_PACKAGE); } } - serializer.endTag(null, "stopped-packages"); + serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS); serializer.endDocument(); @@ -710,36 +967,39 @@ final class Settings { // New settings successfully written, old ones are no longer // needed. - mBackupStoppedPackagesFilename.delete(); - FileUtils.setPermissions(mStoppedPackagesFilename.toString(), + backupFile.delete(); + FileUtils.setPermissions(userPackagesStateFile.toString(), FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, + |FileUtils.S_IRGRP|FileUtils.S_IWGRP, -1, -1); // Done, all is good! return; } catch(java.io.IOException e) { - Log.wtf(PackageManagerService.TAG, "Unable to write package manager stopped packages, " + Log.wtf(PackageManagerService.TAG, + "Unable to write package manager user packages state, " + " current changes will be lost at reboot", e); } // Clean up partially written files - if (mStoppedPackagesFilename.exists()) { - if (!mStoppedPackagesFilename.delete()) { - Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: " + mStoppedPackagesFilename); + if (userPackagesStateFile.exists()) { + if (!userPackagesStateFile.delete()) { + Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: " + + mStoppedPackagesFilename); } } } // Note: assumed "stopped" field is already cleared in all packages. + // Legacy reader, used to read in the old file format after an upgrade. Not used after that. void readStoppedLPw() { FileInputStream str = null; if (mBackupStoppedPackagesFilename.exists()) { try { str = new FileInputStream(mBackupStoppedPackagesFilename); mReadMessages.append("Reading from backup stopped packages file\n"); - PackageManagerService.reportSettingsProblem(Log.INFO, "Need to read from backup stopped packages file"); + PackageManagerService.reportSettingsProblem(Log.INFO, + "Need to read from backup stopped packages file"); if (mSettingsFilename.exists()) { // If both the backup and normal file exist, we // ignore the normal one since it might have been @@ -757,14 +1017,14 @@ final class Settings { if (str == null) { if (!mStoppedPackagesFilename.exists()) { mReadMessages.append("No stopped packages file found\n"); - PackageManagerService.reportSettingsProblem(Log.INFO, "No stopped packages file file; " - + "assuming all started"); + PackageManagerService.reportSettingsProblem(Log.INFO, + "No stopped packages file file; 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. for (PackageSetting pkg : mPackages.values()) { - pkg.stopped = false; - pkg.notLaunched = false; + pkg.setStopped(false, 0); + pkg.setNotLaunched(false, 0); } return; } @@ -796,16 +1056,17 @@ final class Settings { } String tagName = parser.getName(); - if (tagName.equals("pkg")) { - String name = parser.getAttributeValue(null, "name"); + if (tagName.equals(TAG_PACKAGE)) { + String name = parser.getAttributeValue(null, ATTR_NAME); PackageSetting ps = mPackages.get(name); if (ps != null) { - ps.stopped = true; - if ("1".equals(parser.getAttributeValue(null, "nl"))) { - ps.notLaunched = true; + ps.setStopped(true, 0); + if ("1".equals(parser.getAttributeValue(null, ATTR_NOT_LAUNCHED))) { + ps.setNotLaunched(true, 0); } } else { - Slog.w(PackageManagerService.TAG, "No package known for stopped package: " + name); + Slog.w(PackageManagerService.TAG, + "No package known for stopped package: " + name); } XmlUtils.skipCurrentTag(parser); } else { @@ -817,12 +1078,13 @@ final class Settings { str.close(); - } catch(XmlPullParserException e) { + } catch (XmlPullParserException e) { mReadMessages.append("Error reading: " + e.toString()); - PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading stopped packages: " + e); + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Error reading stopped packages: " + e); Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e); - } catch(java.io.IOException e) { + } catch (java.io.IOException e) { mReadMessages.append("Error reading: " + e.toString()); PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e); @@ -870,13 +1132,21 @@ final class Settings { serializer.attribute(null, "internal", Integer.toString(mInternalSdkPlatform)); serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform)); serializer.endTag(null, "last-platform-version"); - + if (mVerifierDeviceIdentity != null) { serializer.startTag(null, "verifier"); serializer.attribute(null, "device", mVerifierDeviceIdentity.toString()); serializer.endTag(null, "verifier"); } + if (mReadExternalStorageEnforced + != PackageManager.DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE) { + serializer.startTag(null, TAG_READ_EXTERNAL_STORAGE); + serializer.attribute( + null, ATTR_ENFORCEMENT, mReadExternalStorageEnforced ? "1" : "0"); + serializer.endTag(null, TAG_READ_EXTERNAL_STORAGE); + } + serializer.startTag(null, "permission-trees"); for (BasePermission bp : mPermissionTrees.values()) { writePermissionLPr(serializer, bp); @@ -899,23 +1169,23 @@ final class Settings { serializer.startTag(null, "preferred-activities"); for (final PreferredActivity pa : mPreferredActivities.filterSet()) { - serializer.startTag(null, "item"); + serializer.startTag(null, TAG_ITEM); pa.writeToXml(serializer); - serializer.endTag(null, "item"); + serializer.endTag(null, TAG_ITEM); } serializer.endTag(null, "preferred-activities"); for (final SharedUserSetting usr : mSharedUsers.values()) { serializer.startTag(null, "shared-user"); - serializer.attribute(null, "name", usr.name); + serializer.attribute(null, ATTR_NAME, usr.name); serializer.attribute(null, "userId", Integer.toString(usr.userId)); usr.signatures.writeXml(serializer, "sigs", mPastSignatures); serializer.startTag(null, "perms"); for (String name : usr.grantedPermissions) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, name); + serializer.endTag(null, TAG_ITEM); } serializer.endTag(null, "perms"); serializer.endTag(null, "shared-user"); @@ -924,13 +1194,13 @@ final class Settings { if (mPackagesToBeCleaned.size() > 0) { for (int i=0; i<mPackagesToBeCleaned.size(); i++) { serializer.startTag(null, "cleaning-package"); - serializer.attribute(null, "name", mPackagesToBeCleaned.get(i)); + serializer.attribute(null, ATTR_NAME, mPackagesToBeCleaned.get(i)); serializer.endTag(null, "cleaning-package"); } } if (mRenamedPackages.size() > 0) { - for (HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { + for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) { serializer.startTag(null, "renamed-package"); serializer.attribute(null, "new", e.getKey()); serializer.attribute(null, "old", e.getValue()); @@ -951,8 +1221,7 @@ final class Settings { mBackupSettingsFilename.delete(); FileUtils.setPermissions(mSettingsFilename.toString(), FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, + |FileUtils.S_IRGRP|FileUtils.S_IWGRP, -1, -1); // Write package list file now, use a JournaledFile. @@ -1007,12 +1276,10 @@ final class Settings { FileUtils.setPermissions(mPackageListFilename.toString(), FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, + |FileUtils.S_IRGRP|FileUtils.S_IWGRP, -1, -1); - writeStoppedLPr(); - + writeAllUsersPackageRestrictionsLPr(); return; } catch(XmlPullParserException e) { @@ -1025,7 +1292,8 @@ final class Settings { // Clean up partially written files if (mSettingsFilename.exists()) { if (!mSettingsFilename.delete()) { - Log.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: " + mSettingsFilename); + Log.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: " + + mSettingsFilename); } } //Debug.stopMethodTracing(); @@ -1034,7 +1302,7 @@ final class Settings { void writeDisabledSysPackageLPr(XmlSerializer serializer, final PackageSetting pkg) throws java.io.IOException { serializer.startTag(null, "updated-package"); - serializer.attribute(null, "name", pkg.name); + serializer.attribute(null, ATTR_NAME, pkg.name); if (pkg.realName != null) { serializer.attribute(null, "realName", pkg.realName); } @@ -1050,9 +1318,9 @@ final class Settings { serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); } if (pkg.sharedUser == null) { - serializer.attribute(null, "userId", Integer.toString(pkg.userId)); + serializer.attribute(null, "userId", Integer.toString(pkg.appId)); } else { - serializer.attribute(null, "sharedUserId", Integer.toString(pkg.userId)); + serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId)); } serializer.startTag(null, "perms"); if (pkg.sharedUser == null) { @@ -1067,9 +1335,9 @@ final class Settings { // this wont // match the semantics of grantedPermissions. So write all // permissions. - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, name); + serializer.endTag(null, TAG_ITEM); } } } @@ -1080,7 +1348,7 @@ final class Settings { void writePackageLPr(XmlSerializer serializer, final PackageSetting pkg) throws java.io.IOException { serializer.startTag(null, "package"); - serializer.attribute(null, "name", pkg.name); + serializer.attribute(null, ATTR_NAME, pkg.name); if (pkg.realName != null) { serializer.attribute(null, "realName", pkg.realName); } @@ -1097,16 +1365,13 @@ final class Settings { serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); if (pkg.sharedUser == null) { - serializer.attribute(null, "userId", Integer.toString(pkg.userId)); + serializer.attribute(null, "userId", Integer.toString(pkg.appId)); } else { - serializer.attribute(null, "sharedUserId", Integer.toString(pkg.userId)); + serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId)); } if (pkg.uidError) { serializer.attribute(null, "uidError", "true"); } - if (pkg.enabled != COMPONENT_ENABLED_STATE_DEFAULT) { - serializer.attribute(null, "enabled", Integer.toString(pkg.enabled)); - } if (pkg.installStatus == PackageSettingBase.PKG_INSTALL_INCOMPLETE) { serializer.attribute(null, "installStatus", "false"); } @@ -1122,31 +1387,13 @@ final class Settings { // empty permissions list so permissionsFixed will // be set. for (final String name : pkg.grantedPermissions) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, name); + serializer.endTag(null, TAG_ITEM); } } serializer.endTag(null, "perms"); } - if (pkg.disabledComponents.size() > 0) { - serializer.startTag(null, "disabled-components"); - for (final String name : pkg.disabledComponents) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "disabled-components"); - } - if (pkg.enabledComponents.size() > 0) { - serializer.startTag(null, "enabled-components"); - for (final String name : pkg.enabledComponents) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "enabled-components"); - } serializer.endTag(null, "package"); } @@ -1154,8 +1401,8 @@ final class Settings { void writePermissionLPr(XmlSerializer serializer, BasePermission bp) throws XmlPullParserException, java.io.IOException { if (bp.type != BasePermission.TYPE_BUILTIN && bp.sourcePackage != null) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", bp.name); + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, bp.name); serializer.attribute(null, "package", bp.sourcePackage); if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) { serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel)); @@ -1175,7 +1422,7 @@ final class Settings { } } } - serializer.endTag(null, "item"); + serializer.endTag(null, TAG_ITEM); } } @@ -1193,7 +1440,7 @@ final class Settings { return ret; } - boolean readLPw() { + boolean readLPw(List<UserInfo> users) { FileInputStream str = null; if (mBackupSettingsFilename.exists()) { try { @@ -1268,7 +1515,7 @@ final class Settings { } else if (tagName.equals("updated-package")) { readDisabledSysPackageLPw(parser); } else if (tagName.equals("cleaning-package")) { - String name = parser.getAttributeValue(null, "name"); + String name = parser.getAttributeValue(null, ATTR_NAME); if (name != null) { mPackagesToBeCleaned.add(name); } @@ -1299,6 +1546,9 @@ final class Settings { Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: " + e.getMessage()); } + } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) { + final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT); + mReadExternalStorageEnforced = "1".equals(enforcement); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: " + parser.getName()); @@ -1355,14 +1605,29 @@ final class Settings { final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator(); while (disabledIt.hasNext()) { final PackageSetting disabledPs = disabledIt.next(); - final Object id = getUserIdLPr(disabledPs.userId); + final Object id = getUserIdLPr(disabledPs.appId); if (id != null && id instanceof SharedUserSetting) { disabledPs.sharedUser = (SharedUserSetting) id; } } - readStoppedLPw(); - + 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"); @@ -1396,8 +1661,8 @@ final class Settings { } final String tagName = parser.getName(); - if (tagName.equals("item")) { - final String name = parser.getAttributeValue(null, "name"); + if (tagName.equals(TAG_ITEM)) { + final String name = parser.getAttributeValue(null, ATTR_NAME); final String sourcePackage = parser.getAttributeValue(null, "package"); final String ptype = parser.getAttributeValue(null, "type"); if (name != null && sourcePackage != null) { @@ -1406,6 +1671,7 @@ final class Settings { dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL); bp.protectionLevel = readInt(parser, null, "protection", PermissionInfo.PROTECTION_NORMAL); + bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel); if (dynamic) { PermissionInfo pi = new PermissionInfo(); pi.packageName = sourcePackage.intern(); @@ -1432,7 +1698,7 @@ final class Settings { private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException { - String name = parser.getAttributeValue(null, "name"); + String name = parser.getAttributeValue(null, ATTR_NAME); String realName = parser.getAttributeValue(null, "realName"); String codePathStr = parser.getAttributeValue(null, "codePath"); String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); @@ -1485,10 +1751,10 @@ final class Settings { } } String idStr = parser.getAttributeValue(null, "userId"); - ps.userId = idStr != null ? Integer.parseInt(idStr) : 0; - if (ps.userId <= 0) { + ps.appId = idStr != null ? Integer.parseInt(idStr) : 0; + if (ps.appId <= 0) { String sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); - ps.userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; + ps.appId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; } int outerDepth = parser.getDepth(); int type; @@ -1529,7 +1795,7 @@ final class Settings { String version = null; int versionCode = 0; try { - name = parser.getAttributeValue(null, "name"); + name = parser.getAttributeValue(null, ATTR_NAME); realName = parser.getAttributeValue(null, "realName"); idStr = parser.getAttributeValue(null, "userId"); uidError = parser.getAttributeValue(null, "uidError"); @@ -1660,17 +1926,18 @@ final class Settings { packageSetting.uidError = "true".equals(uidError); packageSetting.installerPackageName = installerPackageName; packageSetting.nativeLibraryPathString = nativeLibraryPathStr; - final String enabledStr = parser.getAttributeValue(null, "enabled"); + // Handle legacy string here for single-user mode + final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED); if (enabledStr != null) { try { - packageSetting.enabled = Integer.parseInt(enabledStr); + packageSetting.setEnabled(Integer.parseInt(enabledStr), 0 /* userId */); } catch (NumberFormatException e) { if (enabledStr.equalsIgnoreCase("true")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_ENABLED; + packageSetting.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 0); } else if (enabledStr.equalsIgnoreCase("false")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DISABLED; + packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0); } else if (enabledStr.equalsIgnoreCase("default")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; + packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: package " + name @@ -1679,8 +1946,9 @@ final class Settings { } } } else { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; + packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0); } + final String installStatusStr = parser.getAttributeValue(null, "installStatus"); if (installStatusStr != null) { if (installStatusStr.equalsIgnoreCase("false")) { @@ -1699,10 +1967,11 @@ final class Settings { } String tagName = parser.getName(); - if (tagName.equals("disabled-components")) { - readDisabledComponentsLPw(packageSetting, parser); - } else if (tagName.equals("enabled-components")) { - readEnabledComponentsLPw(packageSetting, parser); + // Legacy + if (tagName.equals(TAG_DISABLED_COMPONENTS)) { + readDisabledComponentsLPw(packageSetting, parser, 0); + } else if (tagName.equals(TAG_ENABLED_COMPONENTS)) { + readEnabledComponentsLPw(packageSetting, parser, 0); } else if (tagName.equals("sigs")) { packageSetting.signatures.readXml(parser, mPastSignatures); } else if (tagName.equals("perms")) { @@ -1719,8 +1988,8 @@ final class Settings { } } - private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser) - throws IOException, XmlPullParserException { + private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser, + int userId) throws IOException, XmlPullParserException { int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -1730,10 +1999,10 @@ final class Settings { } String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); + if (tagName.equals(TAG_ITEM)) { + String name = parser.getAttributeValue(null, ATTR_NAME); if (name != null) { - packageSetting.disabledComponents.add(name.intern()); + packageSetting.addDisabledComponent(name.intern(), userId); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <disabled-components> has" @@ -1747,8 +2016,8 @@ final class Settings { } } - private void readEnabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser) - throws IOException, XmlPullParserException { + private void readEnabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser, + int userId) throws IOException, XmlPullParserException { int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -1758,10 +2027,10 @@ final class Settings { } String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); + if (tagName.equals(TAG_ITEM)) { + String name = parser.getAttributeValue(null, ATTR_NAME); if (name != null) { - packageSetting.enabledComponents.add(name.intern()); + packageSetting.addEnabledComponent(name.intern(), userId); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <enabled-components> has" @@ -1775,13 +2044,13 @@ final class Settings { } } - private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException, IOException { + private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException,IOException { String name = null; String idStr = null; int pkgFlags = 0; SharedUserSetting su = null; try { - name = parser.getAttributeValue(null, "name"); + name = parser.getAttributeValue(null, ATTR_NAME); idStr = parser.getAttributeValue(null, "userId"); int userId = idStr != null ? Integer.parseInt(idStr) : 0; if ("true".equals(parser.getAttributeValue(null, "system"))) { @@ -1847,8 +2116,8 @@ final class Settings { } String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); + if (tagName.equals(TAG_ITEM)) { + String name = parser.getAttributeValue(null, ATTR_NAME); if (name != null) { outPerms.add(name.intern()); } else { @@ -1875,7 +2144,7 @@ final class Settings { } String tagName = parser.getName(); - if (tagName.equals("item")) { + if (tagName.equals(TAG_ITEM)) { PreferredActivity pa = new PreferredActivity(parser); if (pa.mPref.getParseError() == null) { mPreferredActivities.addFilter(pa); @@ -1893,6 +2162,13 @@ final class Settings { } } + void removeUserLPr(int userId) { + File file = getUserPackagesStateFile(userId); + file.delete(); + file = getUserPackagesStateBackupFile(userId); + file.delete(); + } + // Returns -1 if we could not find an available UserId to assign private int newUserIdLPw(Object obj) { // Let's be stupidly inefficient for now... @@ -1900,17 +2176,17 @@ final class Settings { for (int i = 0; i < N; i++) { if (mUserIds.get(i) == null) { mUserIds.set(i, obj); - return PackageManagerService.FIRST_APPLICATION_UID + i; + return Process.FIRST_APPLICATION_UID + i; } } // None left? - if (N >= PackageManagerService.MAX_APPLICATION_UIDS) { + if (N > (Process.LAST_APPLICATION_UID-Process.FIRST_APPLICATION_UID)) { return -1; } mUserIds.add(obj); - return PackageManagerService.FIRST_APPLICATION_UID + N; + return Process.FIRST_APPLICATION_UID + N; } public VerifierDeviceIdentity getVerifierDeviceIdentityLPw() { @@ -1928,32 +2204,34 @@ final class Settings { return ps; } - boolean isEnabledLPr(ComponentInfo componentInfo, int flags) { + boolean isEnabledLPr(ComponentInfo componentInfo, int flags, int userId) { if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { return true; } - final PackageSetting packageSettings = mPackages.get(componentInfo.packageName); + final String pkgName = componentInfo.packageName; + final PackageSetting packageSettings = mPackages.get(pkgName); if (PackageManagerService.DEBUG_SETTINGS) { - Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = " + componentInfo.packageName - + " componentName = " + componentInfo.name); + Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = " + + componentInfo.packageName + " componentName = " + componentInfo.name); Log.v(PackageManagerService.TAG, "enabledComponents: " - + Arrays.toString(packageSettings.enabledComponents.toArray())); + + Arrays.toString(packageSettings.getEnabledComponents(userId).toArray())); Log.v(PackageManagerService.TAG, "disabledComponents: " - + Arrays.toString(packageSettings.disabledComponents.toArray())); + + Arrays.toString(packageSettings.getDisabledComponents(userId).toArray())); } if (packageSettings == null) { return false; } - if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED - || packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED_USER + final int enabled = packageSettings.getEnabled(userId); + if (enabled == COMPONENT_ENABLED_STATE_DISABLED + || enabled == COMPONENT_ENABLED_STATE_DISABLED_USER || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled - && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { + && enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { return false; } - if (packageSettings.enabledComponents.contains(componentInfo.name)) { + if (packageSettings.getEnabledComponents(userId).contains(componentInfo.name)) { return true; } - if (packageSettings.disabledComponents.contains(componentInfo.name)) { + if (packageSettings.getDisabledComponents(userId).contains(componentInfo.name)) { return false; } return componentInfo.enabled; @@ -1967,35 +2245,36 @@ final class Settings { return pkg.installerPackageName; } - int getApplicationEnabledSettingLPr(String packageName) { + int getApplicationEnabledSettingLPr(String packageName, int userId) { final PackageSetting pkg = mPackages.get(packageName); if (pkg == null) { throw new IllegalArgumentException("Unknown package: " + packageName); } - return pkg.enabled; + return pkg.getEnabled(userId); } - int getComponentEnabledSettingLPr(ComponentName componentName) { + int getComponentEnabledSettingLPr(ComponentName componentName, int userId) { final String packageName = componentName.getPackageName(); final PackageSetting pkg = mPackages.get(packageName); if (pkg == null) { throw new IllegalArgumentException("Unknown component: " + componentName); } final String classNameStr = componentName.getClassName(); - return pkg.getCurrentEnabledStateLPr(classNameStr); + return pkg.getCurrentEnabledStateLPr(classNameStr, userId); } - + boolean setPackageStoppedStateLPw(String packageName, boolean stopped, - boolean allowedByPermission, int uid) { + boolean allowedByPermission, int uid, int userId) { + int appId = UserId.getAppId(uid); final PackageSetting pkgSetting = mPackages.get(packageName); if (pkgSetting == null) { throw new IllegalArgumentException("Unknown package: " + packageName); } - if (!allowedByPermission && (uid != pkgSetting.userId)) { + if (!allowedByPermission && (appId != pkgSetting.appId)) { throw new SecurityException( "Permission Denial: attempt to change stopped state from pid=" + Binder.getCallingPid() - + ", uid=" + uid + ", package uid=" + pkgSetting.userId); + + ", uid=" + uid + ", package uid=" + pkgSetting.appId); } if (DEBUG_STOPPED) { if (stopped) { @@ -2004,26 +2283,74 @@ final class Settings { Slog.i(TAG, "Stopping package " + packageName, e); } } - if (pkgSetting.stopped != stopped) { - pkgSetting.stopped = stopped; - pkgSetting.pkg.mSetStopped = stopped; - if (pkgSetting.notLaunched) { + if (pkgSetting.getStopped(userId) != stopped) { + pkgSetting.setStopped(stopped, userId); + // pkgSetting.pkg.mSetStopped = stopped; + if (pkgSetting.getNotLaunched(userId)) { if (pkgSetting.installerPackageName != null) { PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgSetting.name, null, - pkgSetting.installerPackageName, null); + pkgSetting.installerPackageName, null, userId); } - pkgSetting.notLaunched = false; + pkgSetting.setNotLaunched(false, userId); } return true; } return false; } + private List<UserInfo> getAllUsers() { + long id = Binder.clearCallingIdentity(); + try { + return AppGlobals.getPackageManager().getUsers(); + } catch (RemoteException re) { + // Local to system process, shouldn't happen + } catch (NullPointerException npe) { + // packagemanager not yet initialized + } finally { + Binder.restoreCallingIdentity(id); + } + return null; + } + + static final void printFlags(PrintWriter pw, int val, Object[] spec) { + pw.print("[ "); + for (int i=0; i<spec.length; i+=2) { + int mask = (Integer)spec[i]; + if ((val & mask) != 0) { + pw.print(spec[i+1]); + pw.print(" "); + } + } + pw.print("]"); + } + + static final Object[] FLAG_DUMP_SPEC = new Object[] { + ApplicationInfo.FLAG_SYSTEM, "SYSTEM", + ApplicationInfo.FLAG_DEBUGGABLE, "DEBUGGABLE", + ApplicationInfo.FLAG_HAS_CODE, "HAS_CODE", + ApplicationInfo.FLAG_PERSISTENT, "PERSISTENT", + ApplicationInfo.FLAG_FACTORY_TEST, "FACTORY_TEST", + ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING, "ALLOW_TASK_REPARENTING", + ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA, "ALLOW_CLEAR_USER_DATA", + ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, "UPDATED_SYSTEM_APP", + ApplicationInfo.FLAG_TEST_ONLY, "TEST_ONLY", + ApplicationInfo.FLAG_VM_SAFE_MODE, "VM_SAFE_MODE", + ApplicationInfo.FLAG_ALLOW_BACKUP, "ALLOW_BACKUP", + ApplicationInfo.FLAG_KILL_AFTER_RESTORE, "KILL_AFTER_RESTORE", + 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", + }; + void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final Date date = new Date(); boolean printedSomething = false; + List<UserInfo> users = getAllUsers(); for (final PackageSetting ps : mPackages.values()) { if (packageName != null && !packageName.equals(ps.realName) && !packageName.equals(ps.name)) { @@ -2051,7 +2378,7 @@ final class Settings { pw.println(ps.name); } - pw.print(" userId="); pw.print(ps.userId); + pw.print(" userId="); pw.print(ps.appId); pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids)); pw.print(" sharedUser="); pw.println(ps.sharedUser); pw.print(" pkg="); pw.println(ps.pkg); @@ -2060,6 +2387,7 @@ final class Settings { pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); pw.print(" versionCode="); pw.println(ps.versionCode); if (ps.pkg != null) { + pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println(); pw.print(" versionName="); pw.println(ps.pkg.mVersionName); pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); @@ -2123,18 +2451,23 @@ final class Settings { 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(" stopped="); pw.print(ps.stopped); - pw.print(" enabled="); pw.println(ps.enabled); - if (ps.disabledComponents.size() > 0) { - pw.println(" disabledComponents:"); - for (String s : ps.disabledComponents) { - pw.print(" "); pw.println(s); + for (UserInfo user : users) { + pw.print(" User "); pw.print(user.id); pw.print(": "); + pw.print(" stopped="); + pw.print(ps.getStopped(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)) { + pw.print(" "); pw.println(s); + } } - } - if (ps.enabledComponents.size() > 0) { - pw.println(" enabledComponents:"); - for (String s : ps.enabledComponents) { - pw.print(" "); pw.println(s); + if (ps.getEnabledComponents(user.id).size() > 0) { + pw.println(" enabledComponents:"); + for (String s : ps.getEnabledComponents(user.id)) { + pw.print(" "); pw.println(s); + } } } if (ps.grantedPermissions.size() > 0) { @@ -2147,7 +2480,7 @@ final class Settings { printedSomething = false; if (mRenamedPackages.size() > 0) { - for (final HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { + for (final Map.Entry<String, String> e : mRenamedPackages.entrySet()) { if (packageName != null && !packageName.equals(e.getKey()) && !packageName.equals(e.getValue())) { continue; @@ -2188,7 +2521,7 @@ final class Settings { pw.println(ps.name); } pw.print(" userId="); - pw.println(ps.userId); + pw.println(ps.appId); pw.print(" sharedUser="); pw.println(ps.sharedUser); pw.print(" codePath="); @@ -2198,7 +2531,7 @@ final class Settings { } } } - + void dumpPermissionsLPr(PrintWriter pw, String packageName, DumpState dumpState) { boolean printedSomething = false; for (BasePermission p : mPermissions.values()) { @@ -2218,16 +2551,21 @@ final class Settings { pw.print(" uid="); pw.print(p.uid); pw.print(" gids="); pw.print(PackageManagerService.arrayToString(p.gids)); pw.print(" type="); pw.print(p.type); - pw.print(" prot="); pw.println(p.protectionLevel); + pw.print(" prot="); + pw.println(PermissionInfo.protectionToString(p.protectionLevel)); if (p.packageSetting != null) { pw.print(" packageSetting="); pw.println(p.packageSetting); } if (p.perm != null) { pw.print(" perm="); pw.println(p.perm); } + if (READ_EXTERNAL_STORAGE.equals(p.name)) { + pw.print(" enforced="); + pw.println(mReadExternalStorageEnforced); + } } } - + void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState) { boolean printedSomething = false; for (SharedUserSetting su : mSharedUsers.values()) { diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java index 76fa5ab..4e9e666 100644 --- a/services/java/com/android/server/pm/UserManager.java +++ b/services/java/com/android/server/pm/UserManager.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import android.content.pm.ApplicationInfo; @@ -24,6 +25,7 @@ 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; @@ -57,7 +59,7 @@ public class 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; + private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>(); private final File mUsersDir; private final File mUserListFile; @@ -72,6 +74,9 @@ public class UserManager { 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 @@ -87,11 +92,36 @@ public class UserManager { } public List<UserInfo> getUsers() { - ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size()); - for (int i = 0; i < mUsers.size(); i++) { - users.add(mUsers.valueAt(i)); + 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); + } } - return users; } /** @@ -104,9 +134,14 @@ public class UserManager { } private void readUserList() { - mUsers = new SparseArray<UserInfo>(); + synchronized (mUsers) { + readUserListLocked(); + } + } + + private void readUserListLocked() { if (!mUserListFile.exists()) { - fallbackToSingleUser(); + fallbackToSingleUserLocked(); return; } FileInputStream fis = null; @@ -122,7 +157,7 @@ public class UserManager { if (type != XmlPullParser.START_TAG) { Slog.e(LOG_TAG, "Unable to read user list"); - fallbackToSingleUser(); + fallbackToSingleUserLocked(); return; } @@ -135,23 +170,30 @@ public class UserManager { } } } - updateUserIds(); + updateUserIdsLocked(); } catch (IOException ioe) { - fallbackToSingleUser(); + fallbackToSingleUserLocked(); } catch (XmlPullParserException pe) { - fallbackToSingleUser(); + fallbackToSingleUserLocked(); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } } } - private void fallbackToSingleUser() { + private void fallbackToSingleUserLocked() { // Create the primary user UserInfo primary = new UserInfo(0, "Primary", UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); mUsers.put(0, primary); - updateUserIds(); + updateUserIdsLocked(); - writeUserList(); - writeUser(primary); + writeUserListLocked(); + writeUserLocked(primary); } /* @@ -161,10 +203,11 @@ public class UserManager { * <name>Primary</name> * </user> */ - private void writeUser(UserInfo userInfo) { + private void writeUserLocked(UserInfo userInfo) { + FileOutputStream fos = null; try { final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); - final FileOutputStream fos = new FileOutputStream(mUserFile); + fos = new FileOutputStream(mUserFile); final BufferedOutputStream bos = new BufferedOutputStream(fos); // XmlSerializer serializer = XmlUtils.serializerInstance(); @@ -186,6 +229,13 @@ public class UserManager { 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) { + } + } } } @@ -197,9 +247,10 @@ public class UserManager { * <user id="2"></user> * </users> */ - private void writeUserList() { + private void writeUserListLocked() { + FileOutputStream fos = null; try { - final FileOutputStream fos = new FileOutputStream(mUserListFile); + fos = new FileOutputStream(mUserListFile); final BufferedOutputStream bos = new BufferedOutputStream(fos); // XmlSerializer serializer = XmlUtils.serializerInstance(); @@ -222,6 +273,13 @@ public class UserManager { serializer.endDocument(); } catch (IOException ioe) { Slog.e(LOG_TAG, "Error writing user list"); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException ioe) { + } + } } } @@ -265,28 +323,36 @@ public class UserManager { } } } - fis.close(); 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, List<ApplicationInfo> apps) { + 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, apps)) { + if (!createPackageFolders(userId, userPath)) { return null; } - mUsers.put(userId, userInfo); - writeUserList(); - writeUser(userInfo); - updateUserIds(); + synchronized (mUsers) { + mUsers.put(userId, userInfo); + writeUserListLocked(); + writeUserLocked(userInfo); + updateUserIdsLocked(); + } return userInfo; } @@ -295,7 +361,13 @@ public class UserManager { * after the user's processes have been terminated. * @param id the user's id */ - public void removeUser(int 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) { @@ -305,11 +377,11 @@ public class UserManager { File userFile = new File(mUsersDir, id + ".xml"); userFile.delete(); // Update the user list - writeUserList(); - // Remove the data directories for all packages for this user - removePackageFolders(id); - updateUserIds(); + writeUserListLocked(); + updateUserIdsLocked(); + return true; } + return false; } public void installPackageForAllUsers(String packageName, int uid) { @@ -317,7 +389,7 @@ public class UserManager { // Don't do it for the primary user, it will become recursive. if (userId == 0) continue; - mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid), + mInstaller.createUserData(packageName, UserId.getUid(userId, uid), userId); } } @@ -343,7 +415,7 @@ public class UserManager { /** * Caches the list of user ids in an array, adjusting the array size when necessary. */ - private void updateUserIds() { + private void updateUserIdsLocked() { if (mUserIds == null || mUserIds.length != mUsers.size()) { mUserIds = new int[mUsers.size()]; } @@ -354,6 +426,8 @@ public class UserManager { /** * 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() { @@ -367,31 +441,21 @@ public class UserManager { return i; } - private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) { + private boolean createPackageFolders(int id, File userPath) { // mInstaller may not be available for unit-tests. - if (mInstaller == null || apps == null) return true; + if (mInstaller == null) return true; - final long startTime = SystemClock.elapsedRealtime(); // Create the user path userPath.mkdir(); FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1); - // Create the individual data directories - for (ApplicationInfo app : apps) { - if (app.uid > android.os.Process.FIRST_APPLICATION_UID - && app.uid < PackageManager.PER_USER_RANGE) { - mInstaller.createUserData(app.packageName, - PackageManager.getUid(id, app.uid), id); - } - } - final long stopTime = SystemClock.elapsedRealtime(); - Log.i(LOG_TAG, - "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); + mInstaller.cloneUserData(0, id, false); + return true; } - private boolean removePackageFolders(int id) { + boolean removePackageFolders(int id) { // mInstaller may not be available for unit-tests. if (mInstaller == null) return true; diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index ed83fbe..33612b0 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -43,6 +43,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.UEventObserver; import android.provider.Settings; @@ -59,6 +60,7 @@ import java.util.LinkedList; import java.util.List; import java.util.HashMap; import java.util.Map; +import java.util.Scanner; /** * UsbDeviceManager manages USB state in device mode. @@ -80,13 +82,18 @@ public class UsbDeviceManager { "/sys/class/android_usb/android0/f_mass_storage/lun/file"; private static final String RNDIS_ETH_ADDR_PATH = "/sys/class/android_usb/android0/f_rndis/ethaddr"; + private static final String AUDIO_SOURCE_PCM_PATH = + "/sys/class/android_usb/android0/f_audio_source/pcm"; private static final int MSG_UPDATE_STATE = 0; private static final int MSG_ENABLE_ADB = 1; - private static final int MSG_SET_CURRENT_FUNCTION = 2; + private static final int MSG_SET_CURRENT_FUNCTIONS = 2; private static final int MSG_SYSTEM_READY = 3; private static final int MSG_BOOT_COMPLETED = 4; + private static final int AUDIO_MODE_NONE = 0; + private static final int AUDIO_MODE_SOURCE = 1; + // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, // which need debouncing. @@ -104,7 +111,9 @@ public class UsbDeviceManager { private final boolean mHasUsbAccessory; private boolean mUseUsbNotification; private boolean mAdbEnabled; + private boolean mAudioSourceEnabled; private Map<String, List<Pair<String, String>>> mOemModeMap; + private String[] mAccessoryStrings; private class AdbSettingsObserver extends ContentObserver { public AdbSettingsObserver() { @@ -132,7 +141,7 @@ public class UsbDeviceManager { mHandler.updateState(state); } else if ("START".equals(accessory)) { if (DEBUG) Slog.d(TAG, "got accessory start"); - setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false); + startAccessoryMode(); } } }; @@ -155,7 +164,7 @@ public class UsbDeviceManager { if (nativeIsStartRequested()) { if (DEBUG) Slog.d(TAG, "accessory attached at boot"); - setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false); + startAccessoryMode(); } } @@ -182,6 +191,29 @@ public class UsbDeviceManager { mHandler.sendEmptyMessage(MSG_SYSTEM_READY); } + private void startAccessoryMode() { + mAccessoryStrings = nativeGetAccessoryStrings(); + boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE); + // don't start accessory mode if our mandatory strings have not been set + boolean enableAccessory = (mAccessoryStrings != null && + mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null && + mAccessoryStrings[UsbAccessory.MODEL_STRING] != null); + String functions = null; + + if (enableAccessory && enableAudio) { + functions = UsbManager.USB_FUNCTION_ACCESSORY + "," + + UsbManager.USB_FUNCTION_AUDIO_SOURCE; + } else if (enableAccessory) { + functions = UsbManager.USB_FUNCTION_ACCESSORY; + } else if (enableAudio) { + functions = UsbManager.USB_FUNCTION_AUDIO_SOURCE; + } + + if (functions != null) { + setCurrentFunctions(functions, false); + } + } + private static void initRndisAddress() { // configure RNDIS ethernet address based on our serial number using the same algorithm // we had been previously using in kernel board files @@ -206,6 +238,9 @@ public class UsbDeviceManager { } private static String addFunction(String functions, String function) { + if ("none".equals(functions)) { + return function; + } if (!containsFunction(functions, function)) { if (functions.length() > 0) { functions += ","; @@ -222,6 +257,9 @@ public class UsbDeviceManager { split[i] = null; } } + if (split.length == 1 && split[0] == null) { + return "none"; + } StringBuilder builder = new StringBuilder(); for (int i = 0; i < split.length; i++) { String s = split[i]; @@ -284,6 +322,8 @@ public class UsbDeviceManager { String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim(); updateState(state); mAdbEnabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB); + mAudioSourceEnabled = containsFunction(mCurrentFunctions, + UsbManager.USB_FUNCTION_AUDIO_SOURCE); // Upgrade step for previous versions that used persist.service.adb.enable String value = SystemProperties.get("persist.service.adb.enable", ""); @@ -365,11 +405,7 @@ public class UsbDeviceManager { for (int i = 0; i < 20; i++) { // State transition is done when sys.usb.state is set to the new configuration if (state.equals(SystemProperties.get("sys.usb.state"))) return true; - try { - // try again in 50ms - Thread.sleep(50); - } catch (InterruptedException e) { - } + SystemClock.sleep(50); } Slog.e(TAG, "waitForState(" + state + ") FAILED"); return false; @@ -458,9 +494,8 @@ public class UsbDeviceManager { if (!mHasUsbAccessory) return; if (mConfigured) { - String[] strings = nativeGetAccessoryStrings(); - if (strings != null) { - mCurrentAccessory = new UsbAccessory(strings); + if (mAccessoryStrings != null) { + mCurrentAccessory = new UsbAccessory(mAccessoryStrings); Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); // defer accessoryAttached if system is not ready if (mBootCompleted) { @@ -480,6 +515,7 @@ public class UsbDeviceManager { mSettingsManager.accessoryDetached(mCurrentAccessory); } mCurrentAccessory = null; + mAccessoryStrings = null; } } } @@ -501,6 +537,28 @@ public class UsbDeviceManager { mContext.sendStickyBroadcast(intent); } + private void updateAudioSourceFunction(boolean enabled) { + // send a sticky broadcast containing current USB state + Intent intent = new Intent(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.putExtra("state", (enabled ? 1 : 0)); + if (enabled) { + try { + Scanner scanner = new Scanner(new File(AUDIO_SOURCE_PCM_PATH)); + int card = scanner.nextInt(); + int device = scanner.nextInt(); + intent.putExtra("card", card); + intent.putExtra("device", device); + } catch (FileNotFoundException e) { + Slog.e(TAG, "could not open audio source PCM file", e); + } + } + + mContext.sendStickyBroadcast(intent); + mAudioSourceEnabled = enabled; + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -520,15 +578,20 @@ public class UsbDeviceManager { } if (mBootCompleted) { updateUsbState(); + boolean audioSourceEnabled = containsFunction(mCurrentFunctions, + UsbManager.USB_FUNCTION_AUDIO_SOURCE); + if (audioSourceEnabled != mAudioSourceEnabled) { + updateAudioSourceFunction(audioSourceEnabled); + } } break; case MSG_ENABLE_ADB: setAdbEnabled(msg.arg1 == 1); break; - case MSG_SET_CURRENT_FUNCTION: - String function = (String)msg.obj; + case MSG_SET_CURRENT_FUNCTIONS: + String functions = (String)msg.obj; boolean makeDefault = (msg.arg1 == 1); - setEnabledFunctions(function, makeDefault); + setEnabledFunctions(functions, makeDefault); break; case MSG_SYSTEM_READY: updateUsbNotification(); @@ -540,6 +603,7 @@ public class UsbDeviceManager { if (mCurrentAccessory != null) { mSettingsManager.accessoryAttached(mCurrentAccessory); } + updateAudioSourceFunction(mAudioSourceEnabled); break; } } @@ -588,6 +652,7 @@ public class UsbDeviceManager { notification.defaults = 0; // please be quiet notification.sound = null; notification.vibrate = null; + notification.priority = Notification.PRIORITY_MIN; Intent intent = Intent.makeRestartActivityTask( new ComponentName("com.android.settings", @@ -621,6 +686,7 @@ public class UsbDeviceManager { notification.defaults = 0; // please be quiet notification.sound = null; notification.vibrate = null; + notification.priority = Notification.PRIORITY_MIN; Intent intent = Intent.makeRestartActivityTask( new ComponentName("com.android.settings", @@ -678,9 +744,9 @@ public class UsbDeviceManager { return nativeOpenAccessory(); } - public void setCurrentFunction(String function, boolean makeDefault) { - if (DEBUG) Slog.d(TAG, "setCurrentFunction(" + function + ") default: " + makeDefault); - mHandler.sendMessage(MSG_SET_CURRENT_FUNCTION, function, makeDefault); + public void setCurrentFunctions(String functions, boolean makeDefault) { + if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ") default: " + makeDefault); + mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, makeDefault); } public void setMassStorageBackingFile(String path) { @@ -748,4 +814,5 @@ public class UsbDeviceManager { private native String[] nativeGetAccessoryStrings(); private native ParcelFileDescriptor nativeOpenAccessory(); private native boolean nativeIsStartRequested(); + private native int nativeGetAudioMode(); } diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index 9f2c17a..0205ef8 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -149,7 +149,7 @@ public class UsbService extends IUsbManager.Stub { public void setCurrentFunction(String function, boolean makeDefault) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); if (mDeviceManager != null) { - mDeviceManager.setCurrentFunction(function, makeDefault); + mDeviceManager.setCurrentFunctions(function, makeDefault); } else { throw new IllegalStateException("USB device mode not supported"); } diff --git a/services/java/com/android/server/usb/UsbSettingsManager.java b/services/java/com/android/server/usb/UsbSettingsManager.java index 0baafbb..7dde340 100644 --- a/services/java/com/android/server/usb/UsbSettingsManager.java +++ b/services/java/com/android/server/usb/UsbSettingsManager.java @@ -370,7 +370,7 @@ class UsbSettingsManager { synchronized (mLock) { readSettingsLocked(); } - mPackageMonitor.register(context, true); + mPackageMonitor.register(context, null, true); } private void readPreference(XmlPullParser parser) diff --git a/services/java/com/android/server/wm/AppWindowAnimator.java b/services/java/com/android/server/wm/AppWindowAnimator.java new file mode 100644 index 0000000..d635e8c --- /dev/null +++ b/services/java/com/android/server/wm/AppWindowAnimator.java @@ -0,0 +1,315 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +package com.android.server.wm; + +import android.graphics.Matrix; +import android.util.Slog; +import android.view.Surface; +import android.view.WindowManagerPolicy; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +import java.io.PrintWriter; + +/** + * + */ +public class AppWindowAnimator { + static final String TAG = "AppWindowAnimator"; + + final AppWindowToken mAppToken; + final WindowManagerService mService; + final WindowAnimator mAnimator; + + boolean animating; + Animation animation; + boolean animInitialized; + boolean hasTransformation; + final Transformation transformation = new Transformation(); + + // Have we been asked to have this token keep the screen frozen? + // Protect with mAnimator. + boolean freezingScreen; + + // Offset to the window of all layers in the token, for use by + // AppWindowToken animations. + int animLayerAdjustment; + + // Special surface for thumbnail animation. + Surface thumbnail; + int thumbnailTransactionSeq; + int thumbnailX; + int thumbnailY; + int thumbnailLayer; + Animation thumbnailAnimation; + final Transformation thumbnailTransformation = new Transformation(); + + static final Animation sDummyAnimation = new DummyAnimation(); + + public AppWindowAnimator(final WindowManagerService service, final AppWindowToken atoken) { + mService = service; + mAppToken = atoken; + mAnimator = service.mAnimator; + } + + public void setAnimation(Animation anim, boolean initialized) { + if (WindowManagerService.localLOGV) Slog.v( + TAG, "Setting animation in " + mAppToken + ": " + anim); + animation = anim; + animating = false; + animInitialized = initialized; + anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); + anim.scaleCurrentDuration(mService.mTransitionAnimationScale); + int zorder = anim.getZAdjustment(); + int adj = 0; + if (zorder == Animation.ZORDER_TOP) { + adj = WindowManagerService.TYPE_LAYER_OFFSET; + } else if (zorder == Animation.ZORDER_BOTTOM) { + adj = -WindowManagerService.TYPE_LAYER_OFFSET; + } + + if (animLayerAdjustment != adj) { + animLayerAdjustment = adj; + updateLayers(); + } + // Start out animation gone if window is gone, or visible if window is visible. + transformation.clear(); + transformation.setAlpha(mAppToken.reportedVisible ? 1 : 0); + hasTransformation = true; + } + + public void setDummyAnimation() { + if (animation == null) { + if (WindowManagerService.localLOGV) Slog.v( + TAG, "Setting dummy animation in " + mAppToken); + animation = sDummyAnimation; + animInitialized = false; + } + } + + public void clearAnimation() { + if (animation != null) { + animation = null; + animating = true; + animInitialized = false; + } + clearThumbnail(); + } + + public void clearThumbnail() { + if (thumbnail != null) { + thumbnail.destroy(); + thumbnail = null; + } + } + + void updateLayers() { + final int N = mAppToken.allAppWindows.size(); + final int adj = animLayerAdjustment; + thumbnailLayer = -1; + for (int i=0; i<N; i++) { + final WindowState w = mAppToken.allAppWindows.get(i); + final WindowStateAnimator winAnimator = w.mWinAnimator; + winAnimator.mAnimLayer = w.mLayer + adj; + if (winAnimator.mAnimLayer > thumbnailLayer) { + thumbnailLayer = winAnimator.mAnimLayer; + } + if (WindowManagerService.DEBUG_LAYERS) Slog.v(TAG, "Updating layer " + w + ": " + + winAnimator.mAnimLayer); + if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) { + mService.setInputMethodAnimLayerAdjustment(adj); + } + if (w == mService.mWallpaperTarget && mService.mLowerWallpaperTarget == null) { + mService.setWallpaperAnimLayerAdjustmentLocked(adj); + } + } + } + + private void stepThumbnailAnimation(long currentTime) { + thumbnailTransformation.clear(); + thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation); + thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY); + final boolean screenAnimation = mAnimator.mScreenRotationAnimation != null + && mAnimator.mScreenRotationAnimation.isAnimating(); + if (screenAnimation) { + thumbnailTransformation.postCompose( + mAnimator.mScreenRotationAnimation.getEnterTransformation()); + } + // cache often used attributes locally + final float tmpFloats[] = mService.mTmpFloats; + thumbnailTransformation.getMatrix().getValues(tmpFloats); + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail, + "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X] + + ", " + tmpFloats[Matrix.MTRANS_Y], null); + thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]); + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail, + "thumbnail", "alpha=" + thumbnailTransformation.getAlpha() + + " layer=" + thumbnailLayer + + " matrix=[" + tmpFloats[Matrix.MSCALE_X] + + "," + tmpFloats[Matrix.MSKEW_Y] + + "][" + tmpFloats[Matrix.MSKEW_X] + + "," + tmpFloats[Matrix.MSCALE_Y] + "]", null); + thumbnail.setAlpha(thumbnailTransformation.getAlpha()); + // The thumbnail is layered below the window immediately above this + // token's anim layer. + thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER + - WindowManagerService.LAYER_OFFSET_THUMBNAIL); + thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], + tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); + } + + private boolean stepAnimation(long currentTime) { + if (animation == null) { + return false; + } + transformation.clear(); + final boolean more = animation.getTransformation(currentTime, transformation); + if (WindowManagerService.DEBUG_ANIM) Slog.v( + TAG, "Stepped animation in " + mAppToken + ": more=" + more + ", xform=" + transformation); + if (!more) { + animation = null; + clearThumbnail(); + if (WindowManagerService.DEBUG_ANIM) Slog.v( + TAG, "Finished animation in " + mAppToken + " @ " + currentTime); + } + hasTransformation = more; + return more; + } + + // This must be called while inside a transaction. + boolean stepAnimationLocked(long currentTime, int dw, int dh) { + if (mService.okToDisplay()) { + // We will run animations as long as the display isn't frozen. + + if (animation == sDummyAnimation) { + // This guy is going to animate, but not yet. For now count + // it as not animating for purposes of scheduling transactions; + // when it is really time to animate, this will be set to + // a real animation and the next call will execute normally. + hasTransformation = true; + transformation.setAlpha(mAppToken.reportedVisible ? 1 : 0); + return false; + } + + if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed) + && animation != null) { + if (!animating) { + if (WindowManagerService.DEBUG_ANIM) Slog.v( + TAG, "Starting animation in " + mAppToken + + " @ " + currentTime + ": dw=" + dw + " dh=" + dh + + " scale=" + mService.mTransitionAnimationScale + + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating); + if (!animInitialized) { + animation.initialize(dw, dh, dw, dh); + } + animation.setStartTime(currentTime); + animating = true; + if (thumbnail != null) { + thumbnail.show(); + thumbnailAnimation.setStartTime(currentTime); + } + } + if (stepAnimation(currentTime)) { + // animation isn't over, step any thumbnail and that's + // it for now. + if (thumbnail != null) { + stepThumbnailAnimation(currentTime); + } + return true; + } + } + } else if (animation != null) { + // If the display is frozen, and there is a pending animation, + // clear it and make sure we run the cleanup code. + animating = true; + animation = null; + } + + hasTransformation = false; + + if (!animating && animation == null) { + return false; + } + + mAnimator.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + mService.debugLayoutRepeats("AppWindowToken", mAnimator.mPendingLayoutChanges); + } + + clearAnimation(); + animating = false; + if (animLayerAdjustment != 0) { + animLayerAdjustment = 0; + updateLayers(); + } + if (mService.mInputMethodTarget != null + && mService.mInputMethodTarget.mAppToken == mAppToken) { + mService.moveInputMethodWindowsIfNeededLocked(true); + } + + if (WindowManagerService.DEBUG_ANIM) Slog.v( + TAG, "Animation done in " + mAppToken + + ": reportedVisible=" + mAppToken.reportedVisible); + + transformation.clear(); + + final int N = mAppToken.windows.size(); + for (int i=0; i<N; i++) { + mAppToken.windows.get(i).mWinAnimator.finishExit(); + } + mAppToken.updateReportedVisibilityLocked(); + + return false; + } + + boolean showAllWindowsLocked() { + boolean isAnimating = false; + final int NW = mAppToken.allAppWindows.size(); + for (int i=0; i<NW; i++) { + WindowStateAnimator winAnimator = mAppToken.allAppWindows.get(i).mWinAnimator; + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, + "performing show on: " + winAnimator); + winAnimator.performShowLocked(); + isAnimating |= winAnimator.isAnimating(); + } + return isAnimating; + } + + void dump(PrintWriter pw, String prefix) { + if (freezingScreen) { + pw.print(prefix); pw.print(" freezingScreen="); pw.println(freezingScreen); + } + if (animating || animation != null) { + pw.print(prefix); pw.print("animating="); pw.print(animating); + pw.print(" animation="); pw.println(animation); + } + if (hasTransformation) { + pw.print(prefix); pw.print("XForm: "); + transformation.printShortString(pw); + pw.println(); + } + if (animLayerAdjustment != 0) { + pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment); + } + if (thumbnail != null) { + pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail); + pw.print(" x="); pw.print(thumbnailX); + pw.print(" y="); pw.print(thumbnailY); + pw.print(" layer="); pw.println(thumbnailLayer); + pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation); + pw.print(prefix); pw.print("thumbnailTransformation="); + pw.println(thumbnailTransformation.toShortString()); + } + } + + // This is an animation that does nothing: it just immediately finishes + // itself every time it is called. It is used as a stub animation in cases + // where we want to synchronize multiple things that may be animating. + static final class DummyAnimation extends Animation { + @Override + public boolean getTransformation(long currentTime, Transformation outTransformation) { + return false; + } + } + +} diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index 0e3d20a..bf35154 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import com.android.server.input.InputApplicationHandle; import com.android.server.wm.WindowManagerService.H; import android.content.pm.ActivityInfo; @@ -27,8 +28,6 @@ import android.util.Slog; import android.view.IApplicationToken; import android.view.View; import android.view.WindowManager; -import android.view.animation.Animation; -import android.view.animation.Transformation; import java.io.PrintWriter; import java.util.ArrayList; @@ -44,18 +43,22 @@ class AppWindowToken extends WindowToken { // All of the windows and child windows that are included in this // application token. Note this list is NOT sorted! final ArrayList<WindowState> allAppWindows = new ArrayList<WindowState>(); + final AppWindowAnimator mAppAnimator; + + final WindowAnimator mAnimator; int groupId = -1; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - + // The input dispatching timeout for this application token in nanoseconds. long inputDispatchingTimeoutNanos; // These are used for determining when all windows associated with // an activity have been drawn, so they can be made visible together // at the same time. - int lastTransactionSequence; + // initialize so that it doesn't match mTransactionSequence which is an int. + long lastTransactionSequence = Long.MIN_VALUE; int numInterestingWindows; int numDrawnWindows; boolean inPendingTransaction; @@ -83,18 +86,6 @@ class AppWindowToken extends WindowToken { // Set to true when the token has been removed from the window mgr. boolean removed; - // Have we been asked to have this token keep the screen frozen? - boolean freezingScreen; - - boolean animating; - Animation animation; - boolean hasTransformation; - final Transformation transformation = new Transformation(); - - // Offset to the window of all layers in the token, for use by - // AppWindowToken animations. - int animLayerAdjustment; - // Information about an application starting window if displayed. StartingData startingData; WindowState startingWindow; @@ -112,60 +103,8 @@ class AppWindowToken extends WindowToken { appWindowToken = this; appToken = _token; mInputApplicationHandle = new InputApplicationHandle(this); - lastTransactionSequence = service.mTransactionSequence-1; - } - - public void setAnimation(Animation anim) { - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Setting animation in " + this + ": " + anim); - animation = anim; - animating = false; - anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); - anim.scaleCurrentDuration(service.mTransitionAnimationScale); - int zorder = anim.getZAdjustment(); - int adj = 0; - if (zorder == Animation.ZORDER_TOP) { - adj = WindowManagerService.TYPE_LAYER_OFFSET; - } else if (zorder == Animation.ZORDER_BOTTOM) { - adj = -WindowManagerService.TYPE_LAYER_OFFSET; - } - - if (animLayerAdjustment != adj) { - animLayerAdjustment = adj; - updateLayers(); - } - } - - public void setDummyAnimation() { - if (animation == null) { - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Setting dummy animation in " + this); - animation = WindowManagerService.sDummyAnimation; - } - } - - public void clearAnimation() { - if (animation != null) { - animation = null; - animating = true; - } - } - - void updateLayers() { - final int N = allAppWindows.size(); - final int adj = animLayerAdjustment; - for (int i=0; i<N; i++) { - WindowState w = allAppWindows.get(i); - w.mAnimLayer = w.mLayer + adj; - if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Updating layer " + w + ": " - + w.mAnimLayer); - if (w == service.mInputMethodTarget && !service.mInputMethodTargetWaitingAnim) { - service.setInputMethodAnimLayerAdjustment(adj); - } - if (w == service.mWallpaperTarget && service.mLowerWallpaperTarget == null) { - service.setWallpaperAnimLayerAdjustmentLocked(adj); - } - } + mAnimator = service.mAnimator; + mAppAnimator = new AppWindowAnimator(_service, this); } void sendAppVisibilityToClients() { @@ -185,94 +124,6 @@ class AppWindowToken extends WindowToken { } } - void showAllWindowsLocked() { - final int NW = allAppWindows.size(); - for (int i=0; i<NW; i++) { - WindowState w = allAppWindows.get(i); - if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, - "performing show on: " + w); - w.performShowLocked(); - } - } - - // This must be called while inside a transaction. - boolean stepAnimationLocked(long currentTime, int dw, int dh) { - if (!service.mDisplayFrozen && service.mPolicy.isScreenOnFully()) { - // We will run animations as long as the display isn't frozen. - - if (animation == WindowManagerService.sDummyAnimation) { - // This guy is going to animate, but not yet. For now count - // it as not animating for purposes of scheduling transactions; - // when it is really time to animate, this will be set to - // a real animation and the next call will execute normally. - return false; - } - - if ((allDrawn || animating || startingDisplayed) && animation != null) { - if (!animating) { - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Starting animation in " + this + - " @ " + currentTime + ": dw=" + dw + " dh=" + dh - + " scale=" + service.mTransitionAnimationScale - + " allDrawn=" + allDrawn + " animating=" + animating); - animation.initialize(dw, dh, dw, dh); - animation.setStartTime(currentTime); - animating = true; - } - transformation.clear(); - final boolean more = animation.getTransformation( - currentTime, transformation); - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Stepped animation in " + this + - ": more=" + more + ", xform=" + transformation); - if (more) { - // we're done! - hasTransformation = true; - return true; - } - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Finished animation in " + this + - " @ " + currentTime); - animation = null; - } - } else if (animation != null) { - // If the display is frozen, and there is a pending animation, - // clear it and make sure we run the cleanup code. - animating = true; - animation = null; - } - - hasTransformation = false; - - if (!animating) { - return false; - } - - clearAnimation(); - animating = false; - if (animLayerAdjustment != 0) { - animLayerAdjustment = 0; - updateLayers(); - } - if (service.mInputMethodTarget != null && service.mInputMethodTarget.mAppToken == this) { - service.moveInputMethodWindowsIfNeededLocked(true); - } - - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Animation done in " + this - + ": reportedVisible=" + reportedVisible); - - transformation.clear(); - - final int N = windows.size(); - for (int i=0; i<N; i++) { - windows.get(i).finishExit(); - } - updateReportedVisibilityLocked(); - - return false; - } - void updateReportedVisibilityLocked() { if (appToken == null) { return; @@ -283,7 +134,8 @@ class AppWindowToken extends WindowToken { int numDrawn = 0; boolean nowGone = true; - if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Update reported visibility: " + this); + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, + "Update reported visibility: " + this); final int N = allAppWindows.size(); for (int i=0; i<N; i++) { WindowState win = allAppWindows.get(i); @@ -296,27 +148,26 @@ class AppWindowToken extends WindowToken { if (WindowManagerService.DEBUG_VISIBILITY) { Slog.v(WindowManagerService.TAG, "Win " + win + ": isDrawn=" + win.isDrawnLw() - + ", isAnimating=" + win.isAnimating()); + + ", isAnimating=" + win.mWinAnimator.isAnimating()); if (!win.isDrawnLw()) { - Slog.v(WindowManagerService.TAG, "Not displayed: s=" + win.mSurface + Slog.v(WindowManagerService.TAG, "Not displayed: s=" + win.mWinAnimator.mSurface + " pv=" + win.mPolicyVisibility - + " dp=" + win.mDrawPending - + " cdp=" + win.mCommitDrawPending + + " mDrawState=" + win.mWinAnimator.mDrawState + " ah=" + win.mAttachedHidden + " th=" + (win.mAppToken != null ? win.mAppToken.hiddenRequested : false) - + " a=" + win.mAnimating); + + " a=" + win.mWinAnimator.mAnimating); } } numInteresting++; if (win.isDrawnLw()) { numDrawn++; - if (!win.isAnimating()) { + if (!win.mWinAnimator.isAnimating()) { numVisible++; } nowGone = false; - } else if (win.isAnimating()) { + } else if (win.mWinAnimator.isAnimating()) { nowGone = false; } } @@ -369,6 +220,7 @@ class AppWindowToken extends WindowToken { return null; } + @Override void dump(PrintWriter pw, String prefix) { super.dump(pw, prefix); if (appToken != null) { @@ -385,9 +237,8 @@ class AppWindowToken extends WindowToken { pw.print(" willBeHidden="); pw.print(willBeHidden); pw.print(" reportedDrawn="); pw.print(reportedDrawn); pw.print(" reportedVisible="); pw.println(reportedVisible); - if (paused || freezingScreen) { - pw.print(prefix); pw.print("paused="); pw.print(paused); - pw.print(" freezingScreen="); pw.println(freezingScreen); + if (paused) { + pw.print(prefix); pw.print("paused="); pw.println(paused); } if (numInterestingWindows != 0 || numDrawnWindows != 0 || inPendingTransaction || allDrawn) { @@ -397,18 +248,6 @@ class AppWindowToken extends WindowToken { pw.print(" inPendingTransaction="); pw.print(inPendingTransaction); pw.print(" allDrawn="); pw.println(allDrawn); } - if (animating || animation != null) { - pw.print(prefix); pw.print("animating="); pw.print(animating); - pw.print(" animation="); pw.println(animation); - } - if (hasTransformation) { - pw.print(prefix); pw.print("XForm: "); - transformation.printShortString(pw); - pw.println(); - } - if (animLayerAdjustment != 0) { - pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment); - } if (startingData != null || removed || firstWindowDrawn) { pw.print(prefix); pw.print("startingData="); pw.print(startingData); pw.print(" removed="); pw.print(removed); @@ -434,4 +273,4 @@ class AppWindowToken extends WindowToken { } return stringName; } -}
\ No newline at end of file +} diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java index 10e294b..c915932 100644 --- a/services/java/com/android/server/wm/BlackFrame.java +++ b/services/java/com/android/server/wm/BlackFrame.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import java.io.PrintWriter; + import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -30,12 +32,14 @@ public class BlackFrame { class BlackSurface { final int left; final int top; + final int layer; final Surface surface; BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b) throws Surface.OutOfResourcesException { left = l; top = t; + this.layer = layer; int w = r-l; int h = b-t; surface = new Surface(session, 0, "BlackSurface", @@ -43,8 +47,6 @@ public class BlackFrame { if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, " BLACK " + surface + ": CREATE layer=" + layer); - surface.setAlpha(1.0f); - surface.setLayer(layer); } void setMatrix(Matrix matrix) { @@ -56,6 +58,8 @@ public class BlackFrame { surface.setMatrix( mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); + surface.setAlpha(1.0f); + surface.setLayer(layer); if (false) { Slog.i(WindowManagerService.TAG, "Black Surface @ (" + left + "," + top + "): (" + mTmpFloats[Matrix.MTRANS_X] + "," @@ -72,14 +76,31 @@ public class BlackFrame { } } + final Rect mOuterRect; + final Rect mInnerRect; final Matrix mTmpMatrix = new Matrix(); final float[] mTmpFloats = new float[9]; final BlackSurface[] mBlackSurfaces = new BlackSurface[4]; + public void printTo(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("Outer: "); mOuterRect.printShortString(pw); + pw.print(" / Inner: "); mInnerRect.printShortString(pw); + pw.println(); + for (int i=0; i<mBlackSurfaces.length; i++) { + BlackSurface bs = mBlackSurfaces[i]; + pw.print(prefix); pw.print("#"); pw.print(i); + pw.print(": "); pw.print(bs.surface); + pw.print(" left="); pw.print(bs.left); + pw.print(" top="); pw.println(bs.top); + } + } + public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer) throws Surface.OutOfResourcesException { boolean success = false; + mOuterRect = new Rect(outer); + mInnerRect = new Rect(inner); try { if (outer.top < inner.top) { mBlackSurfaces[0] = new BlackSurface(session, layer, @@ -138,6 +159,14 @@ public class BlackFrame { } } + public void setAlpha(float alpha) { + for (int i=0; i<mBlackSurfaces.length; i++) { + if (mBlackSurfaces[i] != null) { + mBlackSurfaces[i].surface.setAlpha(alpha); + } + } + } + public void clearMatrix() { for (int i=0; i<mBlackSurfaces.length; i++) { if (mBlackSurfaces[i] != null) { diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java index a9d4e01..f9f9d1a 100644 --- a/services/java/com/android/server/wm/DimAnimator.java +++ b/services/java/com/android/server/wm/DimAnimator.java @@ -41,14 +41,21 @@ class DimAnimator { DimAnimator (SurfaceSession session) { if (mDimSurface == null) { - if (WindowManagerService.SHOW_TRANSACTIONS || - WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, - " DIM " + mDimSurface + ": CREATE"); try { - mDimSurface = new Surface(session, 0, + if (WindowManagerService.DEBUG_SURFACE_TRACE) { + mDimSurface = new WindowStateAnimator.SurfaceTrace(session, 0, "DimAnimator", -1, 16, 16, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); + } else { + mDimSurface = new Surface(session, 0, + "DimAnimator", + -1, 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM); + } + if (WindowManagerService.SHOW_TRANSACTIONS || + WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, + " DIM " + mDimSurface + ": CREATE"); mDimSurface.setAlpha(0.0f); } catch (Exception e) { Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e); @@ -57,12 +64,17 @@ class DimAnimator { } /** - * Show the dim surface. + * Set's the dim surface's layer and update dim parameters that will be used in + * {@link #updateSurface} after all windows are examined. */ - void show(int dw, int dh) { + void updateParameters(final Resources res, final Parameters params, final long currentTime) { + final int dw = params.mDimWidth; + final int dh = params.mDimHeight; + final WindowStateAnimator winAnimator = params.mDimWinAnimator; + final float target = params.mDimTarget; if (!mDimShown) { - if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + - dw + "x" + dh + ")"); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + dw + "x" + dh + ")"); mDimShown = true; try { mLastDimWidth = dw; @@ -78,31 +90,24 @@ class DimAnimator { mLastDimHeight = dh; mDimSurface.setSize(dw, dh); } - } - /** - * Set's the dim surface's layer and update dim parameters that will be used in - * {@link updateSurface} after all windows are examined. - */ - void updateParameters(Resources res, WindowState w, long currentTime) { - mDimSurface.setLayer(w.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM); + mDimSurface.setLayer(winAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM); - final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; - if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface - + ": layer=" + (w.mAnimLayer-1) + " target=" + target); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + + mDimSurface + ": layer=" + (winAnimator.mAnimLayer-1) + " target=" + target); if (mDimTargetAlpha != target) { // If the desired dim level has changed, then // start an animation to it. mLastDimAnimTime = currentTime; - long duration = (w.mAnimating && w.mAnimation != null) - ? w.mAnimation.computeDurationHint() + long duration = (winAnimator.mAnimating && winAnimator.mAnimation != null) + ? winAnimator.mAnimation.computeDurationHint() : WindowManagerService.DEFAULT_DIM_DURATION; if (target > mDimTargetAlpha) { TypedValue tv = new TypedValue(); res.getValue(com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true); if (tv.type == TypedValue.TYPE_FRACTION) { - duration = (long)tv.getFraction((float)duration, (float)duration); + duration = (long)tv.getFraction(duration, duration); } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) { duration = tv.data; @@ -130,33 +135,31 @@ class DimAnimator { } } - boolean animating = false; - if (mLastDimAnimTime != 0) { + boolean animating = mLastDimAnimTime != 0; + if (animating) { mDimCurrentAlpha += mDimDeltaPerMs * (currentTime-mLastDimAnimTime); - boolean more = true; if (displayFrozen) { // If the display is frozen, there is no reason to animate. - more = false; + animating = false; } else if (mDimDeltaPerMs > 0) { if (mDimCurrentAlpha > mDimTargetAlpha) { - more = false; + animating = false; } } else if (mDimDeltaPerMs < 0) { if (mDimCurrentAlpha < mDimTargetAlpha) { - more = false; + animating = false; } } else { - more = false; + animating = false; } // Do we need to continue animating? - if (more) { + if (animating) { if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": alpha=" + mDimCurrentAlpha); mLastDimAnimTime = currentTime; mDimSurface.setAlpha(mDimCurrentAlpha); - animating = true; } else { mDimCurrentAlpha = mDimTargetAlpha; mLastDimAnimTime = 0; @@ -190,4 +193,18 @@ class DimAnimator { pw.print(" delta="); pw.print(mDimDeltaPerMs); pw.print(" lastAnimTime="); pw.println(mLastDimAnimTime); } + + static class Parameters { + final WindowStateAnimator mDimWinAnimator; + final int mDimWidth; + final int mDimHeight; + final float mDimTarget; + Parameters(final WindowStateAnimator dimWinAnimator, final int dimWidth, + final int dimHeight, final float dimTarget) { + mDimWinAnimator = dimWinAnimator; + mDimWidth = dimWidth; + mDimHeight = dimHeight; + mDimTarget = dimTarget; + } + } }
\ 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 dc6cc0d..9fca418 100644 --- a/services/java/com/android/server/wm/DimSurface.java +++ b/services/java/com/android/server/wm/DimSurface.java @@ -32,14 +32,21 @@ class DimSurface { DimSurface(SurfaceSession session) { if (mDimSurface == null) { - if (WindowManagerService.SHOW_TRANSACTIONS || - WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, - " DIM " + mDimSurface + ": CREATE"); try { - mDimSurface = new Surface(session, 0, + if (WindowManagerService.DEBUG_SURFACE_TRACE) { + mDimSurface = new WindowStateAnimator.SurfaceTrace(session, 0, "DimSurface", -1, 16, 16, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM); + } else { + mDimSurface = new Surface(session, 0, + "DimSurface", + -1, 16, 16, PixelFormat.OPAQUE, + Surface.FX_SURFACE_DIM); + } + if (WindowManagerService.SHOW_TRANSACTIONS || + WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, + " DIM " + mDimSurface + ": CREATE"); mDimSurface.setAlpha(0.0f); } catch (Exception e) { Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e); diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java index 73cd64e..b2cf3e0 100644 --- a/services/java/com/android/server/wm/DragState.java +++ b/services/java/com/android/server/wm/DragState.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import com.android.server.input.InputApplicationHandle; +import com.android.server.input.InputWindowHandle; +import com.android.server.wm.WindowManagerService.DragInputEventReceiver; import com.android.server.wm.WindowManagerService.H; import android.content.ClipData; @@ -28,7 +31,6 @@ import android.os.RemoteException; import android.util.Slog; import android.view.DragEvent; import android.view.InputChannel; -import android.view.InputQueue; import android.view.Surface; import android.view.View; import android.view.WindowManager; @@ -50,6 +52,7 @@ class DragState { float mCurrentX, mCurrentY; float mThumbOffsetX, mThumbOffsetY; InputChannel mServerChannel, mClientChannel; + DragInputEventReceiver mInputEventReceiver; InputApplicationHandle mDragApplicationHandle; InputWindowHandle mDragWindowHandle; WindowState mTargetWindow; @@ -90,8 +93,8 @@ class DragState { mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); - InputQueue.registerInputChannel(mClientChannel, mService.mDragInputHandler, - mService.mH.getLooper().getQueue()); + mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel, + mService.mH.getLooper()); mDragApplicationHandle = new InputApplicationHandle(null); mDragApplicationHandle.name = "drag"; @@ -139,7 +142,8 @@ class DragState { Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel"); } else { mService.mInputManager.unregisterInputChannel(mServerChannel); - InputQueue.unregisterInputChannel(mClientChannel); + mInputEventReceiver.dispose(); + mInputEventReceiver = null; mClientChannel.dispose(); mServerChannel.dispose(); mClientChannel = null; diff --git a/services/java/com/android/server/wm/FakeWindowImpl.java b/services/java/com/android/server/wm/FakeWindowImpl.java index 0e72f7d..2527f46 100644 --- a/services/java/com/android/server/wm/FakeWindowImpl.java +++ b/services/java/com/android/server/wm/FakeWindowImpl.java @@ -16,11 +16,14 @@ package com.android.server.wm; +import com.android.server.input.InputApplicationHandle; +import com.android.server.input.InputWindowHandle; + import android.os.Looper; import android.os.Process; import android.util.Slog; import android.view.InputChannel; -import android.view.InputHandler; +import android.view.InputEventReceiver; import android.view.InputQueue; import android.view.WindowManagerPolicy; @@ -29,11 +32,13 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { final InputChannel mServerChannel, mClientChannel; final InputApplicationHandle mApplicationHandle; final InputWindowHandle mWindowHandle; + final InputEventReceiver mInputEventReceiver; final int mWindowLayer; boolean mTouchFullscreen; - public FakeWindowImpl(WindowManagerService service, Looper looper, InputHandler inputHandler, + public FakeWindowImpl(WindowManagerService service, + Looper looper, InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { mService = service; @@ -42,7 +47,9 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); - InputQueue.registerInputChannel(mClientChannel, inputHandler, looper.getQueue()); + + mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver( + mClientChannel, looper); mApplicationHandle = new InputApplicationHandle(null); mApplicationHandle.name = name; @@ -87,8 +94,8 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { public void dismiss() { synchronized (mService.mWindowMap) { if (mService.removeFakeWindowLocked(this)) { + mInputEventReceiver.dispose(); mService.mInputManager.unregisterInputChannel(mServerChannel); - InputQueue.unregisterInputChannel(mClientChannel); mClientChannel.dispose(); mServerChannel.dispose(); } diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java deleted file mode 100644 index a4f0a0c..0000000 --- a/services/java/com/android/server/wm/InputManager.java +++ /dev/null @@ -1,702 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import com.android.internal.util.XmlUtils; -import com.android.server.Watchdog; - -import org.xmlpull.v1.XmlPullParser; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.database.ContentObserver; -import android.os.Environment; -import android.os.Looper; -import android.os.MessageQueue; -import android.os.SystemProperties; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.util.Slog; -import android.util.Xml; -import android.view.InputChannel; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.KeyEvent; -import android.view.PointerIcon; -import android.view.Surface; -import android.view.ViewConfiguration; -import android.view.WindowManager; -import android.view.WindowManagerPolicy; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; - -/* - * Wraps the C++ InputManager and provides its callbacks. - */ -public class InputManager implements Watchdog.Monitor { - static final String TAG = "InputManager"; - - private static final boolean DEBUG = false; - - private final Callbacks mCallbacks; - private final Context mContext; - private final WindowManagerService mWindowManagerService; - - private static native void nativeInit(Context context, - Callbacks callbacks, MessageQueue messageQueue); - private static native void nativeStart(); - private static native void nativeSetDisplaySize(int displayId, int width, int height, - int externalWidth, int externalHeight); - private static native void nativeSetDisplayOrientation(int displayId, int rotation); - - private static native int nativeGetScanCodeState(int deviceId, int sourceMask, - int scanCode); - private static native int nativeGetKeyCodeState(int deviceId, int sourceMask, - int keyCode); - private static native int nativeGetSwitchState(int deviceId, int sourceMask, - int sw); - private static native boolean nativeHasKeys(int deviceId, int sourceMask, - int[] keyCodes, boolean[] keyExists); - private static native void nativeRegisterInputChannel(InputChannel inputChannel, - InputWindowHandle inputWindowHandle, boolean monitor); - private static native void nativeUnregisterInputChannel(InputChannel inputChannel); - private static native void nativeSetInputFilterEnabled(boolean enable); - private static native int nativeInjectInputEvent(InputEvent event, - int injectorPid, int injectorUid, int syncMode, int timeoutMillis, - int policyFlags); - private static native void nativeSetInputWindows(InputWindowHandle[] windowHandles); - private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); - private static native void nativeSetSystemUiVisibility(int visibility); - private static native void nativeSetFocusedApplication(InputApplicationHandle application); - private static native InputDevice nativeGetInputDevice(int deviceId); - private static native void nativeGetInputConfiguration(Configuration configuration); - private static native int[] nativeGetInputDeviceIds(); - private static native boolean nativeTransferTouchFocus(InputChannel fromChannel, - InputChannel toChannel); - private static native void nativeSetPointerSpeed(int speed); - private static native void nativeSetShowTouches(boolean enabled); - private static native String nativeDump(); - private static native void nativeMonitor(); - - // Input event injection constants defined in InputDispatcher.h. - static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0; - static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1; - static final int INPUT_EVENT_INJECTION_FAILED = 2; - static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3; - - // Input event injection synchronization modes defined in InputDispatcher.h - static final int INPUT_EVENT_INJECTION_SYNC_NONE = 0; - static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1; - static final int INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH = 2; - - // Key states (may be returned by queries about the current state of a - // particular key code, scan code or switch). - - /** The key state is unknown or the requested key itself is not supported. */ - public static final int KEY_STATE_UNKNOWN = -1; - - /** The key is up. /*/ - public static final int KEY_STATE_UP = 0; - - /** The key is down. */ - public static final int KEY_STATE_DOWN = 1; - - /** The key is down but is a virtual key press that is being emulated by the system. */ - public static final int KEY_STATE_VIRTUAL = 2; - - // State for the currently installed input filter. - final Object mInputFilterLock = new Object(); - InputFilter mInputFilter; - InputFilterHost mInputFilterHost; - - public InputManager(Context context, WindowManagerService windowManagerService) { - this.mContext = context; - this.mWindowManagerService = windowManagerService; - this.mCallbacks = new Callbacks(); - - Looper looper = windowManagerService.mH.getLooper(); - - Slog.i(TAG, "Initializing input manager"); - nativeInit(mContext, mCallbacks, looper.getQueue()); - - // Add ourself to the Watchdog monitors. - Watchdog.getInstance().addMonitor(this); - } - - public void start() { - Slog.i(TAG, "Starting input manager"); - nativeStart(); - - registerPointerSpeedSettingObserver(); - registerShowTouchesSettingObserver(); - - updatePointerSpeedFromSettings(); - updateShowTouchesFromSettings(); - } - - 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."); - } - - if (DEBUG) { - Slog.d(TAG, "Setting display #" + displayId + " size to " + width + "x" + height - + " external size " + externalWidth + "x" + externalHeight); - } - nativeSetDisplaySize(displayId, width, height, externalWidth, externalHeight); - } - - public void setDisplayOrientation(int displayId, int rotation) { - 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); - } - nativeSetDisplayOrientation(displayId, rotation); - } - - public void getInputConfiguration(Configuration config) { - if (config == null) { - throw new IllegalArgumentException("config must not be null."); - } - - nativeGetInputConfiguration(config); - } - - /** - * Gets the current state of a key or button by key code. - * @param deviceId The input device id, or -1 to consult all devices. - * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to - * consider all input sources. An input device is consulted if at least one of its - * non-class input source bits matches the specified source mask. - * @param keyCode The key code to check. - * @return The key state. - */ - public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) { - return nativeGetKeyCodeState(deviceId, sourceMask, keyCode); - } - - /** - * Gets the current state of a key or button by scan code. - * @param deviceId The input device id, or -1 to consult all devices. - * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to - * consider all input sources. An input device is consulted if at least one of its - * non-class input source bits matches the specified source mask. - * @param scanCode The scan code to check. - * @return The key state. - */ - public int getScanCodeState(int deviceId, int sourceMask, int scanCode) { - return nativeGetScanCodeState(deviceId, sourceMask, scanCode); - } - - /** - * Gets the current state of a switch by switch code. - * @param deviceId The input device id, or -1 to consult all devices. - * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to - * consider all input sources. An input device is consulted if at least one of its - * non-class input source bits matches the specified source mask. - * @param switchCode The switch code to check. - * @return The switch state. - */ - public int getSwitchState(int deviceId, int sourceMask, int switchCode) { - return nativeGetSwitchState(deviceId, sourceMask, switchCode); - } - - /** - * Determines whether the specified key codes are supported by a particular device. - * @param deviceId The input device id, or -1 to consult all devices. - * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to - * consider all input sources. An input device is consulted if at least one of its - * non-class input source bits matches the specified source mask. - * @param keyCodes The array of key codes to check. - * @param keyExists An array at least as large as keyCodes whose entries will be set - * to true or false based on the presence or absence of support for the corresponding - * key codes. - * @return True if the lookup was successful, false otherwise. - */ - public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) { - if (keyCodes == null) { - throw new IllegalArgumentException("keyCodes must not be null."); - } - if (keyExists == null || keyExists.length < keyCodes.length) { - throw new IllegalArgumentException("keyExists must not be null and must be at " - + "least as large as keyCodes."); - } - - return nativeHasKeys(deviceId, sourceMask, keyCodes, keyExists); - } - - /** - * Creates an input channel that will receive all input from the input dispatcher. - * @param inputChannelName The input channel name. - * @return The input channel. - */ - public InputChannel monitorInput(String inputChannelName) { - if (inputChannelName == null) { - throw new IllegalArgumentException("inputChannelName must not be null."); - } - - InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); - nativeRegisterInputChannel(inputChannels[0], null, true); - inputChannels[0].dispose(); // don't need to retain the Java object reference - return inputChannels[1]; - } - - /** - * Registers an input channel so that it can be used as an input event target. - * @param inputChannel The input channel to register. - * @param inputWindowHandle The handle of the input window associated with the - * input channel, or null if none. - */ - public void registerInputChannel(InputChannel inputChannel, - InputWindowHandle inputWindowHandle) { - if (inputChannel == null) { - throw new IllegalArgumentException("inputChannel must not be null."); - } - - nativeRegisterInputChannel(inputChannel, inputWindowHandle, false); - } - - /** - * Unregisters an input channel. - * @param inputChannel The input channel to unregister. - */ - public void unregisterInputChannel(InputChannel inputChannel) { - if (inputChannel == null) { - throw new IllegalArgumentException("inputChannel must not be null."); - } - - nativeUnregisterInputChannel(inputChannel); - } - - /** - * Sets an input filter that will receive all input events before they are dispatched. - * The input filter may then reinterpret input events or inject new ones. - * - * To ensure consistency, the input dispatcher automatically drops all events - * in progress whenever an input filter is installed or uninstalled. After an input - * filter is uninstalled, it can no longer send input events unless it is reinstalled. - * Any events it attempts to send after it has been uninstalled will be dropped. - * - * @param filter The input filter, or null to remove the current filter. - */ - public void setInputFilter(InputFilter filter) { - synchronized (mInputFilterLock) { - final InputFilter oldFilter = mInputFilter; - if (oldFilter == filter) { - return; // nothing to do - } - - if (oldFilter != null) { - mInputFilter = null; - mInputFilterHost.disconnectLocked(); - mInputFilterHost = null; - oldFilter.uninstall(); - } - - if (filter != null) { - mInputFilter = filter; - mInputFilterHost = new InputFilterHost(); - filter.install(mInputFilterHost); - } - - nativeSetInputFilterEnabled(filter != null); - } - } - - /** - * Injects an input event into the event system on behalf of an application. - * The synchronization mode determines whether the method blocks while waiting for - * input injection to proceed. - * - * {@link #INPUT_EVENT_INJECTION_SYNC_NONE} never blocks. Injection is asynchronous and - * is assumed always to be successful. - * - * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT} waits for previous events to be - * dispatched so that the input dispatcher can determine whether input event injection will - * be permitted based on the current input focus. Does not wait for the input event to - * finish processing. - * - * {@link #INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH} waits for the input event to - * be completely processed. - * - * @param event The event to inject. - * @param injectorPid The pid of the injecting application. - * @param injectorUid The uid of the injecting application. - * @param syncMode The synchronization mode. - * @param timeoutMillis The injection timeout in milliseconds. - * @return One of the INPUT_EVENT_INJECTION_XXX constants. - */ - public int injectInputEvent(InputEvent event, int injectorPid, int injectorUid, - int syncMode, int timeoutMillis) { - if (event == null) { - throw new IllegalArgumentException("event must not be null"); - } - if (injectorPid < 0 || injectorUid < 0) { - throw new IllegalArgumentException("injectorPid and injectorUid must not be negative."); - } - if (timeoutMillis <= 0) { - throw new IllegalArgumentException("timeoutMillis must be positive"); - } - - return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis, - WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); - } - - /** - * Gets information about the input device with the specified id. - * @param id The device id. - * @return The input device or null if not found. - */ - public InputDevice getInputDevice(int deviceId) { - return nativeGetInputDevice(deviceId); - } - - /** - * Gets the ids of all input devices in the system. - * @return The input device ids. - */ - public int[] getInputDeviceIds() { - return nativeGetInputDeviceIds(); - } - - public void setInputWindows(InputWindowHandle[] windowHandles) { - nativeSetInputWindows(windowHandles); - } - - public void setFocusedApplication(InputApplicationHandle application) { - nativeSetFocusedApplication(application); - } - - public void setInputDispatchMode(boolean enabled, boolean frozen) { - nativeSetInputDispatchMode(enabled, frozen); - } - - public void setSystemUiVisibility(int visibility) { - nativeSetSystemUiVisibility(visibility); - } - - /** - * Atomically transfers touch focus from one window to another as identified by - * their input channels. It is possible for multiple windows to have - * touch focus if they support split touch dispatch - * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this - * method only transfers touch focus of the specified window without affecting - * other windows that may also have touch focus at the same time. - * @param fromChannel The channel of a window that currently has touch focus. - * @param toChannel The channel of the window that should receive touch focus in - * place of the first. - * @return True if the transfer was successful. False if the window with the - * specified channel did not actually have touch focus at the time of the request. - */ - public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { - if (fromChannel == null) { - throw new IllegalArgumentException("fromChannel must not be null."); - } - if (toChannel == null) { - throw new IllegalArgumentException("toChannel must not be null."); - } - return nativeTransferTouchFocus(fromChannel, toChannel); - } - - /** - * Set the pointer speed. - * @param speed The pointer speed as a value between -7 (slowest) and 7 (fastest) - * where 0 is the default speed. - */ - public void setPointerSpeed(int speed) { - speed = Math.min(Math.max(speed, -7), 7); - nativeSetPointerSpeed(speed); - } - - public void updatePointerSpeedFromSettings() { - int speed = getPointerSpeedSetting(0); - setPointerSpeed(speed); - } - - private void registerPointerSpeedSettingObserver() { - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.POINTER_SPEED), true, - new ContentObserver(mWindowManagerService.mH) { - @Override - public void onChange(boolean selfChange) { - updatePointerSpeedFromSettings(); - } - }); - } - - private int getPointerSpeedSetting(int defaultValue) { - int speed = defaultValue; - try { - speed = Settings.System.getInt(mContext.getContentResolver(), - Settings.System.POINTER_SPEED); - } catch (SettingNotFoundException snfe) { - } - return speed; - } - - public void updateShowTouchesFromSettings() { - int setting = getShowTouchesSetting(0); - nativeSetShowTouches(setting != 0); - } - - private void registerShowTouchesSettingObserver() { - mContext.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true, - new ContentObserver(mWindowManagerService.mH) { - @Override - public void onChange(boolean selfChange) { - updateShowTouchesFromSettings(); - } - }); - } - - private int getShowTouchesSetting(int defaultValue) { - int result = defaultValue; - try { - result = Settings.System.getInt(mContext.getContentResolver(), - Settings.System.SHOW_TOUCHES); - } catch (SettingNotFoundException snfe) { - } - return result; - } - - public void dump(PrintWriter pw) { - String dumpStr = nativeDump(); - if (dumpStr != null) { - pw.println(dumpStr); - } - } - - // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection). - public void monitor() { - synchronized (mInputFilterLock) { } - nativeMonitor(); - } - - private final class InputFilterHost implements InputFilter.Host { - private boolean mDisconnected; - - public void disconnectLocked() { - mDisconnected = true; - } - - public void sendInputEvent(InputEvent event, int policyFlags) { - if (event == null) { - throw new IllegalArgumentException("event must not be null"); - } - - synchronized (mInputFilterLock) { - if (!mDisconnected) { - nativeInjectInputEvent(event, 0, 0, INPUT_EVENT_INJECTION_SYNC_NONE, 0, - policyFlags | WindowManagerPolicy.FLAG_FILTERED); - } - } - } - } - - /* - * Callbacks from native. - */ - private final class Callbacks { - static final String TAG = "InputManager-Callbacks"; - - private static final boolean DEBUG_VIRTUAL_KEYS = false; - private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; - private static final String CALIBRATION_DIR_PATH = "usr/idc/"; - - @SuppressWarnings("unused") - public void notifyConfigurationChanged(long whenNanos) { - mWindowManagerService.mInputMonitor.notifyConfigurationChanged(); - } - - @SuppressWarnings("unused") - public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { - mWindowManagerService.mInputMonitor.notifyLidSwitchChanged(whenNanos, lidOpen); - } - - @SuppressWarnings("unused") - public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { - mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputWindowHandle); - } - - @SuppressWarnings("unused") - public long notifyANR(InputApplicationHandle inputApplicationHandle, - InputWindowHandle inputWindowHandle) { - return mWindowManagerService.mInputMonitor.notifyANR( - inputApplicationHandle, inputWindowHandle); - } - - @SuppressWarnings("unused") - final boolean filterInputEvent(InputEvent event, int policyFlags) { - synchronized (mInputFilterLock) { - if (mInputFilter != null) { - mInputFilter.filterInputEvent(event, policyFlags); - return false; - } - } - event.recycle(); - return true; - } - - @SuppressWarnings("unused") - public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { - return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( - event, policyFlags, isScreenOn); - } - - @SuppressWarnings("unused") - public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags) { - return mWindowManagerService.mInputMonitor.interceptMotionBeforeQueueingWhenScreenOff( - policyFlags); - } - - @SuppressWarnings("unused") - public long interceptKeyBeforeDispatching(InputWindowHandle focus, - KeyEvent event, int policyFlags) { - return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching( - focus, event, policyFlags); - } - - @SuppressWarnings("unused") - public KeyEvent dispatchUnhandledKey(InputWindowHandle focus, - KeyEvent event, int policyFlags) { - return mWindowManagerService.mInputMonitor.dispatchUnhandledKey( - focus, event, policyFlags); - } - - @SuppressWarnings("unused") - public boolean checkInjectEventsPermission(int injectorPid, int injectorUid) { - return mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid) - == PackageManager.PERMISSION_GRANTED; - } - - @SuppressWarnings("unused") - public int getVirtualKeyQuietTimeMillis() { - return mContext.getResources().getInteger( - com.android.internal.R.integer.config_virtualKeyQuietTimeMillis); - } - - @SuppressWarnings("unused") - public String[] getExcludedDeviceNames() { - ArrayList<String> names = new ArrayList<String>(); - - // Read partner-provided list of excluded input devices - XmlPullParser parser = null; - // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". - File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); - FileReader confreader = null; - try { - confreader = new FileReader(confFile); - parser = Xml.newPullParser(); - parser.setInput(confreader); - XmlUtils.beginDocument(parser, "devices"); - - while (true) { - XmlUtils.nextElement(parser); - if (!"device".equals(parser.getName())) { - break; - } - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - names.add(name); - } - } - } catch (FileNotFoundException e) { - // It's ok if the file does not exist. - } catch (Exception e) { - Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); - } finally { - try { if (confreader != null) confreader.close(); } catch (IOException e) { } - } - - return names.toArray(new String[names.size()]); - } - - @SuppressWarnings("unused") - public int getKeyRepeatTimeout() { - return ViewConfiguration.getKeyRepeatTimeout(); - } - - @SuppressWarnings("unused") - public int getKeyRepeatDelay() { - return ViewConfiguration.getKeyRepeatDelay(); - } - - @SuppressWarnings("unused") - public int getHoverTapTimeout() { - return ViewConfiguration.getHoverTapTimeout(); - } - - @SuppressWarnings("unused") - public int getHoverTapSlop() { - return ViewConfiguration.getHoverTapSlop(); - } - - @SuppressWarnings("unused") - public int getDoubleTapTimeout() { - return ViewConfiguration.getDoubleTapTimeout(); - } - - @SuppressWarnings("unused") - public int getLongPressTimeout() { - return ViewConfiguration.getLongPressTimeout(); - } - - @SuppressWarnings("unused") - public int getMaxEventsPerSecond() { - int result = 0; - try { - result = Integer.parseInt(SystemProperties.get("windowsmgr.max_events_per_sec")); - } catch (NumberFormatException e) { - } - if (result < 1) { - // This number equates to the refresh rate * 1.5. The rate should be at least - // equal to the screen refresh rate. We increase the rate by 50% to compensate for - // the discontinuity between the actual rate that events come in at (they do - // not necessarily come in constantly and are not handled synchronously). - // Ideally, we would use Display.getRefreshRate(), but as this does not necessarily - // return a sensible result, we use '60' as our default assumed refresh rate. - result = 90; - } - return result; - } - - @SuppressWarnings("unused") - public int getPointerLayer() { - return mWindowManagerService.mPolicy.windowTypeToLayerLw( - WindowManager.LayoutParams.TYPE_POINTER) - * WindowManagerService.TYPE_LAYER_MULTIPLIER - + WindowManagerService.TYPE_LAYER_OFFSET; - } - - @SuppressWarnings("unused") - public PointerIcon getPointerIcon() { - return PointerIcon.getDefaultIcon(mContext); - } - } -} diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java index fb74d27..c28cfa2 100644 --- a/services/java/com/android/server/wm/InputMonitor.java +++ b/services/java/com/android/server/wm/InputMonitor.java @@ -16,6 +16,10 @@ package com.android.server.wm; +import com.android.server.input.InputManagerService; +import com.android.server.input.InputApplicationHandle; +import com.android.server.input.InputWindowHandle; + import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; @@ -27,7 +31,7 @@ import android.view.WindowManager; import java.util.ArrayList; import java.util.Arrays; -final class InputMonitor { +final class InputMonitor implements InputManagerService.Callbacks { private final WindowManagerService mService; // Current window with input focus for keys and other non-touch events. May be null. @@ -93,7 +97,7 @@ final class InputMonitor { } if (appWindowToken == null && inputApplicationHandle != null) { - appWindowToken = inputApplicationHandle.appWindowToken; + appWindowToken = (AppWindowToken)inputApplicationHandle.appWindowToken; if (appWindowToken != null) { Slog.i(WindowManagerService.TAG, "Input event dispatching timed out sending to application " @@ -301,7 +305,14 @@ final class InputMonitor { WindowState windowState = focus != null ? (WindowState) focus.windowState : null; return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags); } - + + /* Callback to get pointer layer. */ + public int getPointerLayer() { + return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_POINTER) + * WindowManagerService.TYPE_LAYER_MULTIPLIER + + WindowManagerService.TYPE_LAYER_OFFSET; + } + /* Called when the current input focus changes. * Layer assignment is assumed to be complete by the time this is called. */ diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java index 8fc9a70..13013a8 100644 --- a/services/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java @@ -16,14 +16,13 @@ package com.android.server.wm; +import java.io.PrintWriter; + +import static com.android.server.wm.WindowStateAnimator.SurfaceTrace; + import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Matrix; -import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.util.Slog; import android.view.Surface; @@ -34,38 +33,163 @@ import android.view.animation.Transformation; class ScreenRotationAnimation { static final String TAG = "ScreenRotationAnimation"; - static final boolean DEBUG = false; + static final boolean DEBUG_STATE = false; + static final boolean DEBUG_TRANSFORMS = false; + static final boolean TWO_PHASE_ANIMATION = false; + static final boolean USE_CUSTOM_BLACK_FRAME = false; static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200; final Context mContext; Surface mSurface; - BlackFrame mBlackFrame; + BlackFrame mCustomBlackFrame; + BlackFrame mExitingBlackFrame; + BlackFrame mEnteringBlackFrame; int mWidth, mHeight; - int mSnapshotRotation; - int mSnapshotDeltaRotation; int mOriginalRotation; int mOriginalWidth, mOriginalHeight; int mCurRotation; - Animation mExitAnimation; + // For all animations, "exit" is for the UI elements that are going + // away (that is the snapshot of the old screen), and "enter" is for + // the new UI elements that are appearing (that is the active windows + // in their final orientation). + + // The starting animation for the exiting and entering elements. This + // animation applies a transformation while the rotation is in progress. + // It is started immediately, before the new entering UI is ready. + Animation mStartExitAnimation; + final Transformation mStartExitTransformation = new Transformation(); + Animation mStartEnterAnimation; + final Transformation mStartEnterTransformation = new Transformation(); + Animation mStartFrameAnimation; + final Transformation mStartFrameTransformation = new Transformation(); + + // The finishing animation for the exiting and entering elements. This + // animation needs to undo the transformation of the starting animation. + // It starts running once the new rotation UI elements are ready to be + // displayed. + Animation mFinishExitAnimation; + final Transformation mFinishExitTransformation = new Transformation(); + Animation mFinishEnterAnimation; + final Transformation mFinishEnterTransformation = new Transformation(); + Animation mFinishFrameAnimation; + final Transformation mFinishFrameTransformation = new Transformation(); + + // The current active animation to move from the old to the new rotated + // state. Which animation is run here will depend on the old and new + // rotations. + Animation mRotateExitAnimation; + final Transformation mRotateExitTransformation = new Transformation(); + Animation mRotateEnterAnimation; + final Transformation mRotateEnterTransformation = new Transformation(); + Animation mRotateFrameAnimation; + final Transformation mRotateFrameTransformation = new Transformation(); + + // A previously running rotate animation. This will be used if we need + // to switch to a new rotation before finishing the previous one. + Animation mLastRotateExitAnimation; + final Transformation mLastRotateExitTransformation = new Transformation(); + Animation mLastRotateEnterAnimation; + final Transformation mLastRotateEnterTransformation = new Transformation(); + Animation mLastRotateFrameAnimation; + final Transformation mLastRotateFrameTransformation = new Transformation(); + + // Complete transformations being applied. final Transformation mExitTransformation = new Transformation(); - Animation mEnterAnimation; final Transformation mEnterTransformation = new Transformation(); + final Transformation mFrameTransformation = new Transformation(); + boolean mStarted; + boolean mAnimRunning; + boolean mFinishAnimReady; + long mFinishAnimStartTime; + final Matrix mFrameInitialMatrix = new Matrix(); final Matrix mSnapshotInitialMatrix = new Matrix(); final Matrix mSnapshotFinalMatrix = new Matrix(); + final Matrix mExitFrameFinalMatrix = new Matrix(); final Matrix mTmpMatrix = new Matrix(); final float[] mTmpFloats = new float[9]; + private boolean mMoreRotateEnter; + private boolean mMoreRotateExit; + private boolean mMoreRotateFrame; + private boolean mMoreFinishEnter; + private boolean mMoreFinishExit; + private boolean mMoreFinishFrame; + private boolean mMoreStartEnter; + private boolean mMoreStartExit; + private boolean mMoreStartFrame; + long mHalfwayPoint; + + public void printTo(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mSurface="); pw.print(mSurface); + pw.print(" mWidth="); pw.print(mWidth); + pw.print(" mHeight="); pw.println(mHeight); + if (USE_CUSTOM_BLACK_FRAME) { + pw.print(prefix); pw.print("mCustomBlackFrame="); pw.println(mCustomBlackFrame); + if (mCustomBlackFrame != null) { + mCustomBlackFrame.printTo(prefix + " ", pw); + } + } + pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame); + if (mExitingBlackFrame != null) { + mExitingBlackFrame.printTo(prefix + " ", pw); + } + pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame); + if (mEnteringBlackFrame != null) { + mEnteringBlackFrame.printTo(prefix + " ", pw); + } + pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation); + pw.print(" mOriginalRotation="); pw.println(mOriginalRotation); + pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth); + pw.print(" mOriginalHeight="); pw.println(mOriginalHeight); + pw.print(prefix); pw.print("mStarted="); pw.print(mStarted); + pw.print(" mAnimRunning="); pw.print(mAnimRunning); + pw.print(" mFinishAnimReady="); pw.print(mFinishAnimReady); + pw.print(" mFinishAnimStartTime="); pw.println(mFinishAnimStartTime); + pw.print(prefix); pw.print("mStartExitAnimation="); pw.print(mStartExitAnimation); + pw.print(" "); mStartExitTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mStartEnterAnimation="); pw.print(mStartEnterAnimation); + pw.print(" "); mStartEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mStartFrameAnimation="); pw.print(mStartFrameAnimation); + pw.print(" "); mStartFrameTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mFinishExitAnimation="); pw.print(mFinishExitAnimation); + pw.print(" "); mFinishExitTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mFinishEnterAnimation="); pw.print(mFinishEnterAnimation); + pw.print(" "); mFinishEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mFinishFrameAnimation="); pw.print(mFinishFrameAnimation); + pw.print(" "); mFinishFrameTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mRotateExitAnimation="); pw.print(mRotateExitAnimation); + pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation); + pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mRotateFrameAnimation="); pw.print(mRotateFrameAnimation); + pw.print(" "); mRotateFrameTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mExitTransformation="); + mExitTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mEnterTransformation="); + mEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mFrameTransformation="); + mEnterTransformation.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mFrameInitialMatrix="); + mFrameInitialMatrix.printShortString(pw); + pw.println(); + pw.print(prefix); pw.print("mSnapshotInitialMatrix="); + mSnapshotInitialMatrix.printShortString(pw); + pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw); + pw.println(); + pw.print(prefix); pw.print("mExitFrameFinalMatrix="); + mExitFrameFinalMatrix.printShortString(pw); + pw.println(); + } public ScreenRotationAnimation(Context context, SurfaceSession session, boolean inTransaction, int originalWidth, int originalHeight, int originalRotation) { mContext = context; // Screenshot does NOT include rotation! - mSnapshotRotation = 0; if (originalRotation == Surface.ROTATION_90 || originalRotation == Surface.ROTATION_270) { mWidth = originalHeight; @@ -84,17 +208,23 @@ class ScreenRotationAnimation { ">>> OPEN TRANSACTION ScreenRotationAnimation"); Surface.openTransaction(); } - + try { try { - mSurface = new Surface(session, 0, "FreezeSurface", - -1, mWidth, mHeight, PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN); - if (mSurface == null || !mSurface.isValid()) { + if (WindowManagerService.DEBUG_SURFACE_TRACE) { + mSurface = new SurfaceTrace(session, 0, "FreezeSurface", -1, 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); + } + if (!mSurface.isValid()) { // Screenshot failed, punt. mSurface = null; return; } mSurface.setLayer(FREEZE_LAYER + 1); + mSurface.setAlpha(0); mSurface.show(); } catch (Surface.OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate freeze surface", e); @@ -133,7 +263,7 @@ class ScreenRotationAnimation { mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); mSurface.setAlpha(alpha); - if (DEBUG) { + if (DEBUG_TRANSFORMS) { float[] srcPnts = new float[] { 0, 0, mWidth, mHeight }; float[] dstPnts = new float[4]; matrix.mapPoints(dstPnts, srcPnts); @@ -167,92 +297,289 @@ class ScreenRotationAnimation { } // Must be called while in a transaction. - public void setRotation(int rotation) { + private void setRotation(int rotation) { mCurRotation = rotation; // Compute the transformation matrix that must be applied // to the snapshot to make it stay in the same original position // with the current screen rotation. - int delta = deltaRotation(rotation, mSnapshotRotation); + int delta = deltaRotation(rotation, Surface.ROTATION_0); createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); - if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta); + if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta); setSnapshotTransform(mSnapshotInitialMatrix, 1.0f); } + // Must be called while in a transaction. + public boolean setRotation(int rotation, SurfaceSession session, + long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) { + setRotation(rotation); + if (TWO_PHASE_ANIMATION) { + return startAnimation(session, maxAnimationDuration, animationScale, + finalWidth, finalHeight, false); + } + + // Don't start animation yet. + return false; + } + /** * Returns true if animating. */ - public boolean dismiss(SurfaceSession session, long maxAnimationDuration, - float animationScale, int finalWidth, int finalHeight) { + private boolean startAnimation(SurfaceSession session, long maxAnimationDuration, + float animationScale, int finalWidth, int finalHeight, boolean dismissing) { if (mSurface == null) { // Can't do animation. return false; } + if (mStarted) { + return true; + } + + mStarted = true; + + boolean firstStart = false; // Figure out how the screen has moved from the original rotation. int delta = deltaRotation(mCurRotation, mOriginalRotation); + if (TWO_PHASE_ANIMATION && mFinishExitAnimation == null + && (!dismissing || delta != Surface.ROTATION_0)) { + if (DEBUG_STATE) Slog.v(TAG, "Creating start and finish animations"); + firstStart = true; + mStartExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_start_exit); + mStartEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_start_enter); + if (USE_CUSTOM_BLACK_FRAME) { + mStartFrameAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_start_frame); + } + mFinishExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_finish_exit); + mFinishEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_finish_enter); + if (USE_CUSTOM_BLACK_FRAME) { + mFinishFrameAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_finish_frame); + } + } + + if (DEBUG_STATE) Slog.v(TAG, "Rotation delta: " + delta + " finalWidth=" + + finalWidth + " finalHeight=" + finalHeight + + " origWidth=" + mOriginalWidth + " origHeight=" + mOriginalHeight); + switch (delta) { case Surface.ROTATION_0: - mExitAnimation = AnimationUtils.loadAnimation(mContext, + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_0_exit); - mEnterAnimation = AnimationUtils.loadAnimation(mContext, + 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: - mExitAnimation = AnimationUtils.loadAnimation(mContext, + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_plus_90_exit); - mEnterAnimation = AnimationUtils.loadAnimation(mContext, + 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: - mExitAnimation = AnimationUtils.loadAnimation(mContext, + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_180_exit); - mEnterAnimation = AnimationUtils.loadAnimation(mContext, + 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: - mExitAnimation = AnimationUtils.loadAnimation(mContext, + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_minus_90_exit); - mEnterAnimation = AnimationUtils.loadAnimation(mContext, + 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. - mEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); - mExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); - mStarted = false; + if (TWO_PHASE_ANIMATION && firstStart) { + if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations"); + mStartEnterAnimation.initialize(finalWidth, finalHeight, + halfWidth, halfHeight); + mStartExitAnimation.initialize(halfWidth, halfHeight, + mOriginalWidth, mOriginalHeight); + mFinishEnterAnimation.initialize(finalWidth, finalHeight, + halfWidth, halfHeight); + mFinishExitAnimation.initialize(halfWidth, halfHeight, + mOriginalWidth, mOriginalHeight); + if (USE_CUSTOM_BLACK_FRAME) { + mStartFrameAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + mFinishFrameAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + } + } + mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); + mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); + if (USE_CUSTOM_BLACK_FRAME) { + mRotateFrameAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, + mOriginalHeight); + } + mAnimRunning = false; + mFinishAnimReady = false; + mFinishAnimStartTime = -1; + + if (TWO_PHASE_ANIMATION && firstStart) { + mStartExitAnimation.restrictDuration(maxAnimationDuration); + mStartExitAnimation.scaleCurrentDuration(animationScale); + mStartEnterAnimation.restrictDuration(maxAnimationDuration); + mStartEnterAnimation.scaleCurrentDuration(animationScale); + mFinishExitAnimation.restrictDuration(maxAnimationDuration); + mFinishExitAnimation.scaleCurrentDuration(animationScale); + mFinishEnterAnimation.restrictDuration(maxAnimationDuration); + mFinishEnterAnimation.scaleCurrentDuration(animationScale); + if (USE_CUSTOM_BLACK_FRAME) { + mStartFrameAnimation.restrictDuration(maxAnimationDuration); + mStartFrameAnimation.scaleCurrentDuration(animationScale); + mFinishFrameAnimation.restrictDuration(maxAnimationDuration); + mFinishFrameAnimation.scaleCurrentDuration(animationScale); + } + } + mRotateExitAnimation.restrictDuration(maxAnimationDuration); + mRotateExitAnimation.scaleCurrentDuration(animationScale); + mRotateEnterAnimation.restrictDuration(maxAnimationDuration); + mRotateEnterAnimation.scaleCurrentDuration(animationScale); + if (USE_CUSTOM_BLACK_FRAME) { + mRotateFrameAnimation.restrictDuration(maxAnimationDuration); + mRotateFrameAnimation.scaleCurrentDuration(animationScale); + } - mExitAnimation.restrictDuration(maxAnimationDuration); - mExitAnimation.scaleCurrentDuration(animationScale); - mEnterAnimation.restrictDuration(maxAnimationDuration); - mEnterAnimation.scaleCurrentDuration(animationScale); + if (USE_CUSTOM_BLACK_FRAME && mCustomBlackFrame == null) { + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation"); + Surface.openTransaction(); - if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, - ">>> OPEN TRANSACTION ScreenRotationAnimation.dismiss"); - 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 { - Rect outer = new Rect(-finalWidth, -finalHeight, finalWidth * 2, finalHeight * 2); - Rect inner = new Rect(0, 0, finalWidth, finalHeight); - mBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER); - } catch (Surface.OutOfResourcesException e) { - Slog.w(TAG, "Unable to allocate black surface", e); - } finally { - Surface.closeTransaction(); - if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, - "<<< CLOSE TRANSACTION ScreenRotationAnimation.dismiss"); + try { + 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.setMatrix(mFrameInitialMatrix); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate black surface", e); + } finally { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation"); + } } + if (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 { + 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.setMatrix(mFrameInitialMatrix); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate black surface", e); + } finally { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation"); + } + } + + if (false && mEnteringBlackFrame == null) { + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation"); + Surface.openTransaction(); + + try { + 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); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate black surface", e); + } finally { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation"); + } + } + + return true; + } + + /** + * Returns true if animating. + */ + public boolean dismiss(SurfaceSession session, long maxAnimationDuration, + float animationScale, int finalWidth, int finalHeight) { + if (DEBUG_STATE) Slog.v(TAG, "Dismiss!"); + if (mSurface == null) { + // Can't do animation. + return false; + } + if (!mStarted) { + startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight, + true); + } + if (!mStarted) { + return false; + } + if (DEBUG_STATE) Slog.v(TAG, "Setting mFinishAnimReady = true"); + mFinishAnimReady = true; return true; } public void kill() { + if (DEBUG_STATE) Slog.v(TAG, "Kill!"); if (mSurface != null) { if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, @@ -260,76 +587,342 @@ class ScreenRotationAnimation { mSurface.destroy(); mSurface = null; } - if (mBlackFrame != null) { - mBlackFrame.kill(); + if (mCustomBlackFrame != null) { + mCustomBlackFrame.kill(); + mCustomBlackFrame = null; + } + if (mExitingBlackFrame != null) { + mExitingBlackFrame.kill(); + mExitingBlackFrame = null; + } + if (mEnteringBlackFrame != null) { + mEnteringBlackFrame.kill(); + mEnteringBlackFrame = null; + } + if (TWO_PHASE_ANIMATION) { + if (mStartExitAnimation != null) { + mStartExitAnimation.cancel(); + mStartExitAnimation = null; + } + if (mStartEnterAnimation != null) { + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; + } + if (mFinishExitAnimation != null) { + mFinishExitAnimation.cancel(); + mFinishExitAnimation = null; + } + if (mFinishEnterAnimation != null) { + mFinishEnterAnimation.cancel(); + mFinishEnterAnimation = null; + } + } + if (USE_CUSTOM_BLACK_FRAME) { + if (mStartFrameAnimation != null) { + mStartFrameAnimation.cancel(); + mStartFrameAnimation = null; + } + if (mRotateFrameAnimation != null) { + mRotateFrameAnimation.cancel(); + mRotateFrameAnimation = null; + } + if (mFinishFrameAnimation != null) { + mFinishFrameAnimation.cancel(); + mFinishFrameAnimation = null; + } } - if (mExitAnimation != null) { - mExitAnimation.cancel(); - mExitAnimation = null; + if (mRotateExitAnimation != null) { + mRotateExitAnimation.cancel(); + mRotateExitAnimation = null; } - if (mEnterAnimation != null) { - mEnterAnimation.cancel(); - mEnterAnimation = null; + if (mRotateEnterAnimation != null) { + mRotateEnterAnimation.cancel(); + mRotateEnterAnimation = null; } } public boolean isAnimating() { - return mEnterAnimation != null || mExitAnimation != null; + return hasAnimations() || (TWO_PHASE_ANIMATION && mFinishAnimReady); } - public boolean stepAnimation(long now) { - if (mEnterAnimation == null && mExitAnimation == null) { - return false; + private boolean hasAnimations() { + return (TWO_PHASE_ANIMATION && + (mStartEnterAnimation != null || mStartExitAnimation != null + || mFinishEnterAnimation != null || mFinishExitAnimation != null)) + || (USE_CUSTOM_BLACK_FRAME && + (mStartFrameAnimation != null || mRotateFrameAnimation != null + || mFinishFrameAnimation != null)) + || mRotateEnterAnimation != null || mRotateExitAnimation != null; + } + + private boolean stepAnimation(long now) { + if (now > mHalfwayPoint) { + mHalfwayPoint = Long.MAX_VALUE; + } + if (mFinishAnimReady && mFinishAnimStartTime < 0) { + if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready"); + mFinishAnimStartTime = now; } - if (!mStarted) { - if (mEnterAnimation != null) { - mEnterAnimation.setStartTime(now); - } - if (mExitAnimation != null) { - mExitAnimation.setStartTime(now); - } - mStarted = true; - } - - mExitTransformation.clear(); - boolean moreExit = false; - if (mExitAnimation != null) { - moreExit = mExitAnimation.getTransformation(now, mExitTransformation); - if (DEBUG) Slog.v(TAG, "Stepped exit: " + mExitTransformation); - if (!moreExit) { - if (DEBUG) Slog.v(TAG, "Exit animation done!"); - mExitAnimation.cancel(); - mExitAnimation = null; - mExitTransformation.clear(); - if (mSurface != null) { - mSurface.hide(); + if (TWO_PHASE_ANIMATION) { + mMoreStartExit = false; + if (mStartExitAnimation != null) { + mMoreStartExit = mStartExitAnimation.getTransformation(now, mStartExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start exit: " + mStartExitTransformation); + } + + mMoreStartEnter = false; + if (mStartEnterAnimation != null) { + mMoreStartEnter = mStartEnterAnimation.getTransformation(now, mStartEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start enter: " + mStartEnterTransformation); + } + } + if (USE_CUSTOM_BLACK_FRAME) { + mMoreStartFrame = false; + if (mStartFrameAnimation != null) { + mMoreStartFrame = mStartFrameAnimation.getTransformation(now, mStartFrameTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start frame: " + mStartFrameTransformation); + } + } + + long finishNow = mFinishAnimReady ? (now - mFinishAnimStartTime) : 0; + if (DEBUG_STATE) Slog.v(TAG, "Step: finishNow=" + finishNow); + + if (TWO_PHASE_ANIMATION) { + mMoreFinishExit = false; + if (mFinishExitAnimation != null) { + mMoreFinishExit = mFinishExitAnimation.getTransformation(finishNow, mFinishExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish exit: " + mFinishExitTransformation); + } + + mMoreFinishEnter = false; + if (mFinishEnterAnimation != null) { + mMoreFinishEnter = mFinishEnterAnimation.getTransformation(finishNow, mFinishEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish enter: " + mFinishEnterTransformation); + } + } + if (USE_CUSTOM_BLACK_FRAME) { + mMoreFinishFrame = false; + if (mFinishFrameAnimation != null) { + mMoreFinishFrame = mFinishFrameAnimation.getTransformation(finishNow, mFinishFrameTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish frame: " + mFinishFrameTransformation); + } + } + + mMoreRotateExit = false; + if (mRotateExitAnimation != null) { + mMoreRotateExit = mRotateExitAnimation.getTransformation(now, mRotateExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate exit: " + mRotateExitTransformation); + } + + mMoreRotateEnter = false; + if (mRotateEnterAnimation != null) { + mMoreRotateEnter = mRotateEnterAnimation.getTransformation(now, mRotateEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate enter: " + mRotateEnterTransformation); + } + + if (USE_CUSTOM_BLACK_FRAME) { + mMoreRotateFrame = false; + if (mRotateFrameAnimation != null) { + mMoreRotateFrame = mRotateFrameAnimation.getTransformation(now, mRotateFrameTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate frame: " + mRotateFrameTransformation); + } + } + + if (!mMoreRotateExit && (!TWO_PHASE_ANIMATION || (!mMoreStartExit && !mMoreFinishExit))) { + if (TWO_PHASE_ANIMATION) { + if (mStartExitAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing start exit anim!"); + mStartExitAnimation.cancel(); + mStartExitAnimation = null; + mStartExitTransformation.clear(); + } + if (mFinishExitAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing finish exit anim!"); + mFinishExitAnimation.cancel(); + mFinishExitAnimation = null; + mFinishExitTransformation.clear(); } } + if (mRotateExitAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing rotate exit anim!"); + mRotateExitAnimation.cancel(); + mRotateExitAnimation = null; + mRotateExitTransformation.clear(); + } } - mEnterTransformation.clear(); - boolean moreEnter = false; - if (mEnterAnimation != null) { - moreEnter = mEnterAnimation.getTransformation(now, mEnterTransformation); - if (!moreEnter) { - mEnterAnimation.cancel(); - mEnterAnimation = null; - mEnterTransformation.clear(); - if (mBlackFrame != null) { - mBlackFrame.hide(); + if (!mMoreRotateEnter && (!TWO_PHASE_ANIMATION || (!mMoreStartEnter && !mMoreFinishEnter))) { + if (TWO_PHASE_ANIMATION) { + if (mStartEnterAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing start enter anim!"); + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; + mStartEnterTransformation.clear(); } - } else { - if (mBlackFrame != null) { - mBlackFrame.setMatrix(mEnterTransformation.getMatrix()); + if (mFinishEnterAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing finish enter anim!"); + mFinishEnterAnimation.cancel(); + mFinishEnterAnimation = null; + mFinishEnterTransformation.clear(); } } + if (mRotateEnterAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing rotate enter anim!"); + mRotateEnterAnimation.cancel(); + mRotateEnterAnimation = null; + mRotateEnterTransformation.clear(); + } + } + + if (USE_CUSTOM_BLACK_FRAME && !mMoreStartFrame && !mMoreRotateFrame && !mMoreFinishFrame) { + if (mStartFrameAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing start frame anim!"); + mStartFrameAnimation.cancel(); + mStartFrameAnimation = null; + mStartFrameTransformation.clear(); + } + if (mFinishFrameAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing finish frame anim!"); + mFinishFrameAnimation.cancel(); + mFinishFrameAnimation = null; + mFinishFrameTransformation.clear(); + } + if (mRotateFrameAnimation != null) { + if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing rotate frame anim!"); + mRotateFrameAnimation.cancel(); + mRotateFrameAnimation = null; + mRotateFrameTransformation.clear(); + } } + mExitTransformation.set(mRotateExitTransformation); + mEnterTransformation.set(mRotateEnterTransformation); + if (TWO_PHASE_ANIMATION) { + mExitTransformation.compose(mStartExitTransformation); + mExitTransformation.compose(mFinishExitTransformation); + + mEnterTransformation.compose(mStartEnterTransformation); + mEnterTransformation.compose(mFinishEnterTransformation); + } + + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final exit: " + mExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final enter: " + mEnterTransformation); + + if (USE_CUSTOM_BLACK_FRAME) { + //mFrameTransformation.set(mRotateExitTransformation); + //mFrameTransformation.compose(mStartExitTransformation); + //mFrameTransformation.compose(mFinishExitTransformation); + mFrameTransformation.set(mRotateFrameTransformation); + mFrameTransformation.compose(mStartFrameTransformation); + mFrameTransformation.compose(mFinishFrameTransformation); + mFrameTransformation.getMatrix().preConcat(mFrameInitialMatrix); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final frame: " + mFrameTransformation); + } + + final boolean more = (TWO_PHASE_ANIMATION + && (mMoreStartEnter || mMoreStartExit || mMoreFinishEnter || mMoreFinishExit)) + || (USE_CUSTOM_BLACK_FRAME + && (mMoreStartFrame || mMoreRotateFrame || mMoreFinishFrame)) + || mMoreRotateEnter || mMoreRotateExit + || !mFinishAnimReady; + mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix); + + if (DEBUG_STATE) Slog.v(TAG, "Step: more=" + more); + + return more; + } + + void updateSurfaces() { + if (!mStarted) { + return; + } + + if (mSurface != null) { + if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) { + if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface"); + mSurface.hide(); + } + } + + if (mCustomBlackFrame != null) { + if (!mMoreStartFrame && !mMoreFinishFrame && !mMoreRotateFrame) { + if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding black frame"); + mCustomBlackFrame.hide(); + } else { + mCustomBlackFrame.setMatrix(mFrameTransformation.getMatrix()); + } + } + + if (mExitingBlackFrame != null) { + if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) { + if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding exiting frame"); + mExitingBlackFrame.hide(); + } else { + mExitFrameFinalMatrix.setConcat(mExitTransformation.getMatrix(), mFrameInitialMatrix); + mExitingBlackFrame.setMatrix(mExitFrameFinalMatrix); + mExitingBlackFrame.setAlpha(mExitTransformation.getAlpha()); + } + } + + if (mEnteringBlackFrame != null) { + if (!mMoreStartEnter && !mMoreFinishEnter && !mMoreRotateEnter) { + if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding entering frame"); + mEnteringBlackFrame.hide(); + } else { + mEnteringBlackFrame.setMatrix(mEnterTransformation.getMatrix()); + } + } + setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha()); + } + + public boolean stepAnimationLocked(long now) { + if (!hasAnimations()) { + if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running"); + mFinishAnimReady = false; + return false; + } + + if (!mAnimRunning) { + if (DEBUG_STATE) Slog.v(TAG, "Step: starting start, finish, rotate"); + if (TWO_PHASE_ANIMATION) { + if (mStartEnterAnimation != null) { + mStartEnterAnimation.setStartTime(now); + } + if (mStartExitAnimation != null) { + mStartExitAnimation.setStartTime(now); + } + if (mFinishEnterAnimation != null) { + mFinishEnterAnimation.setStartTime(0); + } + if (mFinishExitAnimation != null) { + mFinishExitAnimation.setStartTime(0); + } + } + if (USE_CUSTOM_BLACK_FRAME) { + if (mStartFrameAnimation != null) { + mStartFrameAnimation.setStartTime(now); + } + if (mFinishFrameAnimation != null) { + mFinishFrameAnimation.setStartTime(0); + } + if (mRotateFrameAnimation != null) { + mRotateFrameAnimation.setStartTime(now); + } + } + if (mRotateEnterAnimation != null) { + mRotateEnterAnimation.setStartTime(now); + } + if (mRotateExitAnimation != null) { + mRotateExitAnimation.setStartTime(now); + } + mAnimRunning = true; + mHalfwayPoint = now + mRotateEnterAnimation.getDuration() / 2; + } - return moreEnter || moreExit; + return stepAnimation(now); } public Transformation getEnterTransformation() { diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java index 77575f2..53c0e07 100644 --- a/services/java/com/android/server/wm/Session.java +++ b/services/java/com/android/server/wm/Session.java @@ -151,13 +151,14 @@ final class Session extends IWindowSession.Stub public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, - int flags, Rect outFrame, Rect outContentInsets, + int flags, Rect outFrame, Rect outSystemInsets, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); int res = mService.relayoutWindow(this, window, seq, attrs, requestedWidth, requestedHeight, viewFlags, flags, - outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface); + outFrame, outSystemInsets, outContentInsets, outVisibleInsets, + outConfig, outSurface); if (false) Slog.d(WindowManagerService.TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); return res; diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java new file mode 100644 index 0000000..e5b1f2c --- /dev/null +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -0,0 +1,571 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +package com.android.server.wm; + +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + +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.H.SET_DIM_PARAMETERS; + +import android.content.Context; +import android.os.SystemClock; +import android.util.Log; +import android.util.Slog; +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 java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Singleton class that carries out the animations and Surface operations in a separate task + * on behalf of WindowManagerService. + */ +public class WindowAnimator { + private static final String TAG = "WindowAnimator"; + + final WindowManagerService mService; + final Context mContext; + final WindowManagerPolicy mPolicy; + + ArrayList<WindowStateAnimator> mWinAnimators = new ArrayList<WindowStateAnimator>(); + + boolean mAnimating; + boolean mTokenMayBeDrawn; + boolean mForceHiding; + WindowState mWindowAnimationBackground; + int mWindowAnimationBackgroundColor; + int mAdjResult; + + int mPendingLayoutChanges; + + /** Overall window dimensions */ + int mDw, mDh; + + /** Interior window dimensions */ + int mInnerDw, mInnerDh; + + /** Time of current animation step. Reset on each iteration */ + long mCurrentTime; + + /** 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. */ + private int mTransactionSequence; + + /** 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. + WindowState mWindowDetachedWallpaper = null; + WindowState mDetachedWallpaper = null; + DimSurface mWindowAnimationBackgroundSurface = null; + + int mBulkUpdateParams = 0; + + DimAnimator mDimAnimator = null; + DimAnimator.Parameters mDimParams = null; + + 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; + } + + 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; + } + + 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; + } + } + } + if (mWindowAnimationBackgroundSurface == null) { + mWindowAnimationBackgroundSurface = new DimSurface(mService.mFxSession); + } + 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() { + int i; + final int NAT = mService.mAppTokens.size(); + for (i=0; i<NAT; i++) { + final AppWindowAnimator appAnimator = mService.mAppTokens.get(i).mAppAnimator; + 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); + } + } + } + + final int NEAT = mService.mExitingAppTokens.size(); + for (i=0; i<NEAT; i++) { + final AppWindowAnimator appAnimator = mService.mExitingAppTokens.get(i).mAppAnimator; + 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("exiting appToken " + appAnimator.mAppToken + + " done", mPendingLayoutChanges); + } + } + } + + if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) { + if (mScreenRotationAnimation.stepAnimationLocked(mCurrentTime)) { + mAnimating = true; + } else { + mBulkUpdateParams |= SET_UPDATE_ROTATION; + mScreenRotationAnimation.kill(); + mScreenRotationAnimation = null; + } + } + } + + private void updateWindowsAndWallpaperLocked() { + ++mTransactionSequence; + + ArrayList<WindowStateAnimator> unForceHiding = null; + boolean wallpaperInUnForceHiding = false; + + for (int i = mService.mWindows.size() - 1; i >= 0; i--) { + WindowState win = mService.mWindows.get(i); + WindowStateAnimator winAnimator = win.mWinAnimator; + final int flags = winAnimator.mAttrFlags; + + if (winAnimator.mSurface != null) { + final boolean wasAnimating = winAnimator.mWasAnimating; + final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime); + + if (WindowManagerService.DEBUG_WALLPAPER) { + Slog.v(TAG, win + ": wasAnimating=" + wasAnimating + + ", 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) { + mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; + mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2", + mPendingLayoutChanges); + } + } + + if (mPolicy.doesForceHide(win, win.mAttrs)) { + if (!wasAnimating && nowAnimating) { + if (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; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 3", + mPendingLayoutChanges); + } + mService.mFocusMayChange = true; + } else if (win.isReadyForDisplay() && winAnimator.mAnimation == null) { + mForceHiding = true; + } + } else if (mPolicy.canBeForceHidden(win, win.mAttrs)) { + final boolean changed; + if (mForceHiding) { + changed = win.hideLw(false, false); + if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG, + "Now policy hidden: " + win); + } else { + changed = win.showLw(false, false); + if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG, + "Now policy shown: " + win); + if (changed) { + if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0 + && win.isVisibleNow() /*w.isReadyForDisplay()*/) { + if (unForceHiding == null) { + unForceHiding = new ArrayList<WindowStateAnimator>(); + } + unForceHiding.add(winAnimator); + if ((win.mAttrs.flags&WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + wallpaperInUnForceHiding = true; + } + } + if (mCurrentFocus == null || mCurrentFocus.mLayer < win.mLayer) { + // We are showing on to of the current + // focus, so re-evaluate focus to make + // sure it is correct. + mService.mFocusMayChange = true; + } + } + } + if (changed && (flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { + mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; + mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4", + mPendingLayoutChanges); + } + } + } + } + + final AppWindowToken atoken = win.mAppToken; + if (atoken != null && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) { + if (atoken.lastTransactionSequence != mTransactionSequence) { + atoken.lastTransactionSequence = mTransactionSequence; + atoken.numInterestingWindows = atoken.numDrawnWindows = 0; + atoken.startingDisplayed = false; + } + if ((win.isOnScreen() || winAnimator.mAttrType + == WindowManager.LayoutParams.TYPE_BASE_APPLICATION) + && !win.mExiting && !win.mDestroying) { + if (WindowManagerService.DEBUG_VISIBILITY || + WindowManagerService.DEBUG_ORIENTATION) { + Slog.v(TAG, "Eval win " + win + ": isDrawn=" + win.isDrawnLw() + + ", isAnimating=" + winAnimator.isAnimating()); + if (!win.isDrawnLw()) { + Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurface + + " pv=" + win.mPolicyVisibility + + " mDrawState=" + winAnimator.mDrawState + + " ah=" + win.mAttachedHidden + + " th=" + atoken.hiddenRequested + + " a=" + winAnimator.mAnimating); + } + } + if (win != atoken.startingWindow) { + if (!atoken.mAppAnimator.freezingScreen || !win.mAppFreezing) { + atoken.numInterestingWindows++; + if (win.isDrawnLw()) { + atoken.numDrawnWindows++; + if (WindowManagerService.DEBUG_VISIBILITY || + WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG, + "tokenMayBeDrawn: " + atoken + + " freezingScreen=" + atoken.mAppAnimator.freezingScreen + + " mAppFreezing=" + win.mAppFreezing); + mTokenMayBeDrawn = true; + } + } + } else if (win.isDrawnLw()) { + atoken.startingDisplayed = true; + } + } + } else if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) { + if (winAnimator.performShowLocked()) { + mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 5", + mPendingLayoutChanges); + } + } + } + final AppWindowAnimator appAnimator = + atoken == null ? null : atoken.mAppAnimator; + if (appAnimator != null && appAnimator.thumbnail != null) { + if (appAnimator.thumbnailTransactionSeq != mTransactionSequence) { + appAnimator.thumbnailTransactionSeq = mTransactionSequence; + appAnimator.thumbnailLayer = 0; + } + if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) { + appAnimator.thumbnailLayer = winAnimator.mAnimLayer; + } + } + } // end forall windows + + // If we have windows that are being show due to them no longer + // being force-hidden, apply the appropriate animation to them. + if (unForceHiding != null) { + for (int i=unForceHiding.size()-1; i>=0; i--) { + Animation a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding); + if (a != null) { + final WindowStateAnimator winAnimator = unForceHiding.get(i); + winAnimator.setAnimation(a); + winAnimator.mAnimationIsEntrance = true; + } + } + } + } + + private void testTokenMayBeDrawnLocked() { + // See if any windows have been drawn, so they (and others + // associated with them) can now be shown. + final int NT = mService.mAppTokens.size(); + for (int i=0; i<NT; i++) { + AppWindowToken wtoken = mService.mAppTokens.get(i); + if (wtoken.mAppAnimator.freezingScreen) { + int numInteresting = wtoken.numInterestingWindows; + if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, + "allDrawn: " + wtoken + + " interesting=" + numInteresting + + " drawn=" + wtoken.numDrawnWindows); + wtoken.mAppAnimator.showAllWindowsLocked(); + mService.unsetAppFreezingScreenLocked(wtoken, false, true); + if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(TAG, + "Setting mOrientationChangeComplete=true because wtoken " + + wtoken + " numInteresting=" + numInteresting + + " numDrawn=" + wtoken.numDrawnWindows); + // This will set mOrientationChangeComplete and cause a pass through layout. + mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + } + } else if (!wtoken.allDrawn) { + int numInteresting = wtoken.numInterestingWindows; + if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, + "allDrawn: " + wtoken + + " interesting=" + numInteresting + + " drawn=" + wtoken.numDrawnWindows); + wtoken.allDrawn = true; + mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + mService.debugLayoutRepeats("testTokenMayBeDrawnLocked", + mPendingLayoutChanges); + } + + // We can now show all of the drawn windows! + if (!mService.mOpeningApps.contains(wtoken)) { + mAnimating |= wtoken.mAppAnimator.showAllWindowsLocked(); + } + } + } + } + } + + private void performAnimationsLocked() { + mTokenMayBeDrawn = false; + mForceHiding = false; + mDetachedWallpaper = null; + mWindowAnimationBackground = null; + mWindowAnimationBackgroundColor = 0; + + updateWindowsAndWallpaperLocked(); + if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { + mPendingActions |= WALLPAPER_ACTION_PENDING; + } + + if (mTokenMayBeDrawn) { + testTokenMayBeDrawnLocked(); + } + } + + synchronized void animate() { + mPendingLayoutChanges = 0; + mCurrentTime = SystemClock.uptimeMillis(); + mBulkUpdateParams = 0; + 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 + Surface.openTransaction(); + + try { + updateWindowsAppsAndRotationAnimationsLocked(); + performAnimationsLocked(); + testWallpaperAndBackgroundLocked(); + + // THIRD LOOP: Update the surfaces of all windows. + + if (mScreenRotationAnimation != null) { + mScreenRotationAnimation.updateSurfaces(); + } + + final int N = mWinAnimators.size(); + for (int i = 0; i < N; i++) { + mWinAnimators.get(i).prepareSurfaceLocked(true); + } + + if (mDimParams != null) { + mDimAnimator.updateParameters(mContext.getResources(), mDimParams, mCurrentTime); + } + if (mDimAnimator != null && mDimAnimator.mDimShown) { + mAnimating |= mDimAnimator.updateSurface(isDimming(), mCurrentTime, + !mService.okToDisplay()); + } + + if (mService.mBlackFrame != null) { + if (mScreenRotationAnimation != null) { + mService.mBlackFrame.setMatrix( + mScreenRotationAnimation.getEnterTransformation().getMatrix()); + } else { + mService.mBlackFrame.clearMatrix(); + } + } + + if (mService.mWatermark != null) { + mService.mWatermark.drawIfNeeded(); + } + } catch (RuntimeException e) { + Log.wtf(TAG, "Unhandled exception in Window Manager", e); + } finally { + Surface.closeTransaction(); + } + + mService.bulkSetParameters(mBulkUpdateParams, mPendingLayoutChanges); + + if (mAnimating) { + mService.scheduleAnimationLocked(); + } + if (WindowManagerService.DEBUG_WINDOW_TRACE) { + Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating + + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams) + + " mPendingLayoutChanges=" + Integer.toHexString(mPendingLayoutChanges)); + } + } + + WindowState mCurrentFocus; + void setCurrentFocus(final WindowState currentFocus) { + mCurrentFocus = currentFocus; + } + + void setDisplayDimensions(final int curWidth, final int curHeight, + final int appWidth, final int appHeight) { + mDw = curWidth; + mDh = curHeight; + mInnerDw = appWidth; + mInnerDh = appHeight; + } + + void startDimming(final WindowStateAnimator winAnimator, final float target, + final int width, final int height) { + if (mDimAnimator == null) { + mDimAnimator = new DimAnimator(mService.mFxSession); + } + 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 isDimming() { + return mDimParams != null; + } + + public void dump(PrintWriter pw, String prefix, boolean dumpAll) { + if (mWindowDetachedWallpaper != null) { + pw.print(" mWindowDetachedWallpaper="); pw.println(mWindowDetachedWallpaper); + } + if (mWindowAnimationBackgroundSurface != null) { + pw.println(" mWindowAnimationBackgroundSurface:"); + mWindowAnimationBackgroundSurface.printTo(" ", pw); + } + if (mDimAnimator != null) { + pw.println(" mDimAnimator:"); + mDimAnimator.printTo(" ", pw); + } else { + pw.println( " no DimAnimator "); + } + } + + 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; + } + } + + synchronized void clearPendingActions() { + mPendingActions = 0; + } +} diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index c833919..28fca69 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -18,7 +18,6 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; @@ -29,14 +28,15 @@ import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 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_WALLPAPER; import com.android.internal.app.IBatteryStats; +import com.android.internal.app.ShutdownThread; import com.android.internal.policy.PolicyManager; import com.android.internal.policy.impl.PhoneWindowManager; -import com.android.internal.view.BaseInputHandler; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; @@ -46,9 +46,12 @@ 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.input.InputManagerService; import android.Manifest; import android.app.ActivityManagerNative; +import android.app.ActivityOptions; import android.app.IActivityManager; import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; @@ -88,14 +91,17 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.TokenWatcher; +import android.os.Trace; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.EventLog; +import android.util.FloatMath; import android.util.Log; 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.Gravity; import android.view.IApplicationToken; @@ -107,8 +113,7 @@ import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputHandler; -import android.view.InputQueue; +import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -119,9 +124,14 @@ import android.view.WindowManagerImpl; import android.view.WindowManagerPolicy; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerPolicy.FakeWindow; +import android.view.animation.AlphaAnimation; import android.view.animation.Animation; +import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.ScaleAnimation; import android.view.animation.Transformation; +import android.view.animation.TranslateAnimation; import java.io.BufferedWriter; import java.io.DataInputStream; @@ -168,13 +178,16 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_SCREEN_ON = false; static final boolean DEBUG_SCREENSHOT = false; static final boolean DEBUG_BOOT = false; + static final boolean DEBUG_LAYOUT_REPEATS = true; + static final boolean DEBUG_SURFACE_TRACE = false; + static final boolean DEBUG_WINDOW_TRACE = false; static final boolean SHOW_SURFACE_ALLOC = false; static final boolean SHOW_TRANSACTIONS = false; static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS; static final boolean HIDE_STACK_CRAWLS = true; + static final int LAYOUT_REPEAT_THRESHOLD = 4; static final boolean PROFILE_ORIENTATION = false; - static final boolean BLUR = true; static final boolean localLOGV = DEBUG; /** How much to multiply the policy's type layer, to reserve room @@ -202,6 +215,13 @@ public class WindowManagerService extends IWindowManager.Stub static final int LAYER_OFFSET_BLUR = 2; /** + * Animation thumbnail is as far as possible below the window above + * the thumbnail (or in other words as far as possible above the window + * below it). + */ + static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER-1; + + /** * Layer at which to put the rotation freeze snapshot. */ static final int FREEZE_LAYER = (TYPE_LAYER_MULTIPLIER * 200) + 1; @@ -232,10 +252,6 @@ public class WindowManagerService extends IWindowManager.Stub */ static final boolean CUSTOM_SCREEN_ROTATION = true; - // Maximum number of milliseconds to wait for input event injection. - // FIXME is this value reasonable? - private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; - // Maximum number of milliseconds to wait for input devices to be enumerated before // proceding with safe mode detection. private static final int INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS = 1000; @@ -250,6 +266,7 @@ 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 @@ -259,6 +276,8 @@ public class WindowManagerService extends IWindowManager.Stub */ private boolean mKeyguardDisabled = false; + 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 @@ -266,6 +285,7 @@ public class WindowManagerService extends IWindowManager.Stub final TokenWatcher mKeyguardTokenWatcher = new TokenWatcher( new Handler(), "WindowManagerService.mKeyguardTokenWatcher") { + @Override public void acquired() { if (shouldAllowDisableKeyguard()) { mPolicy.enableKeyguard(false); @@ -274,6 +294,7 @@ public class WindowManagerService extends IWindowManager.Stub Log.v(TAG, "Not disabling keyguard since device policy is enforced"); } } + @Override public void released() { mPolicy.enableKeyguard(true); synchronized (mKeyguardTokenWatcher) { @@ -403,6 +424,12 @@ public class WindowManagerService extends IWindowManager.Stub = new ArrayList<Pair<WindowState, IRemoteCallback>>(); /** + * Windows that have called relayout() while we were running animations, + * so we need to tell when the animation is done. + */ + final ArrayList<WindowState> mRelayoutWhileAnimating = new ArrayList<WindowState>(); + + /** * Used when rebuilding window list to keep track of windows that have * been removed. */ @@ -410,18 +437,12 @@ public class WindowManagerService extends IWindowManager.Stub IInputMethodManager mInputMethodManager; - SurfaceSession mFxSession; - private DimAnimator mDimAnimator = null; - Surface mBlurSurface; - boolean mBlurShown; + final SurfaceSession mFxSession; Watermark mWatermark; StrictModeFlash mStrictModeFlash; - ScreenRotationAnimation mScreenRotationAnimation; BlackFrame mBlackFrame; - int mTransactionSequence = 0; - final float[] mTmpFloats = new float[9]; boolean mSafeMode; @@ -449,6 +470,10 @@ public class WindowManagerService extends IWindowManager.Stub int mCurDisplayHeight = 0; int mAppDisplayWidth = 0; int mAppDisplayHeight = 0; + int mSmallestDisplayWidth = 0; + int mSmallestDisplayHeight = 0; + int mLargestDisplayWidth = 0; + int mLargestDisplayHeight = 0; int mRotation = 0; int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -457,8 +482,9 @@ public class WindowManagerService extends IWindowManager.Stub = new ArrayList<IRotationWatcher>(); int mDeferredRotationPauseCount; + int mPendingLayoutChanges = 0; boolean mLayoutNeeded = true; - boolean mAnimationPending = false; + boolean mTraversalScheduled = false; boolean mDisplayFrozen = false; boolean mWaitingForConfig = false; boolean mWindowsFreezingScreen = false; @@ -485,9 +511,16 @@ public class WindowManagerService extends IWindowManager.Stub // mOpeningApps and mClosingApps are the lists of tokens that will be // made visible or hidden at the next transition. int mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; + int mNextAppTransitionType = ActivityOptions.ANIM_NONE; String mNextAppTransitionPackage; + Bitmap mNextAppTransitionThumbnail; + IRemoteCallback mNextAppTransitionCallback; int mNextAppTransitionEnter; int mNextAppTransitionExit; + int mNextAppTransitionStartX; + int mNextAppTransitionStartY; + int mNextAppTransitionStartWidth; + int mNextAppTransitionStartHeight; boolean mAppTransitionReady = false; boolean mAppTransitionRunning = false; boolean mAppTransitionTimeout = false; @@ -505,14 +538,18 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics(); - H mH = new H(); + final H mH = new H(); + + final Choreographer mChoreographer = Choreographer.getInstance(); WindowState mCurrentFocus = null; WindowState mLastFocus = null; - // This just indicates the window the input method is on top of, not - // necessarily the window its input is going to. + /** This just indicates the window the input method is on top of, not + * necessarily the window its input is going to. */ WindowState mInputMethodTarget = null; + + /** If true hold off on modifying the animation layer of mInputMethodTarget */ boolean mInputMethodTargetWaitingAnim; int mInputMethodAnimLayerAdjustment; @@ -534,12 +571,6 @@ public class WindowManagerService extends IWindowManager.Stub // 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; - // 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. - WindowState mWindowDetachedWallpaper = null; - DimSurface mWindowAnimationBackgroundSurface = null; int mWallpaperAnimLayerAdjustment; float mLastWallpaperX = -1; float mLastWallpaperY = -1; @@ -561,8 +592,9 @@ public class WindowManagerService extends IWindowManager.Stub float mWindowAnimationScale = 1.0f; float mTransitionAnimationScale = 1.0f; + float mAnimatorDurationScale = 1.0f; - final InputManager mInputManager; + final InputManagerService mInputManager; // Who is holding the screen on. Session mHoldingScreenOn; @@ -571,18 +603,80 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; DragState mDragState = null; - final InputHandler mDragInputHandler = new BaseInputHandler() { + + /** Pulled out of performLayoutAndPlaceSurfacesLockedInner in order to refactor into multiple + * methods. */ + class LayoutFields { + 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_TURN_ON_SCREEN = 1 << 4; + + boolean mWallpaperForceHidingChanged = false; + boolean mWallpaperMayChange = false; + boolean mOrientationChangeComplete = true; + int mAdjResult = 0; + private Session mHoldScreen = null; + private boolean mObscured = false; + boolean mDimming = false; + private boolean mSyswin = false; + private float mScreenBrightness = -1; + private float mButtonBrightness = -1; + private boolean mUpdateRotation = false; + } + LayoutFields mInnerFields = new LayoutFields(); + + /** Only do a maximum of 6 repeated layouts. After that quit */ + private int mLayoutRepeatCount; + + private final class AnimationRunnable implements Runnable { @Override - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { + 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 { + public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + + @Override + public void onInputEvent(InputEvent event) { boolean handled = false; try { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 + if (event instanceof MotionEvent + && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 && mDragState != null) { + final MotionEvent motionEvent = (MotionEvent)event; boolean endDrag = false; - final float newX = event.getRawX(); - final float newY = event.getRawY(); + final float newX = motionEvent.getRawX(); + final float newY = motionEvent.getRawY(); - switch (event.getAction()) { + switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: { if (DEBUG_DRAG) { Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer"); @@ -623,10 +717,10 @@ public class WindowManagerService extends IWindowManager.Stub } catch (Exception e) { Slog.e(TAG, "Exception caught by drag handleMotion", e); } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } - }; + } /** * Whether the UI is currently running in touch mode (not showing @@ -682,6 +776,7 @@ public class WindowManagerService extends IWindowManager.Stub mAllowBootMessages = allowBootMsgs; } + @Override public void run() { Looper.prepare(); WindowManagerService s = new WindowManagerService(mContext, mPM, @@ -721,6 +816,7 @@ public class WindowManagerService extends IWindowManager.Stub mPM = pm; } + @Override public void run() { Looper.prepare(); WindowManagerPolicyThread.set(this, Looper.myLooper()); @@ -753,6 +849,7 @@ public class WindowManagerService extends IWindowManager.Stub mAllowBootMessages = showBootMsgs; mLimitedAlphaCompositing = context.getResources().getBoolean( com.android.internal.R.bool.config_sf_limitedAlpha); + mHeadless = "1".equals(SystemProperties.get(SYSTEM_HEADLESS, "0")); mPowerManager = pm; mPowerManager.setPolicy(mPolicy); @@ -769,6 +866,8 @@ 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); // Track changes to DevicePolicyManager state so we can enable/disable keyguard. IntentFilter filter = new IntentFilter(); @@ -779,7 +878,8 @@ public class WindowManagerService extends IWindowManager.Stub "KEEP_SCREEN_ON_FLAG"); mHoldingScreenWakeLock.setReferenceCounted(false); - mInputManager = new InputManager(context, this); + mInputManager = new InputManagerService(context, mInputMonitor); + mAnimator = new WindowAnimator(this, context, mPolicy); PolicyThread thr = new PolicyThread(mPolicy, this, context, pm); thr.start(); @@ -797,6 +897,15 @@ public class WindowManagerService extends IWindowManager.Stub // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); + mFxSession = new SurfaceSession(); + + Surface.openTransaction(); + createWatermark(); + Surface.closeTransaction(); + } + + public InputManagerService getInputManagerService() { + return mInputManager; } @Override @@ -1063,7 +1172,8 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_INPUT_METHOD) { Slog.i(TAG, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding()); if (!w.isVisibleOrAdding()) { - Slog.i(TAG, " mSurface=" + w.mSurface + " reportDestroy=" + w.mReportDestroySurface + Slog.i(TAG, " mSurface=" + w.mWinAnimator.mSurface + " reportDestroy=" + + w.mWinAnimator.mReportDestroySurface + " relayoutCalled=" + w.mRelayoutCalled + " viewVis=" + w.mViewVisibility + " policyVis=" + w.mPolicyVisibility + " attachHid=" + w.mAttachedHidden + " exiting=" + w.mExiting + " destroying=" + w.mDestroying); @@ -1077,6 +1187,11 @@ public class WindowManagerService extends IWindowManager.Stub return false; } + /** + * Dig through the WindowStates and find the one that the Input Method will target. + * @param willMove + * @return The index+1 in mWindows of the discovered target. + */ int findDesiredInputMethodWindowIndexLocked(boolean willMove) { final ArrayList<WindowState> localmWindows = mWindows; final int N = localmWindows.size(); @@ -1109,8 +1224,10 @@ public class WindowManagerService extends IWindowManager.Stub } } + // Now w is either mWindows[0] or an IME (or null if mWindows is empty). + if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG, "Proposed new IME target: " + w); - + // Now, a special case -- if the last target's window is in the // process of exiting, and is above the new target, keep on the // last target to avoid flicker. Consider for example a Dialog with @@ -1120,7 +1237,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mInputMethodTarget != null && w != null && mInputMethodTarget.isDisplayedLw() && mInputMethodTarget.mExiting) { - if (mInputMethodTarget.mAnimLayer > w.mAnimLayer) { + if (mInputMethodTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) { w = mInputMethodTarget; i = localmWindows.indexOf(w); if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Current target higher, switching to: " + w); @@ -1141,17 +1258,16 @@ public class WindowManagerService extends IWindowManager.Stub AppWindowToken token = curTarget.mAppToken; WindowState highestTarget = null; int highestPos = 0; - if (token.animating || token.animation != null) { - int pos = 0; - pos = localmWindows.indexOf(curTarget); + if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) { + int pos = localmWindows.indexOf(curTarget); while (pos >= 0) { WindowState win = localmWindows.get(pos); if (win.mAppToken != token) { break; } if (!win.mRemoved) { - if (highestTarget == null || win.mAnimLayer > - highestTarget.mAnimLayer) { + if (highestTarget == null || win.mWinAnimator.mAnimLayer > + highestTarget.mWinAnimator.mAnimLayer) { highestTarget = win; highestPos = pos; } @@ -1163,9 +1279,9 @@ public class WindowManagerService extends IWindowManager.Stub if (highestTarget != null) { if (DEBUG_INPUT_METHOD) Slog.v(TAG, "mNextAppTransition=" + mNextAppTransition + " " + highestTarget - + " animating=" + highestTarget.isAnimating() - + " layer=" + highestTarget.mAnimLayer - + " new layer=" + w.mAnimLayer); + + " animating=" + highestTarget.mWinAnimator.isAnimating() + + " layer=" + highestTarget.mWinAnimator.mAnimLayer + + " new layer=" + w.mWinAnimator.mAnimLayer); if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { // If we are currently setting up for an animation, @@ -1173,8 +1289,8 @@ public class WindowManagerService extends IWindowManager.Stub mInputMethodTargetWaitingAnim = true; mInputMethodTarget = highestTarget; return highestPos + 1; - } else if (highestTarget.isAnimating() && - highestTarget.mAnimLayer > w.mAnimLayer) { + } else if (highestTarget.mWinAnimator.isAnimating() && + highestTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) { // If the window we are currently targeting is involved // with an animation, and it is on top of the next target // we will be over, then hold off on moving until @@ -1202,7 +1318,7 @@ public class WindowManagerService extends IWindowManager.Stub mInputMethodTarget = w; mInputMethodTargetWaitingAnim = false; if (w.mAppToken != null) { - setInputMethodAnimLayerAdjustment(w.mAppToken.animLayerAdjustment); + setInputMethodAnimLayerAdjustment(w.mAppToken.mAppAnimator.animLayerAdjustment); } else { setInputMethodAnimLayerAdjustment(0); } @@ -1246,25 +1362,25 @@ public class WindowManagerService extends IWindowManager.Stub mInputMethodAnimLayerAdjustment = adj; WindowState imw = mInputMethodWindow; if (imw != null) { - imw.mAnimLayer = imw.mLayer + adj; + imw.mWinAnimator.mAnimLayer = imw.mLayer + adj; if (DEBUG_LAYERS) Slog.v(TAG, "IM win " + imw - + " anim layer: " + imw.mAnimLayer); + + " anim layer: " + imw.mWinAnimator.mAnimLayer); int wi = imw.mChildWindows.size(); while (wi > 0) { wi--; WindowState cw = imw.mChildWindows.get(wi); - cw.mAnimLayer = cw.mLayer + adj; + cw.mWinAnimator.mAnimLayer = cw.mLayer + adj; if (DEBUG_LAYERS) Slog.v(TAG, "IM win " + cw - + " anim layer: " + cw.mAnimLayer); + + " anim layer: " + cw.mWinAnimator.mAnimLayer); } } int di = mInputMethodDialogs.size(); while (di > 0) { di --; imw = mInputMethodDialogs.get(di); - imw.mAnimLayer = imw.mLayer + adj; + imw.mWinAnimator.mAnimLayer = imw.mLayer + adj; if (DEBUG_LAYERS) Slog.v(TAG, "IM win " + imw - + " anim layer: " + imw.mAnimLayer); + + " anim layer: " + imw.mWinAnimator.mAnimLayer); } } @@ -1462,15 +1578,15 @@ public class WindowManagerService extends IWindowManager.Stub } final boolean isWallpaperVisible(WindowState wallpaperTarget) { - if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target obscured=" + if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured=" + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??") + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null) - ? wallpaperTarget.mAppToken.animation : null) + ? wallpaperTarget.mAppToken.mAppAnimator.animation : null) + " upper=" + mUpperWallpaperTarget + " lower=" + mLowerWallpaperTarget); return (wallpaperTarget != null && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null - && wallpaperTarget.mAppToken.animation != null))) + && wallpaperTarget.mAppToken.mAppAnimator.animation != null))) || mUpperWallpaperTarget != null || mLowerWallpaperTarget != null; } @@ -1479,6 +1595,7 @@ public class WindowManagerService extends IWindowManager.Stub static final int ADJUST_WALLPAPER_VISIBILITY_CHANGED = 1<<2; int adjustWallpaperWindowsLocked() { + mInnerFields.mWallpaperMayChange = false; int changed = 0; final int dw = mAppDisplayWidth; @@ -1506,28 +1623,24 @@ public class WindowManagerService extends IWindowManager.Stub continue; } topCurW = null; - if (w != mWindowDetachedWallpaper && w.mAppToken != null) { + if (w != mAnimator.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.animation == null) { + if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping not hidden or animating token: " + w); continue; } } if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": readyfordisplay=" - + w.isReadyForDisplay() + " drawpending=" + w.mDrawPending - + " commitdrawpending=" + w.mCommitDrawPending); + + w.isReadyForDisplay() + " mDrawState=" + w.mWinAnimator.mDrawState); if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isReadyForDisplay() - && (mWallpaperTarget == w - || (!w.mDrawPending && !w.mCommitDrawPending))) { + && (mWallpaperTarget == w || w.isDrawnLw())) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper activity: #" + i + "=" + w); foundW = w; foundI = i; - if (w == mWallpaperTarget && ((w.mAppToken != null - && w.mAppToken.animation != null) - || w.mAnimation != null)) { + if (w == mWallpaperTarget && w.mWinAnimator.isAnimating()) { // The current wallpaper target is animating, so we'll // look behind it for another possible target and figure // out what is going on below. @@ -1536,7 +1649,7 @@ public class WindowManagerService extends IWindowManager.Stub continue; } break; - } else if (w == mWindowDetachedWallpaper) { + } else if (w == mAnimator.mWindowDetachedWallpaper) { windowDetachedI = i; } } @@ -1584,10 +1697,12 @@ public class WindowManagerService extends IWindowManager.Stub // Now what is happening... if the current and new targets are // animating, then we are in our super special mode! if (foundW != null && oldW != null) { - boolean oldAnim = oldW.mAnimation != null - || (oldW.mAppToken != null && oldW.mAppToken.animation != null); - boolean foundAnim = foundW.mAnimation != null - || (foundW.mAppToken != null && foundW.mAppToken.animation != null); + boolean oldAnim = oldW.mWinAnimator.mAnimation != null + || (oldW.mAppToken != null + && oldW.mAppToken.mAppAnimator.animation != null); + boolean foundAnim = foundW.mWinAnimator.mAnimation != null + || (foundW.mAppToken != null && + foundW.mAppToken.mAppAnimator.animation != null); if (DEBUG_WALLPAPER) { Slog.v(TAG, "New animation: " + foundAnim + " old animation: " + oldAnim); @@ -1638,12 +1753,12 @@ public class WindowManagerService extends IWindowManager.Stub } else if (mLowerWallpaperTarget != null) { // Is it time to stop animating? - boolean lowerAnimating = mLowerWallpaperTarget.mAnimation != null + boolean lowerAnimating = mLowerWallpaperTarget.mWinAnimator.mAnimation != null || (mLowerWallpaperTarget.mAppToken != null - && mLowerWallpaperTarget.mAppToken.animation != null); - boolean upperAnimating = mUpperWallpaperTarget.mAnimation != null + && mLowerWallpaperTarget.mAppToken.mAppAnimator.animation != null); + boolean upperAnimating = mUpperWallpaperTarget.mWinAnimator.mAnimation != null || (mUpperWallpaperTarget.mAppToken != null - && mUpperWallpaperTarget.mAppToken.animation != null); + && mUpperWallpaperTarget.mAppToken.mAppAnimator.animation != null); if (!lowerAnimating || !upperAnimating) { if (DEBUG_WALLPAPER) { Slog.v(TAG, "No longer animating wallpaper targets!"); @@ -1665,7 +1780,7 @@ public class WindowManagerService extends IWindowManager.Stub // between two wallpaper targets. mWallpaperAnimLayerAdjustment = (mLowerWallpaperTarget == null && foundW.mAppToken != null) - ? foundW.mAppToken.animLayerAdjustment : 0; + ? foundW.mAppToken.mAppAnimator.animLayerAdjustment : 0; final int maxLayer = mPolicy.getMaxWallpaperLayer() * TYPE_LAYER_MULTIPLIER @@ -1752,9 +1867,9 @@ public class WindowManagerService extends IWindowManager.Stub } } - wallpaper.mAnimLayer = wallpaper.mLayer + mWallpaperAnimLayerAdjustment; + wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + mWallpaperAnimLayerAdjustment; if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper win " - + wallpaper + " anim layer: " + wallpaper.mAnimLayer); + + wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer); // First, if this window is at the current index, then all // is well. @@ -1806,9 +1921,9 @@ public class WindowManagerService extends IWindowManager.Stub while (curWallpaperIndex > 0) { curWallpaperIndex--; WindowState wallpaper = token.windows.get(curWallpaperIndex); - wallpaper.mAnimLayer = wallpaper.mLayer + adj; + wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + adj; if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper win " - + wallpaper + " anim layer: " + wallpaper.mAnimLayer); + + wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer); } } } @@ -1849,7 +1964,7 @@ public class WindowManagerService extends IWindowManager.Stub rawChanged = true; } - if (rawChanged && (wallpaperWin.getAttrs().privateFlags & + if (rawChanged && (wallpaperWin.mAttrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) { try { if (DEBUG_WALLPAPER) Slog.v(TAG, "Report new wp offset " @@ -1900,6 +2015,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; @@ -1927,20 +2047,20 @@ public class WindowManagerService extends IWindowManager.Stub curWallpaperIndex--; WindowState wallpaper = token.windows.get(curWallpaperIndex); if (updateWallpaperOffsetLocked(wallpaper, dw, dh, sync)) { - wallpaper.computeShownFrameLocked(); + WindowStateAnimator winAnimator = wallpaper.mWinAnimator; + winAnimator.computeShownFrameLocked(); // No need to lay out the windows - we can just set the wallpaper position // directly. - if (wallpaper.mSurfaceX != wallpaper.mShownFrame.left - || wallpaper.mSurfaceY != wallpaper.mShownFrame.top) { + // 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); - wallpaper.mSurfaceX = wallpaper.mShownFrame.left; - wallpaper.mSurfaceY = wallpaper.mShownFrame.top; - wallpaper.mSurface.setPosition(wallpaper.mShownFrame.left, - wallpaper.mShownFrame.top); + setWallpaperOffset(winAnimator, (int) wallpaper.mShownFrame.left, + (int) wallpaper.mShownFrame.top); } catch (RuntimeException e) { Slog.w(TAG, "Error positioning surface of " + wallpaper + " pos=(" + wallpaper.mShownFrame.left @@ -2050,6 +2170,11 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerImpl.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; + } token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (attrs.type >= FIRST_APPLICATION_WINDOW @@ -2082,6 +2207,12 @@ public class WindowManagerService extends IWindowManager.Stub + attrs.token + ". Aborting."); return WindowManagerImpl.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; + } } win = new WindowState(this, session, client, token, @@ -2149,14 +2280,14 @@ public class WindowManagerService extends IWindowManager.Stub } } - win.mEnterAnimationPending = true; + win.mWinAnimator.mEnterAnimationPending = true; mPolicy.getContentInsetHintLw(attrs, outContentInsets); if (mInTouchMode) { res |= WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE; } - if (win == null || win.mAppToken == null || !win.mAppToken.clientHidden) { + if (win.mAppToken == null || !win.mAppToken.clientHidden) { res |= WindowManagerImpl.ADD_FLAG_APP_VISIBLE; } @@ -2220,18 +2351,18 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Remove " + win + " client=" + Integer.toHexString(System.identityHashCode( win.mClient.asBinder())) - + ", surface=" + win.mSurface); + + ", surface=" + win.mWinAnimator.mSurface); final long origId = Binder.clearCallingIdentity(); - + win.disposeInputChannel(); if (DEBUG_APP_TRANSITIONS) Slog.v( - TAG, "Remove " + win + ": mSurface=" + win.mSurface + TAG, "Remove " + win + ": mSurface=" + win.mWinAnimator.mSurface + " mExiting=" + win.mExiting - + " isAnimating=" + win.isAnimating() + + " isAnimating=" + win.mWinAnimator.isAnimating() + " app-animation=" - + (win.mAppToken != null ? win.mAppToken.animation : null) + + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null) + " inPendingTransaction=" + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false) + " mDisplayFrozen=" + mDisplayFrozen); @@ -2241,22 +2372,22 @@ public class WindowManagerService extends IWindowManager.Stub // to hold off on removing the window until the animation is done. // If the display is frozen, just remove immediately, since the // animation wouldn't be seen. - if (win.mSurface != null && !mDisplayFrozen && mDisplayEnabled - && mPolicy.isScreenOnFully()) { + if (win.mHasSurface && okToDisplay()) { // If we are not currently running the exit animation, we // need to see about starting one. - if (wasVisible=win.isWinVisibleLw()) { + wasVisible = win.isWinVisibleLw(); + if (wasVisible) { int transit = WindowManagerPolicy.TRANSIT_EXIT; - if (win.getAttrs().type == TYPE_APPLICATION_STARTING) { + if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } // Try starting an animation. - if (applyAnimationLocked(win, transit, false)) { + if (win.mWinAnimator.applyAnimationLocked(transit, false)) { win.mExiting = true; } } - if (win.mExiting || win.isAnimating()) { + if (win.mExiting || win.mWinAnimator.isAnimating()) { // The exit animation is running... wait for it! //Slog.i(TAG, "*** Running exit animation..."); win.mExiting = true; @@ -2385,33 +2516,36 @@ public class WindowManagerService extends IWindowManager.Stub } static void logSurface(WindowState w, String msg, RuntimeException where) { - String str = " SURFACE " + Integer.toHexString(w.hashCode()) - + ": " + msg + " / " + w.mAttrs.getTitle(); + String str = " SURFACE " + msg + ": " + w; if (where != null) { Slog.i(TAG, str, where); } else { Slog.i(TAG, str); } } - + + static void logSurface(Surface s, String title, String msg, RuntimeException where) { + String str = " SURFACE " + s + ": " + msg + " / " + title; + if (where != null) { + Slog.i(TAG, str, where); + } else { + Slog.i(TAG, str); + } + } + + // TODO(cmautner): Move to WindowStateAnimator. + void setTransparentRegionHint(final WindowStateAnimator winAnimator, final Region region) { + mH.sendMessage(mH.obtainMessage(H.SET_TRANSPARENT_REGION, + new Pair<WindowStateAnimator, Region>(winAnimator, region))); + } + void setTransparentRegionWindow(Session session, IWindow client, Region region) { long origId = Binder.clearCallingIdentity(); try { synchronized (mWindowMap) { WindowState w = windowForClientLocked(session, client, false); - if ((w != null) && (w.mSurface != null)) { - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - ">>> OPEN TRANSACTION setTransparentRegion"); - Surface.openTransaction(); - try { - if (SHOW_TRANSACTIONS) logSurface(w, - "transparentRegionHint=" + region, null); - w.mSurface.setTransparentRegionHint(region); - } finally { - Surface.closeTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION setTransparentRegion"); - } + if ((w != null) && w.mHasSurface) { + setTransparentRegionHint(w.mWinAnimator, region); } } } finally { @@ -2513,12 +2647,13 @@ public class WindowManagerService extends IWindowManager.Stub public int relayoutWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, - Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, - Configuration outConfig, Surface outSurface) { + Rect outFrame, Rect outSystemInsets, Rect outContentInsets, + Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { boolean displayed = false; boolean inTouchMode; boolean configChanged; boolean surfaceChanged = false; + boolean animating; // if they don't have this permission, mask out the status bar bits int systemUiVisibility = 0; @@ -2534,10 +2669,12 @@ public class WindowManagerService extends IWindowManager.Stub long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { + // TODO(cmautner): synchronize on mAnimator or win.mWinAnimator. WindowState win = windowForClientLocked(session, client, false); if (win == null) { return 0; } + WindowStateAnimator winAnimator = win.mWinAnimator; if (win.mRequestedWidth != requestedWidth || win.mRequestedHeight != requestedHeight) { win.mLayoutNeeded = true; @@ -2552,7 +2689,7 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.adjustWindowParamsLw(attrs); } - win.mSurfaceDestroyDeferred = + winAnimator.mSurfaceDestroyDeferred = (flags&WindowManagerImpl.RELAYOUT_DEFER_SURFACE_DESTROY) != 0; int attrChanges = 0; @@ -2564,7 +2701,8 @@ public class WindowManagerService extends IWindowManager.Stub } flagChanges = win.mAttrs.flags ^= attrs.flags; attrChanges = win.mAttrs.copyFrom(attrs); - if ((attrChanges&WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) { + if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED + | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) { win.mLayoutNeeded = true; } } @@ -2574,7 +2712,7 @@ public class WindowManagerService extends IWindowManager.Stub win.mEnforceSizeCompat = (win.mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0; if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) { - win.mAlpha = attrs.alpha; + winAnimator.mAlpha = attrs.alpha; } final boolean scaledWindow = @@ -2616,20 +2754,19 @@ public class WindowManagerService extends IWindowManager.Stub (win.mAppToken == null || !win.mAppToken.clientHidden)) { displayed = !win.isVisibleLw(); if (win.mExiting) { - win.cancelExitAnimationForNextAnimationLocked(); + winAnimator.cancelExitAnimationForNextAnimationLocked(); + win.mExiting = false; } if (win.mDestroying) { win.mDestroying = false; mDestroySurface.remove(win); } if (oldVisibility == View.GONE) { - win.mEnterAnimationPending = true; + winAnimator.mEnterAnimationPending = true; } if (displayed) { - if (win.mSurface != null && !win.mDrawPending - && !win.mCommitDrawPending && !mDisplayFrozen - && mDisplayEnabled && mPolicy.isScreenOnFully()) { - applyEnterAnimationLocked(win); + if (win.isDrawnLw() && okToDisplay()) { + winAnimator.applyEnterAnimationLocked(); } if ((win.mAttrs.flags & WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0) { @@ -2652,19 +2789,19 @@ public class WindowManagerService extends IWindowManager.Stub } if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) { // To change the format, we need to re-build the surface. - win.destroySurfaceLocked(); + winAnimator.destroySurfaceLocked(); displayed = true; surfaceChanged = true; } try { - if (win.mSurface == null) { + if (!win.mHasSurface) { surfaceChanged = true; } - Surface surface = win.createSurfaceLocked(); + Surface surface = winAnimator.createSurfaceLocked(); if (surface != null) { outSurface.copyFrom(surface); - win.mReportDestroySurface = false; - win.mSurfacePendingDestroy = false; + winAnimator.mReportDestroySurface = false; + winAnimator.mSurfacePendingDestroy = false; if (SHOW_TRANSACTIONS) Slog.i(TAG, " OUT SURFACE " + outSurface + ": copied"); } else { @@ -2703,26 +2840,26 @@ public class WindowManagerService extends IWindowManager.Stub sa.flags = (sa.flags&~mask) | (win.mAttrs.flags&mask); } } else { - win.mEnterAnimationPending = false; - if (win.mSurface != null) { + winAnimator.mEnterAnimationPending = false; + if (winAnimator.mSurface != null) { if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win + ": mExiting=" + win.mExiting - + " mSurfacePendingDestroy=" + win.mSurfacePendingDestroy); + + " mSurfacePendingDestroy=" + winAnimator.mSurfacePendingDestroy); // If we are not currently running the exit animation, we // need to see about starting one. - if (!win.mExiting || win.mSurfacePendingDestroy) { + if (!win.mExiting || winAnimator.mSurfacePendingDestroy) { surfaceChanged = true; // Try starting an animation; if there isn't one, we // can destroy the surface right away. int transit = WindowManagerPolicy.TRANSIT_EXIT; - if (win.getAttrs().type == TYPE_APPLICATION_STARTING) { + if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE; } - if (!win.mSurfacePendingDestroy && win.isWinVisibleLw() && - applyAnimationLocked(win, transit, false)) { + if (!winAnimator.mSurfacePendingDestroy && win.isWinVisibleLw() && + winAnimator.applyAnimationLocked(transit, false)) { focusMayChange = true; win.mExiting = true; - } else if (win.isAnimating()) { + } else if (win.mWinAnimator.isAnimating()) { // Currently in a hide animation... turn this into // an exit. win.mExiting = true; @@ -2731,31 +2868,31 @@ public class WindowManagerService extends IWindowManager.Stub // window, we need to change both of them inside // of a transaction to avoid artifacts. win.mExiting = true; - win.mAnimating = true; + win.mWinAnimator.mAnimating = true; } else { if (mInputMethodWindow == win) { mInputMethodWindow = null; } - win.destroySurfaceLocked(); + winAnimator.destroySurfaceLocked(); } } } - if (win.mSurface == null || (win.getAttrs().flags + if (winAnimator.mSurface == null || (win.getAttrs().flags & WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING) == 0 - || win.mSurfacePendingDestroy) { + || winAnimator.mSurfacePendingDestroy) { // We could be called from a local process, which // means outSurface holds its current surface. Ensure the // surface object is cleared, but we don't necessarily want // it actually destroyed at this point. - win.mSurfacePendingDestroy = false; + winAnimator.mSurfacePendingDestroy = false; outSurface.release(); if (DEBUG_VISIBILITY) Slog.i(TAG, "Releasing surface in: " + win); - } else if (win.mSurface != null) { + } else if (winAnimator.mSurface != null) { if (DEBUG_VISIBILITY) Slog.i(TAG, "Keeping surface, will report destroy: " + win); - win.mReportDestroySurface = true; - outSurface.copyFrom(win.mSurface); + winAnimator.mReportDestroySurface = true; + outSurface.copyFrom(winAnimator.mSurface); } } @@ -2802,6 +2939,7 @@ public class WindowManagerService extends IWindowManager.Stub win.mAppToken.updateReportedVisibilityLocked(); } outFrame.set(win.mCompatFrame); + outSystemInsets.set(win.mSystemInsets); outContentInsets.set(win.mContentInsets); outVisibleInsets.set(win.mVisibleInsets); if (localLOGV) Slog.v( @@ -2816,7 +2954,11 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Relayout of " + win + ": focusMayChange=" + focusMayChange); inTouchMode = mInTouchMode; - + animating = mAnimator.mAnimating; + if (animating && !mRelayoutWhileAnimating.contains(win)) { + mRelayoutWhileAnimating.add(win); + } + mInputMonitor.updateInputWindowsLw(true /*force*/); } @@ -2828,7 +2970,8 @@ public class WindowManagerService extends IWindowManager.Stub return (inTouchMode ? WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE : 0) | (displayed ? WindowManagerImpl.RELAYOUT_RES_FIRST_TIME : 0) - | (surfaceChanged ? WindowManagerImpl.RELAYOUT_RES_SURFACE_CHANGED : 0); + | (surfaceChanged ? WindowManagerImpl.RELAYOUT_RES_SURFACE_CHANGED : 0) + | (animating ? WindowManagerImpl.RELAYOUT_RES_ANIMATING : 0); } public void performDeferredDestroyWindow(Session session, IWindow client) { @@ -2840,7 +2983,7 @@ public class WindowManagerService extends IWindowManager.Stub if (win == null) { return; } - win.destroyDeferredSurfaceLocked(); + win.mWinAnimator.destroyDeferredSurfaceLocked(); } } finally { Binder.restoreCallingIdentity(origId); @@ -2856,7 +2999,7 @@ public class WindowManagerService extends IWindowManager.Stub if (win == null) { return false; } - return reclaimSomeSurfaceMemoryLocked(win, "from-client", false); + return reclaimSomeSurfaceMemoryLocked(win.mWinAnimator, "from-client", false); } } finally { Binder.restoreCallingIdentity(origId); @@ -2867,7 +3010,7 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { WindowState win = windowForClientLocked(session, client, false); - if (win != null && win.finishDrawingLocked()) { + if (win != null && win.mWinAnimator.finishDrawingLocked()) { if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) { adjustWallpaperWindowsLocked(); } @@ -2921,77 +3064,7 @@ public class WindowManagerService extends IWindowManager.Stub return null; } - void applyEnterAnimationLocked(WindowState win) { - int transit = WindowManagerPolicy.TRANSIT_SHOW; - if (win.mEnterAnimationPending) { - win.mEnterAnimationPending = false; - transit = WindowManagerPolicy.TRANSIT_ENTER; - } - - applyAnimationLocked(win, transit, true); - } - - boolean applyAnimationLocked(WindowState win, - int transit, boolean isEntrance) { - if (win.mLocalAnimating && win.mAnimationIsEntrance == isEntrance) { - // If we are trying to apply an animation, but already running - // an animation of the same type, then just leave that one alone. - return true; - } - - // 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 - // artifacts when we unfreeze the display if some different animation - // is running. - if (!mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully()) { - int anim = mPolicy.selectAnimationLw(win, transit); - int attr = -1; - Animation a = null; - if (anim != 0) { - a = AnimationUtils.loadAnimation(mContext, anim); - } else { - switch (transit) { - case WindowManagerPolicy.TRANSIT_ENTER: - attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation; - break; - case WindowManagerPolicy.TRANSIT_EXIT: - attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation; - break; - case WindowManagerPolicy.TRANSIT_SHOW: - attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation; - break; - case WindowManagerPolicy.TRANSIT_HIDE: - attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation; - break; - } - if (attr >= 0) { - a = loadAnimation(win.mAttrs, attr); - } - } - if (DEBUG_ANIM) Slog.v(TAG, "applyAnimation: win=" + win - + " anim=" + anim + " attr=0x" + Integer.toHexString(attr) - + " mAnimation=" + win.mAnimation - + " isEntrance=" + isEntrance); - if (a != null) { - if (DEBUG_ANIM) { - RuntimeException e = null; - if (!HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - Slog.v(TAG, "Loaded animation " + a + " for " + win, e); - } - win.setAnimation(a); - win.mAnimationIsEntrance = isEntrance; - } - } else { - win.clearAnimation(); - } - - return win.mAnimation != null; - } - - private Animation loadAnimation(WindowManager.LayoutParams lp, int animAttr) { + Animation loadAnimation(WindowManager.LayoutParams lp, int animAttr) { int anim = 0; Context context = mContext; if (animAttr >= 0) { @@ -3023,17 +3096,160 @@ public class WindowManagerService extends IWindowManager.Stub return null; } + private Animation createExitAnimationLocked(int transit, int duration) { + if (transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN || + transit == WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE) { + // If we are on top of the wallpaper, we need an animation that + // correctly handles the wallpaper staying static behind all of + // the animated elements. To do this, will just have the existing + // element fade out. + Animation a = new AlphaAnimation(1, 0); + 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; + } + } + + /** + * Compute the pivot point for an animation that is scaling from a small + * rect on screen to a larger rect. The pivot point varies depending on + * the distance between the inner and outer edges on both sides. This + * function computes the pivot point for one dimension. + * @param startPos Offset from left/top edge of outer rectangle to + * left/top edge of inner rectangle. + * @param finalScale The scaling factor between the size of the outer + * and inner rectangles. + */ + private static float computePivot(int startPos, float finalScale) { + final float denom = finalScale-1; + if (Math.abs(denom) < .0001f) { + return startPos; + } + return -startPos / denom; + } + + private Animation createScaleUpAnimationLocked(int transit, boolean enter) { + Animation a; + // Pick the desired duration. If this is an inter-activity transition, + // it is the standard duration for that. Otherwise we use the longer + // task transition duration. + int duration; + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + duration = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shortAnimTime); + break; + default: + duration = 300; + break; + } + if (enter) { + // Entering app zooms out from the center of the initial rect. + float scaleW = mNextAppTransitionStartWidth / (float) mAppDisplayWidth; + float scaleH = mNextAppTransitionStartHeight / (float) mAppDisplayHeight; + Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mNextAppTransitionStartX, scaleW), + computePivot(mNextAppTransitionStartY, scaleH)); + scale.setDuration(duration); + AnimationSet set = new AnimationSet(true); + Animation alpha = new AlphaAnimation(0, 1); + scale.setDuration(duration); + set.addAnimation(scale); + alpha.setDuration(duration); + set.addAnimation(alpha); + a = set; + } else { + a = createExitAnimationLocked(transit, duration); + } + a.setFillAfter(true); + final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext, + com.android.internal.R.interpolator.decelerate_cubic); + a.setInterpolator(interpolator); + a.initialize(mAppDisplayWidth, mAppDisplayHeight, + mAppDisplayWidth, mAppDisplayHeight); + return a; + } + + private Animation createThumbnailAnimationLocked(int transit, + boolean enter, boolean thumb) { + Animation a; + final int thumbWidthI = mNextAppTransitionThumbnail.getWidth(); + final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; + final int thumbHeightI = mNextAppTransitionThumbnail.getHeight(); + final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; + // Pick the desired duration. If this is an inter-activity transition, + // it is the standard duration for that. Otherwise we use the longer + // task transition duration. + int duration; + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + duration = mContext.getResources().getInteger( + com.android.internal.R.integer.config_shortAnimTime); + break; + default: + duration = 300; + break; + } + 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); + set.addAnimation(scale); + alpha.setDuration(duration); + set.addAnimation(alpha); + a = set; + } else if (enter) { + // Entering app zooms out from the center of the thumbnail. + float scaleW = thumbWidth/mAppDisplayWidth; + float scaleH = thumbHeight/mAppDisplayHeight; + a = new ScaleAnimation(scaleW, 1, scaleH, 1, + computePivot(mNextAppTransitionStartX, scaleW), + computePivot(mNextAppTransitionStartY, scaleH)); + a.setDuration(duration); + } else { + a = createExitAnimationLocked(transit, duration); + } + 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); + return a; + } + private boolean applyAnimationLocked(AppWindowToken wtoken, 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 // artifacts when we unfreeze the display if some different animation // is running. - if (!mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully()) { + if (okToDisplay()) { Animation a; - if (mNextAppTransitionPackage != null) { + boolean initialized = false; + if (mNextAppTransitionType == ActivityOptions.ANIM_CUSTOM) { a = loadAnimation(mNextAppTransitionPackage, enter ? mNextAppTransitionEnter : mNextAppTransitionExit); + } else if (mNextAppTransitionType == ActivityOptions.ANIM_SCALE_UP) { + a = createScaleUpAnimationLocked(transit, enter); + initialized = true; + } else if (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL) { + a = createThumbnailAnimationLocked(transit, enter, false); + initialized = true; } else { int animAttr = 0; switch (transit) { @@ -3103,13 +3319,13 @@ public class WindowManagerService extends IWindowManager.Stub } Slog.v(TAG, "Loaded animation " + a + " for " + wtoken, e); } - wtoken.setAnimation(a); + wtoken.mAppAnimator.setAnimation(a, initialized); } } else { - wtoken.clearAnimation(); + wtoken.mAppAnimator.clearAnimation(); } - return wtoken.animation != null; + return wtoken.mAppAnimator.animation != null; } // ------------------------------------------------------------- @@ -3162,6 +3378,10 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, msg); return false; } + + boolean okToDisplay() { + return !mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully(); + } AppWindowToken findAppWindowToken(IBinder token) { WindowToken wtoken = mTokenMap.get(token); @@ -3171,6 +3391,7 @@ public class WindowManagerService extends IWindowManager.Stub return wtoken.appWindowToken; } + @Override public void addWindowToken(IBinder token, int type) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addWindowToken()")) { @@ -3211,13 +3432,12 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<N; i++) { WindowState win = wtoken.windows.get(i); - if (win.isAnimating()) { + if (win.mWinAnimator.isAnimating()) { delayed = true; } if (win.isVisibleNow()) { - applyAnimationLocked(win, - WindowManagerPolicy.TRANSIT_EXIT, false); + win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false); changed = true; } } @@ -3250,7 +3470,7 @@ public class WindowManagerService extends IWindowManager.Stub "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } - + // Get the dispatching timeout here while we are not holding any locks so that it // can be cached by the AppWindowToken. The timeout value is used later by the // input dispatcher in code that does hold locks. If we did not cache the value @@ -3338,15 +3558,13 @@ public class WindowManagerService extends IWindowManager.Stub } public int getOrientationFromAppTokensLocked() { - int pos = mAppTokens.size() - 1; int curGroup = 0; int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; boolean findingBehind = false; boolean haveGroup = false; boolean lastFullscreen = false; - while (pos >= 0) { + for (int pos = mAppTokens.size() - 1; pos >= 0; pos--) { AppWindowToken wtoken = mAppTokens.get(pos); - pos--; if (DEBUG_APP_ORIENTATION) Slog.v(TAG, "Checking app orientation: " + wtoken); @@ -3452,7 +3670,7 @@ public class WindowManagerService extends IWindowManager.Stub // the value of the previous configuration. mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = currentConfig.fontScale; - if (computeNewConfigurationLocked(mTempConfiguration)) { + if (computeScreenConfigurationLocked(mTempConfiguration)) { if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; mLayoutNeeded = true; @@ -3480,7 +3698,6 @@ public class WindowManagerService extends IWindowManager.Stub * android.os.IBinder) */ boolean updateOrientationFromAppTokensLocked(boolean inTransaction) { - boolean changed = false; long ident = Binder.clearCallingIdentity(); try { int req = computeForcedAppOrientationLocked(); @@ -3491,11 +3708,12 @@ public class WindowManagerService extends IWindowManager.Stub //action like disabling/enabling sensors etc., mPolicy.setCurrentOrientationLw(req); if (updateRotationUncheckedLocked(inTransaction)) { - changed = true; + // changed + return true; } } - return changed; + return false; } finally { Binder.restoreCallingIdentity(ident); } @@ -3597,7 +3815,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Prepare app transition: transit=" + transit + " mNextAppTransition=" + mNextAppTransition); - if (!mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully()) { + if (okToDisplay()) { if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET || mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) { mNextAppTransition = transit; @@ -3630,12 +3848,39 @@ public class WindowManagerService extends IWindowManager.Stub public void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim) { if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mNextAppTransitionType = ActivityOptions.ANIM_CUSTOM; mNextAppTransitionPackage = packageName; + mNextAppTransitionThumbnail = null; mNextAppTransitionEnter = enterAnim; mNextAppTransitionExit = exitAnim; } } + public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth, + int startHeight) { + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mNextAppTransitionType = ActivityOptions.ANIM_SCALE_UP; + mNextAppTransitionPackage = null; + mNextAppTransitionThumbnail = null; + mNextAppTransitionStartX = startX; + mNextAppTransitionStartY = startY; + mNextAppTransitionStartWidth = startWidth; + mNextAppTransitionStartHeight = startHeight; + } + } + + public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, + int startY, IRemoteCallback startedCallback) { + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + mNextAppTransitionType = ActivityOptions.ANIM_THUMBNAIL; + mNextAppTransitionPackage = null; + mNextAppTransitionThumbnail = srcThumb; + mNextAppTransitionStartX = startX; + mNextAppTransitionStartY = startY; + mNextAppTransitionCallback = startedCallback; + } + } + public void executeAppTransition() { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "executeAppTransition()")) { @@ -3681,7 +3926,7 @@ public class WindowManagerService extends IWindowManager.Stub // If the display is frozen, we won't do anything until the // actual window is displayed so there is no reason to put in // the starting window. - if (mDisplayFrozen || !mDisplayEnabled || !mPolicy.isScreenOnFully()) { + if (!okToDisplay()) { return; } @@ -3747,14 +3992,16 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.clientHidden = ttoken.clientHidden; wtoken.sendAppVisibilityToClients(); } - if (ttoken.animation != null) { - wtoken.animation = ttoken.animation; - wtoken.animating = ttoken.animating; - wtoken.animLayerAdjustment = ttoken.animLayerAdjustment; - ttoken.animation = null; - ttoken.animLayerAdjustment = 0; - wtoken.updateLayers(); - ttoken.updateLayers(); + final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator; + final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator; + if (tAppAnimator.animation != null) { + wAppAnimator.animation = tAppAnimator.animation; + wAppAnimator.animating = tAppAnimator.animating; + wAppAnimator.animLayerAdjustment = tAppAnimator.animLayerAdjustment; + tAppAnimator.animation = null; + tAppAnimator.animLayerAdjustment = 0; + wAppAnimator.updateLayers(); + tAppAnimator.updateLayers(); } updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, @@ -3779,6 +4026,21 @@ public class WindowManagerService extends IWindowManager.Stub mH.sendMessageAtFrontOfQueue(m); return; } + final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator; + final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator; + if (tAppAnimator.thumbnail != null) { + // The old token is animating with a thumbnail, transfer + // that to the new token. + if (wAppAnimator.thumbnail != null) { + wAppAnimator.thumbnail.destroy(); + } + wAppAnimator.thumbnail = tAppAnimator.thumbnail; + wAppAnimator.thumbnailX = tAppAnimator.thumbnailX; + wAppAnimator.thumbnailY = tAppAnimator.thumbnailY; + wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer; + wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation; + tAppAnimator.thumbnail = null; + } } } @@ -3858,7 +4120,6 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.willBeHidden = false; if (wtoken.hidden == visible) { - final int N = wtoken.allAppWindows.size(); boolean changed = false; if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden @@ -3867,39 +4128,35 @@ public class WindowManagerService extends IWindowManager.Stub boolean runningAppAnimation = false; if (transit != WindowManagerPolicy.TRANSIT_UNSET) { - if (wtoken.animation == sDummyAnimation) { - wtoken.animation = null; + if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) { + wtoken.mAppAnimator.animation = null; } - applyAnimationLocked(wtoken, lp, transit, visible); - changed = true; - if (wtoken.animation != null) { + if (applyAnimationLocked(wtoken, lp, transit, visible)) { delayed = runningAppAnimation = true; } + changed = true; } + final int N = wtoken.allAppWindows.size(); for (int i=0; i<N; i++) { WindowState win = wtoken.allAppWindows.get(i); if (win == wtoken.startingWindow) { continue; } - if (win.isAnimating()) { - delayed = true; - } - //Slog.i(TAG, "Window " + win + ": vis=" + win.isVisible()); //win.dump(" "); if (visible) { if (!win.isVisibleNow()) { if (!runningAppAnimation) { - applyAnimationLocked(win, + win.mWinAnimator.applyAnimationLocked( WindowManagerPolicy.TRANSIT_ENTER, true); } changed = true; } } else if (win.isVisibleNow()) { if (!runningAppAnimation) { - applyAnimationLocked(win, + win.mWinAnimator.applyAnimationLocked( WindowManagerPolicy.TRANSIT_EXIT, false); } changed = true; @@ -3913,8 +4170,7 @@ public class WindowManagerService extends IWindowManager.Stub // If we are being set visible, and the starting window is // not yet displayed, then make sure it doesn't get displayed. WindowState swin = wtoken.startingWindow; - if (swin != null && (swin.mDrawPending - || swin.mCommitDrawPending)) { + if (swin != null && !swin.isDrawnLw()) { swin.mPolicyVisibility = false; swin.mPolicyVisibilityAfterAnim = false; } @@ -3936,10 +4192,16 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (wtoken.animation != null) { + if (wtoken.mAppAnimator.animation != null) { delayed = true; } + for (int i = wtoken.allAppWindows.size() - 1; i >= 0 && !delayed; i--) { + if (wtoken.allAppWindows.get(i).mWinAnimator.isWindowAnimating()) { + delayed = true; + } + } + return delayed; } @@ -3972,8 +4234,7 @@ public class WindowManagerService extends IWindowManager.Stub // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. - if (!mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully() - && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { + if (okToDisplay() && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { // Already in requested state, don't do anything more. if (wtoken.hiddenRequested != visible) { return; @@ -3982,7 +4243,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Setting dummy animation on: " + wtoken); - wtoken.setDummyAnimation(); + wtoken.mAppAnimator.setDummyAnimation(); mOpeningApps.remove(wtoken); mClosingApps.remove(wtoken); wtoken.waitingToShow = wtoken.waitingToHide = false; @@ -4032,7 +4293,7 @@ public class WindowManagerService extends IWindowManager.Stub void unsetAppFreezingScreenLocked(AppWindowToken wtoken, boolean unfreezeSurfaceNow, boolean force) { - if (wtoken.freezingScreen) { + if (wtoken.mAppAnimator.freezingScreen) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Clear freezing of " + wtoken + " force=" + force); final int N = wtoken.allAppWindows.size(); @@ -4041,16 +4302,17 @@ public class WindowManagerService extends IWindowManager.Stub WindowState w = wtoken.allAppWindows.get(i); if (w.mAppFreezing) { w.mAppFreezing = false; - if (w.mSurface != null && !w.mOrientationChanging) { + if (w.mHasSurface && !w.mOrientationChanging) { if (DEBUG_ORIENTATION) Slog.v(TAG, "set mOrientationChanging of " + w); w.mOrientationChanging = true; + mInnerFields.mOrientationChangeComplete = false; } unfrozeWindows = true; } } if (force || unfrozeWindows) { if (DEBUG_ORIENTATION) Slog.v(TAG, "No longer freezing: " + wtoken); - wtoken.freezingScreen = false; + wtoken.mAppAnimator.freezingScreen = false; mAppsFreezingScreen--; } if (unfreezeSurfaceNow) { @@ -4073,11 +4335,11 @@ public class WindowManagerService extends IWindowManager.Stub } Slog.i(TAG, "Set freezing of " + wtoken.appToken + ": hidden=" + wtoken.hidden + " freezing=" - + wtoken.freezingScreen, e); + + wtoken.mAppAnimator.freezingScreen, e); } if (!wtoken.hiddenRequested) { - if (!wtoken.freezingScreen) { - wtoken.freezingScreen = true; + if (!wtoken.mAppAnimator.freezingScreen) { + wtoken.mAppAnimator.freezingScreen = true; mAppsFreezingScreen++; if (mAppsFreezingScreen == 1) { startFreezingDisplayLocked(false); @@ -4101,7 +4363,7 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized(mWindowMap) { - if (configChanges == 0 && !mDisplayFrozen && mPolicy.isScreenOnFully()) { + if (configChanges == 0 && okToDisplay()) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping set freeze of " + token); return; } @@ -4130,7 +4392,7 @@ public class WindowManagerService extends IWindowManager.Stub } final long origId = Binder.clearCallingIdentity(); if (DEBUG_ORIENTATION) Slog.v(TAG, "Clear freezing of " + token - + ": hidden=" + wtoken.hidden + " freezing=" + wtoken.freezingScreen); + + ": hidden=" + wtoken.hidden + " freezing=" + wtoken.mAppAnimator.freezingScreen); unsetAppFreezingScreenLocked(wtoken, true, force); Binder.restoreCallingIdentity(origId); } @@ -4165,8 +4427,8 @@ public class WindowManagerService extends IWindowManager.Stub } if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Removing app " + wtoken + " delayed=" + delayed - + " animation=" + wtoken.animation - + " animating=" + wtoken.animating); + + " animation=" + wtoken.mAppAnimator.animation + + " animating=" + wtoken.mAppAnimator.animating); if (delayed) { // set the token aside because it has an active animation to be finished if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, @@ -4176,8 +4438,8 @@ public class WindowManagerService extends IWindowManager.Stub // Make sure there is no animation running on this token, // so any windows associated with it will be removed as // soon as their animations are complete - wtoken.animation = null; - wtoken.animating = false; + wtoken.mAppAnimator.clearAnimation(); + wtoken.mAppAnimator.animating = false; } if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "removeAppToken: " + wtoken); @@ -4628,7 +4890,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); - if (w.mSurface != null) { + if (w.mHasSurface) { try { w.mClient.closeSystemDialogs(reason); } catch (RemoteException e) { @@ -4656,6 +4918,7 @@ public class WindowManagerService extends IWindowManager.Stub switch (which) { case 0: mWindowAnimationScale = fixScale(scale); break; case 1: mTransitionAnimationScale = fixScale(scale); break; + case 2: mAnimatorDurationScale = fixScale(scale); break; } // Persist setting @@ -4675,6 +4938,9 @@ public class WindowManagerService extends IWindowManager.Stub if (scales.length >= 2) { mTransitionAnimationScale = fixScale(scales[1]); } + if (scales.length >= 3) { + mAnimatorDurationScale = fixScale(scales[2]); + } } // Persist setting @@ -4685,103 +4951,36 @@ public class WindowManagerService extends IWindowManager.Stub switch (which) { case 0: return mWindowAnimationScale; case 1: return mTransitionAnimationScale; + case 2: return mAnimatorDurationScale; } return 0; } public float[] getAnimationScales() { - return new float[] { mWindowAnimationScale, mTransitionAnimationScale }; - } - - public int getSwitchState(int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getSwitchState()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, sw); - } - - public int getSwitchStateForDevice(int devid, int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getSwitchStateForDevice()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getSwitchState(devid, InputDevice.SOURCE_ANY, sw); - } - - public int getScancodeState(int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getScancodeState()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_ANY, sw); - } - - public int getScancodeStateForDevice(int devid, int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getScancodeStateForDevice()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getScanCodeState(devid, InputDevice.SOURCE_ANY, sw); - } - - public int getTrackballScancodeState(int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getTrackballScancodeState()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw); - } - - public int getDPadScancodeState(int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getDPadScancodeState()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_DPAD, sw); - } - - public int getKeycodeState(int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getKeycodeState()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, sw); + return new float[] { mWindowAnimationScale, mTransitionAnimationScale, + mAnimatorDurationScale }; } - public int getKeycodeStateForDevice(int devid, int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getKeycodeStateForDevice()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getKeyCodeState(devid, InputDevice.SOURCE_ANY, sw); - } - - public int getTrackballKeycodeState(int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getTrackballKeycodeState()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } - return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw); - } - - public int getDPadKeycodeState(int sw) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "getDPadKeycodeState()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); + // Called by window manager policy. Not exposed externally. + @Override + public int getLidState() { + int sw = mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, + InputManagerService.SW_LID); + if (sw > 0) { + // Switch state: AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL. + return LID_CLOSED; + } else if (sw == 0) { + // Switch state: AKEY_STATE_UP. + return LID_OPEN; + } else { + // Switch state: AKEY_STATE_UNKNOWN. + return LID_ABSENT; } - return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD, sw); - } - - public boolean hasKeys(int[] keycodes, boolean[] keyExists) { - return mInputManager.hasKeys(-1, InputDevice.SOURCE_ANY, keycodes, keyExists); } + // Called by window manager policy. Not exposed externally. + @Override public InputChannel monitorInput(String inputChannelName) { - if (!checkCallingPermission(android.Manifest.permission.READ_INPUT_STATE, - "monitorInput()")) { - throw new SecurityException("Requires READ_INPUT_STATE permission"); - } return mInputManager.monitorInput(inputChannelName); } @@ -4789,14 +4988,6 @@ public class WindowManagerService extends IWindowManager.Stub mInputManager.setInputFilter(filter); } - public InputDevice getInputDevice(int deviceId) { - return mInputManager.getInputDevice(deviceId); - } - - public int[] getInputDeviceIds() { - return mInputManager.getInputDeviceIds(); - } - public void enableScreenAfterBoot() { synchronized(mWindowMap) { if (DEBUG_BOOT) { @@ -4843,7 +5034,7 @@ public class WindowManagerService extends IWindowManager.Stub public void performBootTimeout() { synchronized(mWindowMap) { - if (mDisplayEnabled) { + if (mDisplayEnabled || mHeadless) { return; } Slog.w(TAG, "***** BOOT TIMEOUT: forcing display enabled"); @@ -4874,7 +5065,11 @@ public class WindowManagerService extends IWindowManager.Stub // have been drawn. boolean haveBootMsg = false; boolean haveApp = false; + // if the wallpaper service is disabled on the device, we're never going to have + // wallpaper, don't bother waiting for it boolean haveWallpaper = false; + boolean wallpaperEnabled = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableWallpaperService); boolean haveKeyguard = true; final int N = mWindows.size(); for (int i=0; i<N; i++) { @@ -4910,7 +5105,8 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_SCREEN_ON || DEBUG_BOOT) { Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp - + " haveWall=" + haveWallpaper + " haveKeyguard=" + haveKeyguard); + + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled + + " haveKeyguard=" + haveKeyguard); } // If we are turning on the screen to show the boot message, @@ -4922,7 +5118,8 @@ public class WindowManagerService extends IWindowManager.Stub // If we are turning on the screen after the boot is completed // normally, don't do so until we have the application and // wallpaper. - if (mSystemBooted && ((!haveApp && !haveKeyguard) || !haveWallpaper)) { + if (mSystemBooted && ((!haveApp && !haveKeyguard) || + (wallpaperEnabled && !haveWallpaper))) { return; } } @@ -4953,7 +5150,7 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.enableScreenAfterBoot(); // Make sure the last requested orientation has been applied. - updateRotationUnchecked(false); + updateRotationUnchecked(false, false); } public void showBootMessage(final CharSequence msg, final boolean always) { @@ -5011,6 +5208,8 @@ public class WindowManagerService extends IWindowManager.Stub // TODO: more accounting of which pid(s) turned it on, keep count, // only allow disables from pids which have count on, etc. public void showStrictModeViolation(boolean on) { + if (mHeadless) return; + int pid = Binder.getCallingPid(); synchronized(mWindowMap) { // Ignoring requests to enable the red border from clients @@ -5075,8 +5274,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { long ident = Binder.clearCallingIdentity(); - dw = mAppDisplayWidth; - dh = mAppDisplayHeight; + dw = mCurDisplayWidth; + dh = mCurDisplayHeight; int aboveAppLayer = mPolicy.windowTypeToLayerLw( WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER @@ -5092,7 +5291,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean including = false; for (int i=mWindows.size()-1; i>=0; i--) { WindowState ws = mWindows.get(i); - if (ws.mSurface == null) { + if (!ws.mHasSurface) { continue; } if (ws.mLayer >= aboveAppLayer) { @@ -5118,8 +5317,8 @@ public class WindowManagerService extends IWindowManager.Stub // window. including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh); - if (maxLayer < ws.mAnimLayer) { - maxLayer = ws.mAnimLayer; + if (maxLayer < ws.mWinAnimator.mAnimLayer) { + maxLayer = ws.mWinAnimator.mAnimLayer; } // Don't include wallpaper in bounds calculation @@ -5180,8 +5379,8 @@ public class WindowManagerService extends IWindowManager.Stub 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).mAnimLayer - + " surfaceLayer=" + mWindows.get(i).mSurfaceLayer); + + " animLayer=" + mWindows.get(i).mWinAnimator.mAnimLayer + + " surfaceLayer=" + mWindows.get(i).mWinAnimator.mSurfaceLayer); } } rawss = Surface.screenshot(dw, dh, 0, maxLayer); @@ -5196,7 +5395,7 @@ public class WindowManagerService extends IWindowManager.Stub Bitmap bm = Bitmap.createBitmap(width, height, rawss.getConfig()); Matrix matrix = new Matrix(); ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix); - matrix.postTranslate(-(int)(frame.left*scale), -(int)(frame.top*scale)); + matrix.postTranslate(-FloatMath.ceil(frame.left*scale), -FloatMath.ceil(frame.top*scale)); Canvas canvas = new Canvas(bm); canvas.drawBitmap(rawss, matrix, null); canvas.setBitmap(null); @@ -5225,7 +5424,7 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation == -1 ? mRotation : rotation); - updateRotationUnchecked(false); + updateRotationUnchecked(false, false); } /** @@ -5241,7 +5440,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) Slog.v(TAG, "thawRotation: mRotation=" + mRotation); mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, 777); // rot not used - updateRotationUnchecked(false); + updateRotationUnchecked(false, false); } /** @@ -5251,8 +5450,8 @@ public class WindowManagerService extends IWindowManager.Stub * such that the current rotation might need to be updated, such as when the * device is docked or rotated into a new posture. */ - public void updateRotation(boolean alwaysSendConfiguration) { - updateRotationUnchecked(alwaysSendConfiguration); + public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) { + updateRotationUnchecked(alwaysSendConfiguration, forceRelayout); } /** @@ -5282,8 +5481,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - public void updateRotationUnchecked( - boolean alwaysSendConfiguration) { + public void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) { if(DEBUG_ORIENTATION) Slog.v(TAG, "updateRotationUnchecked(" + "alwaysSendConfiguration=" + alwaysSendConfiguration + ")"); @@ -5291,6 +5489,10 @@ public class WindowManagerService extends IWindowManager.Stub boolean changed; synchronized(mWindowMap) { changed = updateRotationUncheckedLocked(false); + if (!changed || forceRelayout) { + mLayoutNeeded = true; + performLayoutAndPlaceSurfacesLocked(); + } } if (changed || alwaysSendConfiguration) { @@ -5314,7 +5516,8 @@ public class WindowManagerService extends IWindowManager.Stub return false; } - if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) { + if (mAnimator.mScreenRotationAnimation != null && + mAnimator.mScreenRotationAnimation.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. @@ -5368,6 +5571,14 @@ public class WindowManagerService extends IWindowManager.Stub startFreezingDisplayLocked(inTransaction); mInputManager.setDisplayOrientation(0, rotation); + // 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 + // the rotation animation for the new rotation. + computeScreenConfigurationLocked(null); + if (!inTransaction) { if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setRotationUnchecked"); @@ -5376,9 +5587,13 @@ public class WindowManagerService extends IWindowManager.Stub try { // NOTE: We disable the rotation in the emulator because // it doesn't support hardware OpenGL emulation yet. - if (CUSTOM_SCREEN_ROTATION && mScreenRotationAnimation != null - && mScreenRotationAnimation.hasScreenshot()) { - mScreenRotationAnimation.setRotation(rotation); + if (CUSTOM_SCREEN_ROTATION && mAnimator.mScreenRotationAnimation != null + && mAnimator.mScreenRotationAnimation.hasScreenshot()) { + if (mAnimator.mScreenRotationAnimation.setRotation(rotation, mFxSession, + MAX_ANIMATION_DURATION, mTransitionAnimationScale, + mCurDisplayWidth, mCurDisplayHeight)) { + scheduleAnimationLocked(); + } } Surface.setOrientation(0, rotation); } finally { @@ -5389,13 +5604,14 @@ public class WindowManagerService extends IWindowManager.Stub } } - rebuildBlackFrame(inTransaction); + rebuildBlackFrame(); for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); - if (w.mSurface != null) { + 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--) { @@ -5866,18 +6082,27 @@ public class WindowManagerService extends IWindowManager.Stub Configuration computeNewConfigurationLocked() { Configuration config = new Configuration(); config.fontScale = 0; - if (!computeNewConfigurationLocked(config)) { + if (!computeScreenConfigurationLocked(config)) { return null; } return config; } - private int reduceConfigWidthSize(int curSize, int rotation, float density, int dw, int dh) { - int size = (int)(mPolicy.getConfigDisplayWidth(dw, dh, rotation) / density); - if (size < curSize) { - curSize = size; + private void adjustDisplaySizeRanges(int rotation, int dw, int dh) { + final int width = mPolicy.getConfigDisplayWidth(dw, dh, rotation); + if (width < mSmallestDisplayWidth) { + mSmallestDisplayWidth = width; + } + if (width > mLargestDisplayWidth) { + mLargestDisplayWidth = width; + } + final int height = mPolicy.getConfigDisplayHeight(dw, dh, rotation); + if (height < mSmallestDisplayHeight) { + mSmallestDisplayHeight = height; + } + if (height > mLargestDisplayHeight) { + mLargestDisplayHeight = height; } - return curSize; } private int reduceConfigLayout(int curLayout, int rotation, float density, @@ -5959,7 +6184,7 @@ public class WindowManagerService extends IWindowManager.Stub return curLayout; } - private void computeSmallestWidthAndScreenLayout(boolean rotated, int dw, int dh, + private void computeSizeRangesAndScreenLayout(boolean rotated, int dw, int dh, float density, Configuration outConfig) { // We need to determine the smallest width that will occur under normal // operation. To this, start with the base screen size and compute the @@ -5973,17 +6198,21 @@ public class WindowManagerService extends IWindowManager.Stub unrotDw = dw; unrotDh = dh; } - int sw = reduceConfigWidthSize(unrotDw, Surface.ROTATION_0, density, unrotDw, unrotDh); - sw = reduceConfigWidthSize(sw, Surface.ROTATION_90, density, unrotDh, unrotDw); - sw = reduceConfigWidthSize(sw, Surface.ROTATION_180, density, unrotDw, unrotDh); - sw = reduceConfigWidthSize(sw, Surface.ROTATION_270, density, unrotDh, unrotDw); + 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); 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 = sw; + outConfig.smallestScreenWidthDp = (int)(mSmallestDisplayWidth / density); outConfig.screenLayout = sl; } @@ -6017,12 +6246,10 @@ public class WindowManagerService extends IWindowManager.Stub return sw; } - boolean computeNewConfigurationLocked(Configuration config) { + boolean computeScreenConfigurationLocked(Configuration config) { if (mDisplay == null) { return false; } - - mInputManager.getInputConfiguration(config); // Use the effective "visual" dimensions based on current rotation final boolean rotated = (mRotation == Surface.ROTATION_90 @@ -6056,13 +6283,17 @@ public class WindowManagerService extends IWindowManager.Stub final int dw = mCurDisplayWidth; final int dh = mCurDisplayHeight; - int orientation = Configuration.ORIENTATION_SQUARE; - if (dw < dh) { - orientation = Configuration.ORIENTATION_PORTRAIT; - } else if (dw > dh) { - orientation = Configuration.ORIENTATION_LANDSCAPE; + if (config != null) { + mInputManager.getInputConfiguration(config); + + 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 = orientation; // Update real display metrics. mDisplay.getMetricsWithSize(mRealDisplayMetrics, mCurDisplayWidth, mCurDisplayHeight); @@ -6074,6 +6305,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mDisplaySizeLock) { mAppDisplayWidth = appWidth; mAppDisplayHeight = appHeight; + mAnimator.setDisplayDimensions(mCurDisplayWidth, mCurDisplayHeight, + mAppDisplayWidth, mAppDisplayHeight); } if (false) { Slog.i(TAG, "Set app display size: " + mAppDisplayWidth @@ -6084,36 +6317,39 @@ public class WindowManagerService extends IWindowManager.Stub mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm, mCompatDisplayMetrics); - config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation) - / dm.density); - config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) - / dm.density); - computeSmallestWidthAndScreenLayout(rotated, dw, dh, dm.density, config); + if (config != null) { + config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation) + / dm.density); + config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) + / dm.density); + computeSizeRangesAndScreenLayout(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.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); + config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); + config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh); - // Determine whether a hard keyboard is available and enabled. - boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; - if (hardKeyboardAvailable != mHardKeyboardAvailable) { - mHardKeyboardAvailable = hardKeyboardAvailable; - mHardKeyboardEnabled = hardKeyboardAvailable; + // Determine whether a hard keyboard is available and enabled. + boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; + if (hardKeyboardAvailable != mHardKeyboardAvailable) { + mHardKeyboardAvailable = hardKeyboardAvailable; + mHardKeyboardEnabled = hardKeyboardAvailable; - mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); - mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); - } - if (!mHardKeyboardEnabled) { - config.keyboard = Configuration.KEYBOARD_NOKEYS; + mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + } + if (!mHardKeyboardEnabled) { + config.keyboard = Configuration.KEYBOARD_NOKEYS; + } + + // Update value of keyboardHidden, hardKeyboardHidden and navigationHidden + // based on whether a hard or soft keyboard is present, whether navigation keys + // are present and the lid switch state. + config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; + config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; + config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; + mPolicy.adjustConfigurationLw(config); } - // Update value of keyboardHidden, hardKeyboardHidden and navigationHidden - // based on whether a hard or soft keyboard is present, whether navigation keys - // are present and the lid switch state. - config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; - config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; - config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; - mPolicy.adjustConfigurationLw(config); return true; } @@ -6186,7 +6422,6 @@ public class WindowManagerService extends IWindowManager.Stub final IBinder winBinder = window.asBinder(); token = new Binder(); mDragState = new DragState(this, token, surface, /*flags*/ 0, winBinder); - mDragState.mSurface = surface; token = mDragState.mToken = new Binder(); // 5 second timeout for this window to actually begin the drag @@ -6254,165 +6489,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { mInputMonitor.setEventDispatchingLw(enabled); } - } - - /** - * Injects a keystroke event into the UI. - * Even when sync is false, this method may block while waiting for current - * input events to be dispatched. - * - * @param ev A motion event describing the keystroke action. (Be sure to use - * {@link SystemClock#uptimeMillis()} as the timebase.) - * @param sync If true, wait for the event to be completed before returning to the caller. - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - public boolean injectKeyEvent(KeyEvent ev, boolean sync) { - long downTime = ev.getDownTime(); - long eventTime = ev.getEventTime(); - - int action = ev.getAction(); - int code = ev.getKeyCode(); - int repeatCount = ev.getRepeatCount(); - int metaState = ev.getMetaState(); - int deviceId = ev.getDeviceId(); - int scancode = ev.getScanCode(); - int source = ev.getSource(); - int flags = ev.getFlags(); - - if (source == InputDevice.SOURCE_UNKNOWN) { - source = InputDevice.SOURCE_KEYBOARD; - } - if (eventTime == 0) eventTime = SystemClock.uptimeMillis(); - if (downTime == 0) downTime = eventTime; - - KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState, - deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source); - - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long ident = Binder.clearCallingIdentity(); - - final int result = mInputManager.injectInputEvent(newEvent, pid, uid, - sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH - : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, - INJECTION_TIMEOUT_MILLIS); - - Binder.restoreCallingIdentity(ident); - return reportInjectionResult(result); - } - - /** - * Inject a pointer (touch) event into the UI. - * Even when sync is false, this method may block while waiting for current - * input events to be dispatched. - * - * @param ev A motion event describing the pointer (touch) action. (As noted in - * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use - * {@link SystemClock#uptimeMillis()} as the timebase.) - * @param sync If true, wait for the event to be completed before returning to the caller. - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - public boolean injectPointerEvent(MotionEvent ev, boolean sync) { - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long ident = Binder.clearCallingIdentity(); - - MotionEvent newEvent = MotionEvent.obtain(ev); - if ((newEvent.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { - newEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN); - } - - final int result = mInputManager.injectInputEvent(newEvent, pid, uid, - sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH - : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, - INJECTION_TIMEOUT_MILLIS); - - Binder.restoreCallingIdentity(ident); - return reportInjectionResult(result); - } - - /** - * Inject a trackball (navigation device) event into the UI. - * Even when sync is false, this method may block while waiting for current - * input events to be dispatched. - * - * @param ev A motion event describing the trackball action. (As noted in - * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use - * {@link SystemClock#uptimeMillis()} as the timebase.) - * @param sync If true, wait for the event to be completed before returning to the caller. - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - public boolean injectTrackballEvent(MotionEvent ev, boolean sync) { - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long ident = Binder.clearCallingIdentity(); - - MotionEvent newEvent = MotionEvent.obtain(ev); - if ((newEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) { - newEvent.setSource(InputDevice.SOURCE_TRACKBALL); - } - - final int result = mInputManager.injectInputEvent(newEvent, pid, uid, - sync ? InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISH - : InputManager.INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT, - INJECTION_TIMEOUT_MILLIS); - - Binder.restoreCallingIdentity(ident); - return reportInjectionResult(result); - } - - /** - * Inject an input event into the UI without waiting for dispatch to commence. - * This variant is useful for fire-and-forget input event injection. It does not - * block any longer than it takes to enqueue the input event. - * - * @param ev An input event. (Be sure to set the input source correctly.) - * @return Returns true if event was dispatched, false if it was dropped for any reason - */ - public boolean injectInputEventNoWait(InputEvent ev) { - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - final long ident = Binder.clearCallingIdentity(); - - final int result = mInputManager.injectInputEvent(ev, pid, uid, - InputManager.INPUT_EVENT_INJECTION_SYNC_NONE, - INJECTION_TIMEOUT_MILLIS); - - Binder.restoreCallingIdentity(ident); - return reportInjectionResult(result); - } - - private boolean reportInjectionResult(int result) { - switch (result) { - case InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED: - Slog.w(TAG, "Input event injection permission denied."); - throw new SecurityException( - "Injecting to another application requires INJECT_EVENTS permission"); - case InputManager.INPUT_EVENT_INJECTION_SUCCEEDED: - //Slog.v(TAG, "Input event injection succeeded."); - return true; - case InputManager.INPUT_EVENT_INJECTION_TIMED_OUT: - Slog.w(TAG, "Input event injection timed out."); - return false; - case InputManager.INPUT_EVENT_INJECTION_FAILED: - default: - Slog.w(TAG, "Input event injection failed."); - return false; - } - } - - /** - * Temporarily set the pointer speed. Does not save the new setting. - * Used by the settings application. - */ - public void setPointerSpeed(int speed) { - if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED, - "setPointerSpeed()")) { - throw new SecurityException("Requires SET_POINTER_SPEED permission"); - } - - mInputManager.setPointerSpeed(speed); + sendScreenStatusToClients(); } private WindowState getFocusedWindow() { @@ -6429,11 +6507,35 @@ public class WindowManagerService extends IWindowManager.Stub if (!mInputMonitor.waitForInputDevicesReady( INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) { Slog.w(TAG, "Devices still not ready after waiting " - + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS - + " milliseconds before attempting to detect safe mode."); + + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS + + " milliseconds before attempting to detect safe mode."); + } + + int menuState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, + KeyEvent.KEYCODE_MENU); + int sState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, KeyEvent.KEYCODE_S); + int dpadState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD, + KeyEvent.KEYCODE_DPAD_CENTER); + int trackballState = mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL, + InputManagerService.BTN_MOUSE); + int volumeDownState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, + KeyEvent.KEYCODE_VOLUME_DOWN); + mSafeMode = menuState > 0 || sState > 0 || dpadState > 0 || trackballState > 0 + || volumeDownState > 0; + try { + if (SystemProperties.getInt(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, 0) != 0) { + mSafeMode = true; + SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, ""); + } + } catch (IllegalArgumentException e) { } - - mSafeMode = mPolicy.detectSafeMode(); + if (mSafeMode) { + Log.i(TAG, "SAFE MODE ENABLED (menu=" + menuState + " s=" + sState + + " dpad=" + dpadState + " trackball=" + trackballState + ")"); + } else { + Log.i(TAG, "SAFE MODE not enabled"); + } + mPolicy.setSafeMode(mSafeMode); return mSafeMode; } @@ -6457,11 +6559,13 @@ public class WindowManagerService extends IWindowManager.Stub } 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()); - mPolicy.setInitialDisplaySize(mInitialDisplayWidth, mInitialDisplayHeight); + mPolicy.setInitialDisplaySize(mDisplay, mInitialDisplayWidth, mInitialDisplayHeight); } try { @@ -6478,15 +6582,19 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.systemReady(); } - // This is an animation that does nothing: it just immediately finishes - // itself every time it is called. It is used as a stub animation in cases - // where we want to synchronize multiple things that may be animating. - static final class DummyAnimation extends Animation { - public boolean getTransformation(long currentTime, Transformation outTransformation) { - return false; + private void sendScreenStatusToClients() { + 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); + try { + win.mClient.dispatchScreenState(on); + } catch (RemoteException e) { + // Ignored + } } } - static final Animation sDummyAnimation = new DummyAnimation(); // ------------------------------------------------------------- // Async Handler @@ -6495,7 +6603,7 @@ public class WindowManagerService extends IWindowManager.Stub final class H extends Handler { public static final int REPORT_FOCUS_CHANGE = 2; public static final int REPORT_LOSING_FOCUS = 3; - public static final int ANIMATE = 4; + public static final int DO_TRAVERSAL = 4; public static final int ADD_STARTING = 5; public static final int REMOVE_STARTING = 6; public static final int FINISHED_STARTING = 7; @@ -6515,6 +6623,14 @@ 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 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 SET_MOVE_ANIMATION = ANIMATOR_WHAT_OFFSET + 4; + public static final int CLEAR_PENDING_ACTIONS = ANIMATOR_WHAT_OFFSET + 5; private Session mLastReportedHold; @@ -6523,6 +6639,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void handleMessage(Message msg) { + if (DEBUG_WINDOW_TRACE) { + Slog.v(TAG, "handleMessage: entry what=" + msg.what); + } switch (msg.what) { case REPORT_FOCUS_CHANGE: { WindowState lastFocus; @@ -6589,9 +6708,9 @@ public class WindowManagerService extends IWindowManager.Stub } } break; - case ANIMATE: { + case DO_TRAVERSAL: { synchronized(mWindowMap) { - mAnimationPending = false; + mTraversalScheduled = false; performLayoutAndPlaceSurfacesLocked(); } } break; @@ -6807,12 +6926,14 @@ public class WindowManagerService extends IWindowManager.Stub Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale); Settings.System.putFloat(mContext.getContentResolver(), Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale); + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.ANIMATOR_DURATION_SCALE, mAnimatorDurationScale); break; } case FORCE_GC: { synchronized(mWindowMap) { - if (mAnimationPending) { + 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), @@ -6836,14 +6957,16 @@ public class WindowManagerService extends IWindowManager.Stub case APP_FREEZE_TIMEOUT: { synchronized (mWindowMap) { - Slog.w(TAG, "App freeze timeout expired."); - int i = mAppTokens.size(); - while (i > 0) { - i--; - AppWindowToken tok = mAppTokens.get(i); - if (tok.freezingScreen) { - Slog.w(TAG, "Force clearing freeze: " + tok); - unsetAppFreezingScreenLocked(tok, true, true); + synchronized (mAnimator) { + Slog.w(TAG, "App freeze timeout expired."); + int i = mAppTokens.size(); + while (i > 0) { + i--; + AppWindowToken tok = mAppTokens.get(i); + if (tok.mAppAnimator.freezingScreen) { + Slog.w(TAG, "Force clearing freeze: " + tok); + unsetAppFreezingScreenLocked(tok, true, true); + } } } } @@ -6923,6 +7046,93 @@ public class WindowManagerService extends IWindowManager.Stub } break; } + + case BULK_UPDATE_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) { + mH.sendEmptyMessage(CLEAR_PENDING_ACTIONS); + performLayoutAndPlaceSurfacesLocked(); + } + } + break; + } + + // Animation messages. Move to Window{State}Animator + case SET_TRANSPARENT_REGION: { + Pair<WindowStateAnimator, Region> pair = + (Pair<WindowStateAnimator, Region>) msg.obj; + final WindowStateAnimator winAnimator = pair.first; + winAnimator.setTransparentRegionHint(pair.second); + break; + } + + case SET_WALLPAPER_OFFSET: { + final WindowStateAnimator winAnimator = (WindowStateAnimator) msg.obj; + winAnimator.setWallpaperOffset(msg.arg1, msg.arg2); + + scheduleAnimationLocked(); + break; + } + + case SET_DIM_PARAMETERS: { + mAnimator.mDimParams = (DimAnimator.Parameters) msg.obj; + + scheduleAnimationLocked(); + break; + } + + case SET_MOVE_ANIMATION: { + WindowAnimator.SetAnimationParams params = + (WindowAnimator.SetAnimationParams) msg.obj; + WindowStateAnimator winAnimator = params.mWinAnimator; + winAnimator.setAnimation(params.mAnimation); + winAnimator.mAnimDw = params.mAnimDw; + winAnimator.mAnimDh = params.mAnimDh; + + scheduleAnimationLocked(); + break; + } + + case CLEAR_PENDING_ACTIONS: { + mAnimator.clearPendingActions(); + break; + } + } + if (DEBUG_WINDOW_TRACE) { + Slog.v(TAG, "handleMessage: exit"); } } } @@ -6931,6 +7141,7 @@ public class WindowManagerService extends IWindowManager.Stub // IWindowManager API // ------------------------------------------------------------- + @Override public IWindowSession openSession(IInputMethodClient client, IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); @@ -6939,6 +7150,7 @@ public class WindowManagerService extends IWindowManager.Stub return session; } + @Override public boolean inputMethodClientHasFocus(IInputMethodClient client) { synchronized (mWindowMap) { // The focus for the client is the window immediately below @@ -7023,6 +7235,15 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void getCurrentSizeRange(Point smallestSize, Point largestSize) { + synchronized(mDisplaySizeLock) { + smallestSize.x = mSmallestDisplayWidth; + smallestSize.y = mSmallestDisplayHeight; + largestSize.x = mLargestDisplayWidth; + largestSize.y = mLargestDisplayHeight; + } + } + public void setForcedDisplaySize(int longDimen, int shortDimen) { synchronized(mWindowMap) { int width, height; @@ -7043,45 +7264,32 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void rebuildBlackFrame(boolean inTransaction) { - if (!inTransaction) { - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - ">>> OPEN TRANSACTION rebuildBlackFrame"); - Surface.openTransaction(); + private void rebuildBlackFrame() { + if (mBlackFrame != null) { + mBlackFrame.kill(); + mBlackFrame = null; } - try { - 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) { - } + 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; } - } finally { - if (!inTransaction) { - Surface.closeTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION rebuildBlackFrame"); + 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) { } } } @@ -7113,14 +7321,14 @@ public class WindowManagerService extends IWindowManager.Stub mBaseDisplayWidth = width; mBaseDisplayHeight = height; } - mPolicy.setInitialDisplaySize(mBaseDisplayWidth, mBaseDisplayHeight); + mPolicy.setInitialDisplaySize(mDisplay, mBaseDisplayWidth, mBaseDisplayHeight); mLayoutNeeded = true; boolean configChanged = updateOrientationFromAppTokensLocked(false); mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = mCurConfiguration.fontScale; - if (computeNewConfigurationLocked(mTempConfiguration)) { + if (computeScreenConfigurationLocked(mTempConfiguration)) { if (mCurConfiguration.diff(mTempConfiguration) != 0) { configChanged = true; } @@ -7132,7 +7340,7 @@ public class WindowManagerService extends IWindowManager.Stub mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } - rebuildBlackFrame(false); + rebuildBlackFrame(); performLayoutAndPlaceSurfacesLocked(); } @@ -7145,8 +7353,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - public boolean canStatusBarHide() { - return mPolicy.canStatusBarHide(); + public boolean hasSystemNavBar() { + return mPolicy.hasSystemNavBar(); } // ------------------------------------------------------------- @@ -7250,6 +7458,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.flush(); Slog.w(TAG, "This window was lost: " + ws); Slog.w(TAG, sw.toString()); + ws.mWinAnimator.destroySurfaceLocked(); } } Slog.w(TAG, "Current app token list:"); @@ -7282,19 +7491,21 @@ public class WindowManagerService extends IWindowManager.Stub w.mLayer = curLayer; } if (w.mTargetAppToken != null) { - w.mAnimLayer = w.mLayer + w.mTargetAppToken.animLayerAdjustment; + w.mWinAnimator.mAnimLayer = + w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment; } else if (w.mAppToken != null) { - w.mAnimLayer = w.mLayer + w.mAppToken.animLayerAdjustment; + w.mWinAnimator.mAnimLayer = + w.mLayer + w.mAppToken.mAppAnimator.animLayerAdjustment; } else { - w.mAnimLayer = w.mLayer; + w.mWinAnimator.mAnimLayer = w.mLayer; } if (w.mIsImWindow) { - w.mAnimLayer += mInputMethodAnimLayerAdjustment; + w.mWinAnimator.mAnimLayer += mInputMethodAnimLayerAdjustment; } else if (w.mIsWallpaper) { - w.mAnimLayer += mWallpaperAnimLayerAdjustment; + w.mWinAnimator.mAnimLayer += mWallpaperAnimLayerAdjustment; } if (DEBUG_LAYERS) Slog.v(TAG, "Assign layer " + w + ": " - + w.mAnimLayer); + + w.mWinAnimator.mAnimLayer); //System.out.println( // "Assigned layer " + curLayer + " to " + w.mClient.asBinder()); } @@ -7322,6 +7533,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout"); mInLayout = true; boolean recoveringMemory = false; @@ -7351,7 +7563,7 @@ public class WindowManagerService extends IWindowManager.Stub try { performLayoutAndPlaceSurfacesLockedInner(recoveringMemory); - int N = mPendingRemove.size(); + final int N = mPendingRemove.size(); if (N > 0) { if (mPendingRemoveTmp.length < N) { mPendingRemoveTmp = new WindowState[N+10]; @@ -7366,14 +7578,26 @@ public class WindowManagerService extends IWindowManager.Stub 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; - if (mLayoutNeeded) { - requestAnimationLocked(0); + } + + if (mLayoutNeeded) { + if (++mLayoutRepeatCount < 6) { + requestTraversalLocked(); + } else { + Slog.e(TAG, "Performed 6 layouts in a row. Skipping"); + mLayoutRepeatCount = 0; } + } else { + mLayoutRepeatCount = 0; } + if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) { mH.removeMessages(H.REPORT_WINDOWS_CHANGE); mH.sendMessage(mH.obtainMessage(H.REPORT_WINDOWS_CHANGE)); @@ -7382,11 +7606,13 @@ public class WindowManagerService extends IWindowManager.Stub mInLayout = false; Log.wtf(TAG, "Unhandled exception while laying out windows", e); } + + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } - private final int performLayoutLockedInner(boolean initial, boolean updateInputWindows) { + private final void performLayoutLockedInner(boolean initial, boolean updateInputWindows) { if (!mLayoutNeeded) { - return 0; + return; } mLayoutNeeded = false; @@ -7418,7 +7644,7 @@ public class WindowManagerService extends IWindowManager.Stub // to another window). int topAttached = -1; for (i = N-1; i >= 0; i--) { - WindowState win = mWindows.get(i); + final WindowState win = mWindows.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 @@ -7474,7 +7700,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--) { - WindowState win = mWindows.get(i); + final WindowState win = mWindows.get(i); if (win.mLayoutAttached) { if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + win @@ -7510,17 +7736,18 @@ public class WindowManagerService extends IWindowManager.Stub mInputMonitor.updateInputWindowsLw(false /*force*/); } - return mPolicy.finishLayoutLw(); + mPolicy.finishLayoutLw(); } void makeWindowFreezingScreenIfNeededLocked(WindowState w) { // If the screen is currently frozen or off, then keep // it frozen/off until this window draws at its new // orientation. - if (mDisplayFrozen || !mPolicy.isScreenOnFully()) { + if (!okToDisplay()) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Changing surface while display frozen: " + w); w.mOrientationChanging = true; + mInnerFields.mOrientationChangeComplete = false; if (!mWindowsFreezingScreen) { mWindowsFreezingScreen = true; // XXX should probably keep timeout from @@ -7532,9 +7759,474 @@ 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. + */ + public int handleAppTransitionReadyLocked() { + int changes = 0; + int i; + int NN = mOpeningApps.size(); + boolean goodToGo = true; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Checking " + NN + " opening apps (frozen=" + + mDisplayFrozen + " timeout=" + + mAppTransitionTimeout + ")..."); + if (!mDisplayFrozen && !mAppTransitionTimeout) { + // If the display isn't frozen, wait to do anything until + // all of the apps are ready. Otherwise just go because + // we'll unfreeze the display when everyone is ready. + for (i=0; i<NN && goodToGo; i++) { + AppWindowToken wtoken = mOpeningApps.get(i); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Check opening app" + wtoken + ": allDrawn=" + + wtoken.allDrawn + " startingDisplayed=" + + wtoken.startingDisplayed + " startingMoved=" + + wtoken.startingMoved); + if (!wtoken.allDrawn && !wtoken.startingDisplayed + && !wtoken.startingMoved) { + goodToGo = false; + } + } + } + if (goodToGo) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO"); + int transit = mNextAppTransition; + if (mSkipAppTransitionAnimation) { + transit = WindowManagerPolicy.TRANSIT_UNSET; + } + mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; + mAppTransitionReady = false; + mAppTransitionRunning = true; + mAppTransitionTimeout = false; + mStartingIconInTransition = false; + mSkipAppTransitionAnimation = false; + + mH.removeMessages(H.APP_TRANSITION_TIMEOUT); + + // If there are applications waiting to come to the + // top of the stack, now is the time to move their windows. + // (Note that we don't do apps going to the bottom + // here -- we want to keep their windows in the old + // Z-order until the animation completes.) + if (mToTopApps.size() > 0) { + NN = mAppTokens.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken = mAppTokens.get(i); + if (wtoken.sendingToTop) { + wtoken.sendingToTop = false; + moveAppWindowsLocked(wtoken, NN, false); + } + } + mToTopApps.clear(); + } + + WindowState oldWallpaper = mWallpaperTarget; + + adjustWallpaperWindowsLocked(); + mInnerFields.mWallpaperMayChange = false; + + // The top-most window will supply the layout params, + // and we will determine it below. + LayoutParams animLp = null; + int bestAnimLayer = -1; + boolean fullscreenAnim = false; + + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New wallpaper target=" + mWallpaperTarget + + ", lower target=" + mLowerWallpaperTarget + + ", upper target=" + mUpperWallpaperTarget); + int foundWallpapers = 0; + // Do a first pass through the tokens for two + // things: + // (1) Determine if both the closing and opening + // app token sets are wallpaper targets, in which + // case special animations are needed + // (since the wallpaper needs to stay static + // behind them). + // (2) Find the layout params of the top-most + // application window in the tokens, which is + // what will control the animation theme. + final int NC = mClosingApps.size(); + NN = NC + mOpeningApps.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken; + int mode; + if (i < NC) { + wtoken = mClosingApps.get(i); + mode = 1; + } else { + wtoken = mOpeningApps.get(i-NC); + mode = 2; + } + if (mLowerWallpaperTarget != null) { + if (mLowerWallpaperTarget.mAppToken == wtoken + || mUpperWallpaperTarget.mAppToken == wtoken) { + foundWallpapers |= mode; + } + } + if (wtoken.appFullscreen) { + WindowState ws = wtoken.findMainWindow(); + if (ws != null) { + animLp = ws.mAttrs; + bestAnimLayer = ws.mLayer; + fullscreenAnim = true; + } + } else if (!fullscreenAnim) { + WindowState ws = wtoken.findMainWindow(); + if (ws != null) { + if (ws.mLayer > bestAnimLayer) { + animLp = ws.mAttrs; + bestAnimLayer = ws.mLayer; + } + } + } + } + + if (foundWallpapers == 3) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Wallpaper animation!"); + switch (transit) { + case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_OPEN: + case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN; + break; + case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: + case WindowManagerPolicy.TRANSIT_TASK_CLOSE: + case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE; + break; + } + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New transit: " + transit); + } else if (oldWallpaper != null) { + // We are transitioning from an activity with + // a wallpaper to one without. + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New transit away from wallpaper: " + transit); + } else if (mWallpaperTarget != null) { + // We are transitioning from an activity without + // a wallpaper to now showing the wallpaper + transit = WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN; + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "New transit into wallpaper: " + transit); + } + + // If all closing windows are obscured, then there is + // no need to do an animation. This is the case, for + // example, when this transition is being done behind + // the lock screen. + if (!mPolicy.allowAppAnimationsLw()) { + animLp = null; + } + + AppWindowToken topOpeningApp = null; + int topOpeningLayer = 0; + + // TODO(cmautner): Move to animation side. + NN = mOpeningApps.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken = mOpeningApps.get(i); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken); + wtoken.mAppAnimator.clearThumbnail(); + wtoken.reportedVisible = false; + wtoken.inPendingTransaction = false; + wtoken.mAppAnimator.animation = null; + setTokenVisibilityLocked(wtoken, animLp, true, transit, false); + wtoken.updateReportedVisibilityLocked(); + wtoken.waitingToShow = false; + mAnimator.mAnimating |= wtoken.mAppAnimator.showAllWindowsLocked(); + if (animLp != null) { + int layer = -1; + for (int j=0; j<wtoken.windows.size(); j++) { + WindowState win = wtoken.windows.get(j); + if (win.mWinAnimator.mAnimLayer > layer) { + layer = win.mWinAnimator.mAnimLayer; + } + } + if (topOpeningApp == null || layer > topOpeningLayer) { + topOpeningApp = wtoken; + topOpeningLayer = layer; + } + } + } + NN = mClosingApps.size(); + for (i=0; i<NN; i++) { + AppWindowToken wtoken = mClosingApps.get(i); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Now closing app" + wtoken); + wtoken.mAppAnimator.clearThumbnail(); + wtoken.inPendingTransaction = false; + wtoken.mAppAnimator.animation = null; + setTokenVisibilityLocked(wtoken, animLp, false, + transit, false); + wtoken.updateReportedVisibilityLocked(); + wtoken.waitingToHide = false; + // Force the allDrawn flag, because we want to start + // this guy's animations regardless of whether it's + // gotten drawn. + wtoken.allDrawn = true; + } + + if (mNextAppTransitionThumbnail != null && topOpeningApp != null + && topOpeningApp.mAppAnimator.animation != null) { + // This thumbnail animation is very special, we need to have + // an extra surface with the thumbnail included with the animation. + 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(), + PixelFormat.TRANSLUCENT, Surface.HIDDEN); + topOpeningApp.mAppAnimator.thumbnail = surface; + if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL " + + surface + ": CREATE"); + Surface drawSurface = new Surface(); + drawSurface.copyFrom(surface); + Canvas c = drawSurface.lockCanvas(dirty); + c.drawBitmap(mNextAppTransitionThumbnail, 0, 0, null); + drawSurface.unlockCanvasAndPost(c); + drawSurface.release(); + topOpeningApp.mAppAnimator.thumbnailLayer = topOpeningLayer; + Animation anim = createThumbnailAnimationLocked(transit, true, true); + topOpeningApp.mAppAnimator.thumbnailAnimation = anim; + anim.restrictDuration(MAX_ANIMATION_DURATION); + anim.scaleCurrentDuration(mTransitionAnimationScale); + topOpeningApp.mAppAnimator.thumbnailX = mNextAppTransitionStartX; + topOpeningApp.mAppAnimator.thumbnailY = mNextAppTransitionStartY; + } catch (Surface.OutOfResourcesException e) { + Slog.e(TAG, "Can't allocate thumbnail surface w=" + dirty.width() + + " h=" + dirty.height(), e); + topOpeningApp.mAppAnimator.clearThumbnail(); + } + } + + mNextAppTransitionType = ActivityOptions.ANIM_NONE; + mNextAppTransitionPackage = null; + mNextAppTransitionThumbnail = null; + if (mNextAppTransitionCallback != null) { + try { + mNextAppTransitionCallback.sendResult(null); + } catch (RemoteException e) { + } + } + + mOpeningApps.clear(); + mClosingApps.clear(); + + // 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 + | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; + mLayoutNeeded = true; + if (!moveInputMethodWindowsIfNeededLocked(true)) { + assignLayersLocked(); + } + updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/); + mFocusMayChange = false; + } + + return changes; + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @return bitmap indicating if another pass through layout must be made. + */ + private int handleAnimatingStoppedAndTransitionLocked() { + int changes = 0; + + mAppTransitionRunning = false; + // Clear information about apps that were moving. + mToBottomApps.clear(); + + rebuildAppWindowListLocked(); + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + mInnerFields.mAdjResult |= ADJUST_WALLPAPER_LAYERS_CHANGED; + moveInputMethodWindowsIfNeededLocked(false); + mInnerFields.mWallpaperMayChange = true; + // Since the window list has been rebuilt, focus might + // have to be recomputed since the actual order of windows + // might have changed again. + mFocusMayChange = true; + + return changes; + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @return bitmap indicating if another pass through layout must be made. + */ + private int animateAwayWallpaperLocked() { + int changes = 0; + WindowState oldWallpaper = mWallpaperTarget; + if (mLowerWallpaperTarget != null + && mLowerWallpaperTarget.mAppToken != null) { + if (DEBUG_WALLPAPER) Slog.v(TAG, + "wallpaperForceHiding changed with lower=" + + mLowerWallpaperTarget); + if (DEBUG_WALLPAPER) Slog.v(TAG, + "hidden=" + mLowerWallpaperTarget.mAppToken.hidden + + " hiddenRequested=" + mLowerWallpaperTarget.mAppToken.hiddenRequested); + if (mLowerWallpaperTarget.mAppToken.hidden) { + // The lower target has become hidden before we + // actually started the animation... let's completely + // re-evaluate everything. + mLowerWallpaperTarget = mUpperWallpaperTarget = null; + changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; + } + } + mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked(); + if (DEBUG_WALLPAPER) Slog.v(TAG, "****** OLD: " + oldWallpaper + + " NEW: " + mWallpaperTarget + + " LOWER: " + mLowerWallpaperTarget); + return changes; + } + + private void updateResizingWindows(final WindowState w) { + final WindowStateAnimator winAnimator = w.mWinAnimator; + if (w.mHasSurface && !w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { + w.mSystemInsetsChanged |= + !w.mLastSystemInsets.equals(w.mSystemInsets); + w.mContentInsetsChanged |= + !w.mLastContentInsets.equals(w.mContentInsets); + w.mVisibleInsetsChanged |= + !w.mLastVisibleInsets.equals(w.mVisibleInsets); + boolean configChanged = + w.mConfiguration != mCurConfiguration + && (w.mConfiguration == null + || mCurConfiguration.diff(w.mConfiguration) != 0); + if (DEBUG_CONFIGURATION && configChanged) { + Slog.v(TAG, "Win " + w + " config changed: " + + mCurConfiguration); + } + if (localLOGV) Slog.v(TAG, "Resizing " + w + + ": configChanged=" + configChanged + + " last=" + w.mLastFrame + " frame=" + w.mFrame); + w.mLastFrame.set(w.mFrame); + if (w.mSystemInsetsChanged + || w.mContentInsetsChanged + || w.mVisibleInsetsChanged + || winAnimator.mSurfaceResized + || configChanged) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) { + Slog.v(TAG, "Resize reasons: " + + " contentInsetsChanged=" + w.mContentInsetsChanged + + " visibleInsetsChanged=" + w.mVisibleInsetsChanged + + " surfaceResized=" + winAnimator.mSurfaceResized + + " configChanged=" + configChanged); + } + + w.mLastSystemInsets.set(w.mSystemInsets); + w.mLastContentInsets.set(w.mContentInsets); + w.mLastVisibleInsets.set(w.mVisibleInsets); + makeWindowFreezingScreenIfNeededLocked(w); + // If the orientation is changing, then we need to + // hold off on unfreezing the display until this + // window has been redrawn; to do that, we need + // to go through the process of getting informed + // by the application when it has finished drawing. + if (w.mOrientationChanging) { + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation start waiting for draw in " + + w + ", surface " + winAnimator.mSurface); + winAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING; + if (w.mAppToken != null) { + w.mAppToken.allDrawn = false; + } + } + if (!mResizingWindows.contains(w)) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, + "Resizing window " + w + " to " + winAnimator.mSurfaceW + + "x" + winAnimator.mSurfaceH); + mResizingWindows.add(w); + } + } else if (w.mOrientationChanging) { + if (w.isDrawnLw()) { + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation not waiting for draw in " + + w + ", surface " + winAnimator.mSurface); + w.mOrientationChanging = false; + } + } + } + } + + /** + * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method. + * + * @param w WindowState this method is applied to. + * @param currentTime The time which animations use for calculating transitions. + * @param innerDw Width of app window. + * @param innerDh Height of app window. + */ + private void handleNotObscuredLocked(final WindowState w, final long currentTime, + final int innerDw, final int innerDh) { + final WindowManager.LayoutParams attrs = w.mAttrs; + final int attrFlags = attrs.flags; + final boolean canBeSeen = w.isDisplayedLw(); + + if (w.mHasSurface) { + if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) { + mInnerFields.mHoldScreen = w.mSession; + } + if (!mInnerFields.mSyswin && w.mAttrs.screenBrightness >= 0 + && mInnerFields.mScreenBrightness < 0) { + mInnerFields.mScreenBrightness = w.mAttrs.screenBrightness; + } + if (!mInnerFields.mSyswin && w.mAttrs.buttonBrightness >= 0 + && mInnerFields.mButtonBrightness < 0) { + mInnerFields.mButtonBrightness = w.mAttrs.buttonBrightness; + } + if (canBeSeen + && (attrs.type == WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG + || attrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD + || attrs.type == WindowManager.LayoutParams.TYPE_SYSTEM_ERROR)) { + mInnerFields.mSyswin = true; + } + } + + boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); + if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) { + // This window completely covers everything behind it, + // so we want to leave all of them as undimmed (for + // performance reasons). + mInnerFields.mObscured = true; + } else if (canBeSeen && (attrFlags & FLAG_DIM_BEHIND) != 0 + && !(w.mAppToken != null && w.mAppToken.hiddenRequested)) { + if (localLOGV) Slog.v(TAG, "Win " + w + " obscured=" + mInnerFields.mObscured); + if (!mInnerFields.mDimming) { + //Slog.i(TAG, "DIM BEHIND: " + w); + mInnerFields.mDimming = true; + if (!mAnimator.isDimming()) { + final int width, height; + if (attrs.type == WindowManager.LayoutParams.TYPE_BOOT_PROGRESS) { + width = mCurDisplayWidth; + height = mCurDisplayHeight; + } else { + width = innerDw; + height = innerDh; + } + mAnimator.startDimming(w.mWinAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount, + width, height); + } + } + } + } + // "Something has changed! Let's make it correct now." 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; @@ -7553,7 +8245,7 @@ public class WindowManagerService extends IWindowManager.Stub updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); } - + // Initialize state of exiting tokens. for (i=mExitingTokens.size()-1; i>=0; i--) { mExitingTokens.get(i).hasVisible = false; @@ -7564,29 +8256,15 @@ public class WindowManagerService extends IWindowManager.Stub mExitingAppTokens.get(i).hasVisible = false; } - boolean orientationChangeComplete = true; - Session holdScreen = null; - float screenBrightness = -1; - float buttonBrightness = -1; - boolean focusDisplayed = false; - boolean animating = false; - boolean createWatermark = false; - boolean updateRotation = false; - boolean screenRotationFinished = false; - - if (mFxSession == null) { - mFxSession = new SurfaceSession(); - createWatermark = true; - } + mInnerFields.mHoldScreen = null; + mInnerFields.mScreenBrightness = -1; + mInnerFields.mButtonBrightness = -1; if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces"); Surface.openTransaction(); - if (createWatermark) { - createWatermark(); - } if (mWatermark != null) { mWatermark.positionSurface(dw, dh); } @@ -7595,10 +8273,8 @@ public class WindowManagerService extends IWindowManager.Stub } try { - boolean wallpaperForceHidingChanged = false; int repeats = 0; - int changes = 0; - + do { repeats++; if (repeats > 6) { @@ -7606,1107 +8282,213 @@ public class WindowManagerService extends IWindowManager.Stub mLayoutNeeded = false; break; } - - if ((changes&(WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER - | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG - | WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT)) != 0) { - if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { - if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { - assignLayersLocked(); - mLayoutNeeded = true; - } - } - if ((changes&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 ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { - mLayoutNeeded = true; - } - } - - // FIRST LOOP: Perform a layout, if needed. - if (repeats < 4) { - changes = performLayoutLockedInner(repeats == 0, false /*updateInputWindows*/); - if (changes != 0) { - continue; - } - } else { - Slog.w(TAG, "Layout repeat skipped after too many iterations"); - changes = 0; - } - - final int transactionSequence = ++mTransactionSequence; - - // Update animations of all applications, including those - // associated with exiting/removed apps - boolean tokensAnimating = false; - final int NAT = mAppTokens.size(); - for (i=0; i<NAT; i++) { - if (mAppTokens.get(i).stepAnimationLocked(currentTime, - innerDw, innerDh)) { - tokensAnimating = true; - } - } - final int NEAT = mExitingAppTokens.size(); - for (i=0; i<NEAT; i++) { - if (mExitingAppTokens.get(i).stepAnimationLocked(currentTime, - innerDw, innerDh)) { - tokensAnimating = true; - } - } - - // SECOND LOOP: Execute animations and update visibility of windows. - - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: seq=" - + transactionSequence + " tokensAnimating=" - + tokensAnimating); - - animating = tokensAnimating; - - if (mScreenRotationAnimation != null) { - if (mScreenRotationAnimation.isAnimating()) { - if (mScreenRotationAnimation.stepAnimation(currentTime)) { - animating = true; - } else { - screenRotationFinished = true; - updateRotation = true; - } - } - } - - boolean tokenMayBeDrawn = false; - boolean wallpaperMayChange = false; - boolean forceHiding = false; - WindowState windowDetachedWallpaper = null; - WindowState windowAnimationBackground = null; - int windowAnimationBackgroundColor = 0; - mPolicy.beginAnimationLw(dw, dh); - - final int N = mWindows.size(); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner", + mPendingLayoutChanges); - for (i=N-1; i>=0; i--) { - WindowState w = mWindows.get(i); - - final WindowManager.LayoutParams attrs = w.mAttrs; - - if (w.mSurface != null) { - // Take care of the window being ready to display. - if (w.commitFinishDrawingLocked(currentTime)) { - if ((w.mAttrs.flags - & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "First draw done in potential wallpaper target " + w); - wallpaperMayChange = true; - } - } - - final boolean wasAnimating = w.mAnimating; - - int animDw = innerDw; - int animDh = innerDh; - - // If the window has moved due to its containing - // content frame changing, then we'd like to animate - // it. The checks here are ordered by what is least - // likely to be true first. - if (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); - w.setAnimation(a); - animDw = w.mLastFrame.left - w.mFrame.left; - animDh = w.mLastFrame.top - w.mFrame.top; - } - - // Execute animation. - final boolean nowAnimating = w.stepAnimationLocked(currentTime, - animDw, animDh); - - // 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 (w.mAnimation != null) { - if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 - && w.mAnimation.getDetachWallpaper()) { - windowDetachedWallpaper = w; - } - if (w.mAnimation.getBackgroundColor() != 0) { - if (windowAnimationBackground == null || w.mAnimLayer < - windowAnimationBackground.mAnimLayer) { - windowAnimationBackground = w; - windowAnimationBackgroundColor = - w.mAnimation.getBackgroundColor(); - } - } - } - animating = 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. - if (w.mAppToken != null && w.mAppToken.animation != null - && w.mAppToken.animating) { - if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 - && w.mAppToken.animation.getDetachWallpaper()) { - windowDetachedWallpaper = w; - } - if (w.mAppToken.animation.getBackgroundColor() != 0) { - if (windowAnimationBackground == null || w.mAnimLayer < - windowAnimationBackground.mAnimLayer) { - windowAnimationBackground = w; - windowAnimationBackgroundColor = - w.mAppToken.animation.getBackgroundColor(); - } - } - } - - if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) { - wallpaperMayChange = true; - } - - if (mPolicy.doesForceHide(w, attrs)) { - if (!wasAnimating && nowAnimating) { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "Animation started that could impact force hide: " - + w); - wallpaperForceHidingChanged = true; - mFocusMayChange = true; - } else if (w.isReadyForDisplay() && w.mAnimation == null) { - forceHiding = true; - } - } else if (mPolicy.canBeForceHidden(w, attrs)) { - boolean changed; - if (forceHiding) { - changed = w.hideLw(false, false); - if (DEBUG_VISIBILITY && changed) Slog.v(TAG, - "Now policy hidden: " + w); - } else { - changed = w.showLw(false, false); - if (DEBUG_VISIBILITY && changed) Slog.v(TAG, - "Now policy shown: " + w); - if (changed) { - if (wallpaperForceHidingChanged - && w.isVisibleNow() /*w.isReadyForDisplay()*/) { - // Assume we will need to animate. If - // we don't (because the wallpaper will - // stay with the lock screen), then we will - // clean up later. - Animation a = mPolicy.createForceHideEnterAnimation(); - if (a != null) { - w.setAnimation(a); - } - } - if (mCurrentFocus == null || - mCurrentFocus.mLayer < w.mLayer) { - // We are showing on to of the current - // focus, so re-evaluate focus to make - // sure it is correct. - mFocusMayChange = true; - } - } - } - if (changed && (attrs.flags - & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { - wallpaperMayChange = true; - } - } - - mPolicy.animatingWindowLw(w, attrs); - } - - final AppWindowToken atoken = w.mAppToken; - if (atoken != null && (!atoken.allDrawn || atoken.freezingScreen)) { - if (atoken.lastTransactionSequence != transactionSequence) { - atoken.lastTransactionSequence = transactionSequence; - atoken.numInterestingWindows = atoken.numDrawnWindows = 0; - atoken.startingDisplayed = false; - } - if ((w.isOnScreen() || w.mAttrs.type - == WindowManager.LayoutParams.TYPE_BASE_APPLICATION) - && !w.mExiting && !w.mDestroying) { - if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { - Slog.v(TAG, "Eval win " + w + ": isDrawn=" - + w.isDrawnLw() - + ", isAnimating=" + w.isAnimating()); - if (!w.isDrawnLw()) { - Slog.v(TAG, "Not displayed: s=" + w.mSurface - + " pv=" + w.mPolicyVisibility - + " dp=" + w.mDrawPending - + " cdp=" + w.mCommitDrawPending - + " ah=" + w.mAttachedHidden - + " th=" + atoken.hiddenRequested - + " a=" + w.mAnimating); - } - } - if (w != atoken.startingWindow) { - if (!atoken.freezingScreen || !w.mAppFreezing) { - atoken.numInterestingWindows++; - if (w.isDrawnLw()) { - atoken.numDrawnWindows++; - if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, - "tokenMayBeDrawn: " + atoken - + " freezingScreen=" + atoken.freezingScreen - + " mAppFreezing=" + w.mAppFreezing); - tokenMayBeDrawn = true; - } - } - } else if (w.isDrawnLw()) { - atoken.startingDisplayed = true; - } - } - } else if (w.mReadyToShow) { - w.performShowLocked(); - } - } - - changes |= mPolicy.finishAnimationLw(); - - if (tokenMayBeDrawn) { - // See if any windows have been drawn, so they (and others - // associated with them) can now be shown. - final int NT = mAppTokens.size(); - for (i=0; i<NT; i++) { - AppWindowToken wtoken = mAppTokens.get(i); - if (wtoken.freezingScreen) { - int numInteresting = wtoken.numInterestingWindows; - if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "allDrawn: " + wtoken - + " interesting=" + numInteresting - + " drawn=" + wtoken.numDrawnWindows); - wtoken.showAllWindowsLocked(); - unsetAppFreezingScreenLocked(wtoken, false, true); - if (DEBUG_ORIENTATION) Slog.i(TAG, - "Setting orientationChangeComplete=true because wtoken " - + wtoken + " numInteresting=" + numInteresting - + " numDrawn=" + wtoken.numDrawnWindows); - orientationChangeComplete = true; - } - } else if (!wtoken.allDrawn) { - int numInteresting = wtoken.numInterestingWindows; - if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "allDrawn: " + wtoken - + " interesting=" + numInteresting - + " drawn=" + wtoken.numDrawnWindows); - wtoken.allDrawn = true; - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; - - // We can now show all of the drawn windows! - if (!mOpeningApps.contains(wtoken)) { - wtoken.showAllWindowsLocked(); - } - } - } - } - } - - // 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) { - int NN = mOpeningApps.size(); - boolean goodToGo = true; - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Checking " + NN + " opening apps (frozen=" - + mDisplayFrozen + " timeout=" - + mAppTransitionTimeout + ")..."); - if (!mDisplayFrozen && !mAppTransitionTimeout) { - // If the display isn't frozen, wait to do anything until - // all of the apps are ready. Otherwise just go because - // we'll unfreeze the display when everyone is ready. - for (i=0; i<NN && goodToGo; i++) { - AppWindowToken wtoken = mOpeningApps.get(i); - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Check opening app" + wtoken + ": allDrawn=" - + wtoken.allDrawn + " startingDisplayed=" - + wtoken.startingDisplayed); - if (!wtoken.allDrawn && !wtoken.startingDisplayed - && !wtoken.startingMoved) { - goodToGo = false; - } - } - } - if (goodToGo) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO"); - int transit = mNextAppTransition; - if (mSkipAppTransitionAnimation) { - transit = WindowManagerPolicy.TRANSIT_UNSET; - } - mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; - mAppTransitionReady = false; - mAppTransitionRunning = true; - mAppTransitionTimeout = false; - mStartingIconInTransition = false; - mSkipAppTransitionAnimation = false; - - mH.removeMessages(H.APP_TRANSITION_TIMEOUT); - - // If there are applications waiting to come to the - // top of the stack, now is the time to move their windows. - // (Note that we don't do apps going to the bottom - // here -- we want to keep their windows in the old - // Z-order until the animation completes.) - if (mToTopApps.size() > 0) { - NN = mAppTokens.size(); - for (i=0; i<NN; i++) { - AppWindowToken wtoken = mAppTokens.get(i); - if (wtoken.sendingToTop) { - wtoken.sendingToTop = false; - moveAppWindowsLocked(wtoken, NN, false); - } - } - mToTopApps.clear(); - } - - WindowState oldWallpaper = mWallpaperTarget; - - adjustWallpaperWindowsLocked(); - wallpaperMayChange = false; - - // The top-most window will supply the layout params, - // and we will determine it below. - LayoutParams animLp = null; - int bestAnimLayer = -1; - boolean fullscreenAnim = false; - - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New wallpaper target=" + mWallpaperTarget - + ", lower target=" + mLowerWallpaperTarget - + ", upper target=" + mUpperWallpaperTarget); - int foundWallpapers = 0; - // Do a first pass through the tokens for two - // things: - // (1) Determine if both the closing and opening - // app token sets are wallpaper targets, in which - // case special animations are needed - // (since the wallpaper needs to stay static - // behind them). - // (2) Find the layout params of the top-most - // application window in the tokens, which is - // what will control the animation theme. - final int NC = mClosingApps.size(); - NN = NC + mOpeningApps.size(); - for (i=0; i<NN; i++) { - AppWindowToken wtoken; - int mode; - if (i < NC) { - wtoken = mClosingApps.get(i); - mode = 1; - } else { - wtoken = mOpeningApps.get(i-NC); - mode = 2; - } - if (mLowerWallpaperTarget != null) { - if (mLowerWallpaperTarget.mAppToken == wtoken - || mUpperWallpaperTarget.mAppToken == wtoken) { - foundWallpapers |= mode; - } - } - if (wtoken.appFullscreen) { - WindowState ws = wtoken.findMainWindow(); - if (ws != null) { - animLp = ws.mAttrs; - bestAnimLayer = ws.mLayer; - fullscreenAnim = true; - } - } else if (!fullscreenAnim) { - WindowState ws = wtoken.findMainWindow(); - if (ws != null) { - if (ws.mLayer > bestAnimLayer) { - animLp = ws.mAttrs; - bestAnimLayer = ws.mLayer; - } - } - } - } - - if (foundWallpapers == 3) { - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Wallpaper animation!"); - switch (transit) { - case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: - case WindowManagerPolicy.TRANSIT_TASK_OPEN: - case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: - transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN; - break; - case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: - case WindowManagerPolicy.TRANSIT_TASK_CLOSE: - case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: - transit = WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE; - break; - } - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New transit: " + transit); - } else if (oldWallpaper != null) { - // We are transitioning from an activity with - // a wallpaper to one without. - transit = WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE; - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New transit away from wallpaper: " + transit); - } else if (mWallpaperTarget != null) { - // We are transitioning from an activity without - // a wallpaper to now showing the wallpaper - transit = WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN; - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "New transit into wallpaper: " + transit); - } - - // If all closing windows are obscured, then there is - // no need to do an animation. This is the case, for - // example, when this transition is being done behind - // the lock screen. - if (!mPolicy.allowAppAnimationsLw()) { - animLp = null; - } - - NN = mOpeningApps.size(); - for (i=0; i<NN; i++) { - AppWindowToken wtoken = mOpeningApps.get(i); - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Now opening app" + wtoken); - wtoken.reportedVisible = false; - wtoken.inPendingTransaction = false; - wtoken.animation = null; - setTokenVisibilityLocked(wtoken, animLp, true, - transit, false); - wtoken.updateReportedVisibilityLocked(); - wtoken.waitingToShow = false; - wtoken.showAllWindowsLocked(); - } - NN = mClosingApps.size(); - for (i=0; i<NN; i++) { - AppWindowToken wtoken = mClosingApps.get(i); - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Now closing app" + wtoken); - wtoken.inPendingTransaction = false; - wtoken.animation = null; - setTokenVisibilityLocked(wtoken, animLp, false, - transit, false); - wtoken.updateReportedVisibilityLocked(); - wtoken.waitingToHide = false; - // Force the allDrawn flag, because we want to start - // this guy's animations regardless of whether it's - // gotten drawn. - wtoken.allDrawn = true; - } - - mNextAppTransitionPackage = null; - - mOpeningApps.clear(); - mClosingApps.clear(); - - // 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 - | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; + if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { + if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { + assignLayersLocked(); mLayoutNeeded = true; - if (!moveInputMethodWindowsIfNeededLocked(true)) { - assignLayersLocked(); - } - updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, - false /*updateInputWindows*/); - mFocusMayChange = false; - } - } - - int adjResult = 0; - - if (!animating && mAppTransitionRunning) { - // We have finished the animation of an app transition. To do - // this, we have delayed a lot of operations like showing and - // hiding apps, moving apps in Z-order, etc. The app token list - // 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! - mAppTransitionRunning = false; - // Clear information about apps that were moving. - mToBottomApps.clear(); - - rebuildAppWindowListLocked(); - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - adjResult |= ADJUST_WALLPAPER_LAYERS_CHANGED; - moveInputMethodWindowsIfNeededLocked(false); - wallpaperMayChange = true; - // Since the window list has been rebuilt, focus might - // have to be recomputed since the actual order of windows - // might have changed again. - mFocusMayChange = true; - } - - if (wallpaperForceHidingChanged && changes == 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. - WindowState oldWallpaper = mWallpaperTarget; - if (mLowerWallpaperTarget != null - && mLowerWallpaperTarget.mAppToken != null) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "wallpaperForceHiding changed with lower=" - + mLowerWallpaperTarget); - if (DEBUG_WALLPAPER) Slog.v(TAG, - "hidden=" + mLowerWallpaperTarget.mAppToken.hidden + - " hiddenRequested=" + mLowerWallpaperTarget.mAppToken.hiddenRequested); - if (mLowerWallpaperTarget.mAppToken.hidden) { - // The lower target has become hidden before we - // actually started the animation... let's completely - // re-evaluate everything. - mLowerWallpaperTarget = mUpperWallpaperTarget = null; - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; - } - } - adjResult |= adjustWallpaperWindowsLocked(); - wallpaperMayChange = false; - wallpaperForceHidingChanged = false; - if (DEBUG_WALLPAPER) Slog.v(TAG, "****** OLD: " + oldWallpaper - + " NEW: " + mWallpaperTarget - + " LOWER: " + mLowerWallpaperTarget); - if (mLowerWallpaperTarget == null) { - // Whoops, we don't need a special wallpaper animation. - // Clear them out. - forceHiding = false; - for (i=N-1; i>=0; i--) { - WindowState w = mWindows.get(i); - if (w.mSurface != null) { - final WindowManager.LayoutParams attrs = w.mAttrs; - if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) { - if (DEBUG_FOCUS) Slog.i(TAG, "win=" + w + " force hides other windows"); - forceHiding = true; - } else if (mPolicy.canBeForceHidden(w, attrs)) { - if (!w.mAnimating) { - // We set the animation above so it - // is not yet running. - w.clearAnimation(); - } - } - } - } } } - if (mWindowDetachedWallpaper != windowDetachedWallpaper) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Detached wallpaper changed from " + mWindowDetachedWallpaper - + " to " + windowDetachedWallpaper); - mWindowDetachedWallpaper = windowDetachedWallpaper; - wallpaperMayChange = true; - } - - 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. - WindowState target = windowAnimationBackground; - if (mWallpaperTarget == windowAnimationBackground - || mLowerWallpaperTarget == windowAnimationBackground - || mUpperWallpaperTarget == windowAnimationBackground) { - for (i=0; i<mWindows.size(); i++) { - WindowState w = mWindows.get(i); - if (w.mIsWallpaper) { - target = w; - break; - } - } - } - if (mWindowAnimationBackgroundSurface == null) { - mWindowAnimationBackgroundSurface = new DimSurface(mFxSession); + 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); } - mWindowAnimationBackgroundSurface.show(dw, dh, - target.mAnimLayer - LAYER_OFFSET_DIM, - windowAnimationBackgroundColor); - } else if (mWindowAnimationBackgroundSurface != null) { - mWindowAnimationBackgroundSurface.hide(); } - if (wallpaperMayChange) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Wallpaper may change! Adjusting"); - adjResult |= adjustWallpaperWindowsLocked(); + if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) { + mLayoutNeeded = true; } - if ((adjResult&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Wallpaper layer changed: assigning layers + relayout"); - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - assignLayersLocked(); - } else if ((adjResult&ADJUST_WALLPAPER_VISIBILITY_CHANGED) != 0) { - if (DEBUG_WALLPAPER) Slog.v(TAG, - "Wallpaper visibility changed: relayout"); - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + // 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 (mFocusMayChange) { - mFocusMayChange = false; - if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, - false /*updateInputWindows*/)) { - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; - adjResult = 0; + // 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); } } + mPendingLayoutChanges |= mPolicy.finishAnimationLw(); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after finishAnimationLw", + mPendingLayoutChanges); + } while (mPendingLayoutChanges != 0); - if (mLayoutNeeded) { - changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - } - - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x" - + Integer.toHexString(changes)); - } while (changes != 0); - - // THIRD LOOP: Update the surfaces of all windows. - - final boolean someoneLosingFocus = mLosingFocus.size() != 0; + final boolean someoneLosingFocus = !mLosingFocus.isEmpty(); - boolean obscured = false; - boolean blurring = false; - boolean dimming = false; - boolean covered = false; - boolean syswin = false; + mInnerFields.mObscured = false; + mInnerFields.mDimming = false; + mInnerFields.mSyswin = false; + boolean focusDisplayed = false; final int N = mWindows.size(); - for (i=N-1; i>=0; i--) { WindowState w = mWindows.get(i); - boolean displayed = false; - final WindowManager.LayoutParams attrs = w.mAttrs; - final int attrFlags = attrs.flags; - - if (w.mSurface != null) { - // XXX NOTE: The logic here could be improved. We have - // the decision about whether to resize a window separated - // from whether to hide the surface. This can cause us to - // resize a surface even if we are going to hide it. You - // can see this by (1) holding device in landscape mode on - // home screen; (2) tapping browser icon (device will rotate - // to landscape; (3) tap home. The wallpaper will be resized - // in step 2 but then immediately hidden, causing us to - // have to resize and then redraw it again in step 3. It - // would be nice to figure out how to avoid this, but it is - // difficult because we do need to resize surfaces in some - // cases while they are hidden such as when first showing a - // window. - - w.computeShownFrameLocked(); - if (localLOGV) Slog.v( - TAG, "Placing surface #" + i + " " + w.mSurface - + ": new=" + w.mShownFrame); - - if (w.mSurface != null) { - int width, height; - if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) { - // for a scaled surface, we just want to use - // the requested size. - width = w.mRequestedWidth; - height = w.mRequestedHeight; - } else { - width = w.mCompatFrame.width(); - height = w.mCompatFrame.height(); - } - - if (width < 1) { - width = 1; - } - if (height < 1) { - height = 1; - } - final boolean surfaceResized = w.mSurfaceW != width || w.mSurfaceH != height; - if (surfaceResized) { - w.mSurfaceW = width; - w.mSurfaceH = height; - } - - if (w.mSurfaceX != w.mShownFrame.left - || w.mSurfaceY != w.mShownFrame.top) { - try { - if (SHOW_TRANSACTIONS) logSurface(w, - "POS " + w.mShownFrame.left - + ", " + w.mShownFrame.top, null); - w.mSurfaceX = w.mShownFrame.left; - w.mSurfaceY = w.mShownFrame.top; - w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top); - } catch (RuntimeException e) { - Slog.w(TAG, "Error positioning surface of " + w - + " pos=(" + w.mShownFrame.left - + "," + w.mShownFrame.top + ")", e); - if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "position", true); - } - } - } + final boolean obscuredChanged = w.mObscured != mInnerFields.mObscured; - if (surfaceResized) { - try { - if (SHOW_TRANSACTIONS) logSurface(w, - "SIZE " + width + "x" + height, null); - w.mSurfaceResized = true; - w.mSurface.setSize(width, height); - } catch (RuntimeException e) { - // If something goes wrong with the surface (such - // as running out of memory), don't take down the - // entire system. - Slog.e(TAG, "Error resizing surface of " + w - + " size=(" + width + "x" + height + ")", e); - if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "size", true); - } - } - } - } - - if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { - w.mContentInsetsChanged |= - !w.mLastContentInsets.equals(w.mContentInsets); - w.mVisibleInsetsChanged |= - !w.mLastVisibleInsets.equals(w.mVisibleInsets); - boolean configChanged = - w.mConfiguration != mCurConfiguration - && (w.mConfiguration == null - || mCurConfiguration.diff(w.mConfiguration) != 0); - if (DEBUG_CONFIGURATION && configChanged) { - Slog.v(TAG, "Win " + w + " config changed: " - + mCurConfiguration); - } - if (localLOGV) Slog.v(TAG, "Resizing " + w - + ": configChanged=" + configChanged - + " last=" + w.mLastFrame + " frame=" + w.mFrame); - w.mLastFrame.set(w.mFrame); - if (w.mContentInsetsChanged - || w.mVisibleInsetsChanged - || w.mSurfaceResized - || configChanged) { - if (DEBUG_RESIZE || DEBUG_ORIENTATION) { - Slog.v(TAG, "Resize reasons: " - + " contentInsetsChanged=" + w.mContentInsetsChanged - + " visibleInsetsChanged=" + w.mVisibleInsetsChanged - + " surfaceResized=" + w.mSurfaceResized - + " configChanged=" + configChanged); - } - - w.mLastContentInsets.set(w.mContentInsets); - w.mLastVisibleInsets.set(w.mVisibleInsets); - makeWindowFreezingScreenIfNeededLocked(w); - // If the orientation is changing, then we need to - // hold off on unfreezing the display until this - // window has been redrawn; to do that, we need - // to go through the process of getting informed - // by the application when it has finished drawing. - if (w.mOrientationChanging) { - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation start waiting for draw in " - + w + ", surface " + w.mSurface); - w.mDrawPending = true; - w.mCommitDrawPending = false; - w.mReadyToShow = false; - if (w.mAppToken != null) { - w.mAppToken.allDrawn = false; - } - } - if (!mResizingWindows.contains(w)) { - if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, - "Resizing window " + w + " to " + w.mSurfaceW - + "x" + w.mSurfaceH); - mResizingWindows.add(w); - } - } else if (w.mOrientationChanging) { - if (!w.mDrawPending && !w.mCommitDrawPending) { - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation not waiting for draw in " - + w + ", surface " + w.mSurface); - w.mOrientationChanging = false; - } - } - } - - if (w.mAttachedHidden || !w.isReadyForDisplay()) { - if (!w.mLastHidden) { - //dump(); - w.mLastHidden = true; - if (SHOW_TRANSACTIONS) logSurface(w, - "HIDE (performLayout)", null); - if (w.mSurface != null) { - w.mSurfaceShown = false; - try { - w.mSurface.hide(); - } catch (RuntimeException e) { - Slog.w(TAG, "Exception hiding surface in " + w); - } - } - } - // If we are waiting for this window to handle an - // orientation change, well, it is hidden, so - // doesn't really matter. Note that this does - // introduce a potential glitch if the window - // becomes unhidden before it has drawn for the - // new orientation. - if (w.mOrientationChanging) { - w.mOrientationChanging = false; - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation change skips hidden " + w); - } - } else if (w.mLastLayer != w.mAnimLayer - || w.mLastAlpha != w.mShownAlpha - || w.mLastDsDx != w.mDsDx - || w.mLastDtDx != w.mDtDx - || w.mLastDsDy != w.mDsDy - || w.mLastDtDy != w.mDtDy - || w.mLastHScale != w.mHScale - || w.mLastVScale != w.mVScale - || w.mLastHidden) { - displayed = true; - w.mLastAlpha = w.mShownAlpha; - w.mLastLayer = w.mAnimLayer; - w.mLastDsDx = w.mDsDx; - w.mLastDtDx = w.mDtDx; - w.mLastDsDy = w.mDsDy; - w.mLastDtDy = w.mDtDy; - w.mLastHScale = w.mHScale; - w.mLastVScale = w.mVScale; - if (SHOW_TRANSACTIONS) logSurface(w, - "alpha=" + w.mShownAlpha + " layer=" + w.mAnimLayer - + " matrix=[" + (w.mDsDx*w.mHScale) - + "," + (w.mDtDx*w.mVScale) - + "][" + (w.mDsDy*w.mHScale) - + "," + (w.mDtDy*w.mVScale) + "]", null); - if (w.mSurface != null) { - try { - w.mSurfaceAlpha = w.mShownAlpha; - w.mSurface.setAlpha(w.mShownAlpha); - w.mSurfaceLayer = w.mAnimLayer; - w.mSurface.setLayer(w.mAnimLayer); - w.mSurface.setMatrix( - w.mDsDx*w.mHScale, w.mDtDx*w.mVScale, - w.mDsDy*w.mHScale, w.mDtDy*w.mVScale); - } catch (RuntimeException e) { - Slog.w(TAG, "Error updating surface in " + w, e); - if (!recoveringMemory) { - reclaimSomeSurfaceMemoryLocked(w, "update", true); - } - } - } - - if (w.mLastHidden && !w.mDrawPending - && !w.mCommitDrawPending - && !w.mReadyToShow) { - if (SHOW_TRANSACTIONS) logSurface(w, - "SHOW (performLayout)", null); - if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w - + " during relayout"); - if (showSurfaceRobustlyLocked(w)) { - w.mHasDrawn = true; - w.mLastHidden = false; - } else { - w.mOrientationChanging = false; - } - } - if (w.mSurface != null) { - w.mToken.hasVisible = true; - } - } else { - displayed = true; - } - - if (displayed) { - if (!covered) { - if (attrs.width == LayoutParams.MATCH_PARENT - && attrs.height == LayoutParams.MATCH_PARENT) { - covered = true; - } - } - if (w.mOrientationChanging) { - if (w.mDrawPending || w.mCommitDrawPending) { - orientationChangeComplete = false; - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation continue waiting for draw in " + w); - } else { - w.mOrientationChanging = false; - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation change complete in " + w); - } - } - w.mToken.hasVisible = true; - } - } else if (w.mOrientationChanging) { - if (DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation change skips hidden " + w); - w.mOrientationChanging = false; - } - - if (w.mContentChanged) { - //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); - w.mContentChanged = false; + // Update effect. + w.mObscured = mInnerFields.mObscured; + if (!mInnerFields.mObscured) { + handleNotObscuredLocked(w, currentTime, innerDw, innerDh); } - final boolean canBeSeen = w.isDisplayedLw(); - - if (someoneLosingFocus && w == mCurrentFocus && canBeSeen) { - focusDisplayed = true; + if (obscuredChanged && mWallpaperTarget == w) { + // This is the wallpaper target and its obscured state + // changed... make sure the current wallaper's visibility + // has been updated accordingly. + updateWallpaperVisibilityLocked(); } - final boolean obscuredChanged = w.mObscured != obscured; - - // Update effect. - if (!(w.mObscured=obscured)) { - if (w.mSurface != null) { - if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) { - holdScreen = w.mSession; - } - if (!syswin && w.mAttrs.screenBrightness >= 0 - && screenBrightness < 0) { - screenBrightness = w.mAttrs.screenBrightness; - } - if (!syswin && w.mAttrs.buttonBrightness >= 0 - && buttonBrightness < 0) { - buttonBrightness = w.mAttrs.buttonBrightness; - } - if (canBeSeen - && (attrs.type == WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG - || attrs.type == WindowManager.LayoutParams.TYPE_KEYGUARD - || attrs.type == WindowManager.LayoutParams.TYPE_SYSTEM_ERROR)) { - syswin = true; - } - } - - boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); - if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) { - // This window completely covers everything behind it, - // so we want to leave all of them as unblurred (for - // performance reasons). - obscured = true; - } else if (canBeSeen && !obscured && - (attrFlags&FLAG_BLUR_BEHIND|FLAG_DIM_BEHIND) != 0) { - if (localLOGV) Slog.v(TAG, "Win " + w - + ": blurring=" + blurring - + " obscured=" + obscured - + " displayed=" + displayed); - if ((attrFlags&FLAG_DIM_BEHIND) != 0) { - if (!dimming) { - //Slog.i(TAG, "DIM BEHIND: " + w); - dimming = true; - if (mDimAnimator == null) { - mDimAnimator = new DimAnimator(mFxSession); - } - if (attrs.type == WindowManager.LayoutParams.TYPE_BOOT_PROGRESS) { - mDimAnimator.show(dw, dh); - } else { - mDimAnimator.show(innerDw, innerDh); - } - mDimAnimator.updateParameters(mContext.getResources(), - w, currentTime); - } - } - if ((attrFlags&FLAG_BLUR_BEHIND) != 0) { - if (!blurring) { - //Slog.i(TAG, "BLUR BEHIND: " + w); - blurring = true; - if (mBlurSurface == null) { - try { - mBlurSurface = new Surface(mFxSession, 0, - "BlurSurface", - -1, 16, 16, - PixelFormat.OPAQUE, - Surface.FX_SURFACE_BLUR); - } catch (Exception e) { - Slog.e(TAG, "Exception creating Blur surface", e); - } - if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " - + mBlurSurface + ": CREATE"); - } - if (mBlurSurface != null) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " - + mBlurSurface + ": pos=(0,0) (" + - dw + "x" + dh + "), layer=" + (w.mAnimLayer-1)); - mBlurSurface.setPosition(0, 0); - mBlurSurface.setSize(dw, dh); - mBlurSurface.setLayer(w.mAnimLayer-LAYER_OFFSET_BLUR); - if (!mBlurShown) { - try { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " - + mBlurSurface + ": SHOW"); - mBlurSurface.show(); - } catch (RuntimeException e) { - Slog.w(TAG, "Failure showing blur surface", e); - } - mBlurShown = true; - } - } + 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; + } else { + winAnimator.mAnimDw = innerDw; + winAnimator.mAnimDh = innerDh; + } + + //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing"); + w.mContentChanged = false; + + // 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); } } } + + winAnimator.setSurfaceBoundaries(recoveringMemory); } - if (obscuredChanged && mWallpaperTarget == w) { - // This is the wallpaper target and its obscured state - // changed... make sure the current wallaper's visibility - // has been updated accordingly. - updateWallpaperVisibilityLocked(); + if (someoneLosingFocus && w == mCurrentFocus && w.isDisplayedLw()) { + focusDisplayed = true; } - } - if (mDimAnimator != null && mDimAnimator.mDimShown) { - animating |= mDimAnimator.updateSurface(dimming, currentTime, - mDisplayFrozen || !mDisplayEnabled || !mPolicy.isScreenOnFully()); + updateResizingWindows(w); } - if (!blurring && mBlurShown) { - if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR " + mBlurSurface - + ": HIDE"); - try { - mBlurSurface.hide(); - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Illegal argument exception hiding blur surface"); - } - mBlurShown = false; + if (focusDisplayed) { + mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS); } - if (mBlackFrame != null) { - if (mScreenRotationAnimation != null) { - mBlackFrame.setMatrix( - mScreenRotationAnimation.getEnterTransformation().getMatrix()); - } else { - mBlackFrame.clearMatrix(); - } + if (!mInnerFields.mDimming && mAnimator.isDimming()) { + mAnimator.stopDimming(); } } catch (RuntimeException e) { Log.wtf(TAG, "Unhandled exception in Window Manager", e); + } finally { + Surface.closeTransaction(); } - Surface.closeTransaction(); + // 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(); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked", + mPendingLayoutChanges); + } - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); + mInnerFields.mAdjResult = 0; - if (mWatermark != null) { - mWatermark.drawIfNeeded(); + if (!mAnimator.mAnimating && mAppTransitionRunning) { + // We have finished the animation of an app transition. To do + // this, we have delayed a lot of operations like showing and + // hiding apps, moving apps in Z-order, etc. The app token list + // 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(); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAnimStopAndXitionLock", + mPendingLayoutChanges); } - if (DEBUG_ORIENTATION && mDisplayFrozen) Slog.v(TAG, - "With display frozen, orientationChangeComplete=" - + orientationChangeComplete); - if (orientationChangeComplete) { - if (mWindowsFreezingScreen) { - mWindowsFreezingScreen = false; - mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); + if (mInnerFields.mWallpaperForceHidingChanged && mPendingLayoutChanges == 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(); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animateAwayWallpaperLocked", + mPendingLayoutChanges); + } + mInnerFields.mWallpaperForceHidingChanged = false; + + if (mInnerFields.mWallpaperMayChange) { + if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG, + "Wallpaper may change! Adjusting"); + mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked(); + } + + 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(); + } 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; + } + + if (mFocusMayChange) { + mFocusMayChange = false; + if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, + false /*updateInputWindows*/)) { + mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; + mInnerFields.mAdjResult = 0; } - stopFreezingDisplayLocked(); } - i = mResizingWindows.size(); - if (i > 0) { - do { - i--; + if (mLayoutNeeded) { + mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("mLayoutNeeded", mPendingLayoutChanges); + } + + if (!mResizingWindows.isEmpty()) { + for (i = mResizingWindows.size() - 1; i >= 0; i--) { WindowState win = mResizingWindows.get(i); + final WindowStateAnimator winAnimator = win.mWinAnimator; try { if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, "Reporting new frame to " + win + ": " + win.mCompatFrame); @@ -8718,26 +8500,41 @@ public class WindowManagerService extends IWindowManager.Stub if ((DEBUG_RESIZE || DEBUG_ORIENTATION || DEBUG_CONFIGURATION) && configChanged) { Slog.i(TAG, "Sending new config to window " + win + ": " - + win.mSurfaceW + "x" + win.mSurfaceH + + winAnimator.mSurfaceW + "x" + winAnimator.mSurfaceH + " / " + mCurConfiguration + " / 0x" + Integer.toHexString(diff)); } win.mConfiguration = mCurConfiguration; - if (DEBUG_ORIENTATION && win.mDrawPending) Slog.i( - TAG, "Resizing " + win + " WITH DRAW PENDING"); - win.mClient.resized((int)win.mSurfaceW, (int)win.mSurfaceH, - win.mLastContentInsets, win.mLastVisibleInsets, win.mDrawPending, + 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.mLastSystemInsets, + win.mLastContentInsets, win.mLastVisibleInsets, + winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING, configChanged ? win.mConfiguration : null); + win.mSystemInsetsChanged = false; win.mContentInsetsChanged = false; win.mVisibleInsetsChanged = false; - win.mSurfaceResized = false; + winAnimator.mSurfaceResized = false; } catch (RemoteException e) { win.mOrientationChanging = false; } - } while (i > 0); + } mResizingWindows.clear(); } + if (DEBUG_ORIENTATION && mDisplayFrozen) Slog.v(TAG, + "With display frozen, orientationChangeComplete=" + + mInnerFields.mOrientationChangeComplete); + if (mInnerFields.mOrientationChangeComplete) { + if (mWindowsFreezingScreen) { + mWindowsFreezingScreen = false; + mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); + } + stopFreezingDisplayLocked(); + } + // Destroy the surface of any windows that are no longer visible. boolean wallpaperDestroyed = false; i = mDestroySurface.size(); @@ -8752,7 +8549,7 @@ public class WindowManagerService extends IWindowManager.Stub if (win == mWallpaperTarget) { wallpaperDestroyed = true; } - win.destroySurfaceLocked(); + win.mWinAnimator.destroySurfaceLocked(); } while (i > 0); mDestroySurface.clear(); } @@ -8775,8 +8572,8 @@ public class WindowManagerService extends IWindowManager.Stub // Make sure there is no animation running on this token, // so any windows associated with it will be removed as // soon as their animations are complete - token.animation = null; - token.animating = false; + token.mAppAnimator.clearAnimation(); + token.mAppAnimator.animating = false; if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "performLayout: App token exiting now removed" + token); mAppTokens.remove(token); @@ -8784,9 +8581,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - boolean needRelayout = false; - - if (!animating && mAppTransitionRunning) { + if (!mAnimator.mAnimating && mAppTransitionRunning) { // We have finished the animation of an app transition. To do // this, we have delayed a lot of operations like showing and // hiding apps, moving apps in Z-order, etc. The app token list @@ -8794,47 +8589,51 @@ public class WindowManagerService extends IWindowManager.Stub // be out of sync with it. So here we will just rebuild the // entire app window list. Fun! mAppTransitionRunning = false; - needRelayout = true; + mLayoutNeeded = true; rebuildAppWindowListLocked(); assignLayersLocked(); // Clear information about apps that were moving. mToBottomApps.clear(); } - if (focusDisplayed) { - mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS); + if (!mAnimator.mAnimating && mRelayoutWhileAnimating.size() > 0) { + for (int j=mRelayoutWhileAnimating.size()-1; j>=0; j--) { + try { + mRelayoutWhileAnimating.get(j).mClient.doneAnimating(); + } catch (RemoteException e) { + } + } + mRelayoutWhileAnimating.clear(); } + if (wallpaperDestroyed) { - needRelayout = adjustWallpaperWindowsLocked() != 0; + mLayoutNeeded |= adjustWallpaperWindowsLocked() != 0; } - if (needRelayout) { - requestAnimationLocked(0); - } else if (animating) { - final int refreshTimeUs = (int)(1000 / mDisplay.getRefreshRate()); - requestAnimationLocked(currentTime + refreshTimeUs - SystemClock.uptimeMillis()); + if (mPendingLayoutChanges != 0) { + mLayoutNeeded = true; } // Finally update all input windows now that the window changes have stabilized. mInputMonitor.updateInputWindowsLw(true /*force*/); - setHoldScreenLocked(holdScreen != null); + setHoldScreenLocked(mInnerFields.mHoldScreen != null); if (!mDisplayFrozen) { - if (screenBrightness < 0 || screenBrightness > 1.0f) { + if (mInnerFields.mScreenBrightness < 0 || mInnerFields.mScreenBrightness > 1.0f) { mPowerManager.setScreenBrightnessOverride(-1); } else { mPowerManager.setScreenBrightnessOverride((int) - (screenBrightness * Power.BRIGHTNESS_ON)); + (mInnerFields.mScreenBrightness * Power.BRIGHTNESS_ON)); } - if (buttonBrightness < 0 || buttonBrightness > 1.0f) { + if (mInnerFields.mButtonBrightness < 0 || mInnerFields.mButtonBrightness > 1.0f) { mPowerManager.setButtonBrightnessOverride(-1); } else { mPowerManager.setButtonBrightnessOverride((int) - (buttonBrightness * Power.BRIGHTNESS_ON)); + (mInnerFields.mButtonBrightness * Power.BRIGHTNESS_ON)); } } - if (holdScreen != mHoldingScreenOn) { - mHoldingScreenOn = holdScreen; - Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen); + if (mInnerFields.mHoldScreen != mHoldingScreenOn) { + mHoldingScreenOn = mInnerFields.mHoldScreen; + Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, mInnerFields.mHoldScreen); mH.sendMessage(m); } @@ -8844,29 +8643,32 @@ public class WindowManagerService extends IWindowManager.Stub LocalPowerManager.BUTTON_EVENT, true); mTurnOnScreen = false; } - - if (screenRotationFinished && mScreenRotationAnimation != null) { - mScreenRotationAnimation.kill(); - mScreenRotationAnimation = null; - } - if (updateRotation) { + if (mInnerFields.mUpdateRotation) { if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation"); - boolean changed = updateRotationUncheckedLocked(false); - if (changed) { + if (updateRotationUncheckedLocked(false)) { mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION); } else { - updateRotation = false; + mInnerFields.mUpdateRotation = false; } } - if (orientationChangeComplete && !needRelayout && !updateRotation) { + if (mInnerFields.mOrientationChangeComplete && !mLayoutNeeded && + !mInnerFields.mUpdateRotation) { checkDrawnWindowsLocked(); } // 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(); + + if (DEBUG_WINDOW_TRACE) { + Slog.e(TAG, "performLayoutAndPlaceSurfacesLockedInner exit: mPendingLayoutChanges=" + + Integer.toHexString(mPendingLayoutChanges) + " mLayoutNeeded=" + mLayoutNeeded + + " animating=" + mAnimator.mAnimating); + } } void checkDrawnWindowsLocked() { @@ -8887,7 +8689,7 @@ public class WindowManagerService extends IWindowManager.Stub } mWaitingForDrawn.remove(pair); mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, pair); - } else if (win.mSurfaceShown) { + } else if (win.mWinAnimator.mSurfaceShown) { // Window is now drawn (and shown). try { pair.second.sendResult(null); @@ -8930,50 +8732,28 @@ public class WindowManagerService extends IWindowManager.Stub } } - void requestAnimationLocked(long delay) { - if (!mAnimationPending) { - mAnimationPending = true; - mH.sendMessageDelayed(mH.obtainMessage(H.ANIMATE), delay); + void requestTraversalLocked() { + if (!mTraversalScheduled) { + mTraversalScheduled = true; + mH.sendEmptyMessage(H.DO_TRAVERSAL); } } - /** - * Have the surface flinger show a surface, robustly dealing with - * error conditions. In particular, if there is not enough memory - * to show the surface, then we will try to get rid of other surfaces - * in order to succeed. - * - * @return Returns true if the surface was successfully shown. - */ - boolean showSurfaceRobustlyLocked(WindowState win) { - try { - if (win.mSurface != null) { - win.mSurfaceShown = true; - win.mSurface.show(); - if (win.mTurnOnScreen) { - if (DEBUG_VISIBILITY) Slog.v(TAG, - "Show surface turning screen on: " + win); - win.mTurnOnScreen = false; - mTurnOnScreen = true; - } - } - return true; - } catch (RuntimeException e) { - Slog.w(TAG, "Failure showing surface " + win.mSurface + " in " + win, e); + void scheduleAnimationLocked() { + if (!mAnimationScheduled) { + mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationRunnable, null); + mAnimationScheduled = true; } - - reclaimSomeSurfaceMemoryLocked(win, "show", true); - - return false; } - boolean reclaimSomeSurfaceMemoryLocked(WindowState win, String operation, boolean secure) { - final Surface surface = win.mSurface; + boolean reclaimSomeSurfaceMemoryLocked(WindowStateAnimator winAnimator, String operation, + boolean secure) { + final Surface surface = winAnimator.mSurface; boolean leakedSurface = false; boolean killedApps = false; - EventLog.writeEvent(EventLogTags.WM_NO_SURFACE_MEMORY, win.toString(), - win.mSession.mPid, operation); + EventLog.writeEvent(EventLogTags.WM_NO_SURFACE_MEMORY, winAnimator.mWin.toString(), + winAnimator.mSession.mPid, operation); if (mForceRemoves == null) { mForceRemoves = new ArrayList<WindowState>(); @@ -8988,29 +8768,32 @@ public class WindowManagerService extends IWindowManager.Stub Slog.i(TAG, "Out of memory for surface! Looking for leaks..."); for (int i=0; i<N; i++) { WindowState ws = mWindows.get(i); - if (ws.mSurface != null) { - if (!mSessions.contains(ws.mSession)) { + WindowStateAnimator wsa = ws.mWinAnimator; + if (wsa.mSurface != null) { + if (!mSessions.contains(wsa.mSession)) { Slog.w(TAG, "LEAKED SURFACE (session doesn't exist): " - + ws + " surface=" + ws.mSurface - + " token=" + win.mToken + + ws + " surface=" + wsa.mSurface + + " token=" + ws.mToken + " pid=" + ws.mSession.mPid + " uid=" + ws.mSession.mUid); if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null); - ws.mSurface.destroy(); - ws.mSurfaceShown = false; - ws.mSurface = null; + wsa.mSurface.destroy(); + wsa.mSurfaceShown = false; + 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): " - + ws + " surface=" + ws.mSurface - + " token=" + win.mAppToken); + + ws + " surface=" + wsa.mSurface + + " token=" + ws.mAppToken); if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null); - ws.mSurface.destroy(); - ws.mSurfaceShown = false; - ws.mSurface = null; + wsa.mSurface.destroy(); + wsa.mSurfaceShown = false; + wsa.mSurface = null; + ws.mHasSurface = false; leakedSurface = true; } } @@ -9020,9 +8803,9 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "No leaked surfaces; killing applicatons!"); SparseIntArray pidCandidates = new SparseIntArray(); for (int i=0; i<N; i++) { - WindowState ws = mWindows.get(i); - if (ws.mSurface != null) { - pidCandidates.append(ws.mSession.mPid, ws.mSession.mPid); + WindowStateAnimator wsa = mWindows.get(i).mWinAnimator; + if (wsa.mSurface != null) { + pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid); } } if (pidCandidates.size() > 0) { @@ -9044,15 +8827,16 @@ public class WindowManagerService extends IWindowManager.Stub // surface and ask the app to request another one. Slog.w(TAG, "Looks like we have reclaimed some memory, clearing surface for retry."); if (surface != null) { - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) logSurface(win, + if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) logSurface(winAnimator.mWin, "RECOVER DESTROY", null); surface.destroy(); - win.mSurfaceShown = false; - win.mSurface = null; + winAnimator.mSurfaceShown = false; + winAnimator.mSurface = null; + winAnimator.mWin.mHasSurface = false; } try { - win.mClient.dispatchGetNewSurface(); + winAnimator.mWin.mClient.dispatchGetNewSurface(); } catch (RemoteException e) { } } @@ -9066,6 +8850,7 @@ public class WindowManagerService extends IWindowManager.Stub private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { WindowState newFocus = computeFocusedWindowLocked(); if (mCurrentFocus != newFocus) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus"); // This check makes sure that we don't already have the focus // change message pending. mH.removeMessages(H.REPORT_FOCUS_CHANGE); @@ -9074,6 +8859,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Changing focus from " + mCurrentFocus + " to " + newFocus); final WindowState oldFocus = mCurrentFocus; mCurrentFocus = newFocus; + mAnimator.setCurrentFocus(newFocus); mLosingFocus.remove(newFocus); int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus); @@ -9107,6 +8893,8 @@ public class WindowManagerService extends IWindowManager.Stub // doing this part. finishUpdateFocusedWindowAfterAssignLayersLocked(updateInputWindows); } + + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); return true; } return false; @@ -9120,12 +8908,11 @@ public class WindowManagerService extends IWindowManager.Stub WindowState result = null; WindowState win; - int i = mWindows.size() - 1; int nextAppIndex = mAppTokens.size()-1; WindowToken nextApp = nextAppIndex >= 0 ? mAppTokens.get(nextAppIndex) : null; - while (i >= 0) { + for (int i = mWindows.size() - 1; i >= 0; i--) { win = mWindows.get(i); if (localLOGV || DEBUG_FOCUS) Slog.v( @@ -9138,7 +8925,6 @@ public class WindowManagerService extends IWindowManager.Stub // If this window's application has been removed, just skip it. if (thisApp != null && thisApp.removed) { - i--; continue; } @@ -9178,8 +8964,6 @@ public class WindowManagerService extends IWindowManager.Stub result = win; break; } - - i--; } return result; @@ -9199,12 +8983,14 @@ public class WindowManagerService extends IWindowManager.Stub mScreenFrozenLock.acquire(); mDisplayFrozen = true; - + mInputMonitor.freezeInputDispatchingLw(); - + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; + mNextAppTransitionType = ActivityOptions.ANIM_NONE; mNextAppTransitionPackage = null; + mNextAppTransitionThumbnail = null; mAppTransitionReady = true; } @@ -9214,16 +9000,16 @@ public class WindowManagerService extends IWindowManager.Stub } if (CUSTOM_SCREEN_ROTATION) { - if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) { - mScreenRotationAnimation.kill(); - mScreenRotationAnimation = null; + if (mAnimator.mScreenRotationAnimation != null) { + mAnimator.mScreenRotationAnimation.kill(); + mAnimator.mScreenRotationAnimation = null; } - if (mScreenRotationAnimation == null) { - mScreenRotationAnimation = new ScreenRotationAnimation(mContext, - mFxSession, inTransaction, mCurDisplayWidth, mCurDisplayHeight, - mDisplay.getRotation()); - } - if (!mScreenRotationAnimation.hasScreenshot()) { + + mAnimator.mScreenRotationAnimation = new ScreenRotationAnimation(mContext, + mFxSession, inTransaction, mCurDisplayWidth, mCurDisplayHeight, + mDisplay.getRotation()); + + if (!mAnimator.mScreenRotationAnimation.hasScreenshot()) { Surface.freezeDisplay(0); } } else { @@ -9237,6 +9023,10 @@ public class WindowManagerService extends IWindowManager.Stub } if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen) { + if (DEBUG_ORIENTATION) Slog.d(TAG, + "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig + + ", mAppsFreezingScreen=" + mAppsFreezingScreen + + ", mWindowsFreezingScreen=" + mWindowsFreezingScreen); return; } @@ -9248,20 +9038,21 @@ public class WindowManagerService extends IWindowManager.Stub boolean updateRotation = false; - if (CUSTOM_SCREEN_ROTATION && mScreenRotationAnimation != null - && mScreenRotationAnimation.hasScreenshot()) { + if (CUSTOM_SCREEN_ROTATION && mAnimator.mScreenRotationAnimation != null + && mAnimator.mScreenRotationAnimation.hasScreenshot()) { if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation"); - if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, + if (mAnimator.mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) { - requestAnimationLocked(0); + scheduleAnimationLocked(); } else { - mScreenRotationAnimation = null; + mAnimator.mScreenRotationAnimation.kill(); + mAnimator.mScreenRotationAnimation = null; updateRotation = true; } } else { - if (mScreenRotationAnimation != null) { - mScreenRotationAnimation.kill(); - mScreenRotationAnimation = null; + if (mAnimator.mScreenRotationAnimation != null) { + mAnimator.mScreenRotationAnimation.kill(); + mAnimator.mScreenRotationAnimation = null; } updateRotation = true; } @@ -9399,11 +9190,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler, + public FakeWindow addFakeWindow(Looper looper, + InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { synchronized (mWindowMap) { - FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputHandler, name, windowType, + FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputEventReceiverFactory, + name, windowType, layoutParamsFlags, canReceiveKeys, hasFocus, touchFullscreen); int i=0; while (i<mFakeWindows.size()) { @@ -9436,11 +9229,6 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.lockNow(); } - void dumpInput(FileDescriptor fd, PrintWriter pw, boolean dumpAll) { - pw.println("WINDOW MANAGER INPUT (dumpsys window input)"); - mInputManager.dump(pw); - } - void dumpPolicyLocked(FileDescriptor fd, PrintWriter pw, String[] args, boolean dumpAll) { pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)"); mPolicy.dump(" ", fd, pw, args); @@ -9481,8 +9269,8 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(); pw.println(" Application tokens in Z order:"); for (int i=mAppTokens.size()-1; i>=0; i--) { - pw.print(" App #"); pw.print(i); pw.print(": "); - pw.println(mAppTokens.get(i)); + pw.print(" App #"); pw.print(i); pw.println(": "); + mAppTokens.get(i).dump(pw, " "); } } if (mFinishedStarting.size() > 0) { @@ -9673,14 +9461,25 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(); if (mDisplay != null) { pw.print(" Display: init="); pw.print(mInitialDisplayWidth); pw.print("x"); - pw.print(mInitialDisplayHeight); pw.print(" base="); - pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight); + 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(" raw="); pw.print(mDisplay.getRawWidth()); - pw.print("x"); pw.println(mDisplay.getRawHeight()); + pw.print(" rng="); pw.print(mSmallestDisplayWidth); + pw.print("x"); pw.print(mSmallestDisplayHeight); + pw.print("-"); pw.print(mLargestDisplayWidth); + pw.print("x"); pw.println(mLargestDisplayHeight); } else { pw.println(" NO DISPLAY"); } @@ -9708,9 +9507,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mLowerWallpaperTarget="); pw.println(mLowerWallpaperTarget); pw.print(" mUpperWallpaperTarget="); pw.println(mUpperWallpaperTarget); } - if (mWindowDetachedWallpaper != null) { - pw.print(" mWindowDetachedWallpaper="); pw.println(mWindowDetachedWallpaper); - } pw.print(" mLastWallpaperX="); pw.print(mLastWallpaperX); pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY); if (mInputMethodAnimLayerAdjustment != 0 || @@ -9720,44 +9516,58 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mWallpaperAnimLayerAdjustment="); pw.println(mWallpaperAnimLayerAdjustment); } - if (mWindowAnimationBackgroundSurface != null) { - pw.println(" mWindowAnimationBackgroundSurface:"); - mWindowAnimationBackgroundSurface.printTo(" ", pw); - } pw.print(" mSystemBooted="); pw.print(mSystemBooted); pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled); - pw.print(" mLayoutNeeded="); pw.print(mLayoutNeeded); - pw.print(" mBlurShown="); pw.println(mBlurShown); - if (mDimAnimator != null) { - pw.println(" mDimAnimator:"); - mDimAnimator.printTo(" ", pw); - } else { - pw.println( " no DimAnimator "); - } + pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded); 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(" mRotation="); pw.print(mRotation); pw.print(" mAltOrientation="); pw.println(mAltOrientation); - pw.print(" mLastWindowForcedOrientation"); pw.print(mLastWindowForcedOrientation); + pw.print(" mLastWindowForcedOrientation="); pw.print(mLastWindowForcedOrientation); pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation); pw.print(" mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount); - pw.print(" mAnimationPending="); pw.print(mAnimationPending); - pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); - pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale); - pw.print(" mNextAppTransition=0x"); + 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); + pw.print(" mTraversalScheduled="); pw.print(mTraversalScheduled); + pw.print(" mNextAppTransition=0x"); pw.print(Integer.toHexString(mNextAppTransition)); pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady); pw.print(" mAppTransitionRunning="); pw.print(mAppTransitionRunning); - pw.print(" mAppTransitionTimeout="); pw.println( mAppTransitionTimeout); - if (mNextAppTransitionPackage != null) { - pw.print(" mNextAppTransitionPackage="); - pw.print(mNextAppTransitionPackage); - pw.print(" mNextAppTransitionEnter=0x"); - pw.print(Integer.toHexString(mNextAppTransitionEnter)); - pw.print(" mNextAppTransitionExit=0x"); - pw.print(Integer.toHexString(mNextAppTransitionExit)); + pw.print(" mAppTransitionTimeout="); pw.println(mAppTransitionTimeout); + if (mNextAppTransitionType != ActivityOptions.ANIM_NONE) { + pw.print(" mNextAppTransitionType="); pw.println(mNextAppTransitionType); + } + switch (mNextAppTransitionType) { + case ActivityOptions.ANIM_CUSTOM: + pw.print(" mNextAppTransitionPackage="); + pw.print(mNextAppTransitionPackage); + pw.print(" mNextAppTransitionEnter=0x"); + pw.print(Integer.toHexString(mNextAppTransitionEnter)); + pw.print(" mNextAppTransitionExit=0x"); + pw.print(Integer.toHexString(mNextAppTransitionExit)); + break; + case ActivityOptions.ANIM_SCALE_UP: + pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX); + pw.print(" mNextAppTransitionStartY="); + pw.println(mNextAppTransitionStartY); + pw.print(" mNextAppTransitionStartWidth="); + pw.print(mNextAppTransitionStartWidth); + pw.print(" mNextAppTransitionStartHeight="); + pw.println(mNextAppTransitionStartHeight); + break; + case ActivityOptions.ANIM_THUMBNAIL: + pw.print(" mNextAppTransitionThumbnail="); + pw.print(mNextAppTransitionThumbnail); + pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX); + pw.print(" mNextAppTransitionStartY="); pw.println(mNextAppTransitionStartY); + break; } pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition); pw.print(", mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation); @@ -9771,7 +9581,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); - if (w.mSurfaceShown) { + if (w.mWinAnimator.mSurfaceShown) { windows.add(w); } } @@ -9833,7 +9643,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.println("Window manager dump options:"); pw.println(" [-a] [-h] [cmd] ..."); pw.println(" cmd may be one of:"); - pw.println(" i[input]: input subsystem state"); pw.println(" p[policy]: policy state"); pw.println(" s[essions]: active sessions"); pw.println(" t[okens]: token list"); @@ -9854,10 +9663,7 @@ public class WindowManagerService extends IWindowManager.Stub if (opti < args.length) { String cmd = args[opti]; opti++; - if ("input".equals(cmd) || "i".equals(cmd)) { - dumpInput(fd, pw, true); - return; - } else if ("policy".equals(cmd) || "p".equals(cmd)) { + if ("policy".equals(cmd) || "p".equals(cmd)) { synchronized(mWindowMap) { dumpPolicyLocked(fd, pw, args, true); } @@ -9892,8 +9698,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - dumpInput(fd, pw, dumpAll); - synchronized(mWindowMap) { if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); @@ -9926,4 +9730,16 @@ public class WindowManagerService extends IWindowManager.Stub public interface OnHardKeyboardStatusChangeListener { public void onHardKeyboardStatusChange(boolean available, boolean enabled); } + + void debugLayoutRepeats(final String msg, int pendingLayoutChanges) { + if (mLayoutRepeatCount >= LAYOUT_REPEAT_THRESHOLD) { + Slog.v(TAG, "Layouts looping: " + msg + ", mPendingLayoutChanges = 0x" + + Integer.toHexString(pendingLayoutChanges)); + } + } + + void bulkSetParameters(final int bulkUpdateParams, int pendingLayoutChanges) { + mH.sendMessage(mH.obtainMessage(H.BULK_UPDATE_PARAMETERS, bulkUpdateParams, + pendingLayoutChanges)); + } } diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 1067cad..05e7d3a 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -19,13 +19,13 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import com.android.server.wm.WindowManagerService.H; +import com.android.server.input.InputWindowHandle; +import android.content.Context; import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.PixelFormat; @@ -39,14 +39,10 @@ import android.view.Gravity; import android.view.IApplicationToken; import android.view.IWindow; import android.view.InputChannel; -import android.view.Surface; import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerPolicy; -import android.view.WindowManager.LayoutParams; -import android.view.animation.Animation; -import android.view.animation.Transformation; import java.io.PrintWriter; import java.util.ArrayList; @@ -55,18 +51,25 @@ import java.util.ArrayList; * A window in the window manager. */ final class WindowState implements WindowManagerPolicy.WindowState { + static final String TAG = "WindowState"; + static final boolean DEBUG_VISIBILITY = WindowManagerService.DEBUG_VISIBILITY; static final boolean SHOW_TRANSACTIONS = WindowManagerService.SHOW_TRANSACTIONS; static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS; static final boolean SHOW_SURFACE_ALLOC = WindowManagerService.SHOW_SURFACE_ALLOC; final WindowManagerService mService; + final WindowManagerPolicy mPolicy; + final Context mContext; final Session mSession; final IWindow mClient; WindowToken mToken; WindowToken mRootToken; AppWindowToken mAppToken; AppWindowToken mTargetAppToken; + + // mAttrs.flags is tested in animation without being locked. If the bits tested are ever + // modified they will need to be locked. final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams(); final DeathRecipient mDeathRecipient; final WindowState mAttachedWindow; @@ -84,12 +87,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean mPolicyVisibility = true; boolean mPolicyVisibilityAfterAnim = true; boolean mAppFreezing; - Surface mSurface; - Surface mPendingDestroySurface; - boolean mReportDestroySurface; - boolean mSurfacePendingDestroy; boolean mAttachedHidden; // is our parent window hidden? - boolean mLastHidden; // was this window last hidden? boolean mWallpaperVisible; // for wallpaper, what was last vis report? /** @@ -98,18 +96,18 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ int mRequestedWidth; int mRequestedHeight; + int mLastRequestedWidth; + int mLastRequestedHeight; int mLayer; - int mAnimLayer; - int mLastLayer; boolean mHaveFrame; boolean mObscured; boolean mTurnOnScreen; int mLayoutSeq = -1; - + Configuration mConfiguration = null; - + /** * Actual frame shown on-screen (may be modified by animation). These * are in the screen's coordinate space (WITH the compatibility scale @@ -118,18 +116,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { final RectF mShownFrame = new RectF(); /** - * Set when we have changed the size of the surface, to know that - * we must tell them application to resize (and thus redraw itself). - */ - boolean mSurfaceResized; - - /** - * Set if the client has asked that the destroy of its surface be delayed - * until it explicitly says it is okay. - */ - boolean mSurfaceDestroyDeferred; - - /** * Insets that determine the actually visible area. These are in the application's * coordinate space (without compatibility scale applied). */ @@ -138,7 +124,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean mVisibleInsetsChanged; /** - * Insets that are covered by system windows. These are in the application's + * Insets that are covered by system windows (such as the status bar) and + * transient docking windows (such as the IME). These are in the application's * coordinate space (without compatibility scale applied). */ final Rect mContentInsets = new Rect(); @@ -146,6 +133,14 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean mContentInsetsChanged; /** + * Insets that are covered by system windows such as the status bar. These + * are in the application's coordinate space (without compatibility scale applied). + */ + final Rect mSystemInsets = new Rect(); + final Rect mLastSystemInsets = new Rect(); + boolean mSystemInsetsChanged; + + /** * Set to true if we are waiting for this window to receive its * given internal insets before laying out other windows based on it. */ @@ -177,11 +172,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; // Current transformation being applied. - boolean mHaveMatrix; float mGlobalScale=1; float mInvGlobalScale=1; - float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1; - float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1; float mHScale=1, mVScale=1; float mLastHScale=1, mLastVScale=1; final Matrix mTmpMatrix = new Matrix(); @@ -195,29 +187,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { final Rect mContainingFrame = new Rect(); final Rect mDisplayFrame = new Rect(); + final Rect mSystemFrame = new Rect(); final Rect mContentFrame = new Rect(); final Rect mParentFrame = new Rect(); final Rect mVisibleFrame = new Rect(); boolean mContentChanged; - float mShownAlpha = 1; - float mAlpha = 1; - float mLastAlpha = 1; - - // Set to true if, when the window gets displayed, it should perform - // an enter animation. - boolean mEnterAnimationPending; - - // Currently running animation. - boolean mAnimating; - boolean mLocalAnimating; - Animation mAnimation; - boolean mAnimationIsEntrance; - boolean mHasTransformation; - boolean mHasLocalTransformation; - final Transformation mTransformation = new Transformation(); - // If a window showing a wallpaper: the requested offset for the // wallpaper; if a wallpaper window: the currently applied offset. float mWallpaperX = -1; @@ -244,24 +220,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { // when in that case until the layout is done. boolean mLayoutNeeded; - // This is set after the Surface has been created but before the - // window has been drawn. During this time the surface is hidden. - boolean mDrawPending; - - // This is set after the window has finished drawing for the first - // time but before its surface is shown. The surface will be - // displayed when the next layout is run. - boolean mCommitDrawPending; - - // This is set during the time after the window's drawing has been - // committed, and before its surface is actually shown. It is used - // to delay showing the surface until all windows in a token are ready - // to be shown. - boolean mReadyToShow; - - // Set when the window has been shown in the screen the first time. - boolean mHasDrawn; - // Currently running an exit animation? boolean mExiting; @@ -282,12 +240,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { // rebuilding window list. boolean mRebuilding; - // For debugging, this is the last information given to the surface flinger. - boolean mSurfaceShown; - float mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH; - int mSurfaceLayer; - float mSurfaceAlpha; - // Input channel and input window handle used by the input dispatcher. final InputWindowHandle mInputWindowHandle; InputChannel mInputChannel; @@ -297,6 +249,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { CharSequence mLastTitle; boolean mWasPaused; + final WindowStateAnimator mWinAnimator; + + boolean mHasSurface = false; + WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int seq, WindowManager.LayoutParams a, int viewVisibility) { @@ -306,12 +262,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { mToken = token; mAttrs.copyFrom(a); mViewVisibility = viewVisibility; + mPolicy = mService.mPolicy; + mContext = mService.mContext; DeathRecipient deathRecipient = new DeathRecipient(); - mAlpha = a.alpha; mSeq = seq; mEnforceSizeCompat = (mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0; if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Window " + this + " client=" + c.asBinder() + TAG, "Window " + this + " client=" + c.asBinder() + " token=" + token + " (" + mAttrs.token + ")"); try { c.asBinder().linkToDeath(deathRecipient, 0); @@ -325,6 +282,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mBaseLayer = 0; mSubLayer = 0; mInputWindowHandle = null; + mWinAnimator = null; return; } mDeathRecipient = deathRecipient; @@ -333,12 +291,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { mAttrs.type <= LAST_SUB_WINDOW)) { // The multiplier here is to reserve space for multiple // windows in the same type layer. - mBaseLayer = mService.mPolicy.windowTypeToLayerLw( + mBaseLayer = mPolicy.windowTypeToLayerLw( attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; - mSubLayer = mService.mPolicy.subWindowTypeToLayerLw(a.type); + mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type); mAttachedWindow = attachedWindow; - if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(WindowManagerService.TAG, "Adding " + this + " to " + mAttachedWindow); + if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow); mAttachedWindow.mChildWindows.add(this); mLayoutAttached = mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -349,7 +307,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } else { // The multiplier here is to reserve space for multiple // windows in the same type layer. - mBaseLayer = mService.mPolicy.windowTypeToLayerLw(a.type) + mBaseLayer = mPolicy.windowTypeToLayerLw(a.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = 0; @@ -361,9 +319,12 @@ 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 = mAttachedWindow; + appWin = appWin.mAttachedWindow; } WindowToken appToken = appWin.mToken; while (appToken.appWindowToken == null) { @@ -376,26 +337,26 @@ final class WindowState implements WindowManagerPolicy.WindowState { mRootToken = appToken; mAppToken = appToken.appWindowToken; - mSurface = null; mRequestedWidth = 0; mRequestedHeight = 0; + mLastRequestedWidth = 0; + mLastRequestedHeight = 0; mXOffset = 0; mYOffset = 0; mLayer = 0; - mAnimLayer = 0; - mLastLayer = 0; mInputWindowHandle = new InputWindowHandle( mAppToken != null ? mAppToken.mInputApplicationHandle : null, this); } void attach() { if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Attaching " + this + " token=" + mToken + TAG, "Attaching " + this + " token=" + mToken + ", list=" + mToken.windows); mSession.windowAddedLocked(); } - public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) { + @Override + public void computeFrameLw(Rect pf, Rect df, Rect sf, Rect cf, Rect vf) { mHaveFrame = true; final Rect container = mContainingFrame; @@ -446,6 +407,14 @@ final class WindowState implements WindowManagerPolicy.WindowState { mParentFrame.set(pf); mContentChanged = true; } + if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) { + mLastRequestedWidth = mRequestedWidth; + mLastRequestedHeight = mRequestedHeight; + mContentChanged = true; + } + + final Rect system = mSystemFrame; + system.set(sf); final Rect content = mContentFrame; content.set(cf); @@ -478,8 +447,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { // Now make sure the window fits in the overall display. Gravity.applyDisplay(mAttrs.gravity, df, frame); - // Make sure the content and visible frames are inside of the + // Make sure the system, content and visible frames are inside of the // final window frame. + if (system.left < frame.left) system.left = frame.left; + if (system.top < frame.top) system.top = frame.top; + if (system.right > frame.right) system.right = frame.right; + if (system.bottom > frame.bottom) system.bottom = frame.bottom; if (content.left < frame.left) content.left = frame.left; if (content.top < frame.top) content.top = frame.top; if (content.right > frame.right) content.right = frame.right; @@ -489,6 +462,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (visible.right > frame.right) visible.right = frame.right; if (visible.bottom > frame.bottom) visible.bottom = frame.bottom; + final Rect systemInsets = mSystemInsets; + systemInsets.left = system.left-frame.left; + systemInsets.top = system.top-frame.top; + systemInsets.right = frame.right-system.right; + systemInsets.bottom = frame.bottom-system.bottom; + final Rect contentInsets = mContentInsets; contentInsets.left = content.left-frame.left; contentInsets.top = content.top-frame.top; @@ -506,6 +485,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { // If there is a size compatibility scale being applied to the // window, we need to apply this to its insets so that they are // reported to the app in its coordinate space. + systemInsets.scale(mInvGlobalScale); contentInsets.scale(mInvGlobalScale); visibleInsets.scale(mInvGlobalScale); @@ -522,48 +502,63 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (WindowManagerService.localLOGV) { //if ("com.google.android.youtube".equals(mAttrs.packageName) // && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { - Slog.v(WindowManagerService.TAG, "Resolving (mRequestedWidth=" + Slog.v(TAG, "Resolving (mRequestedWidth=" + mRequestedWidth + ", mRequestedheight=" + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph + "): frame=" + mFrame.toShortString() + + " si=" + systemInsets.toShortString() + " ci=" + contentInsets.toShortString() + " vi=" + visibleInsets.toShortString()); //} } } + @Override public Rect getFrameLw() { return mFrame; } + @Override public RectF getShownFrameLw() { return mShownFrame; } + @Override public Rect getDisplayFrameLw() { return mDisplayFrame; } + @Override + public Rect getSystemFrameLw() { + return mSystemFrame; + } + + @Override public Rect getContentFrameLw() { return mContentFrame; } + @Override public Rect getVisibleFrameLw() { return mVisibleFrame; } + @Override public boolean getGivenInsetsPendingLw() { return mGivenInsetsPending; } + @Override public Rect getGivenContentInsetsLw() { return mGivenContentInsets; } + @Override public Rect getGivenVisibleInsetsLw() { return mGivenVisibleInsets; } + @Override public WindowManager.LayoutParams getAttrs() { return mAttrs; } @@ -617,538 +612,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mAppToken != null ? mAppToken.firstWindowDrawn : false; } - public void setAnimation(Animation anim) { - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Setting animation in " + this + ": " + anim); - mAnimating = false; - mLocalAnimating = false; - mAnimation = anim; - mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); - mAnimation.scaleCurrentDuration(mService.mWindowAnimationScale); - } - - public void clearAnimation() { - if (mAnimation != null) { - mAnimating = true; - mLocalAnimating = false; - mAnimation.cancel(); - mAnimation = null; - } - } - - // TODO: Fix and call finishExit() instead of cancelExitAnimationForNextAnimationLocked() - // for avoiding the code duplication. - void cancelExitAnimationForNextAnimationLocked() { - if (!mExiting) return; - if (mAnimation != null) { - mAnimation.cancel(); - mAnimation = null; - destroySurfaceLocked(); - } - mExiting = false; - } - - Surface createSurfaceLocked() { - if (mSurface == null) { - mReportDestroySurface = false; - mSurfacePendingDestroy = false; - if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(WindowManagerService.TAG, - "createSurface " + this + ": DRAW NOW PENDING"); - mDrawPending = true; - mCommitDrawPending = false; - mReadyToShow = false; - if (mAppToken != null) { - mAppToken.allDrawn = false; - } - - mService.makeWindowFreezingScreenIfNeededLocked(this); - - int flags = 0; - - if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { - flags |= Surface.SECURE; - } - if (DEBUG_VISIBILITY) Slog.v( - WindowManagerService.TAG, "Creating surface in session " - + mSession.mSurfaceSession + " window " + this - + " w=" + mCompatFrame.width() - + " h=" + mCompatFrame.height() + " format=" - + mAttrs.format + " flags=" + flags); - - int w = mCompatFrame.width(); - int h = mCompatFrame.height(); - if ((mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { - // for a scaled surface, we always want the requested - // size. - w = mRequestedWidth; - h = mRequestedHeight; - } - - // Something is wrong and SurfaceFlinger will not like this, - // try to revert to sane values - if (w <= 0) w = 1; - if (h <= 0) h = 1; - - mSurfaceShown = false; - mSurfaceLayer = 0; - mSurfaceAlpha = 1; - mSurfaceX = 0; - mSurfaceY = 0; - mSurfaceW = w; - mSurfaceH = h; - try { - final boolean isHwAccelerated = (mAttrs.flags & - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; - final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : mAttrs.format; - if (!PixelFormat.formatHasAlpha(mAttrs.format)) { - flags |= Surface.OPAQUE; - } - mSurface = new Surface( - mSession.mSurfaceSession, mSession.mPid, - mAttrs.getTitle().toString(), - 0, w, h, format, flags); - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, - " CREATE SURFACE " - + mSurface + " IN SESSION " - + mSession.mSurfaceSession - + ": pid=" + mSession.mPid + " format=" - + mAttrs.format + " flags=0x" - + Integer.toHexString(flags) - + " / " + this); - } catch (Surface.OutOfResourcesException e) { - Slog.w(WindowManagerService.TAG, "OutOfResourcesException creating surface"); - mService.reclaimSomeSurfaceMemoryLocked(this, "create", true); - return null; - } catch (Exception e) { - Slog.e(WindowManagerService.TAG, "Exception creating surface", e); - return null; - } - - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Got surface: " + mSurface - + ", set left=" + mFrame.left + " top=" + mFrame.top - + ", animLayer=" + mAnimLayer); - if (SHOW_LIGHT_TRANSACTIONS) { - Slog.i(WindowManagerService.TAG, ">>> OPEN TRANSACTION createSurfaceLocked"); - WindowManagerService.logSurface(this, "CREATE pos=(" + mFrame.left - + "," + mFrame.top + ") (" + - mCompatFrame.width() + "x" + mCompatFrame.height() + "), layer=" + - mAnimLayer + " HIDE", null); - } - Surface.openTransaction(); - try { - try { - mSurfaceX = mFrame.left + mXOffset; - mSurfaceY = mFrame.top + mYOffset; - mSurface.setPosition(mSurfaceX, mSurfaceY); - mSurfaceLayer = mAnimLayer; - mSurface.setLayer(mAnimLayer); - mSurfaceShown = false; - mSurface.hide(); - if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_DITHER) != 0) { - if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(this, "DITHER", null); - mSurface.setFlags(Surface.SURFACE_DITHER, - Surface.SURFACE_DITHER); - } - } catch (RuntimeException e) { - Slog.w(WindowManagerService.TAG, "Error creating surface in " + w, e); - mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true); - } - mLastHidden = true; - } finally { - Surface.closeTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, - "<<< CLOSE TRANSACTION createSurfaceLocked"); - } - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Created surface " + this); - } - return mSurface; - } - - void destroySurfaceLocked() { - if (mAppToken != null && this == mAppToken.startingWindow) { - mAppToken.startingDisplayed = false; - } - - if (mSurface != null) { - mDrawPending = false; - mCommitDrawPending = false; - mReadyToShow = false; - - int i = mChildWindows.size(); - while (i > 0) { - i--; - WindowState c = mChildWindows.get(i); - c.mAttachedHidden = true; - } - - if (mReportDestroySurface) { - mReportDestroySurface = false; - mSurfacePendingDestroy = true; - try { - mClient.dispatchGetNewSurface(); - // We'll really destroy on the next time around. - return; - } catch (RemoteException e) { - } - } - - try { - if (DEBUG_VISIBILITY) { - RuntimeException e = null; - if (!WindowManagerService.HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - Slog.w(WindowManagerService.TAG, "Window " + this + " destroying surface " - + mSurface + ", session " + mSession, e); - } - if (mSurfaceDestroyDeferred) { - if (mSurface != null && mPendingDestroySurface != mSurface) { - if (mPendingDestroySurface != null) { - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { - RuntimeException e = null; - if (!WindowManagerService.HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - WindowManagerService.logSurface(this, "DESTROY PENDING", e); - } - mPendingDestroySurface.destroy(); - } - mPendingDestroySurface = mSurface; - } - } else { - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { - RuntimeException e = null; - if (!WindowManagerService.HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - WindowManagerService.logSurface(this, "DESTROY", e); - } - mSurface.destroy(); - } - } catch (RuntimeException e) { - Slog.w(WindowManagerService.TAG, "Exception thrown when destroying Window " + this - + " surface " + mSurface + " session " + mSession - + ": " + e.toString()); - } - - mSurfaceShown = false; - mSurface = null; - } - } - - void destroyDeferredSurfaceLocked() { - try { - if (mPendingDestroySurface != null) { - if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { - RuntimeException e = null; - if (!WindowManagerService.HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - mService.logSurface(this, "DESTROY PENDING", e); - } - mPendingDestroySurface.destroy(); - } - } catch (RuntimeException e) { - Slog.w(WindowManagerService.TAG, "Exception thrown when destroying Window " - + this + " surface " + mPendingDestroySurface - + " session " + mSession + ": " + e.toString()); - } - mSurfaceDestroyDeferred = false; - mPendingDestroySurface = null; - } - - boolean finishDrawingLocked() { - if (mDrawPending) { - if (SHOW_TRANSACTIONS || WindowManagerService.DEBUG_ORIENTATION) Slog.v( - WindowManagerService.TAG, "finishDrawingLocked: " + this + " in " + mSurface); - mCommitDrawPending = true; - mDrawPending = false; - return true; - } - return false; - } - - // This must be called while inside a transaction. - boolean commitFinishDrawingLocked(long currentTime) { - //Slog.i(TAG, "commitFinishDrawingLocked: " + mSurface); - if (!mCommitDrawPending) { - return false; - } - mCommitDrawPending = false; - mReadyToShow = true; - final boolean starting = mAttrs.type == TYPE_APPLICATION_STARTING; - final AppWindowToken atoken = mAppToken; - if (atoken == null || atoken.allDrawn || starting) { - performShowLocked(); - } - return true; - } - - // This must be called while inside a transaction. - boolean performShowLocked() { - if (DEBUG_VISIBILITY) { - RuntimeException e = null; - if (!WindowManagerService.HIDE_STACK_CRAWLS) { - e = new RuntimeException(); - e.fillInStackTrace(); - } - Slog.v(WindowManagerService.TAG, "performShow on " + this - + ": readyToShow=" + mReadyToShow + " readyForDisplay=" + isReadyForDisplay() - + " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING), e); - } - if (mReadyToShow && isReadyForDisplay()) { - if (SHOW_TRANSACTIONS || WindowManagerService.DEBUG_ORIENTATION) WindowManagerService.logSurface(this, - "SHOW (performShowLocked)", null); - if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Showing " + this - + " during animation: policyVis=" + mPolicyVisibility - + " attHidden=" + mAttachedHidden - + " tok.hiddenRequested=" - + (mAppToken != null ? mAppToken.hiddenRequested : false) - + " tok.hidden=" - + (mAppToken != null ? mAppToken.hidden : false) - + " animating=" + mAnimating - + " tok animating=" - + (mAppToken != null ? mAppToken.animating : false)); - if (!mService.showSurfaceRobustlyLocked(this)) { - return false; - } - mLastAlpha = -1; - mHasDrawn = true; - mLastHidden = false; - mReadyToShow = false; - mService.enableScreenIfNeededLocked(); - - mService.applyEnterAnimationLocked(this); - - int i = mChildWindows.size(); - while (i > 0) { - i--; - WindowState c = mChildWindows.get(i); - if (c.mAttachedHidden) { - c.mAttachedHidden = false; - if (c.mSurface != null) { - c.performShowLocked(); - // It hadn't been shown, which means layout not - // performed on it, so now we want to make sure to - // do a layout. If called from within the transaction - // loop, this will cause it to restart with a new - // layout. - mService.mLayoutNeeded = true; - } - } - } - - if (mAttrs.type != TYPE_APPLICATION_STARTING - && mAppToken != null) { - mAppToken.firstWindowDrawn = true; - - if (mAppToken.startingData != null) { - if (WindowManagerService.DEBUG_STARTING_WINDOW || WindowManagerService.DEBUG_ANIM) Slog.v(WindowManagerService.TAG, - "Finish starting " + mToken - + ": first real window is shown, no animation"); - // If this initial window is animating, stop it -- we - // will do an animation to reveal it from behind the - // starting window, so there is no need for it to also - // be doing its own stuff. - if (mAnimation != null) { - mAnimation.cancel(); - mAnimation = null; - // Make sure we clean up the animation. - mAnimating = true; - } - mService.mFinishedStarting.add(mAppToken); - mService.mH.sendEmptyMessage(H.FINISHED_STARTING); - } - mAppToken.updateReportedVisibilityLocked(); - } - } - return true; - } - - // This must be called while inside a transaction. Returns true if - // there is more animation to run. - boolean stepAnimationLocked(long currentTime, int dw, int dh) { - if (!mService.mDisplayFrozen && mService.mPolicy.isScreenOnFully()) { - // We will run animations as long as the display isn't frozen. - - if (!mDrawPending && !mCommitDrawPending && mAnimation != null) { - mHasTransformation = true; - mHasLocalTransformation = true; - if (!mLocalAnimating) { - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Starting animation in " + this + - " @ " + currentTime + ": ww=" + mFrame.width() + - " wh=" + mFrame.height() + - " dw=" + dw + " dh=" + dh + " scale=" + mService.mWindowAnimationScale); - mAnimation.initialize(mFrame.width(), mFrame.height(), dw, dh); - mAnimation.setStartTime(currentTime); - mLocalAnimating = true; - mAnimating = true; - } - mTransformation.clear(); - final boolean more = mAnimation.getTransformation( - currentTime, mTransformation); - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Stepped animation in " + this + - ": more=" + more + ", xform=" + mTransformation); - if (more) { - // we're not done! - return true; - } - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Finished animation in " + this + - " @ " + currentTime); - - if (mAnimation != null) { - mAnimation.cancel(); - mAnimation = null; - } - //WindowManagerService.this.dump(); - } - mHasLocalTransformation = false; - if ((!mLocalAnimating || mAnimationIsEntrance) && mAppToken != null - && mAppToken.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 - // our window is not currently exiting, or it is not - // locally animating itself. The idea being that one that - // is exiting and doing a local animation should be removed - // once that animation is done. - mAnimating = true; - mHasTransformation = true; - mTransformation.clear(); - return false; - } else if (mHasTransformation) { - // Little trick to get through the path below to act like - // we have finished an animation. - mAnimating = true; - } else if (isAnimating()) { - mAnimating = true; - } - } else if (mAnimation != null) { - // If the display is frozen, and there is a pending animation, - // clear it and make sure we run the cleanup code. - mAnimating = true; - mLocalAnimating = true; - mAnimation.cancel(); - mAnimation = null; - } - - if (!mAnimating && !mLocalAnimating) { - return false; - } - - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Animation done in " + this + ": exiting=" + mExiting - + ", reportedVisible=" - + (mAppToken != null ? mAppToken.reportedVisible : false)); - - mAnimating = false; - mLocalAnimating = false; - if (mAnimation != null) { - mAnimation.cancel(); - mAnimation = null; - } - if (mService.mWindowDetachedWallpaper == this) { - mService.mWindowDetachedWallpaper = null; - } - mAnimLayer = mLayer; - if (mIsImWindow) { - mAnimLayer += mService.mInputMethodAnimLayerAdjustment; - } else if (mIsWallpaper) { - mAnimLayer += mService.mWallpaperAnimLayerAdjustment; - } - if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Stepping win " + this - + " anim layer: " + mAnimLayer); - mHasTransformation = false; - mHasLocalTransformation = false; - if (mPolicyVisibility != mPolicyVisibilityAfterAnim) { - if (DEBUG_VISIBILITY) { - Slog.v(WindowManagerService.TAG, "Policy visibility changing after anim in " + this + ": " - + mPolicyVisibilityAfterAnim); - } - mPolicyVisibility = mPolicyVisibilityAfterAnim; - mService.mLayoutNeeded = true; - if (!mPolicyVisibility) { - if (mService.mCurrentFocus == this) { - mService.mFocusMayChange = true; - } - // Window is no longer visible -- make sure if we were waiting - // for it to be displayed before enabling the display, that - // we allow the display to be enabled now. - mService.enableScreenIfNeededLocked(); - } - } - mTransformation.clear(); - if (mHasDrawn - && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING - && mAppToken != null - && mAppToken.firstWindowDrawn - && mAppToken.startingData != null) { - if (WindowManagerService.DEBUG_STARTING_WINDOW) Slog.v(WindowManagerService.TAG, "Finish starting " - + mToken + ": first real window done animating"); - mService.mFinishedStarting.add(mAppToken); - mService.mH.sendEmptyMessage(H.FINISHED_STARTING); - } - - finishExit(); - - if (mAppToken != null) { - mAppToken.updateReportedVisibilityLocked(); - } - - return false; - } - - void finishExit() { - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "finishExit in " + this - + ": exiting=" + mExiting - + " remove=" + mRemoveOnExit - + " windowAnimating=" + isWindowAnimating()); - - final int N = mChildWindows.size(); - for (int i=0; i<N; i++) { - mChildWindows.get(i).finishExit(); - } - - if (!mExiting) { - return; - } - - if (isWindowAnimating()) { - return; - } - - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Exit animation finished in " + this - + ": remove=" + mRemoveOnExit); - if (mSurface != null) { - mService.mDestroySurface.add(this); - mDestroying = true; - if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(this, "HIDE (finishExit)", null); - mSurfaceShown = false; - try { - mSurface.hide(); - } catch (RuntimeException e) { - Slog.w(WindowManagerService.TAG, "Error hiding surface in " + this, e); - } - mLastHidden = true; - } - mExiting = false; - if (mRemoveOnExit) { - mService.mPendingRemove.add(this); - mRemoveOnExit = false; - } - } - boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { if (dsdx < .99999f || dsdx > 1.00001f) return false; if (dtdy < .99999f || dtdy > 1.00001f) return false; @@ -1166,147 +629,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } - void computeShownFrameLocked() { - final boolean selfTransformation = mHasLocalTransformation; - Transformation attachedTransformation = - (mAttachedWindow != null && mAttachedWindow.mHasLocalTransformation) - ? mAttachedWindow.mTransformation : null; - Transformation appTransformation = - (mAppToken != null && mAppToken.hasTransformation) - ? mAppToken.transformation : null; - - // Wallpapers are animated based on the "real" window they - // are currently targeting. - if (mAttrs.type == TYPE_WALLPAPER && mService.mLowerWallpaperTarget == null - && mService.mWallpaperTarget != null) { - if (mService.mWallpaperTarget.mHasLocalTransformation && - mService.mWallpaperTarget.mAnimation != null && - !mService.mWallpaperTarget.mAnimation.getDetachWallpaper()) { - attachedTransformation = mService.mWallpaperTarget.mTransformation; - if (WindowManagerService.DEBUG_WALLPAPER && attachedTransformation != null) { - Slog.v(WindowManagerService.TAG, "WP target attached xform: " + attachedTransformation); - } - } - if (mService.mWallpaperTarget.mAppToken != null && - mService.mWallpaperTarget.mAppToken.hasTransformation && - mService.mWallpaperTarget.mAppToken.animation != null && - !mService.mWallpaperTarget.mAppToken.animation.getDetachWallpaper()) { - appTransformation = mService.mWallpaperTarget.mAppToken.transformation; - if (WindowManagerService.DEBUG_WALLPAPER && appTransformation != null) { - Slog.v(WindowManagerService.TAG, "WP target app xform: " + appTransformation); - } - } - } - - final boolean screenAnimation = mService.mScreenRotationAnimation != null - && mService.mScreenRotationAnimation.isAnimating(); - if (selfTransformation || attachedTransformation != null - || appTransformation != null || screenAnimation) { - // cache often used attributes locally - final Rect frame = mFrame; - final float tmpFloats[] = mService.mTmpFloats; - final Matrix tmpMatrix = mTmpMatrix; - - // Compute the desired transformation. - if (screenAnimation) { - // If we are doing a screen animation, the global rotation - // applied to windows can result in windows that are carefully - // aligned with each other to slightly separate, allowing you - // to see what is behind them. An unsightly mess. This... - // thing... magically makes it call good: scale each window - // slightly (two pixels larger in each dimension, from the - // window's center). - final float w = frame.width(); - final float h = frame.height(); - if (w>=1 && h>=1) { - tmpMatrix.setScale(1 + 2/w, 1 + 2/h, w/2, h/2); - } else { - tmpMatrix.reset(); - } - } else { - tmpMatrix.reset(); - } - tmpMatrix.postScale(mGlobalScale, mGlobalScale); - if (selfTransformation) { - tmpMatrix.postConcat(mTransformation.getMatrix()); - } - tmpMatrix.postTranslate(frame.left + mXOffset, frame.top + mYOffset); - if (attachedTransformation != null) { - tmpMatrix.postConcat(attachedTransformation.getMatrix()); - } - if (appTransformation != null) { - tmpMatrix.postConcat(appTransformation.getMatrix()); - } - if (screenAnimation) { - tmpMatrix.postConcat( - mService.mScreenRotationAnimation.getEnterTransformation().getMatrix()); - } - - // "convert" it into SurfaceFlinger's format - // (a 2x2 matrix + an offset) - // Here we must not transform the position of the surface - // since it is already included in the transformation. - //Slog.i(TAG, "Transform: " + matrix); - - mHaveMatrix = true; - tmpMatrix.getValues(tmpFloats); - 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(); - mShownFrame.set(x, y, x+w, y+h); - - // Now set the alpha... but because our current hardware - // can't do alpha transformation on a non-opaque surface, - // turn it off if we are running an animation that is also - // transforming since it is more important to have that - // animation be smooth. - mShownAlpha = mAlpha; - if (!mService.mLimitedAlphaCompositing - || (!PixelFormat.formatHasAlpha(mAttrs.format) - || (isIdentityMatrix(mDsDx, mDtDx, mDsDy, mDtDy) - && x == frame.left && y == frame.top))) { - //Slog.i(TAG, "Applying alpha transform"); - if (selfTransformation) { - mShownAlpha *= mTransformation.getAlpha(); - } - if (attachedTransformation != null) { - mShownAlpha *= attachedTransformation.getAlpha(); - } - if (appTransformation != null) { - mShownAlpha *= appTransformation.getAlpha(); - } - if (screenAnimation) { - mShownAlpha *= - mService.mScreenRotationAnimation.getEnterTransformation().getAlpha(); - } - } else { - //Slog.i(TAG, "Not applying alpha transform"); - } - - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Continuing animation in " + this + - ": " + mShownFrame + - ", alpha=" + mTransformation.getAlpha()); - return; - } - - mShownFrame.set(mFrame); - if (mXOffset != 0 || mYOffset != 0) { - mShownFrame.offset(mXOffset, mYOffset); - } - mShownAlpha = mAlpha; - mHaveMatrix = false; - mDsDx = mGlobalScale; - mDtDx = 0; - mDsDy = 0; - mDtDy = mGlobalScale; - } - /** * Is this window visible? It is not visible if there is no * surface, or we are in the process of running an exit animation @@ -1314,7 +636,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ public boolean isVisibleLw() { final AppWindowToken atoken = mAppToken; - return mSurface != null && mPolicyVisibility && !mAttachedHidden + return mHasSurface && mPolicyVisibility && !mAttachedHidden && (atoken == null || !atoken.hiddenRequested) && !mExiting && !mDestroying; } @@ -1334,12 +656,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { } final AppWindowToken atoken = mAppToken; final boolean animating = atoken != null - ? (atoken.animation != null) : false; - return mSurface != null && !mDestroying && !mExiting + ? (atoken.mAppAnimator.animation != null) : false; + return mHasSurface && !mDestroying && !mExiting && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden) - || mAnimation != null || animating); + || mWinAnimator.mAnimation != null || animating); } /** @@ -1349,8 +671,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ public boolean isWinVisibleLw() { final AppWindowToken atoken = mAppToken; - return mSurface != null && mPolicyVisibility && !mAttachedHidden - && (atoken == null || !atoken.hiddenRequested || atoken.animating) + return mHasSurface && mPolicyVisibility && !mAttachedHidden + && (atoken == null || !atoken.hiddenRequested || atoken.mAppAnimator.animating) && !mExiting && !mDestroying; } @@ -1359,7 +681,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { * the associated app token, not the pending requested hidden state. */ boolean isVisibleNow() { - return mSurface != null && mPolicyVisibility && !mAttachedHidden + return mHasSurface && mPolicyVisibility && !mAttachedHidden && !mRootToken.hidden && !mExiting && !mDestroying; } @@ -1379,7 +701,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ boolean isVisibleOrAdding() { final AppWindowToken atoken = mAppToken; - return ((mSurface != null && !mReportDestroySurface) + return ((mHasSurface && !mWinAnimator.mReportDestroySurface) || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) && mPolicyVisibility && !mAttachedHidden && (atoken == null || !atoken.hiddenRequested) @@ -1392,15 +714,15 @@ final class WindowState implements WindowManagerPolicy.WindowState { * being visible. */ boolean isOnScreen() { + if (!mHasSurface || !mPolicyVisibility || mDestroying) { + return false; + } final AppWindowToken atoken = mAppToken; if (atoken != null) { - return mSurface != null && mPolicyVisibility && !mDestroying - && ((!mAttachedHidden && !atoken.hiddenRequested) - || mAnimation != null || atoken.animation != null); - } else { - return mSurface != null && mPolicyVisibility && !mDestroying - && (!mAttachedHidden || mAnimation != null); + return ((!mAttachedHidden && !atoken.hiddenRequested) + || mWinAnimator.mAnimation != null || atoken.mAppAnimator.animation != null); } + return !mAttachedHidden || mWinAnimator.mAnimation != null; } /** @@ -1412,29 +734,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { return false; } - final AppWindowToken atoken = mAppToken; - final boolean animating = atoken != null - ? (atoken.animation != null) : false; - return mSurface != null && mPolicyVisibility && !mDestroying + return mHasSurface && mPolicyVisibility && !mDestroying && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden) - || mAnimation != null || animating); - } - - /** Is the window or its container currently animating? */ - boolean isAnimating() { - final WindowState attached = mAttachedWindow; - final AppWindowToken atoken = mAppToken; - return mAnimation != null - || (attached != null && attached.mAnimation != null) - || (atoken != null && - (atoken.animation != null - || atoken.inPendingTransaction)); - } - - /** Is this window currently animating? */ - boolean isWindowAnimating() { - return mAnimation != null; + || mWinAnimator.mAnimation != null + || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null))); } /** @@ -1443,11 +747,18 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ public boolean isDisplayedLw() { final AppWindowToken atoken = mAppToken; - return mSurface != null && mPolicyVisibility && !mDestroying - && !mDrawPending && !mCommitDrawPending + return isDrawnLw() && mPolicyVisibility && ((!mAttachedHidden && (atoken == null || !atoken.hiddenRequested)) - || mAnimating); + || mWinAnimator.mAnimating); + } + + /** + * Return true if this window (or a window it is attached to, but not + * considering its app token) is currently animating. + */ + public boolean isAnimatingLw() { + return mWinAnimator.mAnimation != null; } public boolean isGoneForLayoutLw() { @@ -1465,9 +776,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { * complete UI in to. */ public boolean isDrawnLw() { - final AppWindowToken atoken = mAppToken; - return mSurface != null && !mDestroying - && !mDrawPending && !mCommitDrawPending; + return mHasSurface && !mDestroying && + (mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW + || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN); } /** @@ -1477,9 +788,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean isOpaqueDrawn() { return (mAttrs.format == PixelFormat.OPAQUE || mAttrs.type == TYPE_WALLPAPER) - && mSurface != null && mAnimation == null - && (mAppToken == null || mAppToken.animation == null) - && !mDrawPending && !mCommitDrawPending; + && isDrawnLw() && mWinAnimator.mAnimation == null + && (mAppToken == null || mAppToken.mAppAnimator.animation == null); } /** @@ -1488,11 +798,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { * sense to call from performLayoutAndPlaceSurfacesLockedInner().) */ boolean shouldAnimateMove() { - return mContentChanged && !mExiting && !mLastHidden && !mService.mDisplayFrozen + return mContentChanged && !mExiting && !mWinAnimator.mLastHidden && mService.okToDisplay() && (mFrame.top != mLastFrame.top || mFrame.left != mLastFrame.left) - && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove()) - && mService.mPolicy.isScreenOnFully(); + && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove()); } boolean isFullscreen(int screenWidth, int screenHeight) { @@ -1504,11 +813,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { disposeInputChannel(); if (mAttachedWindow != null) { - if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(WindowManagerService.TAG, "Removing " + this + " from " + mAttachedWindow); + if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + this + " from " + mAttachedWindow); mAttachedWindow.mChildWindows.remove(this); } - destroyDeferredSurfaceLocked(); - destroySurfaceLocked(); + mWinAnimator.destroyDeferredSurfaceLocked(); + mWinAnimator.destroySurfaceLocked(); mSession.windowRemovedLocked(); try { mClient.asBinder().unlinkToDeath(mDeathRecipient, 0); @@ -1543,7 +852,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { try { synchronized(mService.mWindowMap) { WindowState win = mService.windowForClientLocked(mSession, mClient, false); - Slog.i(WindowManagerService.TAG, "WIN DEATH: " + win); + Slog.i(TAG, "WIN DEATH: " + win); if (win != null) { mService.removeWindowLocked(mSession, win); } @@ -1562,25 +871,28 @@ final class WindowState implements WindowManagerPolicy.WindowState { && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0); } + @Override public boolean hasDrawnLw() { - return mHasDrawn; + return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN; } + @Override public boolean showLw(boolean doAnimation) { return showLw(doAnimation, true); } boolean showLw(boolean doAnimation, boolean requestAnim) { if (mPolicyVisibility && mPolicyVisibilityAfterAnim) { + // Already showing. return false; } - if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Policy visibility true: " + this); + if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this); if (doAnimation) { - if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "doAnimation: mPolicyVisibility=" - + mPolicyVisibility + " mAnimation=" + mAnimation); - if (mService.mDisplayFrozen || !mService.mPolicy.isScreenOnFully()) { + if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility=" + + mPolicyVisibility + " mAnimation=" + mWinAnimator.mAnimation); + if (!mService.okToDisplay()) { doAnimation = false; - } else if (mPolicyVisibility && mAnimation == null) { + } else if (mPolicyVisibility && mWinAnimator.mAnimation == null) { // Check for the case where we are currently visible and // not animating; we do not want to do animation at such a // point to become visible when we already are. @@ -1590,39 +902,41 @@ final class WindowState implements WindowManagerPolicy.WindowState { mPolicyVisibility = true; mPolicyVisibilityAfterAnim = true; if (doAnimation) { - mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); + mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true); } if (requestAnim) { - mService.requestAnimationLocked(0); + mService.scheduleAnimationLocked(); } return true; } + @Override public boolean hideLw(boolean doAnimation) { return hideLw(doAnimation, true); } boolean hideLw(boolean doAnimation, boolean requestAnim) { if (doAnimation) { - if (mService.mDisplayFrozen || !mService.mPolicy.isScreenOnFully()) { + if (!mService.okToDisplay()) { doAnimation = false; } } boolean current = doAnimation ? mPolicyVisibilityAfterAnim : mPolicyVisibility; if (!current) { + // Already hiding. return false; } if (doAnimation) { - mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_EXIT, false); - if (mAnimation == null) { + mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false); + if (mWinAnimator.mAnimation == null) { doAnimation = false; } } if (doAnimation) { mPolicyVisibilityAfterAnim = false; } else { - if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Policy visibility false: " + this); + if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this); mPolicyVisibilityAfterAnim = false; mPolicyVisibility = false; // Window is no longer visible -- make sure if we were waiting @@ -1634,11 +948,16 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } if (requestAnim) { - mService.requestAnimationLocked(0); + mService.scheduleAnimationLocked(); } return true; } + @Override + public boolean isAlive() { + return mClient.asBinder().isBinderAlive(); + } + private static void applyInsets(Region outRegion, Rect frame, Rect inset) { outRegion.set( frame.left + inset.left, frame.top + inset.top, @@ -1674,6 +993,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth); pw.print(" h="); pw.print(mRequestedHeight); pw.print(" mLayoutSeq="); pw.println(mLayoutSeq); + if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) { + pw.print(prefix); pw.print("LastRequested w="); pw.print(mLastRequestedWidth); + pw.print(" h="); pw.println(mLastRequestedHeight); + } if (mAttachedWindow != null || mLayoutAttached) { pw.print(prefix); pw.print("mAttachedWindow="); pw.print(mAttachedWindow); pw.print(" mLayoutAttached="); pw.println(mLayoutAttached); @@ -1688,26 +1011,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); pw.print(" mSubLayer="); pw.print(mSubLayer); pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+"); - pw.print((mTargetAppToken != null ? mTargetAppToken.animLayerAdjustment - : (mAppToken != null ? mAppToken.animLayerAdjustment : 0))); - pw.print("="); pw.print(mAnimLayer); - pw.print(" mLastLayer="); pw.println(mLastLayer); - } - if (mSurface != null) { - if (dumpAll) { - pw.print(prefix); pw.print("mSurface="); pw.println(mSurface); - } - pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown); - pw.print(" layer="); pw.print(mSurfaceLayer); - pw.print(" alpha="); pw.print(mSurfaceAlpha); - pw.print(" rect=("); pw.print(mSurfaceX); - pw.print(","); pw.print(mSurfaceY); - pw.print(") "); pw.print(mSurfaceW); - pw.print(" x "); pw.println(mSurfaceH); - } - if (mPendingDestroySurface != null) { - pw.print(prefix); pw.print("mPendingDestroySurface="); - pw.println(mPendingDestroySurface); + pw.print((mTargetAppToken != null ? + mTargetAppToken.mAppAnimator.animLayerAdjustment + : (mAppToken != null ? mAppToken.mAppAnimator.animLayerAdjustment : 0))); + pw.print("="); pw.print(mWinAnimator.mAnimLayer); + pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer); } if (dumpAll) { pw.print(prefix); pw.print("mToken="); pw.println(mToken); @@ -1720,7 +1028,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { } pw.print(prefix); pw.print("mViewVisibility=0x"); pw.print(Integer.toHexString(mViewVisibility)); - pw.print(" mLastHidden="); pw.print(mLastHidden); pw.print(" mHaveFrame="); pw.print(mHaveFrame); pw.print(" mObscured="); pw.println(mObscured); pw.print(prefix); pw.print("mSeq="); pw.print(mSeq); @@ -1738,10 +1045,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled); pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded); } - if (mSurfaceResized || mSurfaceDestroyDeferred) { - pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized); - pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred); - } if (mXOffset != 0 || mYOffset != 0) { pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset); pw.print(" y="); pw.println(mYOffset); @@ -1758,8 +1061,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { } pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration); } - pw.print(prefix); pw.print("mShownFrame="); - mShownFrame.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface); + pw.print(" mShownFrame="); mShownFrame.printShortString(pw); pw.println(); if (dumpAll) { pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw); pw.print(" last="); mLastFrame.printShortString(pw); @@ -1770,54 +1073,25 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.println(); } if (dumpAll) { - pw.print(prefix); pw.print("mContainingFrame="); + pw.print(prefix); pw.print("Frames: containing="); mContainingFrame.printShortString(pw); - pw.print(" mParentFrame="); - mParentFrame.printShortString(pw); - pw.print(" mDisplayFrame="); - mDisplayFrame.printShortString(pw); + pw.print(" parent="); mParentFrame.printShortString(pw); + pw.print(" display="); mDisplayFrame.printShortString(pw); pw.println(); - pw.print(prefix); pw.print("mContentFrame="); mContentFrame.printShortString(pw); - pw.print(" mVisibleFrame="); mVisibleFrame.printShortString(pw); + pw.print(prefix); pw.print(" system="); mSystemFrame.printShortString(pw); + pw.print(" content="); mContentFrame.printShortString(pw); + pw.print(" visible="); mVisibleFrame.printShortString(pw); pw.println(); - pw.print(prefix); pw.print("mContentInsets="); mContentInsets.printShortString(pw); - pw.print(" last="); mLastContentInsets.printShortString(pw); - pw.print(" mVisibleInsets="); mVisibleInsets.printShortString(pw); - pw.print(" last="); mLastVisibleInsets.printShortString(pw); + pw.print(prefix); pw.print("Cur insets: system="); mSystemInsets.printShortString(pw); + pw.print(" content="); mContentInsets.printShortString(pw);; + pw.print(" visible="); mVisibleInsets.printShortString(pw); pw.println(); - } - if (mAnimating || mLocalAnimating || mAnimationIsEntrance - || mAnimation != null) { - pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating); - pw.print(" mLocalAnimating="); pw.print(mLocalAnimating); - pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance); - pw.print(" mAnimation="); pw.println(mAnimation); - } - if (mHasTransformation || mHasLocalTransformation) { - pw.print(prefix); pw.print("XForm: has="); - pw.print(mHasTransformation); - pw.print(" hasLocal="); pw.print(mHasLocalTransformation); - pw.print(" "); mTransformation.printShortString(pw); + pw.print(prefix); pw.print("Lst insets: system="); mLastSystemInsets.printShortString(pw); + pw.print(" content="); mLastContentInsets.printShortString(pw);; + pw.print(" visible="); mLastVisibleInsets.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); - pw.print(" mLastAlpha="); pw.println(mLastAlpha); - } - if (mHaveMatrix || mGlobalScale != 1) { - pw.print(prefix); pw.print("mGlobalScale="); pw.print(mGlobalScale); - pw.print(" mDsDx="); pw.print(mDsDx); - pw.print(" mDtDx="); pw.print(mDtDx); - pw.print(" mDsDy="); pw.print(mDsDy); - pw.print(" mDtDy="); pw.println(mDtDy); - } - if (dumpAll) { - pw.print(prefix); pw.print("mDrawPending="); pw.print(mDrawPending); - pw.print(" mCommitDrawPending="); pw.print(mCommitDrawPending); - pw.print(" mReadyToShow="); pw.print(mReadyToShow); - pw.print(" mHasDrawn="); pw.println(mHasDrawn); - } + mWinAnimator.dump(pw, prefix, dumpAll); if (mExiting || mRemoveOnExit || mDestroying || mRemoved) { pw.print(prefix); pw.print("mExiting="); pw.print(mExiting); pw.print(" mRemoveOnExit="); pw.print(mRemoveOnExit); diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java new file mode 100644 index 0000000..9147a10 --- /dev/null +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -0,0 +1,1367 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +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_TURN_ON_SCREEN; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Debug; +import android.os.RemoteException; +import android.util.Slog; +import android.view.Surface; +import android.view.SurfaceSession; +import android.view.WindowManager; +import android.view.WindowManagerPolicy; +import android.view.WindowManager.LayoutParams; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Transformation; + +import com.android.server.wm.WindowManagerService.H; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Keep track of animations and surface operations for a single WindowState. + **/ +class WindowStateAnimator { + static final boolean DEBUG_VISIBILITY = WindowManagerService.DEBUG_VISIBILITY; + static final boolean DEBUG_ANIM = WindowManagerService.DEBUG_ANIM; + static final boolean DEBUG_LAYERS = WindowManagerService.DEBUG_LAYERS; + static final boolean DEBUG_STARTING_WINDOW = WindowManagerService.DEBUG_STARTING_WINDOW; + static final boolean SHOW_TRANSACTIONS = WindowManagerService.SHOW_TRANSACTIONS; + static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS; + static final boolean SHOW_SURFACE_ALLOC = WindowManagerService.SHOW_SURFACE_ALLOC; + static final boolean localLOGV = WindowManagerService.localLOGV; + static final boolean DEBUG_ORIENTATION = WindowManagerService.DEBUG_ORIENTATION; + static final boolean DEBUG_SURFACE_TRACE = WindowManagerService.DEBUG_SURFACE_TRACE; + + static final String TAG = "WindowStateAnimator"; + + final WindowManagerService mService; + final WindowState mWin; + final WindowState mAttachedWindow; + final WindowAnimator mAnimator; + final Session mSession; + final WindowManagerPolicy mPolicy; + final Context mContext; + + // Currently running animation. + boolean mAnimating; + boolean mLocalAnimating; + Animation mAnimation; + boolean mAnimationIsEntrance; + boolean mHasTransformation; + boolean mHasLocalTransformation; + final Transformation mTransformation = new Transformation(); + boolean mWasAnimating; // Were we animating going into the most recent animation step? + int mAnimLayer; + int mLastLayer; + + Surface mSurface; + Surface mPendingDestroySurface; + boolean mReportDestroySurface; + boolean mSurfacePendingDestroy; + + /** + * Set when we have changed the size of the surface, to know that + * we must tell them application to resize (and thus redraw itself). + */ + boolean mSurfaceResized; + + /** + * Set if the client has asked that the destroy of its surface be delayed + * until it explicitly says it is okay. + */ + boolean mSurfaceDestroyDeferred; + + float mShownAlpha = 0; + float mAlpha = 0; + float mLastAlpha = 0; + + // Used to save animation distances between the time they are calculated and when they are + // used. + int mAnimDw; + int mAnimDh; + float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1; + float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1; + + boolean mHaveMatrix; + + // For debugging, this is the last information given to the surface flinger. + boolean mSurfaceShown; + float mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH; + int mSurfaceLayer; + float mSurfaceAlpha; + + // Set to true if, when the window gets displayed, it should perform + // an enter animation. + boolean mEnterAnimationPending; + + /** This is set when there is no Surface */ + static final int NO_SURFACE = 0; + /** This is set after the Surface has been created but before the window has been drawn. During + * this time the surface is hidden. */ + static final int DRAW_PENDING = 1; + /** This is set after the window has finished drawing for the first time but before its surface + * is shown. The surface will be displayed when the next layout is run. */ + static final int COMMIT_DRAW_PENDING = 2; + /** This is set during the time after the window's drawing has been committed, and before its + * surface is actually shown. It is used to delay showing the surface until all windows in a + * token are ready to be shown. */ + static final int READY_TO_SHOW = 3; + /** Set when the window has been shown in the screen the first time. */ + static final int HAS_DRAWN = 4; + int mDrawState; + + /** Was this window last hidden? */ + boolean mLastHidden; + + int mAttrFlags; + int mAttrType; + + public WindowStateAnimator(final WindowManagerService service, final WindowState win, + final WindowState attachedWindow) { + mService = service; + mWin = win; + mAttachedWindow = attachedWindow; + mAnimator = mService.mAnimator; + mSession = win.mSession; + mPolicy = mService.mPolicy; + mContext = mService.mContext; + mAttrFlags = win.mAttrs.flags; + mAttrType = win.mAttrs.type; + } + + public void setAnimation(Animation anim) { + if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim); + mAnimating = false; + mLocalAnimating = false; + mAnimation = anim; + mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); + mAnimation.scaleCurrentDuration(mService.mWindowAnimationScale); + // Start out animation gone if window is gone, or visible if window is visible. + mTransformation.clear(); + mTransformation.setAlpha(mLastHidden ? 0 : 1); + mHasLocalTransformation = true; + } + + public void clearAnimation() { + if (mAnimation != null) { + mAnimating = true; + mLocalAnimating = false; + mAnimation.cancel(); + mAnimation = null; + } + } + + /** 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)); + } + + /** Is this window currently animating? */ + boolean isWindowAnimating() { + return mAnimation != null; + } + + void cancelExitAnimationForNextAnimationLocked() { + if (mAnimation != null) { + mAnimation.cancel(); + mAnimation = null; + mLocalAnimating = false; + destroySurfaceLocked(); + } + } + + private boolean stepAnimation(long currentTime) { + if ((mAnimation == null) || !mLocalAnimating) { + return false; + } + mTransformation.clear(); + final boolean more = mAnimation.getTransformation(currentTime, mTransformation); + if (DEBUG_ANIM) Slog.v( + TAG, "Stepped animation in " + this + + ": more=" + more + ", xform=" + mTransformation); + return more; + } + + // This must be called while inside a transaction. Returns true if + // there is more animation to run. + boolean stepAnimationLocked(long currentTime) { + // Save the animation state as it was before this step so WindowManagerService can tell if + // we just started or just stopped animating by comparing mWasAnimating with isAnimating(). + mWasAnimating = mAnimating; + if (mService.okToDisplay()) { + // We will run animations as long as the display isn't frozen. + + if (mWin.isDrawnLw() && mAnimation != null) { + mHasTransformation = true; + mHasLocalTransformation = true; + if (!mLocalAnimating) { + if (DEBUG_ANIM) Slog.v( + TAG, "Starting animation in " + this + + " @ " + currentTime + ": ww=" + mWin.mFrame.width() + + " wh=" + mWin.mFrame.height() + + " dw=" + mAnimDw + " dh=" + mAnimDh + + " scale=" + mService.mWindowAnimationScale); + mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), + mAnimDw, mAnimDh); + mAnimation.setStartTime(currentTime); + mLocalAnimating = true; + mAnimating = true; + } + if ((mAnimation != null) && mLocalAnimating) { + if (stepAnimation(currentTime)) { + return true; + } + } + if (DEBUG_ANIM) Slog.v( + TAG, "Finished animation in " + this + + " @ " + currentTime); + //WindowManagerService.this.dump(); + } + mHasLocalTransformation = false; + if ((!mLocalAnimating || mAnimationIsEntrance) && mWin.mAppToken != null + && mWin.mAppToken.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 + // our window is not currently exiting, or it is not + // locally animating itself. The idea being that one that + // is exiting and doing a local animation should be removed + // once that animation is done. + mAnimating = true; + mHasTransformation = true; + mTransformation.clear(); + return false; + } else if (mHasTransformation) { + // Little trick to get through the path below to act like + // we have finished an animation. + mAnimating = true; + } else if (isAnimating()) { + mAnimating = true; + } + } else if (mAnimation != null) { + // If the display is frozen, and there is a pending animation, + // clear it and make sure we run the cleanup code. + mAnimating = true; + } + + if (!mAnimating && !mLocalAnimating) { + return false; + } + + // Done animating, clean up. + if (DEBUG_ANIM) Slog.v( + TAG, "Animation done in " + this + ": exiting=" + mWin.mExiting + + ", reportedVisible=" + + (mWin.mAppToken != null ? mWin.mAppToken.reportedVisible : false)); + + mAnimating = false; + mLocalAnimating = false; + if (mAnimation != null) { + mAnimation.cancel(); + mAnimation = null; + } + if (mAnimator.mWindowDetachedWallpaper == mWin) { + mAnimator.mWindowDetachedWallpaper = null; + } + mAnimLayer = mWin.mLayer; + if (mWin.mIsImWindow) { + mAnimLayer += mService.mInputMethodAnimLayerAdjustment; + } else if (mWin.mIsWallpaper) { + mAnimLayer += mService.mWallpaperAnimLayerAdjustment; + } + if (DEBUG_LAYERS) Slog.v(TAG, "Stepping win " + this + + " anim layer: " + mAnimLayer); + mHasTransformation = false; + mHasLocalTransformation = false; + if (mWin.mPolicyVisibility != mWin.mPolicyVisibilityAfterAnim) { + if (WindowState.DEBUG_VISIBILITY) { + Slog.v(TAG, "Policy visibility changing after anim in " + this + ": " + + mWin.mPolicyVisibilityAfterAnim); + } + mWin.mPolicyVisibility = mWin.mPolicyVisibilityAfterAnim; + mService.mLayoutNeeded = true; + if (!mWin.mPolicyVisibility) { + if (mService.mCurrentFocus == mWin) { + mService.mFocusMayChange = true; + } + // Window is no longer visible -- make sure if we were waiting + // for it to be displayed before enabling the display, that + // we allow the display to be enabled now. + mService.enableScreenIfNeededLocked(); + } + } + mTransformation.clear(); + if (mDrawState == HAS_DRAWN + && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING + && mWin.mAppToken != null + && mWin.mAppToken.firstWindowDrawn + && mWin.mAppToken.startingData != null) { + if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Finish starting " + + mWin.mToken + ": first real window done animating"); + mService.mFinishedStarting.add(mWin.mAppToken); + mService.mH.sendEmptyMessage(H.FINISHED_STARTING); + } + + finishExit(); + mAnimator.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) mService.debugLayoutRepeats( + "WindowStateAnimator", mAnimator.mPendingLayoutChanges); + + if (mWin.mAppToken != null) { + mWin.mAppToken.updateReportedVisibilityLocked(); + } + + return false; + } + + void finishExit() { + if (WindowManagerService.DEBUG_ANIM) Slog.v( + TAG, "finishExit in " + this + + ": exiting=" + mWin.mExiting + + " remove=" + mWin.mRemoveOnExit + + " windowAnimating=" + isWindowAnimating()); + + final int N = mWin.mChildWindows.size(); + for (int i=0; i<N; i++) { + mWin.mChildWindows.get(i).mWinAnimator.finishExit(); + } + + if (!mWin.mExiting) { + return; + } + + if (isWindowAnimating()) { + return; + } + + if (WindowManagerService.localLOGV) Slog.v( + TAG, "Exit animation finished in " + this + + ": remove=" + mWin.mRemoveOnExit); + if (mSurface != null) { + mService.mDestroySurface.add(mWin); + mWin.mDestroying = true; + if (WindowState.SHOW_TRANSACTIONS) WindowManagerService.logSurface( + mWin, "HIDE (finishExit)", null); + mSurfaceShown = false; + try { + mSurface.hide(); + } catch (RuntimeException e) { + Slog.w(TAG, "Error hiding surface in " + this, e); + } + mLastHidden = true; + } + mWin.mExiting = false; + if (mWin.mRemoveOnExit) { + mService.mPendingRemove.add(mWin); + mWin.mRemoveOnExit = false; + } + } + + boolean finishDrawingLocked() { + if (mDrawState == DRAW_PENDING) { + if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION) Slog.v( + TAG, "finishDrawingLocked: " + this + " in " + mSurface); + mDrawState = COMMIT_DRAW_PENDING; + return true; + } + return false; + } + + // This must be called while inside a transaction. + boolean commitFinishDrawingLocked(long currentTime) { + if (mDrawState != COMMIT_DRAW_PENDING) { + return false; + } + //Slog.i(TAG, "commitFinishDrawingLocked: Draw pending. " + mSurface); + mDrawState = READY_TO_SHOW; + final boolean starting = mWin.mAttrs.type == TYPE_APPLICATION_STARTING; + final AppWindowToken atoken = mWin.mAppToken; + if (atoken == null || atoken.allDrawn || starting) { + performShowLocked(); + } + return true; + } + + static class SurfaceTrace extends Surface { + private final static String SURFACE_TAG = "SurfaceTrace"; + final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>(); + + private float mSurfaceTraceAlpha = 0; + private int mLayer; + private PointF mPosition = new PointF(); + private Point mSize; + 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 = new Point(w, h); + Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by " + + Debug.getCallers(3)); + } + + public SurfaceTrace(SurfaceSession s, + int pid, String name, int display, int w, int h, int format, int flags) + throws OutOfResourcesException { + super(s, pid, name, display, w, h, format, flags); + mName = name; + mSize = new Point(w, h); + Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by " + + Debug.getCallers(3)); + } + + @Override + public void setAlpha(float alpha) { + super.setAlpha(alpha); + mSurfaceTraceAlpha = alpha; + Slog.v(SURFACE_TAG, "setAlpha: " + this + ". Called by " + + Debug.getCallers(3)); + } + + @Override + public void setLayer(int zorder) { + super.setLayer(zorder); + mLayer = zorder; + Slog.v(SURFACE_TAG, "setLayer: " + this + ". Called by " + + Debug.getCallers(3)); + + sSurfaces.remove(this); + int i; + for (i = sSurfaces.size() - 1; i >= 0; i--) { + SurfaceTrace s = sSurfaces.get(i); + if (s.mLayer < zorder) { + break; + } + } + sSurfaces.add(i + 1, this); + } + + @Override + public void setPosition(float x, float y) { + super.setPosition(x, y); + mPosition = new PointF(x, y); + Slog.v(SURFACE_TAG, "setPosition: " + this + ". Called by " + + Debug.getCallers(3)); + } + + @Override + public void setSize(int w, int h) { + super.setSize(w, h); + mSize = new Point(w, h); + Slog.v(SURFACE_TAG, "setSize: " + this + ". Called by " + + Debug.getCallers(3)); + } + + @Override + public void hide() { + super.hide(); + mShown = false; + Slog.v(SURFACE_TAG, "hide: " + this + ". Called by " + + Debug.getCallers(3)); + } + @Override + public void show() { + super.show(); + mShown = true; + Slog.v(SURFACE_TAG, "show: " + this + ". Called by " + + Debug.getCallers(3)); + } + + @Override + public void destroy() { + super.destroy(); + Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by " + + Debug.getCallers(3)); + sSurfaces.remove(this); + } + + @Override + public void release() { + super.release(); + Slog.v(SURFACE_TAG, "release: " + this + ". Called by " + + Debug.getCallers(3)); + sSurfaces.remove(this); + } + + static void dumpAllSurfaces() { + final int N = sSurfaces.size(); + for (int i = 0; i < N; i++) { + Slog.i(TAG, "SurfaceDump: " + sSurfaces.get(i)); + } + } + + @Override + public String toString() { + return "Surface " + Integer.toHexString(System.identityHashCode(this)) + " " + + mName + ": shown=" + mShown + " layer=" + mLayer + + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y + + " " + mSize.x + "x" + mSize.y; + } + } + + Surface createSurfaceLocked() { + if (mSurface == null) { + mReportDestroySurface = false; + mSurfacePendingDestroy = false; + if (DEBUG_ORIENTATION) Slog.i(TAG, + "createSurface " + this + ": DRAW NOW PENDING"); + mDrawState = DRAW_PENDING; + if (mWin.mAppToken != null) { + mWin.mAppToken.allDrawn = false; + } + + mService.makeWindowFreezingScreenIfNeededLocked(mWin); + + int flags = 0; + final WindowManager.LayoutParams attrs = mWin.mAttrs; + + if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { + flags |= Surface.SECURE; + } + if (WindowState.DEBUG_VISIBILITY) Slog.v( + TAG, "Creating surface in session " + + mSession.mSurfaceSession + " window " + this + + " w=" + mWin.mCompatFrame.width() + + " h=" + mWin.mCompatFrame.height() + " format=" + + attrs.format + " flags=" + flags); + + int w = mWin.mCompatFrame.width(); + int h = mWin.mCompatFrame.height(); + if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) { + // for a scaled surface, we always want the requested + // size. + w = mWin.mRequestedWidth; + h = mWin.mRequestedHeight; + } + + // Something is wrong and SurfaceFlinger will not like this, + // try to revert to sane values + if (w <= 0) w = 1; + if (h <= 0) h = 1; + + mSurfaceShown = false; + mSurfaceLayer = 0; + mSurfaceAlpha = 0; + mSurfaceX = 0; + mSurfaceY = 0; + mSurfaceW = w; + mSurfaceH = h; + try { + final boolean isHwAccelerated = (attrs.flags & + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; + final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format; + if (!PixelFormat.formatHasAlpha(attrs.format)) { + flags |= Surface.OPAQUE; + } + if (DEBUG_SURFACE_TRACE) { + mSurface = new SurfaceTrace( + mSession.mSurfaceSession, mSession.mPid, + attrs.getTitle().toString(), + 0, w, h, format, flags); + } else { + mSurface = new Surface( + mSession.mSurfaceSession, mSession.mPid, + attrs.getTitle().toString(), + 0, w, h, format, flags); + } + mWin.mHasSurface = true; + if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG, + " CREATE SURFACE " + + mSurface + " IN SESSION " + + mSession.mSurfaceSession + + ": pid=" + mSession.mPid + " format=" + + attrs.format + " flags=0x" + + Integer.toHexString(flags) + + " / " + this); + } catch (Surface.OutOfResourcesException e) { + mWin.mHasSurface = false; + Slog.w(TAG, "OutOfResourcesException creating surface"); + mService.reclaimSomeSurfaceMemoryLocked(this, "create", true); + mDrawState = NO_SURFACE; + return null; + } catch (Exception e) { + mWin.mHasSurface = false; + Slog.e(TAG, "Exception creating surface", e); + mDrawState = NO_SURFACE; + return null; + } + + if (WindowManagerService.localLOGV) Slog.v( + TAG, "Got surface: " + mSurface + + ", set left=" + mWin.mFrame.left + " top=" + mWin.mFrame.top + + ", animLayer=" + mAnimLayer); + if (SHOW_LIGHT_TRANSACTIONS) { + Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked"); + WindowManagerService.logSurface(mWin, "CREATE pos=(" + + mWin.mFrame.left + "," + mWin.mFrame.top + ") (" + + mWin.mCompatFrame.width() + "x" + mWin.mCompatFrame.height() + + "), layer=" + mAnimLayer + " HIDE", null); + } + Surface.openTransaction(); + try { + try { + mSurfaceX = mWin.mFrame.left + mWin.mXOffset; + mSurfaceY = mWin.mFrame.top + mWin.mYOffset; + mSurface.setPosition(mSurfaceX, mSurfaceY); + mSurfaceLayer = mAnimLayer; + 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); + } + mLastHidden = true; + } finally { + Surface.closeTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, + "<<< CLOSE TRANSACTION createSurfaceLocked"); + } + if (WindowManagerService.localLOGV) Slog.v( + TAG, "Created surface " + this); + } + return mSurface; + } + + void destroySurfaceLocked() { + if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) { + mWin.mAppToken.startingDisplayed = false; + } + + mDrawState = NO_SURFACE; + if (mSurface != null) { + + int i = mWin.mChildWindows.size(); + while (i > 0) { + i--; + WindowState c = mWin.mChildWindows.get(i); + c.mAttachedHidden = true; + } + + if (mReportDestroySurface) { + mReportDestroySurface = false; + mSurfacePendingDestroy = true; + try { + mWin.mClient.dispatchGetNewSurface(); + // We'll really destroy on the next time around. + return; + } catch (RemoteException e) { + } + } + + try { + if (DEBUG_VISIBILITY) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + Slog.w(TAG, "Window " + this + " destroying surface " + + mSurface + ", session " + mSession, e); + } + if (mSurfaceDestroyDeferred) { + if (mSurface != null && mPendingDestroySurface != mSurface) { + if (mPendingDestroySurface != null) { + if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + WindowManagerService.logSurface(mWin, "DESTROY PENDING", e); + } + mPendingDestroySurface.destroy(); + } + mPendingDestroySurface = mSurface; + } + } else { + if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + WindowManagerService.logSurface(mWin, "DESTROY", e); + } + mSurface.destroy(); + } + } catch (RuntimeException e) { + Slog.w(TAG, "Exception thrown when destroying Window " + this + + " surface " + mSurface + " session " + mSession + + ": " + e.toString()); + } + + mSurfaceShown = false; + mSurface = null; + mWin.mHasSurface =false; + } + } + + void destroyDeferredSurfaceLocked() { + try { + if (mPendingDestroySurface != null) { + if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + WindowManagerService.logSurface(mWin, "DESTROY PENDING", e); + } + mPendingDestroySurface.destroy(); + } + } catch (RuntimeException e) { + Slog.w(TAG, "Exception thrown when destroying Window " + + this + " surface " + mPendingDestroySurface + + " session " + mSession + ": " + e.toString()); + } + mSurfaceDestroyDeferred = false; + mPendingDestroySurface = null; + } + + 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; + + // 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 (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()) { + appTransformation = wpAppAnimator.transformation; + if (WindowManagerService.DEBUG_WALLPAPER && appTransformation != null) { + Slog.v(TAG, "WP target app xform: " + appTransformation); + } + } + } + + final boolean screenAnimation = mService.mAnimator.mScreenRotationAnimation != null + && mService.mAnimator.mScreenRotationAnimation.isAnimating(); + if (selfTransformation || attachedTransformation != null + || appTransformation != null || screenAnimation) { + // cache often used attributes locally + final Rect frame = mWin.mFrame; + final float tmpFloats[] = mService.mTmpFloats; + final Matrix tmpMatrix = mWin.mTmpMatrix; + + // Compute the desired transformation. + if (screenAnimation) { + // If we are doing a screen animation, the global rotation + // applied to windows can result in windows that are carefully + // aligned with each other to slightly separate, allowing you + // to see what is behind them. An unsightly mess. This... + // thing... magically makes it call good: scale each window + // slightly (two pixels larger in each dimension, from the + // window's center). + final float w = frame.width(); + final float h = frame.height(); + if (w>=1 && h>=1) { + tmpMatrix.setScale(1 + 2/w, 1 + 2/h, w/2, h/2); + } else { + tmpMatrix.reset(); + } + } else { + tmpMatrix.reset(); + } + tmpMatrix.postScale(mWin.mGlobalScale, mWin.mGlobalScale); + if (selfTransformation) { + tmpMatrix.postConcat(mTransformation.getMatrix()); + } + tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset); + if (attachedTransformation != null) { + tmpMatrix.postConcat(attachedTransformation.getMatrix()); + } + if (appTransformation != null) { + tmpMatrix.postConcat(appTransformation.getMatrix()); + } + if (screenAnimation) { + tmpMatrix.postConcat( + mService.mAnimator.mScreenRotationAnimation.getEnterTransformation().getMatrix()); + } + + // "convert" it into SurfaceFlinger's format + // (a 2x2 matrix + an offset) + // Here we must not transform the position of the surface + // since it is already included in the transformation. + //Slog.i(TAG, "Transform: " + matrix); + + mHaveMatrix = true; + tmpMatrix.getValues(tmpFloats); + 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); + + // Now set the alpha... but because our current hardware + // can't do alpha transformation on a non-opaque surface, + // turn it off if we are running an animation that is also + // transforming since it is more important to have that + // animation be smooth. + mShownAlpha = mAlpha; + if (!mService.mLimitedAlphaCompositing + || (!PixelFormat.formatHasAlpha(mWin.mAttrs.format) + || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDsDy, mDtDy) + && x == frame.left && y == frame.top))) { + //Slog.i(TAG, "Applying alpha transform"); + if (selfTransformation) { + mShownAlpha *= mTransformation.getAlpha(); + } + if (attachedTransformation != null) { + mShownAlpha *= attachedTransformation.getAlpha(); + } + if (appTransformation != null) { + mShownAlpha *= appTransformation.getAlpha(); + } + if (screenAnimation) { + mShownAlpha *= + mService.mAnimator.mScreenRotationAnimation.getEnterTransformation().getAlpha(); + } + } else { + //Slog.i(TAG, "Not applying alpha transform"); + } + + if (WindowManagerService.localLOGV) Slog.v( + TAG, "computeShownFrameLocked: Animating " + this + + ": " + mWin.mShownFrame + + ", alpha=" + mTransformation.getAlpha() + ", mShownAlpha=" + mShownAlpha); + return; + } else if (mWin.mIsWallpaper && + (mAnimator.mPendingActions & WindowAnimator.WALLPAPER_ACTION_PENDING) != 0) { + return; + } + + 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; + } + + void setSurfaceBoundaries(final boolean recoveringMemory) { + final WindowState w = mWin; + int width, height; + if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { + // for a scaled surface, we just want to use + // the requested size. + width = w.mRequestedWidth; + height = w.mRequestedHeight; + } else { + width = w.mCompatFrame.width(); + height = w.mCompatFrame.height(); + } + + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height; + if (surfaceResized) { + mSurfaceW = width; + mSurfaceH = height; + } + + final float left = w.mShownFrame.left; + final float top = w.mShownFrame.top; + if (mSurfaceX != left || mSurfaceY != top) { + try { + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, + "POS " + left + ", " + top, null); + mSurfaceX = left; + mSurfaceY = top; + mSurface.setPosition(left, top); + } catch (RuntimeException e) { + Slog.w(TAG, "Error positioning surface of " + w + + " pos=(" + left + + "," + top + ")", e); + if (!recoveringMemory) { + mService.reclaimSomeSurfaceMemoryLocked(this, "position", true); + } + } + } + + if (surfaceResized) { + try { + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, + "SIZE " + width + "x" + height, null); + mSurfaceResized = true; + mSurface.setSize(width, height); + mAnimator.mPendingLayoutChanges |= + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; + } catch (RuntimeException e) { + // If something goes wrong with the surface (such + // as running out of memory), don't take down the + // entire system. + Slog.e(TAG, "Error resizing surface of " + w + + " size=(" + width + "x" + height + ")", e); + if (!recoveringMemory) { + mService.reclaimSomeSurfaceMemoryLocked(this, "size", true); + } + } + } + } + + public void prepareSurfaceLocked(final boolean recoveringMemory) { + final WindowState w = mWin; + if (mSurface == null) { + if (w.mOrientationChanging) { + if (DEBUG_ORIENTATION) { + Slog.v(TAG, "Orientation change skips hidden " + w); + } + w.mOrientationChanging = false; + } + return; + } + + boolean displayed = false; + + computeShownFrameLocked(); + + setSurfaceBoundaries(recoveringMemory); + + if (w.mAttachedHidden || !w.isReadyForDisplay()) { + if (!mLastHidden) { + //dump(); + mLastHidden = true; + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, + "HIDE (performLayout)", null); + if (mSurface != null) { + mSurfaceShown = false; + try { + mSurface.hide(); + } catch (RuntimeException e) { + Slog.w(TAG, "Exception hiding surface in " + w); + } + } + } + // If we are waiting for this window to handle an + // orientation change, well, it is hidden, so + // doesn't really matter. Note that this does + // introduce a potential glitch if the window + // becomes unhidden before it has drawn for the + // new orientation. + if (w.mOrientationChanging) { + w.mOrientationChanging = false; + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation change skips hidden " + w); + } + } else if (mLastLayer != mAnimLayer + || mLastAlpha != mShownAlpha + || mLastDsDx != mDsDx + || mLastDtDx != mDtDx + || mLastDsDy != mDsDy + || mLastDtDy != mDtDy + || w.mLastHScale != w.mHScale + || w.mLastVScale != w.mVScale + || mLastHidden) { + displayed = true; + mLastAlpha = mShownAlpha; + mLastLayer = mAnimLayer; + mLastDsDx = mDsDx; + mLastDtDx = mDtDx; + mLastDsDy = mDsDy; + mLastDtDy = mDtDy; + w.mLastHScale = w.mHScale; + w.mLastVScale = w.mVScale; + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, + "alpha=" + mShownAlpha + " layer=" + mAnimLayer + + " matrix=[" + (mDsDx*w.mHScale) + + "," + (mDtDx*w.mVScale) + + "][" + (mDsDy*w.mHScale) + + "," + (mDtDy*w.mVScale) + "]", null); + if (mSurface != null) { + try { + mSurfaceAlpha = mShownAlpha; + mSurface.setAlpha(mShownAlpha); + mSurfaceLayer = w.mWinAnimator.mAnimLayer; + mSurface.setLayer(w.mWinAnimator.mAnimLayer); + mSurface.setMatrix( + mDsDx*w.mHScale, mDtDx*w.mVScale, + mDsDy*w.mHScale, mDtDy*w.mVScale); + + if (mLastHidden && mDrawState == HAS_DRAWN) { + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, + "SHOW (performLayout)", null); + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w + + " during relayout"); + if (showSurfaceRobustlyLocked()) { + mLastHidden = false; + } else { + w.mOrientationChanging = false; + } + } + if (mSurface != null) { + w.mToken.hasVisible = true; + } + } catch (RuntimeException e) { + Slog.w(TAG, "Error updating surface in " + w, e); + if (!recoveringMemory) { + mService.reclaimSomeSurfaceMemoryLocked(this, "update", true); + } + } + } + } else { + displayed = true; + } + + if (displayed) { + if (w.mOrientationChanging) { + if (!w.isDrawnLw()) { + mAnimator.mBulkUpdateParams |= CLEAR_ORIENTATION_CHANGE_COMPLETE; + if (DEBUG_ORIENTATION) Slog.v(TAG, + "Orientation continue waiting for draw in " + w); + } else { + w.mOrientationChanging = false; + if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w); + } + } + w.mToken.hasVisible = true; + } + } + + void setTransparentRegionHint(final Region region) { + if (mSurface == null) { + Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true"); + return; + } + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, + ">>> OPEN TRANSACTION setTransparentRegion"); + Surface.openTransaction(); + try { + if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, + "transparentRegionHint=" + region, null); + mSurface.setTransparentRegionHint(region); + } finally { + Surface.closeTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, + "<<< CLOSE TRANSACTION setTransparentRegion"); + } + } + + void setWallpaperOffset(int left, int top) { + Surface.openTransaction(); + try { + mSurfaceX = left; + mSurfaceY = top; + mSurface.setPosition(left, top); + } catch (RuntimeException e) { + Slog.w(TAG, "Error positioning surface of " + mWin + + " pos=(" + left + "," + top + ")", e); + } + Surface.closeTransaction(); + } + + // This must be called while inside a transaction. + boolean performShowLocked() { + if (DEBUG_VISIBILITY) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + Slog.v(TAG, "performShow on " + this + + ": mDrawState=" + mDrawState + " readyForDisplay=" + + mWin.isReadyForDisplay() + + " starting=" + (mWin.mAttrs.type == TYPE_APPLICATION_STARTING), e); + } + if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplay()) { + if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION) + WindowManagerService.logSurface(mWin, "SHOW (performShowLocked)", null); + if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + + " during animation: policyVis=" + mWin.mPolicyVisibility + + " attHidden=" + mWin.mAttachedHidden + + " tok.hiddenRequested=" + + (mWin.mAppToken != null ? mWin.mAppToken.hiddenRequested : false) + + " tok.hidden=" + + (mWin.mAppToken != null ? mWin.mAppToken.hidden : false) + + " animating=" + mAnimating + + " tok animating=" + + (mWin.mAppToken != null ? mWin.mAppToken.mAppAnimator.animating : false)); + + mService.enableScreenIfNeededLocked(); + + applyEnterAnimationLocked(); + + // Force the show in the next prepareSurfaceLocked() call. + mLastAlpha = -1; + mDrawState = HAS_DRAWN; + mService.scheduleAnimationLocked(); + + int i = mWin.mChildWindows.size(); + while (i > 0) { + i--; + WindowState c = mWin.mChildWindows.get(i); + if (c.mAttachedHidden) { + c.mAttachedHidden = false; + if (c.mWinAnimator.mSurface != null) { + c.mWinAnimator.performShowLocked(); + // It hadn't been shown, which means layout not + // performed on it, so now we want to make sure to + // do a layout. If called from within the transaction + // loop, this will cause it to restart with a new + // layout. + mService.mLayoutNeeded = true; + } + } + } + + if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING + && mWin.mAppToken != null) { + mWin.mAppToken.firstWindowDrawn = true; + + if (mWin.mAppToken.startingData != null) { + if (WindowManagerService.DEBUG_STARTING_WINDOW || + WindowManagerService.DEBUG_ANIM) Slog.v(TAG, + "Finish starting " + mWin.mToken + + ": first real window is shown, no animation"); + // If this initial window is animating, stop it -- we + // will do an animation to reveal it from behind the + // starting window, so there is no need for it to also + // be doing its own stuff. + clearAnimation(); + mService.mFinishedStarting.add(mWin.mAppToken); + mService.mH.sendEmptyMessage(H.FINISHED_STARTING); + } + mWin.mAppToken.updateReportedVisibilityLocked(); + } + + return true; + } + + return false; + } + + /** + * Have the surface flinger show a surface, robustly dealing with + * error conditions. In particular, if there is not enough memory + * to show the surface, then we will try to get rid of other surfaces + * in order to succeed. + * + * @return Returns true if the surface was successfully shown. + */ + boolean showSurfaceRobustlyLocked() { + try { + if (mSurface != null) { + mSurfaceShown = true; + mSurface.show(); + if (mWin.mTurnOnScreen) { + if (DEBUG_VISIBILITY) Slog.v(TAG, + "Show surface turning screen on: " + mWin); + mWin.mTurnOnScreen = false; + mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN; + } + } + return true; + } catch (RuntimeException e) { + Slog.w(TAG, "Failure showing surface " + mSurface + " in " + mWin, e); + } + + mService.reclaimSomeSurfaceMemoryLocked(this, "show", true); + + return false; + } + + void applyEnterAnimationLocked() { + final int transit; + if (mEnterAnimationPending) { + mEnterAnimationPending = false; + transit = WindowManagerPolicy.TRANSIT_ENTER; + } else { + transit = WindowManagerPolicy.TRANSIT_SHOW; + } + + applyAnimationLocked(transit, true); + } + + // TODO(cmautner): Move back to WindowState? + /** + * Choose the correct animation and set it to the passed WindowState. + * @param transit If WindowManagerPolicy.TRANSIT_PREVIEW_DONE and the app window has been drawn + * then the animation will be app_starting_exit. Any other value loads the animation from + * the switch statement below. + * @param isEntrance The animation type the last time this was called. Used to keep from + * loading the same animation twice. + * @return true if an animation has been loaded. + */ + boolean applyAnimationLocked(int transit, boolean isEntrance) { + if (mLocalAnimating && mAnimationIsEntrance == isEntrance) { + // If we are trying to apply an animation, but already running + // an animation of the same type, then just leave that one alone. + return true; + } + + // 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 + // artifacts when we unfreeze the display if some different animation + // is running. + if (mService.okToDisplay()) { + int anim = mPolicy.selectAnimationLw(mWin, transit); + int attr = -1; + Animation a = null; + if (anim != 0) { + a = AnimationUtils.loadAnimation(mContext, anim); + } else { + switch (transit) { + case WindowManagerPolicy.TRANSIT_ENTER: + attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation; + break; + case WindowManagerPolicy.TRANSIT_EXIT: + attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation; + break; + case WindowManagerPolicy.TRANSIT_SHOW: + attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation; + break; + case WindowManagerPolicy.TRANSIT_HIDE: + attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation; + break; + } + if (attr >= 0) { + a = mService.loadAnimation(mWin.mAttrs, attr); + } + } + if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, + "applyAnimation: win=" + this + + " anim=" + anim + " attr=0x" + Integer.toHexString(attr) + + " a=" + a + + " mAnimation=" + mAnimation + + " isEntrance=" + isEntrance); + if (a != null) { + if (WindowManagerService.DEBUG_ANIM) { + RuntimeException e = null; + if (!WindowManagerService.HIDE_STACK_CRAWLS) { + e = new RuntimeException(); + e.fillInStackTrace(); + } + Slog.v(TAG, "Loaded animation " + a + " for " + this, e); + } + setAnimation(a); + mAnimationIsEntrance = isEntrance; + } + } else { + clearAnimation(); + } + + return mAnimation != null; + } + + public void dump(PrintWriter pw, String prefix, boolean dumpAll) { + if (mAnimating || mLocalAnimating || mAnimationIsEntrance + || mAnimation != null) { + pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating); + pw.print(" mLocalAnimating="); pw.print(mLocalAnimating); + pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance); + pw.print(" mAnimation="); pw.println(mAnimation); + } + if (mHasTransformation || mHasLocalTransformation) { + pw.print(prefix); pw.print("XForm: has="); + pw.print(mHasTransformation); + pw.print(" hasLocal="); pw.print(mHasLocalTransformation); + pw.print(" "); mTransformation.printShortString(pw); + pw.println(); + } + if (mSurface != null) { + if (dumpAll) { + pw.print(prefix); pw.print("mSurface="); pw.println(mSurface); + pw.print(prefix); pw.print("mDrawState="); pw.print(mDrawState); + pw.print(" mLastHidden="); pw.println(mLastHidden); + } + pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown); + pw.print(" layer="); pw.print(mSurfaceLayer); + pw.print(" alpha="); pw.print(mSurfaceAlpha); + pw.print(" rect=("); pw.print(mSurfaceX); + pw.print(","); pw.print(mSurfaceY); + pw.print(") "); pw.print(mSurfaceW); + pw.print(" x "); pw.println(mSurfaceH); + } + if (mPendingDestroySurface != null) { + pw.print(prefix); pw.print("mPendingDestroySurface="); + pw.println(mPendingDestroySurface); + } + if (mSurfaceResized || mSurfaceDestroyDeferred) { + pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized); + pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred); + } + if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) { + pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha); + pw.print(" mAlpha="); pw.print(mAlpha); + pw.print(" mLastAlpha="); pw.println(mLastAlpha); + } + if (mHaveMatrix || mWin.mGlobalScale != 1) { + pw.print(prefix); pw.print("mGlobalScale="); pw.print(mWin.mGlobalScale); + pw.print(" mDsDx="); pw.print(mDsDx); + pw.print(" mDtDx="); pw.print(mDtDx); + pw.print(" mDsDy="); pw.print(mDsDy); + pw.print(" mDtDy="); pw.println(mDtDy); + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer("WindowStateAnimator ("); + sb.append(mWin.mLastTitle + "): "); + sb.append("mSurface " + mSurface); + sb.append(", mAnimation " + mAnimation); + return sb.toString(); + } +} |